From f64b5c9480fabe24d349b4d0dc21f8739f2aa139 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Wed, 7 Jun 2017 10:33:50 -0400 Subject: [PATCH] provider/opc: update opc provider (#15159) * provider/opc: update opc provider * update opc-sdk --- .../go-oracle-terraform/compute/client.go | 57 ++++++++- .../go-oracle-terraform/compute/instances.go | 108 +++++++++++++++++- .../go-oracle-terraform/compute/test_utils.go | 4 +- .../go-oracle-terraform/opc/convert.go | 4 + .../terraform-provider-opc/opc/config.go | 30 +++-- .../terraform-provider-opc/opc/provider.go | 25 ++-- .../opc/resource_instance.go | 59 +++++++++- vendor/vendor.json | 30 +++-- 8 files changed, 283 insertions(+), 34 deletions(-) diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/client.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/client.go index 5b2ee0473..f99f25641 100644 --- a/vendor/github.com/hashicorp/go-oracle-terraform/compute/client.go +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/client.go @@ -16,6 +16,7 @@ import ( const CMP_USERNAME = "/Compute-%s/%s" const CMP_QUALIFIED_NAME = "%s/%s" +const DEFAULT_MAX_RETRIES = 1 // Client represents an authenticated compute client, with compute credentials and an api client. type Client struct { @@ -26,6 +27,7 @@ type Client struct { httpClient *http.Client authCookie *http.Cookie cookieIssued time.Time + maxRetries *int logger opc.Logger loglevel opc.LogLevelType } @@ -38,6 +40,7 @@ func NewComputeClient(c *opc.Config) (*Client, error) { password: c.Password, apiEndpoint: c.APIEndpoint, httpClient: c.HTTPClient, + maxRetries: c.MaxRetries, loglevel: c.LogLevel, } @@ -58,6 +61,16 @@ func NewComputeClient(c *opc.Config) (*Client, error) { return nil, err } + // Default max retries if unset + if c.MaxRetries == nil { + client.maxRetries = opc.Int(DEFAULT_MAX_RETRIES) + } + + // Protect against any nil http client + if c.HTTPClient == nil { + return nil, fmt.Errorf("No HTTP client specified in config") + } + return client, nil } @@ -108,7 +121,8 @@ func (c *Client) executeRequest(method, path string, body interface{}) (*http.Re } // Execute request with supplied client - resp, err := c.httpClient.Do(req) + resp, err := c.retryRequest(req) + //resp, err := c.httpClient.Do(req) if err != nil { return nil, err } @@ -133,6 +147,47 @@ func (c *Client) executeRequest(method, path string, body interface{}) (*http.Re return nil, oracleErr } +// Allow retrying the request until it either returns no error, +// or we exceed the number of max retries +func (c *Client) retryRequest(req *http.Request) (*http.Response, error) { + // Double check maxRetries is not nil + var retries int + if c.maxRetries == nil { + retries = DEFAULT_MAX_RETRIES + } else { + retries = *c.maxRetries + } + + var statusCode int + var errMessage string + + for i := 0; i < retries; i++ { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { + return resp, nil + } + + buf := new(bytes.Buffer) + buf.ReadFrom(resp.Body) + errMessage = buf.String() + statusCode = resp.StatusCode + c.debugLogString(fmt.Sprintf("Encountered HTTP (%d) Error: %s", statusCode, errMessage)) + c.debugLogString(fmt.Sprintf("%d/%d retries left", i+1, retries)) + } + + oracleErr := &opc.OracleError{ + StatusCode: statusCode, + Message: errMessage, + } + + // We ran out of retries to make, return the error and response + return nil, oracleErr +} + func (c *Client) formatURL(path *url.URL) string { return c.apiEndpoint.ResolveReference(path).String() } diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/instances.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/instances.go index 3c27db611..0b166f991 100644 --- a/vendor/github.com/hashicorp/go-oracle-terraform/compute/instances.go +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/instances.go @@ -32,11 +32,20 @@ const ( InstanceRunning InstanceState = "running" InstanceInitializing InstanceState = "initializing" InstancePreparing InstanceState = "preparing" + InstanceStarting InstanceState = "starting" InstanceStopping InstanceState = "stopping" + InstanceShutdown InstanceState = "shutdown" InstanceQueued InstanceState = "queued" InstanceError InstanceState = "error" ) +type InstanceDesiredState string + +const ( + InstanceDesiredRunning InstanceDesiredState = "running" + InstanceDesiredShutdown InstanceDesiredState = "shutdown" +) + // InstanceInfo represents the Compute API's view of the state of an instance. type InstanceInfo struct { // The ID for the instance. Set by the SDK based on the request - not the API. @@ -55,6 +64,9 @@ type InstanceInfo struct { // The default domain to use for the hostname and DNS lookups Domain string `json:"domain"` + // The desired state of an instance + DesiredState InstanceDesiredState `json:"desired_state"` + // Optional ImageListEntry number. Default will be used if not specified Entry int `json:"entry"` @@ -160,6 +172,10 @@ type CreateInstanceInput struct { // Boot order list // Optional BootOrder []int `json:"boot_order"` + // The desired state of the opc instance. Can only be `running` or `shutdown` + // Omits if empty. + // Optional + DesiredState InstanceDesiredState `json:"desired_state,omitempty"` // The host name assigned to the instance. On an Oracle Linux instance, // this host name is displayed in response to the hostname command. // Only relative DNS is supported. The domain name is suffixed to the host name @@ -374,6 +390,55 @@ func (c *InstancesClient) GetInstance(input *GetInstanceInput) (*InstanceInfo, e return &responseBody, nil } +type UpdateInstanceInput struct { + // Name of this instance, generated by the server. + // Required + Name string `json:"name"` + // The desired state of the opc instance. Can only be `running` or `shutdown` + // Omits if empty. + // Optional + DesiredState InstanceDesiredState `json:"desired_state,omitempty"` + // The ID of the instance + // Required + ID string `json:"-"` + // A list of tags to be supplied to the instance + // Optional + Tags []string `json:"tags,omitempty"` +} + +func (g *UpdateInstanceInput) String() string { + return fmt.Sprintf(CMP_QUALIFIED_NAME, g.Name, g.ID) +} + +func (c *InstancesClient) UpdateInstance(input *UpdateInstanceInput) (*InstanceInfo, error) { + if input.Name == "" || input.ID == "" { + return nil, errors.New("Both instance name and ID need to be specified") + } + + input.Name = fmt.Sprintf(CMP_QUALIFIED_NAME, c.getUserName(), input.Name) + + var responseBody InstanceInfo + if err := c.updateResource(input.String(), input, &responseBody); err != nil { + return nil, err + } + + getInput := &GetInstanceInput{ + Name: input.Name, + ID: input.ID, + } + + // Wait for the correct instance action depending on the current desired state. + // If the instance is already running, and the desired state is to be "running", the + // wait loop will only execute a single time to verify the instance state. Otherwise + // we wait until the correct action has finalized, either a shutdown or restart, catching + // any intermittent errors during the process. + if responseBody.DesiredState == InstanceDesiredRunning { + return c.WaitForInstanceRunning(getInput, WaitForInstanceReadyTimeout) + } else { + return c.WaitForInstanceShutdown(getInput, WaitForInstanceDeleteTimeout) + } +} + type DeleteInstanceInput struct { // The Unqualified Name of this Instance Name string @@ -407,7 +472,7 @@ func (c *InstancesClient) WaitForInstanceRunning(input *GetInstanceInput, timeou switch s := info.State; s { case InstanceError: return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason) - case InstanceRunning: + case InstanceRunning: // Target State c.debugLogString("Instance Running") return true, nil case InstanceQueued: @@ -419,6 +484,47 @@ func (c *InstancesClient) WaitForInstanceRunning(input *GetInstanceInput, timeou case InstancePreparing: c.debugLogString("Instance Preparing") return false, nil + case InstanceStarting: + c.debugLogString("Instance Starting") + return false, nil + default: + c.debugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s)) + return false, nil + } + }) + return info, err +} + +// WaitForInstanceShutdown waits for an instance to be shutdown +func (c *InstancesClient) WaitForInstanceShutdown(input *GetInstanceInput, timeoutSeconds int) (*InstanceInfo, error) { + var info *InstanceInfo + var getErr error + err := c.waitFor("instance to be shutdown", timeoutSeconds, func() (bool, error) { + info, getErr = c.GetInstance(input) + if getErr != nil { + return false, getErr + } + switch s := info.State; s { + case InstanceError: + return false, fmt.Errorf("Error initializing instance: %s", info.ErrorReason) + case InstanceRunning: + c.debugLogString("Instance Running") + return false, nil + case InstanceQueued: + c.debugLogString("Instance Queuing") + return false, nil + case InstanceInitializing: + c.debugLogString("Instance Initializing") + return false, nil + case InstancePreparing: + c.debugLogString("Instance Preparing") + return false, nil + case InstanceStarting: + c.debugLogString("Instance Starting") + return false, nil + case InstanceShutdown: // Target State + c.debugLogString("Instance Shutdown") + return true, nil default: c.debugLogString(fmt.Sprintf("Unknown instance state: %s, waiting", s)) return false, nil diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/test_utils.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/test_utils.go index 82b6047ff..1a910b147 100644 --- a/vendor/github.com/hashicorp/go-oracle-terraform/compute/test_utils.go +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/test_utils.go @@ -3,16 +3,14 @@ package compute import ( "bytes" "encoding/json" + "log" "net/http" "net/http/httptest" "net/url" "os" "testing" - "time" - "log" - "github.com/hashicorp/go-oracle-terraform/opc" ) diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/opc/convert.go b/vendor/github.com/hashicorp/go-oracle-terraform/opc/convert.go index 3fa365c1c..52fa08902 100644 --- a/vendor/github.com/hashicorp/go-oracle-terraform/opc/convert.go +++ b/vendor/github.com/hashicorp/go-oracle-terraform/opc/convert.go @@ -3,3 +3,7 @@ package opc func String(v string) *string { return &v } + +func Int(v int) *int { + return &v +} diff --git a/vendor/github.com/hashicorp/terraform-provider-opc/opc/config.go b/vendor/github.com/hashicorp/terraform-provider-opc/opc/config.go index b82594cca..2effe3996 100644 --- a/vendor/github.com/hashicorp/terraform-provider-opc/opc/config.go +++ b/vendor/github.com/hashicorp/terraform-provider-opc/opc/config.go @@ -1,6 +1,7 @@ package opc import ( + "crypto/tls" "fmt" "log" "net/url" @@ -13,16 +14,17 @@ import ( ) type Config struct { - User string - Password string - IdentityDomain string - Endpoint string - MaxRetryTimeout int + User string + Password string + IdentityDomain string + Endpoint string + MaxRetries int + Insecure bool } type OPCClient struct { - Client *compute.Client - MaxRetryTimeout int + Client *compute.Client + MaxRetries int } func (c *Config) Client() (*compute.Client, error) { @@ -36,7 +38,7 @@ func (c *Config) Client() (*compute.Client, error) { Username: &c.User, Password: &c.Password, APIEndpoint: u, - HTTPClient: cleanhttp.DefaultClient(), + MaxRetries: &c.MaxRetries, } if logging.IsDebugOrHigher() { @@ -44,6 +46,18 @@ func (c *Config) Client() (*compute.Client, error) { config.Logger = opcLogger{} } + // Setup HTTP Client based on insecure + httpClient := cleanhttp.DefaultClient() + if c.Insecure { + transport := cleanhttp.DefaultTransport() + transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + httpClient.Transport = transport + } + + config.HTTPClient = httpClient + return compute.NewComputeClient(&config) } diff --git a/vendor/github.com/hashicorp/terraform-provider-opc/opc/provider.go b/vendor/github.com/hashicorp/terraform-provider-opc/opc/provider.go index 8f52bd874..bd5ea9156 100644 --- a/vendor/github.com/hashicorp/terraform-provider-opc/opc/provider.go +++ b/vendor/github.com/hashicorp/terraform-provider-opc/opc/provider.go @@ -36,12 +36,18 @@ func Provider() terraform.ResourceProvider { Description: "The HTTP endpoint for OPC API operations.", }, - // TODO Actually implement this - "max_retry_timeout": { + "max_retries": { Type: schema.TypeInt, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("OPC_MAX_RETRY_TIMEOUT", 3000), - Description: "Max num seconds to wait for successful response when operating on resources within OPC (defaults to 3000)", + DefaultFunc: schema.EnvDefaultFunc("OPC_MAX_RETRIES", 1), + Description: "Maximum number retries to wait for a successful response when operating on resources within OPC (defaults to 1)", + }, + + "insecure": { + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_INSECURE", false), + Description: "Skip TLS Verification for self-signed certificates. Should only be used if absolutely required.", }, }, @@ -82,11 +88,12 @@ func Provider() terraform.ResourceProvider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ - User: d.Get("user").(string), - Password: d.Get("password").(string), - IdentityDomain: d.Get("identity_domain").(string), - Endpoint: d.Get("endpoint").(string), - MaxRetryTimeout: d.Get("max_retry_timeout").(int), + User: d.Get("user").(string), + Password: d.Get("password").(string), + IdentityDomain: d.Get("identity_domain").(string), + Endpoint: d.Get("endpoint").(string), + MaxRetries: d.Get("max_retries").(int), + Insecure: d.Get("insecure").(bool), } return config.Client() diff --git a/vendor/github.com/hashicorp/terraform-provider-opc/opc/resource_instance.go b/vendor/github.com/hashicorp/terraform-provider-opc/opc/resource_instance.go index 7e413b13f..615f4a6c9 100644 --- a/vendor/github.com/hashicorp/terraform-provider-opc/opc/resource_instance.go +++ b/vendor/github.com/hashicorp/terraform-provider-opc/opc/resource_instance.go @@ -18,6 +18,7 @@ func resourceInstance() *schema.Resource { return &schema.Resource{ Create: resourceInstanceCreate, Read: resourceInstanceRead, + Update: resourceInstanceUpdate, Delete: resourceInstanceDelete, Importer: &schema.ResourceImporter{ State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { @@ -84,6 +85,12 @@ func resourceInstance() *schema.Resource { ForceNew: true, }, + "desired_state": { + Type: schema.TypeString, + Optional: true, + Default: compute.InstanceDesiredRunning, + }, + "networking_info": { Type: schema.TypeSet, Optional: true, @@ -213,6 +220,14 @@ func resourceInstance() *schema.Resource { "storage": { Type: schema.TypeSet, Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + desired := compute.InstanceDesiredState(d.Get("desired_state").(string)) + state := compute.InstanceState(d.Get("state").(string)) + if desired == compute.InstanceDesiredShutdown || state == compute.InstanceShutdown { + return true + } + return false + }, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -265,6 +280,11 @@ func resourceInstance() *schema.Resource { Computed: true, }, + "fqdn": { + Type: schema.TypeString, + Computed: true, + }, + "image_format": { Type: schema.TypeString, Computed: true, @@ -456,7 +476,13 @@ func updateInstanceAttributes(d *schema.ResourceData, instance *compute.Instance if err := setIntList(d, "boot_order", instance.BootOrder); err != nil { return err } - d.Set("hostname", instance.Hostname) + + split_hostname := strings.Split(instance.Hostname, ".") + if len(split_hostname) == 0 { + return fmt.Errorf("Unable to parse hostname: %s", instance.Hostname) + } + d.Set("hostname", split_hostname[0]) + d.Set("fqdn", instance.Hostname) d.Set("image_list", instance.ImageList) d.Set("label", instance.Label) @@ -482,6 +508,7 @@ func updateInstanceAttributes(d *schema.ResourceData, instance *compute.Instance d.Set("fingerprint", instance.Fingerprint) d.Set("image_format", instance.ImageFormat) d.Set("ip_address", instance.IPAddress) + d.Set("desired_state", instance.DesiredState) if err := setStringList(d, "placement_requirements", instance.PlacementRequirements); err != nil { return err @@ -514,6 +541,36 @@ func updateInstanceAttributes(d *schema.ResourceData, instance *compute.Instance return nil } +func resourceInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*compute.Client).Instances() + + name := d.Get("name").(string) + + input := &compute.UpdateInstanceInput{ + Name: name, + ID: d.Id(), + } + + if d.HasChange("desired_state") { + input.DesiredState = compute.InstanceDesiredState(d.Get("desired_state").(string)) + } + + if d.HasChange("tags") { + tags := getStringList(d, "tags") + input.Tags = tags + + } + + result, err := client.UpdateInstance(input) + if err != nil { + return fmt.Errorf("Error updating instance %s: %s", input.Name, err) + } + + log.Printf("[DEBUG] Updated instance %s: %#v", result.Name, result.ID) + + return resourceInstanceRead(d, meta) +} + func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*compute.Client).Instances() diff --git a/vendor/vendor.json b/vendor/vendor.json index efd685e28..56c24b127 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2078,22 +2078,28 @@ "revision": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5" }, { - "checksumSHA1": "gyD43l5lH8orGQl7TiqLHRNFtFU=", + "checksumSHA1": "pUFuRSzqgTm6BiYFRTgNj3LnvVU=", "path": "github.com/hashicorp/go-oracle-terraform/compute", - "revision": "cf979675cc5074d460720f7b97626687bcb1e203", - "revisionTime": "2017-05-09T16:19:50Z" + "revision": "f1c5e84899f45d77e94d06bd3c9e61be2165f584", + "revisionTime": "2017-05-31T19:26:20Z", + "version": "v0.1.1", + "versionExact": "v0.1.1" }, { "checksumSHA1": "DzK7lYwHt5Isq5Zf73cnQqBO2LI=", "path": "github.com/hashicorp/go-oracle-terraform/helper", - "revision": "cf979675cc5074d460720f7b97626687bcb1e203", - "revisionTime": "2017-05-09T16:19:50Z" + "revision": "f1c5e84899f45d77e94d06bd3c9e61be2165f584", + "revisionTime": "2017-05-31T19:26:20Z", + "version": "v0.1.1", + "versionExact": "v0.1.1" }, { - "checksumSHA1": "AyNRs19Es9pDw2VMxVKWuLx3Afg=", + "checksumSHA1": "JbuYtbJkx3r1BLuCMymkri0Q1BI=", "path": "github.com/hashicorp/go-oracle-terraform/opc", - "revision": "cf979675cc5074d460720f7b97626687bcb1e203", - "revisionTime": "2017-05-09T16:19:50Z" + "revision": "f1c5e84899f45d77e94d06bd3c9e61be2165f584", + "revisionTime": "2017-05-31T19:26:20Z", + "version": "v0.1.1", + "versionExact": "v0.1.1" }, { "checksumSHA1": "b0nQutPMJHeUmz4SjpreotAo6Yk=", @@ -2263,10 +2269,12 @@ "revisionTime": "2017-03-08T19:39:51Z" }, { - "checksumSHA1": "NWP140S/k5J2L9nLVVgrt9rEh1g=", + "checksumSHA1": "zYSPNTuMDAnTygPEmi8gNeBP/40=", "path": "github.com/hashicorp/terraform-provider-opc/opc", - "revision": "bf837a8edaadefbac871feb8560faa60f811c8d9", - "revisionTime": "2017-05-23T21:46:41Z" + "revision": "291363639bb9403d0c76a6515f04e4b1b680b07f", + "revisionTime": "2017-06-07T13:48:21Z", + "version": "v0.1.3", + "versionExact": "v0.1.3" }, { "checksumSHA1": "2fkVZIzvxIGBLhSiVnkTgGiqpQ4=",