-
Notifications
You must be signed in to change notification settings - Fork 0
/
tfproviders.go
127 lines (112 loc) · 3.38 KB
/
tfproviders.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright 2023 Volvo Car Corporation
// SPDX-License-Identifier: Apache-2.0
package terragen
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/golingon/lingon/pkg/internal/hcl"
"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"
)
// TerraformVersions ...
type TerraformVersions struct {
TerraformBlock TerraformBlock `hcl:"terraform,block"`
}
// TerraformBlock represents a terraform{} block in a Terraform stack
type TerraformBlock struct {
RequiredProviders RequiredProviders `hcl:"required_providers,block"`
}
// RequiredProviders represents the map of required providers for a Terraform
// stack
type RequiredProviders struct {
Providers map[string]Provider `hcl:",remain"`
}
// Provider represents a single element of a map of required providers
type Provider struct {
Name string // Lack of cty tag means it is ignored
Source string `cty:"source"`
Version string `cty:"version"`
}
// GenerateProviderSchema generates the schema for the given Terraform provider.
//
// The provider schema is generated by calling Terraform as follows:
//
// terraform providers schema --json
//
// The JSON schema is then decoded and returned as a ProviderSchemas struct.
func GenerateProviderSchema(
ctx context.Context,
provider Provider,
) (*tfjson.ProviderSchema, error) {
versions := TerraformVersions{
TerraformBlock: TerraformBlock{
RequiredProviders: RequiredProviders{
Providers: map[string]Provider{
provider.Name: {
Source: provider.Source,
Version: provider.Version,
},
},
},
},
}
workingDir := filepath.Join(
".lingon", "schemas", provider.Name,
provider.Version,
)
if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
return nil, fmt.Errorf(
"creating schemas working directory: %s: %w",
workingDir,
err,
)
}
// Write versions.tf file
tfVersionsFile := filepath.Join(workingDir, "versions.tf")
f, err := os.Create(tfVersionsFile)
if err != nil {
return nil, fmt.Errorf("creating file %s: %w", tfVersionsFile, err)
}
if err := hcl.EncodeRaw(f, versions); err != nil {
return nil, fmt.Errorf("encoding file %s: %w", tfVersionsFile, err)
}
tf, err := tfexec.NewTerraform(workingDir, "terraform")
if err != nil {
return nil, fmt.Errorf("creating new terraform runtime: %w", err)
}
if err := tf.Init(ctx, tfexec.Upgrade(true)); err != nil {
return nil, fmt.Errorf("running terraform init: %w", err)
}
providersSchema, err := tf.ProvidersSchema(ctx)
if err != nil {
return nil, fmt.Errorf("running terraform providers schema: %w", err)
}
return providerSchemaBySource(providersSchema, provider.Source)
}
// providerSchemaBySource returns the specific provider schema for the given
// source from the full list of provider schemas that is generated by terraform.
func providerSchemaBySource(
schemas *tfjson.ProviderSchemas,
source string,
) (*tfjson.ProviderSchema, error) {
providerSchema, ok := schemas.Schemas[source]
if !ok {
// Try adding registry.terraform.io/ prefix if not already added
if !strings.HasPrefix(source, "registry.terraform.io/") {
absSource := fmt.Sprintf("registry.terraform.io/%s", source)
providerSchema, ok = schemas.Schemas[absSource]
}
// If still not ok, indicate an error
if !ok {
return nil, fmt.Errorf(
"provider source: %q: %w",
source,
ErrProviderSchemaNotFound,
)
}
}
return providerSchema, nil
}