| package http |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "fmt" |
| "log" |
| "net/http" |
| "net/url" |
| "time" |
| |
| "github.com/hashicorp/go-cleanhttp" |
| "github.com/hashicorp/go-retryablehttp" |
| "github.com/hashicorp/terraform/internal/backend" |
| "github.com/hashicorp/terraform/internal/legacy/helper/schema" |
| "github.com/hashicorp/terraform/internal/logging" |
| "github.com/hashicorp/terraform/internal/states/remote" |
| "github.com/hashicorp/terraform/internal/states/statemgr" |
| ) |
| |
| func New() backend.Backend { |
| s := &schema.Backend{ |
| Schema: map[string]*schema.Schema{ |
| "address": &schema.Schema{ |
| Type: schema.TypeString, |
| Required: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_ADDRESS", nil), |
| Description: "The address of the REST endpoint", |
| }, |
| "update_method": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UPDATE_METHOD", "POST"), |
| Description: "HTTP method to use when updating state", |
| }, |
| "lock_address": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_LOCK_ADDRESS", nil), |
| Description: "The address of the lock REST endpoint", |
| }, |
| "unlock_address": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UNLOCK_ADDRESS", nil), |
| Description: "The address of the unlock REST endpoint", |
| }, |
| "lock_method": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_LOCK_METHOD", "LOCK"), |
| Description: "The HTTP method to use when locking", |
| }, |
| "unlock_method": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UNLOCK_METHOD", "UNLOCK"), |
| Description: "The HTTP method to use when unlocking", |
| }, |
| "username": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_USERNAME", nil), |
| Description: "The username for HTTP basic authentication", |
| }, |
| "password": &schema.Schema{ |
| Type: schema.TypeString, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_PASSWORD", nil), |
| Description: "The password for HTTP basic authentication", |
| }, |
| "skip_cert_verification": &schema.Schema{ |
| Type: schema.TypeBool, |
| Optional: true, |
| Default: false, |
| Description: "Whether to skip TLS verification.", |
| }, |
| "retry_max": &schema.Schema{ |
| Type: schema.TypeInt, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_MAX", 2), |
| Description: "The number of HTTP request retries.", |
| }, |
| "retry_wait_min": &schema.Schema{ |
| Type: schema.TypeInt, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MIN", 1), |
| Description: "The minimum time in seconds to wait between HTTP request attempts.", |
| }, |
| "retry_wait_max": &schema.Schema{ |
| Type: schema.TypeInt, |
| Optional: true, |
| DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MAX", 30), |
| Description: "The maximum time in seconds to wait between HTTP request attempts.", |
| }, |
| }, |
| } |
| |
| b := &Backend{Backend: s} |
| b.Backend.ConfigureFunc = b.configure |
| return b |
| } |
| |
| type Backend struct { |
| *schema.Backend |
| |
| client *httpClient |
| } |
| |
| func (b *Backend) configure(ctx context.Context) error { |
| data := schema.FromContextBackendConfig(ctx) |
| |
| address := data.Get("address").(string) |
| updateURL, err := url.Parse(address) |
| if err != nil { |
| return fmt.Errorf("failed to parse address URL: %s", err) |
| } |
| if updateURL.Scheme != "http" && updateURL.Scheme != "https" { |
| return fmt.Errorf("address must be HTTP or HTTPS") |
| } |
| |
| updateMethod := data.Get("update_method").(string) |
| |
| var lockURL *url.URL |
| if v, ok := data.GetOk("lock_address"); ok && v.(string) != "" { |
| var err error |
| lockURL, err = url.Parse(v.(string)) |
| if err != nil { |
| return fmt.Errorf("failed to parse lockAddress URL: %s", err) |
| } |
| if lockURL.Scheme != "http" && lockURL.Scheme != "https" { |
| return fmt.Errorf("lockAddress must be HTTP or HTTPS") |
| } |
| } |
| |
| lockMethod := data.Get("lock_method").(string) |
| |
| var unlockURL *url.URL |
| if v, ok := data.GetOk("unlock_address"); ok && v.(string) != "" { |
| var err error |
| unlockURL, err = url.Parse(v.(string)) |
| if err != nil { |
| return fmt.Errorf("failed to parse unlockAddress URL: %s", err) |
| } |
| if unlockURL.Scheme != "http" && unlockURL.Scheme != "https" { |
| return fmt.Errorf("unlockAddress must be HTTP or HTTPS") |
| } |
| } |
| |
| unlockMethod := data.Get("unlock_method").(string) |
| |
| client := cleanhttp.DefaultPooledClient() |
| |
| if data.Get("skip_cert_verification").(bool) { |
| // ignores TLS verification |
| client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ |
| InsecureSkipVerify: true, |
| } |
| } |
| |
| rClient := retryablehttp.NewClient() |
| rClient.HTTPClient = client |
| rClient.RetryMax = data.Get("retry_max").(int) |
| rClient.RetryWaitMin = time.Duration(data.Get("retry_wait_min").(int)) * time.Second |
| rClient.RetryWaitMax = time.Duration(data.Get("retry_wait_max").(int)) * time.Second |
| rClient.Logger = log.New(logging.LogOutput(), "", log.Flags()) |
| |
| b.client = &httpClient{ |
| URL: updateURL, |
| UpdateMethod: updateMethod, |
| |
| LockURL: lockURL, |
| LockMethod: lockMethod, |
| UnlockURL: unlockURL, |
| UnlockMethod: unlockMethod, |
| |
| Username: data.Get("username").(string), |
| Password: data.Get("password").(string), |
| |
| // accessible only for testing use |
| Client: rClient, |
| } |
| return nil |
| } |
| |
| func (b *Backend) StateMgr(name string) (statemgr.Full, error) { |
| if name != backend.DefaultStateName { |
| return nil, backend.ErrWorkspacesNotSupported |
| } |
| |
| return &remote.State{Client: b.client}, nil |
| } |
| |
| func (b *Backend) Workspaces() ([]string, error) { |
| return nil, backend.ErrWorkspacesNotSupported |
| } |
| |
| func (b *Backend) DeleteWorkspace(string) error { |
| return backend.ErrWorkspacesNotSupported |
| } |