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  }