Skip to content

Commit e546b8f

Browse files
authored
[QTI-284] Allow complex provider configuration (#66)
* [QTI-284] Allow complex provider configuration This change modifies how we decode and store provider information passed into from the scenario. Before we would only allow attributes, now we allow any number of attributes and blocks. * Create a schemaless block type to handle decoding blocks that have unknown schema. * Update provider decoder to use a schemaless block for its configuration. * Add kubernetes transport support to enos-provider decoder * Add acceptance test for generation * Update the generator to support schemaless blocks in the provider Signed-off-by: Ryan Cragun <[email protected]>
1 parent 546d738 commit e546b8f

File tree

10 files changed

+772
-123
lines changed

10 files changed

+772
-123
lines changed

acceptance/scenario_generate_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ func TestAcc_Cmd_Scenario_Generate(t *testing.T) {
6666
[][]string{{"skip", "keep"}},
6767
fmt.Sprintf("%x", sha256.Sum256([]byte("path [skip:keep]"))),
6868
},
69+
{
70+
"scenario_generate_complex_provider",
71+
"kubernetes",
72+
[][]string{},
73+
fmt.Sprintf("%x", sha256.Sum256([]byte("kubernetes"))),
74+
},
6975
} {
7076
t.Run(fmt.Sprintf("%s %s %s", test.dir, test.name, test.variants), func(t *testing.T) {
7177
outDir := filepath.Join(tmpDir, test.dir)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
terraform "default" {
2+
required_version = ">= 1.0.0"
3+
4+
required_providers {
5+
kubernetes = {
6+
source = "hashicorp/kubernetes"
7+
}
8+
}
9+
}
10+
11+
module "kubernetes" {
12+
source = "./modules/kubernetes"
13+
}
14+
15+
provider "kubernetes" "default" {
16+
host = "http://example.com"
17+
cluster_ca_certificate = "base64cert"
18+
exec {
19+
api_version = "client.authentication.k8s.io/v1alpha1"
20+
args = ["eks", "get-token", "--cluster-name", "foo"]
21+
command = "aws"
22+
}
23+
}
24+
25+
scenario "kubernetes" {
26+
providers = [
27+
provider.kubernetes.default
28+
]
29+
30+
step "kubernetes" {
31+
module = module.kubernetes
32+
}
33+
}

acceptance/scenarios/scenario_generate_complex_provider/enos.vars.hcl

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "random" {
2+
value = "notactuallyrandom"
3+
}

internal/flightplan/flightplan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func (fp *FlightPlan) decodeTerraformCLIs(ctx *hcl.EvalContext) hcl.Diagnostics
328328
// top-level schema.
329329
func (fp *FlightPlan) decodeProviders(ctx *hcl.EvalContext) hcl.Diagnostics {
330330
var diags hcl.Diagnostics
331-
// type -> map of aliases -> provider object
331+
// provider type -> alias name -> provider object value
332332
providers := map[string]map[string]cty.Value{}
333333

334334
for _, block := range fp.BodyContent.Blocks.OfType(blockTypeProvider) {

internal/flightplan/provider.go

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import (
1313

1414
// Provider is a Enos transport configuration
1515
type Provider struct {
16-
Type string `cty:"type"`
17-
Alias string `cty:"alias"`
18-
Attrs map[string]cty.Value `cty:"attrs"`
16+
Type string `cty:"type"`
17+
Alias string `cty:"alias"`
18+
Config *SchemalessBlock `cty:"config"`
1919
}
2020

2121
// NewProvider returns a new Provider
2222
func NewProvider() *Provider {
2323
return &Provider{
24-
Attrs: map[string]cty.Value{},
24+
Config: NewSchemalessBlock(),
2525
}
2626
}
2727

@@ -36,51 +36,38 @@ func (p *Provider) decode(block *hcl.Block, ctx *hcl.EvalContext) hcl.Diagnostic
3636

3737
if p.Type == "enos" {
3838
// Since we know the schema for the "enos" provider we can more fine
39-
// grained decoding.
39+
// grained decoding and validation.
4040
moreDiags := p.decodeEnosProvider(block, ctx)
4141
diags = diags.Extend(moreDiags)
4242
if moreDiags.HasErrors() {
4343
return diags
4444
}
4545
} else {
46-
attrs, moreDiags := block.Body.JustAttributes()
46+
// Decode the entire provider block as a schemaless block
47+
moreDiags := p.Config.Decode(block, ctx)
4748
diags = diags.Extend(moreDiags)
4849
if moreDiags.HasErrors() {
4950
return diags
5051
}
51-
52-
for name, attr := range attrs {
53-
val, moreDiags := attr.Expr.Value(ctx)
54-
diags = diags.Extend(moreDiags)
55-
if moreDiags.HasErrors() {
56-
continue
57-
}
58-
p.Attrs[name] = val
59-
}
6052
}
6153

6254
return diags
6355
}
6456

6557
// ToCtyValue returns the provider contents as an object cty.Value.
6658
func (p *Provider) ToCtyValue() cty.Value {
67-
vals := map[string]cty.Value{
68-
"type": cty.StringVal(p.Type),
69-
"alias": cty.StringVal(p.Alias),
70-
}
71-
72-
if len(p.Attrs) > 0 {
73-
vals["attrs"] = cty.ObjectVal(p.Attrs)
74-
} else {
75-
vals["attrs"] = cty.NullVal(cty.EmptyObject)
76-
}
77-
78-
return cty.ObjectVal(vals)
59+
return cty.ObjectVal(map[string]cty.Value{
60+
"type": cty.StringVal(p.Type),
61+
"alias": cty.StringVal(p.Alias),
62+
"config": p.Config.ToCtyValue(),
63+
})
7964
}
8065

8166
// FromCtyValue takes a cty.Value and unmarshals it onto itself. It expects
8267
// a valid object created from ToCtyValue()
8368
func (p *Provider) FromCtyValue(val cty.Value) error {
69+
var err error
70+
8471
if val.IsNull() {
8572
return nil
8673
}
@@ -105,13 +92,10 @@ func (p *Provider) FromCtyValue(val cty.Value) error {
10592
return fmt.Errorf("provider alias must be a string ")
10693
}
10794
p.Alias = val.AsString()
108-
case "attrs":
109-
if !val.CanIterateElements() {
110-
return fmt.Errorf("provider attrs must a map of attributes")
111-
}
112-
113-
for k, v := range val.AsValueMap() {
114-
p.Attrs[k] = v
95+
case "config":
96+
err = p.Config.FromCtyValue(val)
97+
if err != nil {
98+
return err
11599
}
116100
default:
117101
return fmt.Errorf("unknown key in value object: %s", key)
@@ -141,7 +125,17 @@ func (p *Provider) decodeEnosProvider(block *hcl.Block, ctx *hcl.EvalContext) hc
141125
"user", "host", "private_key", "private_key_path",
142126
"passphrase", "passphrase_path",
143127
}),
144-
}, []string{"ssh"}),
128+
"kubernetes": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
129+
"kubeconfig_base64": cty.String,
130+
"context_name": cty.String,
131+
"namespace": cty.String,
132+
"pod": cty.String,
133+
"container": cty.String,
134+
}, []string{
135+
"kubeconfig_base64", "context_name", "namespace", "pod",
136+
"container",
137+
}),
138+
}, []string{"ssh", "kubernetes"}),
145139
},
146140
}
147141

