opentofu/internal/backend/remote-state/http/backend.go
Martin Atkins f40800b3a4 Move states/ to internal/states/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

198 lines
5.8 KiB
Go

package http
import (
"context"
"crypto/tls"
"fmt"
"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/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
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
}