Skip to content

Commit ed45869

Browse files
committed
Config Generation: Go implementation for executing Terraform binary
1 parent 3e3be17 commit ed45869

File tree

8 files changed

+102
-90
lines changed

8 files changed

+102
-90
lines changed

go.mod

+10-5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ require (
3333
golang.org/x/text v0.15.0
3434
)
3535

36+
require (
37+
github.com/hashicorp/go-version v1.6.0
38+
github.com/hashicorp/hc-install v0.6.4
39+
github.com/hashicorp/terraform-exec v0.21.0
40+
github.com/hashicorp/terraform-json v0.22.1
41+
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
42+
)
43+
3644
require (
3745
github.com/BurntSushi/toml v1.3.2 // indirect
3846
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
@@ -70,11 +78,7 @@ require (
7078
github.com/hashicorp/go-hclog v1.6.3 // indirect
7179
github.com/hashicorp/go-multierror v1.1.1 // indirect
7280
github.com/hashicorp/go-plugin v1.6.0 // indirect
73-
github.com/hashicorp/go-version v1.6.0 // indirect
74-
github.com/hashicorp/hc-install v0.6.4 // indirect
7581
github.com/hashicorp/logutils v1.0.0 // indirect
76-
github.com/hashicorp/terraform-exec v0.21.0 // indirect
77-
github.com/hashicorp/terraform-json v0.22.1 // indirect
7882
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
7983
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
8084
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
@@ -111,7 +115,6 @@ require (
111115
go.opentelemetry.io/otel/metric v1.24.0 // indirect
112116
go.opentelemetry.io/otel/trace v1.24.0 // indirect
113117
golang.org/x/crypto v0.23.0 // indirect
114-
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
115118
golang.org/x/mod v0.16.0 // indirect
116119
golang.org/x/net v0.24.0 // indirect
117120
golang.org/x/sync v0.7.0 // indirect
@@ -125,3 +128,5 @@ require (
125128
gopkg.in/yaml.v2 v2.4.0 // indirect
126129
gopkg.in/yaml.v3 v3.0.1 // indirect
127130
)
131+
132+
replace github.com/hashicorp/terraform-exec v0.21.0 => github.com/hrmsk66/terraform-exec v0.21.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdx
147147
github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
148148
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
149149
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
150-
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
151-
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
152150
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
153151
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
154152
github.com/hashicorp/terraform-plugin-docs v0.19.2 h1:YjdKa1vuqt9EnPYkkrv9HnGZz175HhSJ7Vsn8yZeWus=
@@ -171,6 +169,8 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S
171169
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
172170
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
173171
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
172+
github.com/hrmsk66/terraform-exec v0.21.0 h1:k/hnRAZULf6rkzJW7v2fRKAA1wAshyiUv1clw3RklrQ=
173+
github.com/hrmsk66/terraform-exec v0.21.0/go.mod h1:rHqaL9Y7oPlRDZffl2xr/UkSrIhKL2l9G5P81Sza7Ts=
174174
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
175175
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
176176
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=

pkg/generate/cloud.go

+18-16
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import (
1414
"github.com/grafana/terraform-provider-grafana/v3/internal/resources/cloud"
1515
"github.com/grafana/terraform-provider-grafana/v3/pkg/provider"
1616
"github.com/hashicorp/hcl/v2/hclwrite"
17+
"github.com/hashicorp/terraform-exec/tfexec"
1718
"github.com/hashicorp/terraform-plugin-framework/types"
1819
"github.com/zclconf/go-cty/cty"
1920
)
2021

2122
type stack struct {
23+
name string
2224
slug string
2325
url string
2426
managementKey string
@@ -66,7 +68,7 @@ func generateCloudResources(ctx context.Context, cfg *Config) ([]stack, error) {
6668
}
6769

6870
data := cloud.NewListerData(cfg.Cloud.Org)
69-
if err := generateImportBlocks(ctx, client, data, cloud.Resources, cfg.OutputDir, "cloud", cfg.IncludeResources); err != nil {
71+
if err := generateImportBlocks(ctx, client, data, cloud.Resources, cfg, "cloud"); err != nil {
7072
return nil, err
7173
}
7274

@@ -140,42 +142,42 @@ func generateCloudResources(ctx context.Context, cfg *Config) ([]stack, error) {
140142
}
141143

142144
// Apply then go into the state and find the management key
143-
err := runTerraform(cfg.OutputDir, "apply", "-auto-approve",
144-
"-target=grafana_cloud_stack_service_account."+stack.Slug,
145-
"-target=grafana_cloud_stack_service_account_token."+stack.Slug,
146-
"-target=grafana_cloud_access_policy."+policyResourceName,
147-
"-target=grafana_cloud_access_policy_token."+policyResourceName,
148-
"-target=grafana_synthetic_monitoring_installation."+stack.Slug,
145+
err := cfg.Terraform.Apply(ctx,
146+
tfexec.Target("grafana_cloud_stack_service_account."+stack.Slug),
147+
tfexec.Target("grafana_cloud_stack_service_account_token."+stack.Slug),
148+
tfexec.Target("grafana_cloud_access_policy."+policyResourceName),
149+
tfexec.Target("grafana_cloud_access_policy_token."+policyResourceName),
150+
tfexec.Target("grafana_synthetic_monitoring_installation."+stack.Slug),
149151
)
150152
if err != nil {
151153
return nil, fmt.Errorf("failed to apply management service account blocks for stack %q: %w", stack.Slug, err)
152154
}
153155
}
154156

155157
managedStacks := []stack{}
156-
state, err := getState(cfg.OutputDir)
158+
state, err := getState(ctx, cfg)
157159
if err != nil {
158160
return nil, err
159161
}
160162
stacksMap := map[string]stack{}
161-
for _, resource := range state.resources() {
162-
if resource.resourceType() == "grafana_cloud_stack_service_account_token" {
163-
slug := resource.values()["stack_slug"].(string)
163+
for _, resource := range state.Values.RootModule.Resources {
164+
if resource.Type == "grafana_cloud_stack_service_account_token" {
165+
slug := resource.AttributeValues["stack_slug"].(string)
164166
stack := stacksMap[slug]
165167
stack.slug = slug
166168
stack.url = stacksBySlug[slug].Url
167-
stack.managementKey = resource.values()["key"].(string)
169+
stack.managementKey = resource.AttributeValues["key"].(string)
168170
stacksMap[slug] = stack
169171
}
170-
if resource.resourceType() == "grafana_synthetic_monitoring_installation" {
171-
idStr := resource.values()["stack_id"].(string)
172+
if resource.Type == "grafana_synthetic_monitoring_installation" {
173+
idStr := resource.AttributeValues["stack_id"].(string)
172174
slug := idStr
173175
if id, err := strconv.Atoi(idStr); err == nil {
174176
slug = stacksByID[id].Slug
175177
}
176178
stack := stacksMap[slug]
177-
stack.smToken = resource.values()["sm_access_token"].(string)
178-
stack.smURL = resource.values()["stack_sm_api_url"].(string)
179+
stack.smToken = resource.AttributeValues["sm_access_token"].(string)
180+
stack.smURL = resource.AttributeValues["stack_sm_api_url"].(string)
179181
stacksMap[slug] = stack
180182
}
181183
}

pkg/generate/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package generate
22

3+
import "github.com/hashicorp/terraform-exec/tfexec"
4+
35
type OutputFormat string
46

57
const (
@@ -35,4 +37,5 @@ type Config struct {
3537
ProviderVersion string
3638
Grafana *GrafanaConfig
3739
Cloud *CloudConfig
40+
Terraform *tfexec.Terraform
3841
}

pkg/generate/generate.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
1616
"github.com/hashicorp/hcl/v2/hclwrite"
17+
"github.com/hashicorp/terraform-exec/tfexec"
1718
"github.com/zclconf/go-cty/cty"
1819
)
1920

@@ -48,10 +49,12 @@ func Generate(ctx context.Context, cfg *Config) error {
4849
log.Fatal(err)
4950
}
5051

52+
tf, err := setupTerraform(cfg)
5153
// Terraform init to download the provider
52-
if err := runTerraform(cfg.OutputDir, "init"); err != nil {
54+
if err != nil {
5355
return fmt.Errorf("failed to run terraform init: %w", err)
5456
}
57+
cfg.Terraform = tf
5558

5659
if cfg.Cloud != nil {
5760
stacks, err := generateCloudResources(ctx, cfg)
@@ -60,14 +63,21 @@ func Generate(ctx context.Context, cfg *Config) error {
6063
}
6164

6265
for _, stack := range stacks {
63-
if err := generateGrafanaResources(ctx, stack.managementKey, stack.url, "stack-"+stack.slug, false, cfg.OutputDir, stack.smURL, stack.smToken, cfg.IncludeResources); err != nil {
66+
stack.name = "stack-" + stack.slug
67+
if err := generateGrafanaResources(ctx, cfg, stack, false); err != nil {
6468
return err
6569
}
6670
}
6771
}
6872

6973
if cfg.Grafana != nil {
70-
if err := generateGrafanaResources(ctx, cfg.Grafana.Auth, cfg.Grafana.URL, "", true, cfg.OutputDir, "", "", cfg.IncludeResources); err != nil {
74+
stack := stack{
75+
managementKey: cfg.Grafana.Auth,
76+
url: cfg.Grafana.URL,
77+
smToken: "",
78+
smURL: "",
79+
}
80+
if err := generateGrafanaResources(ctx, cfg, stack, true); err != nil {
7181
return err
7282
}
7383
}
@@ -82,16 +92,16 @@ func Generate(ctx context.Context, cfg *Config) error {
8292
return nil
8393
}
8494

85-
func generateImportBlocks(ctx context.Context, client *common.Client, listerData any, resources []*common.Resource, outPath, provider string, includedResources []string) error {
95+
func generateImportBlocks(ctx context.Context, client *common.Client, listerData any, resources []*common.Resource, cfg *Config, provider string) error {
8696
generatedFilename := func(suffix string) string {
8797
if provider == "" {
88-
return filepath.Join(outPath, suffix)
98+
return filepath.Join(cfg.OutputDir, suffix)
8999
}
90100

91-
return filepath.Join(outPath, provider+"-"+suffix)
101+
return filepath.Join(cfg.OutputDir, provider+"-"+suffix)
92102
}
93103

94-
resources, err := filterResources(resources, includedResources)
104+
resources, err := filterResources(resources, cfg.IncludeResources)
95105
if err != nil {
96106
return err
97107
}
@@ -141,7 +151,7 @@ func generateImportBlocks(ctx context.Context, client *common.Client, listerData
141151
cleanedID = strings.ReplaceAll(provider, "-", "_") + "_" + cleanedID
142152
}
143153

144-
matched, err := filterResourceByName(resource.Name, cleanedID, includedResources)
154+
matched, err := filterResourceByName(resource.Name, cleanedID, cfg.IncludeResources)
145155
if err != nil {
146156
wg.Done()
147157
results <- result{
@@ -198,7 +208,8 @@ func generateImportBlocks(ctx context.Context, client *common.Client, listerData
198208
return err
199209
}
200210

201-
if err := runTerraform(outPath, "plan", "-generate-config-out="+generatedFilename("resources.tf")); err != nil {
211+
_, err = cfg.Terraform.Plan(ctx, tfexec.GenerateConfigOut(generatedFilename("resources.tf")))
212+
if err != nil {
202213
return fmt.Errorf("failed to generate resources: %w", err)
203214
}
204215

pkg/generate/grafana.go

+17-18
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,40 @@ import (
1515
"github.com/zclconf/go-cty/cty"
1616
)
1717

18-
// TODO: Refactor this sig
19-
func generateGrafanaResources(ctx context.Context, auth, url, stackName string, genProvider bool, outPath, smURL, smToken string, includedResources []string) error {
18+
func generateGrafanaResources(ctx context.Context, cfg *Config, stack stack, genProvider bool) error {
2019
generatedFilename := func(suffix string) string {
21-
if stackName == "" {
22-
return filepath.Join(outPath, suffix)
20+
if stack.name == "" {
21+
return filepath.Join(cfg.OutputDir, suffix)
2322
}
2423

25-
return filepath.Join(outPath, stackName+"-"+suffix)
24+
return filepath.Join(cfg.OutputDir, stack.name+"-"+suffix)
2625
}
2726

2827
if genProvider {
2928
providerBlock := hclwrite.NewBlock("provider", []string{"grafana"})
30-
providerBlock.Body().SetAttributeValue("url", cty.StringVal(url))
31-
providerBlock.Body().SetAttributeValue("auth", cty.StringVal(auth))
32-
if stackName != "" {
33-
providerBlock.Body().SetAttributeValue("alias", cty.StringVal(stackName))
29+
providerBlock.Body().SetAttributeValue("url", cty.StringVal(stack.url))
30+
providerBlock.Body().SetAttributeValue("auth", cty.StringVal(stack.managementKey))
31+
if stack.name != "" {
32+
providerBlock.Body().SetAttributeValue("alias", cty.StringVal(stack.name))
3433
}
3534
if err := writeBlocks(generatedFilename("provider.tf"), providerBlock); err != nil {
3635
return err
3736
}
3837
}
3938

40-
singleOrg := !strings.Contains(auth, ":")
39+
singleOrg := !strings.Contains(stack.managementKey, ":")
4140
listerData := grafana.NewListerData(singleOrg)
4241

4342
// Generate resources
4443
config := provider.ProviderConfig{
45-
URL: types.StringValue(url),
46-
Auth: types.StringValue(auth),
44+
URL: types.StringValue(stack.url),
45+
Auth: types.StringValue(stack.managementKey),
4746
}
48-
if smToken != "" {
49-
config.SMAccessToken = types.StringValue(smToken)
47+
if stack.smToken != "" {
48+
config.SMAccessToken = types.StringValue(stack.smToken)
5049
}
51-
if smURL != "" {
52-
config.SMURL = types.StringValue(smURL)
50+
if stack.smURL != "" {
51+
config.SMURL = types.StringValue(stack.smURL)
5352
}
5453
if err := config.SetDefaults(); err != nil {
5554
return err
@@ -61,12 +60,12 @@ func generateGrafanaResources(ctx context.Context, auth, url, stackName string,
6160
}
6261

6362
resources := grafana.Resources
64-
if strings.HasPrefix(stackName, "stack-") { // TODO: is cloud. Find a better way to detect this
63+
if strings.HasPrefix(stack.name, "stack-") { // TODO: is cloud. Find a better way to detect this
6564
resources = append(resources, slo.Resources...)
6665
resources = append(resources, machinelearning.Resources...)
6766
resources = append(resources, syntheticmonitoring.Resources...)
6867
}
69-
if err := generateImportBlocks(ctx, client, listerData, resources, outPath, stackName, includedResources); err != nil {
68+
if err := generateImportBlocks(ctx, client, listerData, resources, cfg, stack.name); err != nil {
7069
return err
7170
}
7271

pkg/generate/terraform.go

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
package generate
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"fmt"
78
"os"
8-
"os/exec"
99
"path/filepath"
1010
"strings"
1111

12+
"github.com/hashicorp/go-version"
13+
"github.com/hashicorp/hc-install/product"
14+
"github.com/hashicorp/hc-install/releases"
1215
"github.com/hashicorp/hcl/v2"
1316
"github.com/hashicorp/hcl/v2/hclparse"
1417
"github.com/hashicorp/hcl/v2/hclwrite"
18+
"github.com/hashicorp/terraform-exec/tfexec"
1519
"github.com/tmccombs/hcl2json/convert"
1620
)
1721

18-
func runTerraformWithOutput(dir string, command ...string) ([]byte, error) {
19-
cmd := exec.Command("terraform", command...)
20-
cmd.Dir = dir
21-
cmd.Stderr = os.Stderr
22-
return cmd.Output()
23-
}
22+
func setupTerraform(cfg *Config) (*tfexec.Terraform, error) {
23+
installer := &releases.ExactVersion{
24+
Product: product.Terraform,
25+
Version: version.Must(version.NewVersion("1.8.4")),
26+
}
27+
28+
execPath, err := installer.Install(context.Background())
29+
if err != nil {
30+
return nil, fmt.Errorf("error installing Terraform: %s", err)
31+
}
32+
33+
tf, err := tfexec.NewTerraform(cfg.OutputDir, execPath)
34+
if err != nil {
35+
return nil, fmt.Errorf("error running NewTerraform: %s", err)
36+
}
37+
38+
err = tf.Init(context.Background(), tfexec.Upgrade(true))
39+
if err != nil {
40+
return nil, fmt.Errorf("error running Init: %s", err)
41+
}
2442

25-
func runTerraform(dir string, command ...string) error {
26-
out, err := runTerraformWithOutput(dir, command...)
27-
fmt.Println(string(out))
28-
return err
43+
return tf, nil
2944
}
3045

3146
func writeBlocks(filepath string, blocks ...*hclwrite.Block) error {

0 commit comments

Comments
 (0)