@@ -164,15 +158,23 @@ func (p *Provider) decodeEnosProvider(block *hcl.Block, ctx *hcl.EvalContext) hc
164158
return diags
165159
}
166160

161+
// We should have either a valid k8s or ssh transport.
162+
p.Config.Type = "provider"
163+
p.Config.Labels = []string{p.Type, p.Alias}
164+
p.Config.Attrs["transport"] = trans
165+
167166
ssh, ok := trans.AsValueMap()["ssh"]
168167
if !ok {
168+
// We're done, we're not going to do anything else with k8s
169169
return diags
170170
}
171171

172172
if ssh.IsNull() || !ssh.IsWhollyKnown() || !ssh.CanIterateElements() {
173173
return diags
174174
}
175175

176+
// We have an ssh transport. Make sure any of the paths that we've been
177+
// given exist.
176178
sshVals := map[string]cty.Value{}
177179
for name, val := range ssh.AsValueMap() {
178180
// Only pass through known values
@@ -224,7 +226,7 @@ func (p *Provider) decodeEnosProvider(block *hcl.Block, ctx *hcl.EvalContext) hc
224226
}
225227
}
226228

227-
p.Attrs["transport"] = cty.ObjectVal(map[string]cty.Value{
229+
p.Config.Attrs["transport"] = cty.ObjectVal(map[string]cty.Value{
228230
"ssh": cty.ObjectVal(sshVals),
229231
})
230232

0 commit comments

Comments
 (0)