provider/opc: update opc provider (#15159)
* provider/opc: update opc provider * update opc-sdk
This commit is contained in:
parent
6b3ec0c193
commit
f64b5c9480
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,3 +3,7 @@ package opc
|
|||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Int(v int) *int {
|
||||
return &v
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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=",
|
||||
|
|
Loading…
Reference in New Issue