1package framework
2
3import (
4	"fmt"
5	"os"
6	"testing"
7
8	capi "github.com/hashicorp/consul/api"
9	napi "github.com/hashicorp/nomad/api"
10	"github.com/hashicorp/nomad/helper/uuid"
11	vapi "github.com/hashicorp/vault/api"
12)
13
14// ClusterInfo is a handle to a provisioned cluster, along with clients
15// a test run can use to connect to the cluster.
16type ClusterInfo struct {
17	ID           string
18	Name         string
19	NomadClient  *napi.Client
20	ConsulClient *capi.Client
21	VaultClient  *vapi.Client
22}
23
24// SetupOptions defines options to be given to the Provisioner when
25// calling Setup* methods.
26type SetupOptions struct {
27	Name         string
28	ExpectConsul bool // If true, fails if a Consul client can't be configured
29	ExpectVault  bool // If true, fails if a Vault client can't be configured
30}
31
32// Provisioner interface is used by the test framework to provision API
33// clients for a Nomad cluster, with the possibility of extending to provision
34// standalone clusters for each test case in the future.
35//
36// The Setup* methods are hooks that get run at the appropriate stage. They
37// return a ClusterInfo handle that helps TestCases isolate test state if
38// they use the ClusterInfo.ID as part of job IDs.
39//
40// The TearDown* methods are hooks to clean up provisioned cluster state
41// that isn't covered by the test case's implementation of AfterEachTest.
42type Provisioner interface {
43	// SetupTestRun is called at the start of the entire test run.
44	SetupTestRun(t *testing.T, opts SetupOptions) (*ClusterInfo, error)
45
46	// SetupTestSuite is called at the start of each TestSuite.
47	// TODO: no current provisioner implementation uses this, but we
48	// could use it to provide each TestSuite with an entirely separate
49	// Nomad cluster.
50	SetupTestSuite(t *testing.T, opts SetupOptions) (*ClusterInfo, error)
51
52	// SetupTestCase is called at the start of each TestCase in every TestSuite.
53	SetupTestCase(t *testing.T, opts SetupOptions) (*ClusterInfo, error)
54
55	// TODO: no current provisioner implementation uses any of these,
56	// but it's the obvious need if we setup/teardown after each TestSuite
57	// or TestCase.
58
59	// TearDownTestCase is called after each TestCase in every TestSuite.
60	TearDownTestCase(t *testing.T, clusterID string) error
61
62	// TearDownTestSuite is called after every TestSuite.
63	TearDownTestSuite(t *testing.T, clusterID string) error
64
65	// TearDownTestRun is called at the end of the entire test run.
66	TearDownTestRun(t *testing.T, clusterID string) error
67}
68
69// DefaultProvisioner is a Provisioner that doesn't deploy a Nomad cluster
70// (because that's handled by Terraform elsewhere), but build clients from
71// environment variables.
72var DefaultProvisioner Provisioner = new(singleClusterProvisioner)
73
74type singleClusterProvisioner struct{}
75
76// SetupTestRun in the default case is a no-op.
77func (p *singleClusterProvisioner) SetupTestRun(t *testing.T, opts SetupOptions) (*ClusterInfo, error) {
78	return &ClusterInfo{ID: "framework", Name: "framework"}, nil
79}
80
81// SetupTestSuite in the default case is a no-op.
82func (p *singleClusterProvisioner) SetupTestSuite(t *testing.T, opts SetupOptions) (*ClusterInfo, error) {
83	return &ClusterInfo{
84		ID:   uuid.Generate()[:8],
85		Name: opts.Name,
86	}, nil
87}
88
89// SetupTestCase in the default case only creates new clients and embeds the
90// TestCase name into the ClusterInfo handle.
91func (p *singleClusterProvisioner) SetupTestCase(t *testing.T, opts SetupOptions) (*ClusterInfo, error) {
92	// Build ID based off given name
93	info := &ClusterInfo{
94		ID:   uuid.Generate()[:8],
95		Name: opts.Name,
96	}
97
98	// Build Nomad api client
99	nomadClient, err := napi.NewClient(napi.DefaultConfig())
100	if err != nil {
101		return nil, err
102	}
103	info.NomadClient = nomadClient
104
105	if opts.ExpectConsul {
106		consulClient, err := capi.NewClient(capi.DefaultConfig())
107		if err != nil {
108			return nil, fmt.Errorf("expected Consul: %v", err)
109		}
110		info.ConsulClient = consulClient
111	}
112
113	if len(os.Getenv(vapi.EnvVaultAddress)) != 0 {
114		vaultClient, err := vapi.NewClient(vapi.DefaultConfig())
115		if err != nil && opts.ExpectVault {
116			return nil, err
117		}
118		info.VaultClient = vaultClient
119	} else if opts.ExpectVault {
120		return nil, fmt.Errorf("vault client expected but environment variable %s not set",
121			vapi.EnvVaultAddress)
122	}
123
124	return info, err
125}
126
127// all TearDown* methods of the default provisioner leave the test environment in place
128
129func (p *singleClusterProvisioner) TearDownTestCase(_ *testing.T, _ string) error  { return nil }
130func (p *singleClusterProvisioner) TearDownTestSuite(_ *testing.T, _ string) error { return nil }
131func (p *singleClusterProvisioner) TearDownTestRun(_ *testing.T, _ string) error   { return nil }
132