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