github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/remote-state/consul/backend.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package consul 5 6 import ( 7 "context" 8 "net" 9 "strings" 10 "time" 11 12 consulapi "github.com/hashicorp/consul/api" 13 "github.com/terramate-io/tf/backend" 14 "github.com/terramate-io/tf/legacy/helper/schema" 15 ) 16 17 // New creates a new backend for Consul remote state. 18 func New() backend.Backend { 19 s := &schema.Backend{ 20 Schema: map[string]*schema.Schema{ 21 "path": &schema.Schema{ 22 Type: schema.TypeString, 23 Required: true, 24 Description: "Path to store state in Consul", 25 }, 26 27 "access_token": &schema.Schema{ 28 Type: schema.TypeString, 29 Optional: true, 30 Description: "Access token for a Consul ACL", 31 Default: "", // To prevent input 32 }, 33 34 "address": &schema.Schema{ 35 Type: schema.TypeString, 36 Optional: true, 37 Description: "Address to the Consul Cluster", 38 Default: "", // To prevent input 39 }, 40 41 "scheme": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 Description: "Scheme to communicate to Consul with", 45 Default: "", // To prevent input 46 }, 47 48 "datacenter": &schema.Schema{ 49 Type: schema.TypeString, 50 Optional: true, 51 Description: "Datacenter to communicate with", 52 Default: "", // To prevent input 53 }, 54 55 "http_auth": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 Description: "HTTP Auth in the format of 'username:password'", 59 Default: "", // To prevent input 60 }, 61 62 "gzip": &schema.Schema{ 63 Type: schema.TypeBool, 64 Optional: true, 65 Description: "Compress the state data using gzip", 66 Default: false, 67 }, 68 69 "lock": &schema.Schema{ 70 Type: schema.TypeBool, 71 Optional: true, 72 Description: "Lock state access", 73 Default: true, 74 }, 75 76 "ca_file": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 Description: "A path to a PEM-encoded certificate authority used to verify the remote agent's certificate.", 80 DefaultFunc: schema.EnvDefaultFunc("CONSUL_CACERT", ""), 81 }, 82 83 "cert_file": &schema.Schema{ 84 Type: schema.TypeString, 85 Optional: true, 86 Description: "A path to a PEM-encoded certificate provided to the remote agent; requires use of key_file.", 87 DefaultFunc: schema.EnvDefaultFunc("CONSUL_CLIENT_CERT", ""), 88 }, 89 90 "key_file": &schema.Schema{ 91 Type: schema.TypeString, 92 Optional: true, 93 Description: "A path to a PEM-encoded private key, required if cert_file is specified.", 94 DefaultFunc: schema.EnvDefaultFunc("CONSUL_CLIENT_KEY", ""), 95 }, 96 }, 97 } 98 99 result := &Backend{Backend: s} 100 result.Backend.ConfigureFunc = result.configure 101 return result 102 } 103 104 type Backend struct { 105 *schema.Backend 106 107 // The fields below are set from configure 108 client *consulapi.Client 109 configData *schema.ResourceData 110 lock bool 111 } 112 113 func (b *Backend) configure(ctx context.Context) error { 114 // Grab the resource data 115 b.configData = schema.FromContextBackendConfig(ctx) 116 117 // Store the lock information 118 b.lock = b.configData.Get("lock").(bool) 119 120 data := b.configData 121 122 // Configure the client 123 config := consulapi.DefaultConfig() 124 125 // replace the default Transport Dialer to reduce the KeepAlive 126 config.Transport.DialContext = dialContext 127 128 if v, ok := data.GetOk("access_token"); ok && v.(string) != "" { 129 config.Token = v.(string) 130 } 131 if v, ok := data.GetOk("address"); ok && v.(string) != "" { 132 config.Address = v.(string) 133 } 134 if v, ok := data.GetOk("scheme"); ok && v.(string) != "" { 135 config.Scheme = v.(string) 136 } 137 if v, ok := data.GetOk("datacenter"); ok && v.(string) != "" { 138 config.Datacenter = v.(string) 139 } 140 141 if v, ok := data.GetOk("ca_file"); ok && v.(string) != "" { 142 config.TLSConfig.CAFile = v.(string) 143 } 144 if v, ok := data.GetOk("cert_file"); ok && v.(string) != "" { 145 config.TLSConfig.CertFile = v.(string) 146 } 147 if v, ok := data.GetOk("key_file"); ok && v.(string) != "" { 148 config.TLSConfig.KeyFile = v.(string) 149 } 150 151 if v, ok := data.GetOk("http_auth"); ok && v.(string) != "" { 152 auth := v.(string) 153 154 var username, password string 155 if strings.Contains(auth, ":") { 156 split := strings.SplitN(auth, ":", 2) 157 username = split[0] 158 password = split[1] 159 } else { 160 username = auth 161 } 162 163 config.HttpAuth = &consulapi.HttpBasicAuth{ 164 Username: username, 165 Password: password, 166 } 167 } 168 169 client, err := consulapi.NewClient(config) 170 if err != nil { 171 return err 172 } 173 174 b.client = client 175 return nil 176 } 177 178 // dialContext is the DialContext function for the consul client transport. 179 // This is stored in a package var to inject a different dialer for tests. 180 var dialContext = (&net.Dialer{ 181 Timeout: 30 * time.Second, 182 KeepAlive: 17 * time.Second, 183 }).DialContext