blob: 8c2270d7605dfb9e40d1184f70937b54918ff101 [file] [log] [blame] [edit]
// 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