| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package consul |
| |
| import ( |
| "net" |
| "strings" |
| "time" |
| |
| consulapi "github.com/hashicorp/consul/api" |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/backend" |
| "github.com/hashicorp/terraform/internal/backend/backendbase" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // New creates a new backend for Consul remote state. |
| func New() backend.Backend { |
| return &Backend{ |
| Base: backendbase.Base{ |
| Schema: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "path": { |
| Type: cty.String, |
| Required: true, |
| Description: "Path to store state in Consul", |
| }, |
| "access_token": { |
| Type: cty.String, |
| Optional: true, |
| Description: "Access token for a Consul ACL", |
| }, |
| "address": { |
| Type: cty.String, |
| Optional: true, |
| Description: "Address to the Consul Cluster", |
| }, |
| "scheme": { |
| Type: cty.String, |
| Optional: true, |
| Description: "Scheme to communicate to Consul with", |
| }, |
| "datacenter": { |
| Type: cty.String, |
| Optional: true, |
| Description: "Datacenter to communicate with", |
| }, |
| "http_auth": { |
| Type: cty.String, |
| Optional: true, |
| Description: "HTTP Auth in the format of 'username:password'", |
| }, |
| "gzip": { |
| Type: cty.Bool, |
| Optional: true, |
| Description: "Compress the state data using gzip", |
| }, |
| "lock": { |
| Type: cty.Bool, |
| Optional: true, |
| Description: "Lock state access", |
| }, |
| "ca_file": { |
| Type: cty.String, |
| Optional: true, |
| Description: "A path to a PEM-encoded certificate authority used to verify the remote agent's certificate", |
| }, |
| "cert_file": { |
| Type: cty.String, |
| Optional: true, |
| Description: "A path to a PEM-encoded certificate provided to the remote agent; requires use of key_file", |
| }, |
| "key_file": { |
| Type: cty.String, |
| Optional: true, |
| Description: "A path to a PEM-encoded private key, required if cert_file is specified", |
| }, |
| }, |
| }, |
| }, |
| } |
| } |
| |
| type Backend struct { |
| backendbase.Base |
| |
| // The fields below are set from configure |
| client *consulapi.Client |
| path string |
| gzip bool |
| lock bool |
| } |
| |
| func (b *Backend) Configure(configVal cty.Value) tfdiags.Diagnostics { |
| b.path = configVal.GetAttr("path").AsString() |
| b.gzip = backendbase.MustBoolValue( |
| backendbase.GetAttrDefault(configVal, "gzip", cty.False), |
| ) |
| b.lock = backendbase.MustBoolValue( |
| backendbase.GetAttrDefault(configVal, "lock", cty.True), |
| ) |
| |
| // Configure the client |
| config := consulapi.DefaultConfig() |
| |
| // replace the default Transport Dialer to reduce the KeepAlive |
| config.Transport.DialContext = dialContext |
| |
| empty := cty.StringVal("") |
| if v := backendbase.GetAttrDefault(configVal, "access_token", empty); v != empty { |
| config.Token = v.AsString() |
| } |
| if v := backendbase.GetAttrDefault(configVal, "address", empty); v != empty { |
| config.Address = v.AsString() |
| } |
| if v := backendbase.GetAttrDefault(configVal, "scheme", empty); v != empty { |
| config.Scheme = v.AsString() |
| } |
| if v := backendbase.GetAttrDefault(configVal, "datacenter", empty); v != empty { |
| config.Datacenter = v.AsString() |
| } |
| |
| if v := backendbase.GetAttrEnvDefaultFallback(configVal, "ca_file", "CONSUL_CACERT", empty); v != empty { |
| config.TLSConfig.CAFile = v.AsString() |
| } |
| if v := backendbase.GetAttrEnvDefaultFallback(configVal, "cert_file", "CONSUL_CLIENT_CERT", empty); v != empty { |
| config.TLSConfig.CertFile = v.AsString() |
| } |
| if v := backendbase.GetAttrEnvDefaultFallback(configVal, "key_file", "CONSUL_CLIENT_KEY", empty); v != empty { |
| config.TLSConfig.KeyFile = v.AsString() |
| } |
| |
| if v := backendbase.GetAttrDefault(configVal, "http_auth", empty); v != empty { |
| auth := v.AsString() |
| |
| var username, password string |
| if strings.Contains(auth, ":") { |
| split := strings.SplitN(auth, ":", 2) |
| username = split[0] |
| password = split[1] |
| } else { |
| username = auth |
| } |
| |
| config.HttpAuth = &consulapi.HttpBasicAuth{ |
| Username: username, |
| Password: password, |
| } |
| } |
| |
| client, err := consulapi.NewClient(config) |
| if err != nil { |
| return backendbase.ErrorAsDiagnostics(err) |
| } |
| |
| b.client = client |
| return nil |
| } |
| |
| // dialContext is the DialContext function for the consul client transport. |
| // This is stored in a package var to inject a different dialer for tests. |
| var dialContext = (&net.Dialer{ |
| Timeout: 30 * time.Second, |
| KeepAlive: 17 * time.Second, |
| }).DialContext |