github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/http/backend.go (about) 1 package http 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "net/http" 8 "net/url" 9 "time" 10 11 "github.com/hashicorp/go-cleanhttp" 12 "github.com/hashicorp/go-retryablehttp" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/helper/schema" 15 "github.com/hashicorp/terraform/state" 16 "github.com/hashicorp/terraform/state/remote" 17 ) 18 19 func New() backend.Backend { 20 s := &schema.Backend{ 21 Schema: map[string]*schema.Schema{ 22 "address": &schema.Schema{ 23 Type: schema.TypeString, 24 Required: true, 25 Description: "The address of the REST endpoint", 26 }, 27 "update_method": &schema.Schema{ 28 Type: schema.TypeString, 29 Optional: true, 30 Default: "POST", 31 Description: "HTTP method to use when updating state", 32 }, 33 "lock_address": &schema.Schema{ 34 Type: schema.TypeString, 35 Optional: true, 36 Description: "The address of the lock REST endpoint", 37 }, 38 "unlock_address": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 Description: "The address of the unlock REST endpoint", 42 }, 43 "lock_method": &schema.Schema{ 44 Type: schema.TypeString, 45 Optional: true, 46 Default: "LOCK", 47 Description: "The HTTP method to use when locking", 48 }, 49 "unlock_method": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 Default: "UNLOCK", 53 Description: "The HTTP method to use when unlocking", 54 }, 55 "username": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 Description: "The username for HTTP basic authentication", 59 }, 60 "password": &schema.Schema{ 61 Type: schema.TypeString, 62 Optional: true, 63 Description: "The password for HTTP basic authentication", 64 }, 65 "skip_cert_verification": &schema.Schema{ 66 Type: schema.TypeBool, 67 Optional: true, 68 Default: false, 69 Description: "Whether to skip TLS verification.", 70 }, 71 "retry_max": &schema.Schema{ 72 Type: schema.TypeInt, 73 Optional: true, 74 Default: 2, 75 Description: "The number of HTTP request retries.", 76 }, 77 "retry_wait_min": &schema.Schema{ 78 Type: schema.TypeInt, 79 Optional: true, 80 Default: 1, 81 Description: "The minimum time in seconds to wait between HTTP request attempts.", 82 }, 83 "retry_wait_max": &schema.Schema{ 84 Type: schema.TypeInt, 85 Optional: true, 86 Default: 30, 87 Description: "The maximum time in seconds to wait between HTTP request attempts.", 88 }, 89 }, 90 } 91 92 b := &Backend{Backend: s} 93 b.Backend.ConfigureFunc = b.configure 94 return b 95 } 96 97 type Backend struct { 98 *schema.Backend 99 100 client *httpClient 101 } 102 103 func (b *Backend) configure(ctx context.Context) error { 104 data := schema.FromContextBackendConfig(ctx) 105 106 address := data.Get("address").(string) 107 updateURL, err := url.Parse(address) 108 if err != nil { 109 return fmt.Errorf("failed to parse address URL: %s", err) 110 } 111 if updateURL.Scheme != "http" && updateURL.Scheme != "https" { 112 return fmt.Errorf("address must be HTTP or HTTPS") 113 } 114 115 updateMethod := data.Get("update_method").(string) 116 117 var lockURL *url.URL 118 if v, ok := data.GetOk("lock_address"); ok && v.(string) != "" { 119 var err error 120 lockURL, err = url.Parse(v.(string)) 121 if err != nil { 122 return fmt.Errorf("failed to parse lockAddress URL: %s", err) 123 } 124 if lockURL.Scheme != "http" && lockURL.Scheme != "https" { 125 return fmt.Errorf("lockAddress must be HTTP or HTTPS") 126 } 127 } 128 129 lockMethod := data.Get("lock_method").(string) 130 131 var unlockURL *url.URL 132 if v, ok := data.GetOk("unlock_address"); ok && v.(string) != "" { 133 var err error 134 unlockURL, err = url.Parse(v.(string)) 135 if err != nil { 136 return fmt.Errorf("failed to parse unlockAddress URL: %s", err) 137 } 138 if unlockURL.Scheme != "http" && unlockURL.Scheme != "https" { 139 return fmt.Errorf("unlockAddress must be HTTP or HTTPS") 140 } 141 } 142 143 unlockMethod := data.Get("unlock_method").(string) 144 145 client := cleanhttp.DefaultPooledClient() 146 147 if data.Get("skip_cert_verification").(bool) { 148 // ignores TLS verification 149 client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ 150 InsecureSkipVerify: true, 151 } 152 } 153 154 rClient := retryablehttp.NewClient() 155 rClient.HTTPClient = client 156 rClient.RetryMax = data.Get("retry_max").(int) 157 rClient.RetryWaitMin = time.Duration(data.Get("retry_wait_min").(int)) * time.Second 158 rClient.RetryWaitMax = time.Duration(data.Get("retry_wait_max").(int)) * time.Second 159 160 b.client = &httpClient{ 161 URL: updateURL, 162 UpdateMethod: updateMethod, 163 164 LockURL: lockURL, 165 LockMethod: lockMethod, 166 UnlockURL: unlockURL, 167 UnlockMethod: unlockMethod, 168 169 Username: data.Get("username").(string), 170 Password: data.Get("password").(string), 171 172 // accessible only for testing use 173 Client: rClient, 174 } 175 return nil 176 } 177 178 func (b *Backend) StateMgr(name string) (state.State, error) { 179 if name != backend.DefaultStateName { 180 return nil, backend.ErrWorkspacesNotSupported 181 } 182 183 return &remote.State{Client: b.client}, nil 184 } 185 186 func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) { 187 return b.StateMgr(name) 188 } 189 190 func (b *Backend) Workspaces() ([]string, error) { 191 return nil, backend.ErrWorkspacesNotSupported 192 } 193 194 func (b *Backend) DeleteWorkspace(string) error { 195 return backend.ErrWorkspacesNotSupported 196 }