diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/plugin')
18 files changed, 1598 insertions, 298 deletions
diff --git a/vendor/github.com/hashicorp/terraform/plugin/client.go b/vendor/github.com/hashicorp/terraform/plugin/client.go index 7e2f4fe..0eab538 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/client.go +++ b/vendor/github.com/hashicorp/terraform/plugin/client.go | |||
@@ -19,11 +19,13 @@ func ClientConfig(m discovery.PluginMeta) *plugin.ClientConfig { | |||
19 | }) | 19 | }) |
20 | 20 | ||
21 | return &plugin.ClientConfig{ | 21 | return &plugin.ClientConfig{ |
22 | Cmd: exec.Command(m.Path), | 22 | Cmd: exec.Command(m.Path), |
23 | HandshakeConfig: Handshake, | 23 | HandshakeConfig: Handshake, |
24 | Managed: true, | 24 | VersionedPlugins: VersionedPlugins, |
25 | Plugins: PluginMap, | 25 | Managed: true, |
26 | Logger: logger, | 26 | Logger: logger, |
27 | AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, | ||
28 | AutoMTLS: true, | ||
27 | } | 29 | } |
28 | } | 30 | } |
29 | 31 | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/convert/diagnostics.go b/vendor/github.com/hashicorp/terraform/plugin/convert/diagnostics.go new file mode 100644 index 0000000..51cb2fe --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/convert/diagnostics.go | |||
@@ -0,0 +1,132 @@ | |||
1 | package convert | ||
2 | |||
3 | import ( | ||
4 | proto "github.com/hashicorp/terraform/internal/tfplugin5" | ||
5 | "github.com/hashicorp/terraform/tfdiags" | ||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | // WarnsAndErrorsToProto converts the warnings and errors return by the legacy | ||
10 | // provider to protobuf diagnostics. | ||
11 | func WarnsAndErrsToProto(warns []string, errs []error) (diags []*proto.Diagnostic) { | ||
12 | for _, w := range warns { | ||
13 | diags = AppendProtoDiag(diags, w) | ||
14 | } | ||
15 | |||
16 | for _, e := range errs { | ||
17 | diags = AppendProtoDiag(diags, e) | ||
18 | } | ||
19 | |||
20 | return diags | ||
21 | } | ||
22 | |||
23 | // AppendProtoDiag appends a new diagnostic from a warning string or an error. | ||
24 | // This panics if d is not a string or error. | ||
25 | func AppendProtoDiag(diags []*proto.Diagnostic, d interface{}) []*proto.Diagnostic { | ||
26 | switch d := d.(type) { | ||
27 | case cty.PathError: | ||
28 | ap := PathToAttributePath(d.Path) | ||
29 | diags = append(diags, &proto.Diagnostic{ | ||
30 | Severity: proto.Diagnostic_ERROR, | ||
31 | Summary: d.Error(), | ||
32 | Attribute: ap, | ||
33 | }) | ||
34 | case error: | ||
35 | diags = append(diags, &proto.Diagnostic{ | ||
36 | Severity: proto.Diagnostic_ERROR, | ||
37 | Summary: d.Error(), | ||
38 | }) | ||
39 | case string: | ||
40 | diags = append(diags, &proto.Diagnostic{ | ||
41 | Severity: proto.Diagnostic_WARNING, | ||
42 | Summary: d, | ||
43 | }) | ||
44 | case *proto.Diagnostic: | ||
45 | diags = append(diags, d) | ||
46 | case []*proto.Diagnostic: | ||
47 | diags = append(diags, d...) | ||
48 | } | ||
49 | return diags | ||
50 | } | ||
51 | |||
52 | // ProtoToDiagnostics converts a list of proto.Diagnostics to a tf.Diagnostics. | ||
53 | func ProtoToDiagnostics(ds []*proto.Diagnostic) tfdiags.Diagnostics { | ||
54 | var diags tfdiags.Diagnostics | ||
55 | for _, d := range ds { | ||
56 | var severity tfdiags.Severity | ||
57 | |||
58 | switch d.Severity { | ||
59 | case proto.Diagnostic_ERROR: | ||
60 | severity = tfdiags.Error | ||
61 | case proto.Diagnostic_WARNING: | ||
62 | severity = tfdiags.Warning | ||
63 | } | ||
64 | |||
65 | var newDiag tfdiags.Diagnostic | ||
66 | |||
67 | // if there's an attribute path, we need to create a AttributeValue diagnostic | ||
68 | if d.Attribute != nil { | ||
69 | path := AttributePathToPath(d.Attribute) | ||
70 | newDiag = tfdiags.AttributeValue(severity, d.Summary, d.Detail, path) | ||
71 | } else { | ||
72 | newDiag = tfdiags.WholeContainingBody(severity, d.Summary, d.Detail) | ||
73 | } | ||
74 | |||
75 | diags = diags.Append(newDiag) | ||
76 | } | ||
77 | |||
78 | return diags | ||
79 | } | ||
80 | |||
81 | // AttributePathToPath takes the proto encoded path and converts it to a cty.Path | ||
82 | func AttributePathToPath(ap *proto.AttributePath) cty.Path { | ||
83 | var p cty.Path | ||
84 | for _, step := range ap.Steps { | ||
85 | switch selector := step.Selector.(type) { | ||
86 | case *proto.AttributePath_Step_AttributeName: | ||
87 | p = p.GetAttr(selector.AttributeName) | ||
88 | case *proto.AttributePath_Step_ElementKeyString: | ||
89 | p = p.Index(cty.StringVal(selector.ElementKeyString)) | ||
90 | case *proto.AttributePath_Step_ElementKeyInt: | ||
91 | p = p.Index(cty.NumberIntVal(selector.ElementKeyInt)) | ||
92 | } | ||
93 | } | ||
94 | return p | ||
95 | } | ||
96 | |||
97 | // AttributePathToPath takes a cty.Path and converts it to a proto-encoded path. | ||
98 | func PathToAttributePath(p cty.Path) *proto.AttributePath { | ||
99 | ap := &proto.AttributePath{} | ||
100 | for _, step := range p { | ||
101 | switch selector := step.(type) { | ||
102 | case cty.GetAttrStep: | ||
103 | ap.Steps = append(ap.Steps, &proto.AttributePath_Step{ | ||
104 | Selector: &proto.AttributePath_Step_AttributeName{ | ||
105 | AttributeName: selector.Name, | ||
106 | }, | ||
107 | }) | ||
108 | case cty.IndexStep: | ||
109 | key := selector.Key | ||
110 | switch key.Type() { | ||
111 | case cty.String: | ||
112 | ap.Steps = append(ap.Steps, &proto.AttributePath_Step{ | ||
113 | Selector: &proto.AttributePath_Step_ElementKeyString{ | ||
114 | ElementKeyString: key.AsString(), | ||
115 | }, | ||
116 | }) | ||
117 | case cty.Number: | ||
118 | v, _ := key.AsBigFloat().Int64() | ||
119 | ap.Steps = append(ap.Steps, &proto.AttributePath_Step{ | ||
120 | Selector: &proto.AttributePath_Step_ElementKeyInt{ | ||
121 | ElementKeyInt: v, | ||
122 | }, | ||
123 | }) | ||
124 | default: | ||
125 | // We'll bail early if we encounter anything else, and just | ||
126 | // return the valid prefix. | ||
127 | return ap | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | return ap | ||
132 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/convert/schema.go b/vendor/github.com/hashicorp/terraform/plugin/convert/schema.go new file mode 100644 index 0000000..6a45f54 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/convert/schema.go | |||
@@ -0,0 +1,154 @@ | |||
1 | package convert | ||
2 | |||
3 | import ( | ||
4 | "encoding/json" | ||
5 | "reflect" | ||
6 | "sort" | ||
7 | |||
8 | "github.com/hashicorp/terraform/configs/configschema" | ||
9 | proto "github.com/hashicorp/terraform/internal/tfplugin5" | ||
10 | "github.com/hashicorp/terraform/providers" | ||
11 | ) | ||
12 | |||
13 | // ConfigSchemaToProto takes a *configschema.Block and converts it to a | ||
14 | // proto.Schema_Block for a grpc response. | ||
15 | func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block { | ||
16 | block := &proto.Schema_Block{} | ||
17 | |||
18 | for _, name := range sortedKeys(b.Attributes) { | ||
19 | a := b.Attributes[name] | ||
20 | attr := &proto.Schema_Attribute{ | ||
21 | Name: name, | ||
22 | Description: a.Description, | ||
23 | Optional: a.Optional, | ||
24 | Computed: a.Computed, | ||
25 | Required: a.Required, | ||
26 | Sensitive: a.Sensitive, | ||
27 | } | ||
28 | |||
29 | ty, err := json.Marshal(a.Type) | ||
30 | if err != nil { | ||
31 | panic(err) | ||
32 | } | ||
33 | |||
34 | attr.Type = ty | ||
35 | |||
36 | block.Attributes = append(block.Attributes, attr) | ||
37 | } | ||
38 | |||
39 | for _, name := range sortedKeys(b.BlockTypes) { | ||
40 | b := b.BlockTypes[name] | ||
41 | block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b)) | ||
42 | } | ||
43 | |||
44 | return block | ||
45 | } | ||
46 | |||
47 | func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock { | ||
48 | var nesting proto.Schema_NestedBlock_NestingMode | ||
49 | switch b.Nesting { | ||
50 | case configschema.NestingSingle: | ||
51 | nesting = proto.Schema_NestedBlock_SINGLE | ||
52 | case configschema.NestingGroup: | ||
53 | nesting = proto.Schema_NestedBlock_GROUP | ||
54 | case configschema.NestingList: | ||
55 | nesting = proto.Schema_NestedBlock_LIST | ||
56 | case configschema.NestingSet: | ||
57 | nesting = proto.Schema_NestedBlock_SET | ||
58 | case configschema.NestingMap: | ||
59 | nesting = proto.Schema_NestedBlock_MAP | ||
60 | default: | ||
61 | nesting = proto.Schema_NestedBlock_INVALID | ||
62 | } | ||
63 | return &proto.Schema_NestedBlock{ | ||
64 | TypeName: name, | ||
65 | Block: ConfigSchemaToProto(&b.Block), | ||
66 | Nesting: nesting, | ||
67 | MinItems: int64(b.MinItems), | ||
68 | MaxItems: int64(b.MaxItems), | ||
69 | } | ||
70 | } | ||
71 | |||
72 | // ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema. | ||
73 | func ProtoToProviderSchema(s *proto.Schema) providers.Schema { | ||
74 | return providers.Schema{ | ||
75 | Version: s.Version, | ||
76 | Block: ProtoToConfigSchema(s.Block), | ||
77 | } | ||
78 | } | ||
79 | |||
80 | // ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it | ||
81 | // to a terraform *configschema.Block. | ||
82 | func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block { | ||
83 | block := &configschema.Block{ | ||
84 | Attributes: make(map[string]*configschema.Attribute), | ||
85 | BlockTypes: make(map[string]*configschema.NestedBlock), | ||
86 | } | ||
87 | |||
88 | for _, a := range b.Attributes { | ||
89 | attr := &configschema.Attribute{ | ||
90 | Description: a.Description, | ||
91 | Required: a.Required, | ||
92 | Optional: a.Optional, | ||
93 | Computed: a.Computed, | ||
94 | Sensitive: a.Sensitive, | ||
95 | } | ||
96 | |||
97 | if err := json.Unmarshal(a.Type, &attr.Type); err != nil { | ||
98 | panic(err) | ||
99 | } | ||
100 | |||
101 | block.Attributes[a.Name] = attr | ||
102 | } | ||
103 | |||
104 | for _, b := range b.BlockTypes { | ||
105 | block.BlockTypes[b.TypeName] = schemaNestedBlock(b) | ||
106 | } | ||
107 | |||
108 | return block | ||
109 | } | ||
110 | |||
111 | func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock { | ||
112 | var nesting configschema.NestingMode | ||
113 | switch b.Nesting { | ||
114 | case proto.Schema_NestedBlock_SINGLE: | ||
115 | nesting = configschema.NestingSingle | ||
116 | case proto.Schema_NestedBlock_GROUP: | ||
117 | nesting = configschema.NestingGroup | ||
118 | case proto.Schema_NestedBlock_LIST: | ||
119 | nesting = configschema.NestingList | ||
120 | case proto.Schema_NestedBlock_MAP: | ||
121 | nesting = configschema.NestingMap | ||
122 | case proto.Schema_NestedBlock_SET: | ||
123 | nesting = configschema.NestingSet | ||
124 | default: | ||
125 | // In all other cases we'll leave it as the zero value (invalid) and | ||
126 | // let the caller validate it and deal with this. | ||
127 | } | ||
128 | |||
129 | nb := &configschema.NestedBlock{ | ||
130 | Nesting: nesting, | ||
131 | MinItems: int(b.MinItems), | ||
132 | MaxItems: int(b.MaxItems), | ||
133 | } | ||
134 | |||
135 | nested := ProtoToConfigSchema(b.Block) | ||
136 | nb.Block = *nested | ||
137 | return nb | ||
138 | } | ||
139 | |||
140 | // sortedKeys returns the lexically sorted keys from the given map. This is | ||
141 | // used to make schema conversions are deterministic. This panics if map keys | ||
142 | // are not a string. | ||
143 | func sortedKeys(m interface{}) []string { | ||
144 | v := reflect.ValueOf(m) | ||
145 | keys := make([]string, v.Len()) | ||
146 | |||
147 | mapKeys := v.MapKeys() | ||
148 | for i, k := range mapKeys { | ||
149 | keys[i] = k.Interface().(string) | ||
150 | } | ||
151 | |||
152 | sort.Strings(keys) | ||
153 | return keys | ||
154 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go index df855a7..729e970 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/error.go | |||
@@ -22,9 +22,43 @@ const ErrorNoSuitableVersion = Error("no suitable version is available") | |||
22 | // version of Terraform. | 22 | // version of Terraform. |
23 | const ErrorNoVersionCompatible = Error("no available version is compatible with this version of Terraform") | 23 | const ErrorNoVersionCompatible = Error("no available version is compatible with this version of Terraform") |
24 | 24 | ||
25 | // ErrorVersionIncompatible indicates that all of the versions within the | ||
26 | // constraints are not compatible with the current version of Terrafrom, though | ||
27 | // there does exist a version outside of the constaints that is compatible. | ||
28 | const ErrorVersionIncompatible = Error("incompatible provider version") | ||
29 | |||
25 | // ErrorNoSuchProvider indicates that no provider exists with a name given | 30 | // ErrorNoSuchProvider indicates that no provider exists with a name given |
26 | const ErrorNoSuchProvider = Error("no provider exists with the given name") | 31 | const ErrorNoSuchProvider = Error("no provider exists with the given name") |
27 | 32 | ||
33 | // ErrorNoVersionCompatibleWithPlatform indicates that all of the available | ||
34 | // versions that otherwise met constraints are not compatible with the | ||
35 | // requested platform | ||
36 | const ErrorNoVersionCompatibleWithPlatform = Error("no available version is compatible for the requested platform") | ||
37 | |||
38 | // ErrorMissingChecksumVerification indicates that either the provider | ||
39 | // distribution is missing the SHA256SUMS file or the checksum file does | ||
40 | // not contain a checksum for the binary plugin | ||
41 | const ErrorMissingChecksumVerification = Error("unable to verify checksum") | ||
42 | |||
43 | // ErrorChecksumVerification indicates that the current checksum of the | ||
44 | // provider plugin has changed since the initial release and is not trusted | ||
45 | // to download | ||
46 | const ErrorChecksumVerification = Error("unexpected plugin checksum") | ||
47 | |||
48 | // ErrorSignatureVerification indicates that the digital signature for a | ||
49 | // provider distribution could not be verified for one of the following | ||
50 | // reasons: missing signature file, missing public key, or the signature | ||
51 | // was not signed by any known key for the publisher | ||
52 | const ErrorSignatureVerification = Error("unable to verify signature") | ||
53 | |||
54 | // ErrorServiceUnreachable indicates that the network was unable to connect | ||
55 | // to the registry service | ||
56 | const ErrorServiceUnreachable = Error("registry service is unreachable") | ||
57 | |||
58 | // ErrorPublicRegistryUnreachable indicates that the network was unable to connect | ||
59 | // to the public registry in particular, so we can show a link to the statuspage | ||
60 | const ErrorPublicRegistryUnreachable = Error("registry service is unreachable, check https://status.hashicorp.com/ for status updates") | ||
61 | |||
28 | func (err Error) Error() string { | 62 | func (err Error) Error() string { |
29 | return string(err) | 63 | return string(err) |
30 | } | 64 | } |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go index 815640f..b1d01fb 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/get.go | |||
@@ -13,28 +13,27 @@ import ( | |||
13 | "strconv" | 13 | "strconv" |
14 | "strings" | 14 | "strings" |
15 | 15 | ||
16 | "golang.org/x/net/html" | 16 | "github.com/hashicorp/errwrap" |
17 | |||
18 | getter "github.com/hashicorp/go-getter" | 17 | getter "github.com/hashicorp/go-getter" |
19 | multierror "github.com/hashicorp/go-multierror" | 18 | multierror "github.com/hashicorp/go-multierror" |
20 | "github.com/hashicorp/terraform/httpclient" | 19 | "github.com/hashicorp/terraform/httpclient" |
20 | "github.com/hashicorp/terraform/registry" | ||
21 | "github.com/hashicorp/terraform/registry/regsrc" | ||
22 | "github.com/hashicorp/terraform/registry/response" | ||
23 | "github.com/hashicorp/terraform/svchost/disco" | ||
24 | "github.com/hashicorp/terraform/tfdiags" | ||
25 | tfversion "github.com/hashicorp/terraform/version" | ||
21 | "github.com/mitchellh/cli" | 26 | "github.com/mitchellh/cli" |
22 | ) | 27 | ) |
23 | 28 | ||
24 | // Releases are located by parsing the html listing from releases.hashicorp.com. | 29 | // Releases are located by querying the terraform registry. |
25 | // | ||
26 | // The URL for releases follows the pattern: | ||
27 | // https://releases.hashicorp.com/terraform-provider-name/<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext> | ||
28 | // | ||
29 | // The plugin protocol version will be saved with the release and returned in | ||
30 | // the header X-TERRAFORM_PROTOCOL_VERSION. | ||
31 | 30 | ||
32 | const protocolVersionHeader = "x-terraform-protocol-version" | 31 | const protocolVersionHeader = "x-terraform-protocol-version" |
33 | 32 | ||
34 | var releaseHost = "https://releases.hashicorp.com" | ||
35 | |||
36 | var httpClient *http.Client | 33 | var httpClient *http.Client |
37 | 34 | ||
35 | var errVersionNotFound = errors.New("version not found") | ||
36 | |||
38 | func init() { | 37 | func init() { |
39 | httpClient = httpclient.New() | 38 | httpClient = httpclient.New() |
40 | 39 | ||
@@ -50,7 +49,7 @@ func init() { | |||
50 | // An Installer maintains a local cache of plugins by downloading plugins | 49 | // An Installer maintains a local cache of plugins by downloading plugins |
51 | // from an online repository. | 50 | // from an online repository. |
52 | type Installer interface { | 51 | type Installer interface { |
53 | Get(name string, req Constraints) (PluginMeta, error) | 52 | Get(name string, req Constraints) (PluginMeta, tfdiags.Diagnostics, error) |
54 | PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error) | 53 | PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error) |
55 | } | 54 | } |
56 | 55 | ||
@@ -79,6 +78,13 @@ type ProviderInstaller struct { | |||
79 | SkipVerify bool | 78 | SkipVerify bool |
80 | 79 | ||
81 | Ui cli.Ui // Ui for output | 80 | Ui cli.Ui // Ui for output |
81 | |||
82 | // Services is a required *disco.Disco, which may have services and | ||
83 | // credentials pre-loaded. | ||
84 | Services *disco.Disco | ||
85 | |||
86 | // registry client | ||
87 | registry *registry.Client | ||
82 | } | 88 | } |
83 | 89 | ||
84 | // Get is part of an implementation of type Installer, and attempts to download | 90 | // Get is part of an implementation of type Installer, and attempts to download |
@@ -100,96 +106,170 @@ type ProviderInstaller struct { | |||
100 | // are produced under the assumption that if presented to the user they will | 106 | // are produced under the assumption that if presented to the user they will |
101 | // be presented alongside context about what is being installed, and thus the | 107 | // be presented alongside context about what is being installed, and thus the |
102 | // error messages do not redundantly include such information. | 108 | // error messages do not redundantly include such information. |
103 | func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) { | 109 | func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, tfdiags.Diagnostics, error) { |
104 | versions, err := i.listProviderVersions(provider) | 110 | var diags tfdiags.Diagnostics |
111 | |||
112 | // a little bit of initialization. | ||
113 | if i.OS == "" { | ||
114 | i.OS = runtime.GOOS | ||
115 | } | ||
116 | if i.Arch == "" { | ||
117 | i.Arch = runtime.GOARCH | ||
118 | } | ||
119 | if i.registry == nil { | ||
120 | i.registry = registry.NewClient(i.Services, nil) | ||
121 | } | ||
122 | |||
123 | // get a full listing of versions for the requested provider | ||
124 | allVersions, err := i.listProviderVersions(provider) | ||
125 | |||
105 | // TODO: return multiple errors | 126 | // TODO: return multiple errors |
106 | if err != nil { | 127 | if err != nil { |
107 | return PluginMeta{}, err | 128 | log.Printf("[DEBUG] %s", err) |
129 | if registry.IsServiceUnreachable(err) { | ||
130 | registryHost, err := i.hostname() | ||
131 | if err == nil && registryHost == regsrc.PublicRegistryHost.Raw { | ||
132 | return PluginMeta{}, diags, ErrorPublicRegistryUnreachable | ||
133 | } | ||
134 | return PluginMeta{}, diags, ErrorServiceUnreachable | ||
135 | } | ||
136 | if registry.IsServiceNotProvided(err) { | ||
137 | return PluginMeta{}, diags, err | ||
138 | } | ||
139 | return PluginMeta{}, diags, ErrorNoSuchProvider | ||
108 | } | 140 | } |
109 | 141 | ||
110 | if len(versions) == 0 { | 142 | // Add any warnings from the response to diags |
111 | return PluginMeta{}, ErrorNoSuitableVersion | 143 | for _, warning := range allVersions.Warnings { |
144 | hostname, err := i.hostname() | ||
145 | if err != nil { | ||
146 | return PluginMeta{}, diags, err | ||
147 | } | ||
148 | diag := tfdiags.SimpleWarning(fmt.Sprintf("%s: %s", hostname, warning)) | ||
149 | diags = diags.Append(diag) | ||
112 | } | 150 | } |
113 | 151 | ||
114 | versions = allowedVersions(versions, req) | 152 | if len(allVersions.Versions) == 0 { |
153 | return PluginMeta{}, diags, ErrorNoSuitableVersion | ||
154 | } | ||
155 | providerSource := allVersions.ID | ||
156 | |||
157 | // Filter the list of plugin versions to those which meet the version constraints | ||
158 | versions := allowedVersions(allVersions, req) | ||
115 | if len(versions) == 0 { | 159 | if len(versions) == 0 { |
116 | return PluginMeta{}, ErrorNoSuitableVersion | 160 | return PluginMeta{}, diags, ErrorNoSuitableVersion |
117 | } | 161 | } |
118 | 162 | ||
119 | // sort them newest to oldest | 163 | // sort them newest to oldest. The newest version wins! |
120 | Versions(versions).Sort() | 164 | response.ProviderVersionCollection(versions).Sort() |
121 | 165 | ||
122 | // Ensure that our installation directory exists | 166 | // if the chosen provider version does not support the requested platform, |
123 | err = os.MkdirAll(i.Dir, os.ModePerm) | 167 | // filter the list of acceptable versions to those that support that platform |
124 | if err != nil { | 168 | if err := i.checkPlatformCompatibility(versions[0]); err != nil { |
125 | return PluginMeta{}, fmt.Errorf("failed to create plugin dir %s: %s", i.Dir, err) | 169 | versions = i.platformCompatibleVersions(versions) |
170 | if len(versions) == 0 { | ||
171 | return PluginMeta{}, diags, ErrorNoVersionCompatibleWithPlatform | ||
172 | } | ||
126 | } | 173 | } |
127 | 174 | ||
128 | // take the first matching plugin we find | 175 | // we now have a winning platform-compatible version |
129 | for _, v := range versions { | 176 | versionMeta := versions[0] |
130 | url := i.providerURL(provider, v.String()) | 177 | v := VersionStr(versionMeta.Version).MustParse() |
131 | 178 | ||
132 | if !i.SkipVerify { | 179 | // check protocol compatibility |
133 | sha256, err := i.getProviderChecksum(provider, v.String()) | 180 | if err := i.checkPluginProtocol(versionMeta); err != nil { |
134 | if err != nil { | 181 | closestMatch, err := i.findClosestProtocolCompatibleVersion(allVersions.Versions) |
135 | return PluginMeta{}, err | 182 | if err != nil { |
136 | } | 183 | // No operation here if we can't find a version with compatible protocol |
184 | return PluginMeta{}, diags, err | ||
185 | } | ||
137 | 186 | ||
138 | // add the checksum parameter for go-getter to verify the download for us. | 187 | // Prompt version suggestion to UI based on closest protocol match |
139 | if sha256 != "" { | 188 | var errMsg string |
140 | url = url + "?checksum=sha256:" + sha256 | 189 | closestVersion := VersionStr(closestMatch.Version).MustParse() |
141 | } | 190 | if v.NewerThan(closestVersion) { |
191 | errMsg = providerProtocolTooNew | ||
192 | } else { | ||
193 | errMsg = providerProtocolTooOld | ||
142 | } | 194 | } |
143 | 195 | ||
144 | log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) | 196 | constraintStr := req.String() |
145 | if checkPlugin(url, i.PluginProtocolVersion) { | 197 | if constraintStr == "" { |
146 | i.Ui.Info(fmt.Sprintf("- Downloading plugin for provider %q (%s)...", provider, v.String())) | 198 | constraintStr = "(any version)" |
147 | log.Printf("[DEBUG] getting provider %q version %q", provider, v) | 199 | } |
148 | err := i.install(provider, v, url) | ||
149 | if err != nil { | ||
150 | return PluginMeta{}, err | ||
151 | } | ||
152 | 200 | ||
153 | // Find what we just installed | 201 | return PluginMeta{}, diags, errwrap.Wrap(ErrorVersionIncompatible, fmt.Errorf(fmt.Sprintf( |
154 | // (This is weird, because go-getter doesn't directly return | 202 | errMsg, provider, v.String(), tfversion.String(), |
155 | // information about what was extracted, and we just extracted | 203 | closestVersion.String(), closestVersion.MinorUpgradeConstraintStr(), constraintStr))) |
156 | // the archive directly into a shared dir here.) | 204 | } |
157 | log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider, v) | ||
158 | metas := FindPlugins("provider", []string{i.Dir}) | ||
159 | log.Printf("[DEBUG] all plugins found %#v", metas) | ||
160 | metas, _ = metas.ValidateVersions() | ||
161 | metas = metas.WithName(provider).WithVersion(v) | ||
162 | log.Printf("[DEBUG] filtered plugins %#v", metas) | ||
163 | if metas.Count() == 0 { | ||
164 | // This should never happen. Suggests that the release archive | ||
165 | // contains an executable file whose name doesn't match the | ||
166 | // expected convention. | ||
167 | return PluginMeta{}, fmt.Errorf( | ||
168 | "failed to find installed plugin version %s; this is a bug in Terraform and should be reported", | ||
169 | v, | ||
170 | ) | ||
171 | } | ||
172 | 205 | ||
173 | if metas.Count() > 1 { | 206 | downloadURLs, err := i.listProviderDownloadURLs(providerSource, versionMeta.Version) |
174 | // This should also never happen, and suggests that a | 207 | providerURL := downloadURLs.DownloadURL |
175 | // particular version was re-released with a different | 208 | |
176 | // executable filename. We consider releases as immutable, so | 209 | if !i.SkipVerify { |
177 | // this is an error. | 210 | // Terraform verifies the integrity of a provider release before downloading |
178 | return PluginMeta{}, fmt.Errorf( | 211 | // the plugin binary. The digital signature (SHA256SUMS.sig) on the |
179 | "multiple plugins installed for version %s; this is a bug in Terraform and should be reported", | 212 | // release distribution (SHA256SUMS) is verified with the public key of the |
180 | v, | 213 | // publisher provided in the Terraform Registry response, ensuring that |
181 | ) | 214 | // everything is as intended by the publisher. The checksum of the provider |
182 | } | 215 | // plugin is expected in the SHA256SUMS file and is double checked to match |
216 | // the checksum of the original published release to the Registry. This | ||
217 | // enforces immutability of releases between the Registry and the plugin's | ||
218 | // host location. Lastly, the integrity of the binary is verified upon | ||
219 | // download matches the Registry and signed checksum. | ||
220 | sha256, err := i.getProviderChecksum(downloadURLs) | ||
221 | if err != nil { | ||
222 | return PluginMeta{}, diags, err | ||
223 | } | ||
183 | 224 | ||
184 | // By now we know we have exactly one meta, and so "Newest" will | 225 | // add the checksum parameter for go-getter to verify the download for us. |
185 | // return that one. | 226 | if sha256 != "" { |
186 | return metas.Newest(), nil | 227 | providerURL = providerURL + "?checksum=sha256:" + sha256 |
187 | } | 228 | } |
229 | } | ||
230 | |||
231 | printedProviderName := fmt.Sprintf("%q (%s)", provider, providerSource) | ||
232 | i.Ui.Info(fmt.Sprintf("- Downloading plugin for provider %s %s...", printedProviderName, versionMeta.Version)) | ||
233 | log.Printf("[DEBUG] getting provider %s version %q", printedProviderName, versionMeta.Version) | ||
234 | err = i.install(provider, v, providerURL) | ||
235 | if err != nil { | ||
236 | return PluginMeta{}, diags, err | ||
237 | } | ||
238 | |||
239 | // Find what we just installed | ||
240 | // (This is weird, because go-getter doesn't directly return | ||
241 | // information about what was extracted, and we just extracted | ||
242 | // the archive directly into a shared dir here.) | ||
243 | log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider, versionMeta.Version) | ||
244 | metas := FindPlugins("provider", []string{i.Dir}) | ||
245 | log.Printf("[DEBUG] all plugins found %#v", metas) | ||
246 | metas, _ = metas.ValidateVersions() | ||
247 | metas = metas.WithName(provider).WithVersion(v) | ||
248 | log.Printf("[DEBUG] filtered plugins %#v", metas) | ||
249 | if metas.Count() == 0 { | ||
250 | // This should never happen. Suggests that the release archive | ||
251 | // contains an executable file whose name doesn't match the | ||
252 | // expected convention. | ||
253 | return PluginMeta{}, diags, fmt.Errorf( | ||
254 | "failed to find installed plugin version %s; this is a bug in Terraform and should be reported", | ||
255 | versionMeta.Version, | ||
256 | ) | ||
257 | } | ||
188 | 258 | ||
189 | log.Printf("[INFO] incompatible ProtocolVersion for %s version %s", provider, v) | 259 | if metas.Count() > 1 { |
260 | // This should also never happen, and suggests that a | ||
261 | // particular version was re-released with a different | ||
262 | // executable filename. We consider releases as immutable, so | ||
263 | // this is an error. | ||
264 | return PluginMeta{}, diags, fmt.Errorf( | ||
265 | "multiple plugins installed for version %s; this is a bug in Terraform and should be reported", | ||
266 | versionMeta.Version, | ||
267 | ) | ||
190 | } | 268 | } |
191 | 269 | ||
192 | return PluginMeta{}, ErrorNoVersionCompatible | 270 | // By now we know we have exactly one meta, and so "Newest" will |
271 | // return that one. | ||
272 | return metas.Newest(), diags, nil | ||
193 | } | 273 | } |
194 | 274 | ||
195 | func (i *ProviderInstaller) install(provider string, version Version, url string) error { | 275 | func (i *ProviderInstaller) install(provider string, version Version, url string) error { |
@@ -215,6 +295,14 @@ func (i *ProviderInstaller) install(provider string, version Version, url string | |||
215 | // normal resolution machinery can find it. | 295 | // normal resolution machinery can find it. |
216 | filename := filepath.Base(cached) | 296 | filename := filepath.Base(cached) |
217 | targetPath := filepath.Join(i.Dir, filename) | 297 | targetPath := filepath.Join(i.Dir, filename) |
298 | // check if the target dir exists, and create it if not | ||
299 | var err error | ||
300 | if _, StatErr := os.Stat(i.Dir); os.IsNotExist(StatErr) { | ||
301 | err = os.MkdirAll(i.Dir, 0700) | ||
302 | } | ||
303 | if err != nil { | ||
304 | return err | ||
305 | } | ||
218 | 306 | ||
219 | log.Printf("[DEBUG] installing %s %s to %s from local cache %s", provider, version, targetPath, cached) | 307 | log.Printf("[DEBUG] installing %s %s to %s from local cache %s", provider, version, targetPath, cached) |
220 | 308 | ||
@@ -280,7 +368,6 @@ func (i *ProviderInstaller) install(provider string, version Version, url string | |||
280 | return err | 368 | return err |
281 | } | 369 | } |
282 | } | 370 | } |
283 | |||
284 | return nil | 371 | return nil |
285 | } | 372 | } |
286 | 373 | ||
@@ -316,182 +403,222 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS | |||
316 | return removed, errs | 403 | return removed, errs |
317 | } | 404 | } |
318 | 405 | ||
319 | // Plugins are referred to by the short name, but all URLs and files will use | 406 | func (i *ProviderInstaller) getProviderChecksum(resp *response.TerraformProviderPlatformLocation) (string, error) { |
320 | // the full name prefixed with terraform-<plugin_type>- | 407 | // Get SHA256SUMS file. |
321 | func (i *ProviderInstaller) providerName(name string) string { | 408 | shasums, err := getFile(resp.ShasumsURL) |
322 | return "terraform-provider-" + name | 409 | if err != nil { |
323 | } | 410 | log.Printf("[ERROR] error fetching checksums from %q: %s", resp.ShasumsURL, err) |
411 | return "", ErrorMissingChecksumVerification | ||
412 | } | ||
324 | 413 | ||
325 | func (i *ProviderInstaller) providerFileName(name, version string) string { | 414 | // Get SHA256SUMS.sig file. |
326 | os := i.OS | 415 | signature, err := getFile(resp.ShasumsSignatureURL) |
327 | arch := i.Arch | 416 | if err != nil { |
328 | if os == "" { | 417 | log.Printf("[ERROR] error fetching checksums signature from %q: %s", resp.ShasumsSignatureURL, err) |
329 | os = runtime.GOOS | 418 | return "", ErrorSignatureVerification |
330 | } | 419 | } |
331 | if arch == "" { | 420 | |
332 | arch = runtime.GOARCH | 421 | // Verify the GPG signature returned from the Registry. |
422 | asciiArmor := resp.SigningKeys.GPGASCIIArmor() | ||
423 | signer, err := verifySig(shasums, signature, asciiArmor) | ||
424 | if err != nil { | ||
425 | log.Printf("[ERROR] error verifying signature: %s", err) | ||
426 | return "", ErrorSignatureVerification | ||
333 | } | 427 | } |
334 | return fmt.Sprintf("%s_%s_%s_%s.zip", i.providerName(name), version, os, arch) | ||
335 | } | ||
336 | 428 | ||
337 | // providerVersionsURL returns the path to the released versions directory for the provider: | 429 | // Also verify the GPG signature against the HashiCorp public key. This is |
338 | // https://releases.hashicorp.com/terraform-provider-name/ | 430 | // a temporary additional check until a more robust key verification |
339 | func (i *ProviderInstaller) providerVersionsURL(name string) string { | 431 | // process is added in a future release. |
340 | return releaseHost + "/" + i.providerName(name) + "/" | 432 | _, err = verifySig(shasums, signature, HashicorpPublicKey) |
341 | } | 433 | if err != nil { |
434 | log.Printf("[ERROR] error verifying signature against HashiCorp public key: %s", err) | ||
435 | return "", ErrorSignatureVerification | ||
436 | } | ||
342 | 437 | ||
343 | // providerURL returns the full path to the provider file, using the current OS | 438 | // Display identity for GPG key which succeeded verifying the signature. |
344 | // and ARCH: | 439 | // This could also be used to display to the user with i.Ui.Info(). |
345 | // .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext> | 440 | identities := []string{} |
346 | func (i *ProviderInstaller) providerURL(name, version string) string { | 441 | for k := range signer.Identities { |
347 | return fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, i.providerFileName(name, version)) | 442 | identities = append(identities, k) |
348 | } | 443 | } |
444 | identity := strings.Join(identities, ", ") | ||
445 | log.Printf("[DEBUG] verified GPG signature with key from %s", identity) | ||
446 | |||
447 | // Extract checksum for this os/arch platform binary and verify against Registry | ||
448 | checksum := checksumForFile(shasums, resp.Filename) | ||
449 | if checksum == "" { | ||
450 | log.Printf("[ERROR] missing checksum for %s from source %s", resp.Filename, resp.ShasumsURL) | ||
451 | return "", ErrorMissingChecksumVerification | ||
452 | } else if checksum != resp.Shasum { | ||
453 | log.Printf("[ERROR] unexpected checksum for %s from source %q", resp.Filename, resp.ShasumsURL) | ||
454 | return "", ErrorChecksumVerification | ||
455 | } | ||
349 | 456 | ||
350 | func (i *ProviderInstaller) providerChecksumURL(name, version string) string { | 457 | return checksum, nil |
351 | fileName := fmt.Sprintf("%s_%s_SHA256SUMS", i.providerName(name), version) | ||
352 | u := fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, fileName) | ||
353 | return u | ||
354 | } | 458 | } |
355 | 459 | ||
356 | func (i *ProviderInstaller) getProviderChecksum(name, version string) (string, error) { | 460 | func (i *ProviderInstaller) hostname() (string, error) { |
357 | checksums, err := getPluginSHA256SUMs(i.providerChecksumURL(name, version)) | 461 | provider := regsrc.NewTerraformProvider("", i.OS, i.Arch) |
462 | svchost, err := provider.SvcHost() | ||
358 | if err != nil { | 463 | if err != nil { |
359 | return "", err | 464 | return "", err |
360 | } | 465 | } |
361 | 466 | ||
362 | return checksumForFile(checksums, i.providerFileName(name, version)), nil | 467 | return svchost.ForDisplay(), nil |
363 | } | 468 | } |
364 | 469 | ||
365 | // Return the plugin version by making a HEAD request to the provided url. | 470 | // list all versions available for the named provider |
366 | // If the header is not present, we assume the latest version will be | 471 | func (i *ProviderInstaller) listProviderVersions(name string) (*response.TerraformProviderVersions, error) { |
367 | // compatible, and leave the check for discovery or execution. | 472 | provider := regsrc.NewTerraformProvider(name, i.OS, i.Arch) |
368 | func checkPlugin(url string, pluginProtocolVersion uint) bool { | 473 | versions, err := i.registry.TerraformProviderVersions(provider) |
369 | resp, err := httpClient.Head(url) | 474 | return versions, err |
370 | if err != nil { | 475 | } |
371 | log.Printf("[ERROR] error fetching plugin headers: %s", err) | ||
372 | return false | ||
373 | } | ||
374 | 476 | ||
375 | if resp.StatusCode != http.StatusOK { | 477 | func (i *ProviderInstaller) listProviderDownloadURLs(name, version string) (*response.TerraformProviderPlatformLocation, error) { |
376 | log.Println("[ERROR] non-200 status fetching plugin headers:", resp.Status) | 478 | urls, err := i.registry.TerraformProviderLocation(regsrc.NewTerraformProvider(name, i.OS, i.Arch), version) |
377 | return false | 479 | if urls == nil { |
480 | return nil, fmt.Errorf("No download urls found for provider %s", name) | ||
378 | } | 481 | } |
482 | return urls, err | ||
483 | } | ||
484 | |||
485 | // findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match. | ||
486 | // Prerelease versions are filtered. | ||
487 | func (i *ProviderInstaller) findClosestProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) { | ||
488 | // Loop through all the provider versions to find the earliest and latest | ||
489 | // versions that match the installer protocol to then select the closest of the two | ||
490 | var latest, earliest *response.TerraformProviderVersion | ||
491 | for _, version := range versions { | ||
492 | // Prereleases are filtered and will not be suggested | ||
493 | v, err := VersionStr(version.Version).Parse() | ||
494 | if err != nil || v.IsPrerelease() { | ||
495 | continue | ||
496 | } | ||
379 | 497 | ||
380 | proto := resp.Header.Get(protocolVersionHeader) | 498 | if err := i.checkPluginProtocol(version); err == nil { |
381 | if proto == "" { | 499 | if earliest == nil { |
382 | // The header isn't present, but we don't make this error fatal since | 500 | // Found the first provider version with compatible protocol |
383 | // the latest version will probably work. | 501 | earliest = version |
384 | log.Printf("[WARN] missing %s from: %s", protocolVersionHeader, url) | 502 | } |
385 | return true | 503 | // Update the latest protocol compatible version |
504 | latest = version | ||
505 | } | ||
506 | } | ||
507 | if earliest == nil { | ||
508 | // No compatible protocol was found for any version | ||
509 | return nil, ErrorNoVersionCompatible | ||
386 | } | 510 | } |
387 | 511 | ||
388 | protoVersion, err := strconv.Atoi(proto) | 512 | // Convert protocols to comparable types |
513 | protoString := strconv.Itoa(int(i.PluginProtocolVersion)) | ||
514 | protocolVersion, err := VersionStr(protoString).Parse() | ||
389 | if err != nil { | 515 | if err != nil { |
390 | log.Printf("[ERROR] invalid ProtocolVersion: %s", proto) | 516 | return nil, fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion) |
391 | return false | ||
392 | } | 517 | } |
393 | 518 | ||
394 | return protoVersion == int(pluginProtocolVersion) | 519 | earliestVersionProtocol, err := VersionStr(earliest.Protocols[0]).Parse() |
395 | } | ||
396 | |||
397 | // list the version available for the named plugin | ||
398 | func (i *ProviderInstaller) listProviderVersions(name string) ([]Version, error) { | ||
399 | versions, err := listPluginVersions(i.providerVersionsURL(name)) | ||
400 | if err != nil { | 520 | if err != nil { |
401 | // listPluginVersions returns a verbose error message indicating | ||
402 | // what was being accessed and what failed | ||
403 | return nil, err | 521 | return nil, err |
404 | } | 522 | } |
405 | return versions, nil | ||
406 | } | ||
407 | |||
408 | var errVersionNotFound = errors.New("version not found") | ||
409 | 523 | ||
410 | // take the list of available versions for a plugin, and filter out those that | 524 | // Compare installer protocol version with the first protocol listed of the earliest match |
411 | // don't fit the constraints. | 525 | // [A, B] where A is assumed the earliest compatible major version of the protocol pair |
412 | func allowedVersions(available []Version, required Constraints) []Version { | 526 | if protocolVersion.NewerThan(earliestVersionProtocol) { |
413 | var allowed []Version | 527 | // Provider protocols are too old, the closest version is the earliest compatible version |
414 | 528 | return earliest, nil | |
415 | for _, v := range available { | ||
416 | if required.Allows(v) { | ||
417 | allowed = append(allowed, v) | ||
418 | } | ||
419 | } | 529 | } |
420 | 530 | ||
421 | return allowed | 531 | // Provider protocols are too new, the closest version is the latest compatible version |
532 | return latest, nil | ||
422 | } | 533 | } |
423 | 534 | ||
424 | // return a list of the plugin versions at the given URL | 535 | func (i *ProviderInstaller) checkPluginProtocol(versionMeta *response.TerraformProviderVersion) error { |
425 | func listPluginVersions(url string) ([]Version, error) { | 536 | // TODO: should this be a different error? We should probably differentiate between |
426 | resp, err := httpClient.Get(url) | 537 | // no compatible versions and no protocol versions listed at all |
538 | if len(versionMeta.Protocols) == 0 { | ||
539 | return fmt.Errorf("no plugin protocol versions listed") | ||
540 | } | ||
541 | |||
542 | protoString := strconv.Itoa(int(i.PluginProtocolVersion)) | ||
543 | protocolVersion, err := VersionStr(protoString).Parse() | ||
427 | if err != nil { | 544 | if err != nil { |
428 | // http library produces a verbose error message that includes the | 545 | return fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion) |
429 | // URL being accessed, etc. | 546 | } |
430 | return nil, err | 547 | protocolConstraint, err := protocolVersion.MinorUpgradeConstraintStr().Parse() |
548 | if err != nil { | ||
549 | // This should not fail if the preceding function succeeded. | ||
550 | return fmt.Errorf("invalid plugin protocol version: %q", protocolVersion.String()) | ||
431 | } | 551 | } |
432 | defer resp.Body.Close() | ||
433 | 552 | ||
434 | if resp.StatusCode != http.StatusOK { | 553 | for _, p := range versionMeta.Protocols { |
435 | body, _ := ioutil.ReadAll(resp.Body) | 554 | proPro, err := VersionStr(p).Parse() |
436 | log.Printf("[ERROR] failed to fetch plugin versions from %s\n%s\n%s", url, resp.Status, body) | 555 | if err != nil { |
437 | 556 | // invalid protocol reported by the registry. Move along. | |
438 | switch resp.StatusCode { | 557 | log.Printf("[WARN] invalid provider protocol version %q found in the registry", versionMeta.Version) |
439 | case http.StatusNotFound, http.StatusForbidden: | 558 | continue |
440 | // These are treated as indicative of the given name not being | 559 | } |
441 | // a valid provider name at all. | 560 | // success! |
442 | return nil, ErrorNoSuchProvider | 561 | if protocolConstraint.Allows(proPro) { |
443 | 562 | return nil | |
444 | default: | ||
445 | // All other errors are assumed to be operational problems. | ||
446 | return nil, fmt.Errorf("error accessing %s: %s", url, resp.Status) | ||
447 | } | 563 | } |
448 | |||
449 | } | 564 | } |
450 | 565 | ||
451 | body, err := html.Parse(resp.Body) | 566 | return ErrorNoVersionCompatible |
452 | if err != nil { | 567 | } |
453 | log.Fatal(err) | 568 | |
569 | // REVIEWER QUESTION (again): this ends up swallowing a bunch of errors from | ||
570 | // checkPluginProtocol. Do they need to be percolated up better, or would | ||
571 | // debug messages would suffice in these situations? | ||
572 | func (i *ProviderInstaller) findPlatformCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) { | ||
573 | for _, version := range versions { | ||
574 | if err := i.checkPlatformCompatibility(version); err == nil { | ||
575 | return version, nil | ||
576 | } | ||
454 | } | 577 | } |
455 | 578 | ||
456 | names := []string{} | 579 | return nil, ErrorNoVersionCompatibleWithPlatform |
580 | } | ||
457 | 581 | ||
458 | // all we need to do is list links on the directory listing page that look like plugins | 582 | // platformCompatibleVersions returns a list of provider versions that are |
459 | var f func(*html.Node) | 583 | // compatible with the requested platform. |
460 | f = func(n *html.Node) { | 584 | func (i *ProviderInstaller) platformCompatibleVersions(versions []*response.TerraformProviderVersion) []*response.TerraformProviderVersion { |
461 | if n.Type == html.ElementNode && n.Data == "a" { | 585 | var v []*response.TerraformProviderVersion |
462 | c := n.FirstChild | 586 | for _, version := range versions { |
463 | if c != nil && c.Type == html.TextNode && strings.HasPrefix(c.Data, "terraform-") { | 587 | if err := i.checkPlatformCompatibility(version); err == nil { |
464 | names = append(names, c.Data) | 588 | v = append(v, version) |
465 | return | ||
466 | } | ||
467 | } | ||
468 | for c := n.FirstChild; c != nil; c = c.NextSibling { | ||
469 | f(c) | ||
470 | } | 589 | } |
471 | } | 590 | } |
472 | f(body) | 591 | return v |
592 | } | ||
473 | 593 | ||
474 | return versionsFromNames(names), nil | 594 | func (i *ProviderInstaller) checkPlatformCompatibility(versionMeta *response.TerraformProviderVersion) error { |
595 | if len(versionMeta.Platforms) == 0 { | ||
596 | return fmt.Errorf("no supported provider platforms listed") | ||
597 | } | ||
598 | for _, p := range versionMeta.Platforms { | ||
599 | if p.Arch == i.Arch && p.OS == i.OS { | ||
600 | return nil | ||
601 | } | ||
602 | } | ||
603 | return fmt.Errorf("version %s does not support the requested platform %s_%s", versionMeta.Version, i.OS, i.Arch) | ||
475 | } | 604 | } |
476 | 605 | ||
477 | // parse the list of directory names into a sorted list of available versions | 606 | // take the list of available versions for a plugin, and filter out those that |
478 | func versionsFromNames(names []string) []Version { | 607 | // don't fit the constraints. |
479 | var versions []Version | 608 | func allowedVersions(available *response.TerraformProviderVersions, required Constraints) []*response.TerraformProviderVersion { |
480 | for _, name := range names { | 609 | var allowed []*response.TerraformProviderVersion |
481 | parts := strings.SplitN(name, "_", 2) | ||
482 | if len(parts) == 2 && parts[1] != "" { | ||
483 | v, err := VersionStr(parts[1]).Parse() | ||
484 | if err != nil { | ||
485 | // filter invalid versions scraped from the page | ||
486 | log.Printf("[WARN] invalid version found for %q: %s", name, err) | ||
487 | continue | ||
488 | } | ||
489 | 610 | ||
490 | versions = append(versions, v) | 611 | for _, v := range available.Versions { |
612 | version, err := VersionStr(v.Version).Parse() | ||
613 | if err != nil { | ||
614 | log.Printf("[WARN] invalid version found for %q: %s", available.ID, err) | ||
615 | continue | ||
616 | } | ||
617 | if required.Allows(version) { | ||
618 | allowed = append(allowed, v) | ||
491 | } | 619 | } |
492 | } | 620 | } |
493 | 621 | return allowed | |
494 | return versions | ||
495 | } | 622 | } |
496 | 623 | ||
497 | func checksumForFile(sums []byte, name string) string { | 624 | func checksumForFile(sums []byte, name string) string { |
@@ -504,27 +631,6 @@ func checksumForFile(sums []byte, name string) string { | |||
504 | return "" | 631 | return "" |
505 | } | 632 | } |
506 | 633 | ||
507 | // fetch the SHA256SUMS file provided, and verify its signature. | ||
508 | func getPluginSHA256SUMs(sumsURL string) ([]byte, error) { | ||
509 | sigURL := sumsURL + ".sig" | ||
510 | |||
511 | sums, err := getFile(sumsURL) | ||
512 | if err != nil { | ||
513 | return nil, fmt.Errorf("error fetching checksums: %s", err) | ||
514 | } | ||
515 | |||
516 | sig, err := getFile(sigURL) | ||
517 | if err != nil { | ||
518 | return nil, fmt.Errorf("error fetching checksums signature: %s", err) | ||
519 | } | ||
520 | |||
521 | if err := verifySig(sums, sig); err != nil { | ||
522 | return nil, err | ||
523 | } | ||
524 | |||
525 | return sums, nil | ||
526 | } | ||
527 | |||
528 | func getFile(url string) ([]byte, error) { | 634 | func getFile(url string) ([]byte, error) { |
529 | resp, err := httpClient.Get(url) | 635 | resp, err := httpClient.Get(url) |
530 | if err != nil { | 636 | if err != nil { |
@@ -543,6 +649,41 @@ func getFile(url string) ([]byte, error) { | |||
543 | return data, nil | 649 | return data, nil |
544 | } | 650 | } |
545 | 651 | ||
546 | func GetReleaseHost() string { | 652 | // providerProtocolTooOld is a message sent to the CLI UI if the provider's |
547 | return releaseHost | 653 | // supported protocol versions are too old for the user's version of terraform, |
548 | } | 654 | // but an older version of the provider is compatible. |
655 | const providerProtocolTooOld = ` | ||
656 | [reset][bold][red]Provider %q v%s is not compatible with Terraform %s.[reset][red] | ||
657 | |||
658 | Provider version %s is the earliest compatible version. Select it with | ||
659 | the following version constraint: | ||
660 | |||
661 | version = %q | ||
662 | |||
663 | Terraform checked all of the plugin versions matching the given constraint: | ||
664 | %s | ||
665 | |||
666 | Consult the documentation for this provider for more information on | ||
667 | compatibility between provider and Terraform versions. | ||
668 | ` | ||
669 | |||
670 | // providerProtocolTooNew is a message sent to the CLI UI if the provider's | ||
671 | // supported protocol versions are too new for the user's version of terraform, | ||
672 | // and the user could either upgrade terraform or choose an older version of the | ||
673 | // provider | ||
674 | const providerProtocolTooNew = ` | ||
675 | [reset][bold][red]Provider %q v%s is not compatible with Terraform %s.[reset][red] | ||
676 | |||
677 | Provider version %s is the latest compatible version. Select it with | ||
678 | the following constraint: | ||
679 | |||
680 | version = %q | ||
681 | |||
682 | Terraform checked all of the plugin versions matching the given constraint: | ||
683 | %s | ||
684 | |||
685 | Consult the documentation for this provider for more information on | ||
686 | compatibility between provider and Terraform versions. | ||
687 | |||
688 | Alternatively, upgrade to the latest version of Terraform for compatibility with newer provider releases. | ||
689 | ` | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/hashicorp.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/hashicorp.go new file mode 100644 index 0000000..4622ca0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/hashicorp.go | |||
@@ -0,0 +1,34 @@ | |||
1 | package discovery | ||
2 | |||
3 | // HashicorpPublicKey is the HashiCorp public key, also available at | ||
4 | // https://www.hashicorp.com/security | ||
5 | const HashicorpPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- | ||
6 | Version: GnuPG v1 | ||
7 | |||
8 | mQENBFMORM0BCADBRyKO1MhCirazOSVwcfTr1xUxjPvfxD3hjUwHtjsOy/bT6p9f | ||
9 | W2mRPfwnq2JB5As+paL3UGDsSRDnK9KAxQb0NNF4+eVhr/EJ18s3wwXXDMjpIifq | ||
10 | fIm2WyH3G+aRLTLPIpscUNKDyxFOUbsmgXAmJ46Re1fn8uKxKRHbfa39aeuEYWFA | ||
11 | 3drdL1WoUngvED7f+RnKBK2G6ZEpO+LDovQk19xGjiMTtPJrjMjZJ3QXqPvx5wca | ||
12 | KSZLr4lMTuoTI/ZXyZy5bD4tShiZz6KcyX27cD70q2iRcEZ0poLKHyEIDAi3TM5k | ||
13 | SwbbWBFd5RNPOR0qzrb/0p9ksKK48IIfH2FvABEBAAG0K0hhc2hpQ29ycCBTZWN1 | ||
14 | cml0eSA8c2VjdXJpdHlAaGFzaGljb3JwLmNvbT6JATgEEwECACIFAlMORM0CGwMG | ||
15 | CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFGFLYc0j/xMyWIIAIPhcVqiQ59n | ||
16 | Jc07gjUX0SWBJAxEG1lKxfzS4Xp+57h2xxTpdotGQ1fZwsihaIqow337YHQI3q0i | ||
17 | SqV534Ms+j/tU7X8sq11xFJIeEVG8PASRCwmryUwghFKPlHETQ8jJ+Y8+1asRydi | ||
18 | psP3B/5Mjhqv/uOK+Vy3zAyIpyDOMtIpOVfjSpCplVRdtSTFWBu9Em7j5I2HMn1w | ||
19 | sJZnJgXKpybpibGiiTtmnFLOwibmprSu04rsnP4ncdC2XRD4wIjoyA+4PKgX3sCO | ||
20 | klEzKryWYBmLkJOMDdo52LttP3279s7XrkLEE7ia0fXa2c12EQ0f0DQ1tGUvyVEW | ||
21 | WmJVccm5bq25AQ0EUw5EzQEIANaPUY04/g7AmYkOMjaCZ6iTp9hB5Rsj/4ee/ln9 | ||
22 | wArzRO9+3eejLWh53FoN1rO+su7tiXJA5YAzVy6tuolrqjM8DBztPxdLBbEi4V+j | ||
23 | 2tK0dATdBQBHEh3OJApO2UBtcjaZBT31zrG9K55D+CrcgIVEHAKY8Cb4kLBkb5wM | ||
24 | skn+DrASKU0BNIV1qRsxfiUdQHZfSqtp004nrql1lbFMLFEuiY8FZrkkQ9qduixo | ||
25 | mTT6f34/oiY+Jam3zCK7RDN/OjuWheIPGj/Qbx9JuNiwgX6yRj7OE1tjUx6d8g9y | ||
26 | 0H1fmLJbb3WZZbuuGFnK6qrE3bGeY8+AWaJAZ37wpWh1p0cAEQEAAYkBHwQYAQIA | ||
27 | CQUCUw5EzQIbDAAKCRBRhS2HNI/8TJntCAClU7TOO/X053eKF1jqNW4A1qpxctVc | ||
28 | z8eTcY8Om5O4f6a/rfxfNFKn9Qyja/OG1xWNobETy7MiMXYjaa8uUx5iFy6kMVaP | ||
29 | 0BXJ59NLZjMARGw6lVTYDTIvzqqqwLxgliSDfSnqUhubGwvykANPO+93BBx89MRG | ||
30 | unNoYGXtPlhNFrAsB1VR8+EyKLv2HQtGCPSFBhrjuzH3gxGibNDDdFQLxxuJWepJ | ||
31 | EK1UbTS4ms0NgZ2Uknqn1WRU1Ki7rE4sTy68iZtWpKQXZEJa0IGnuI2sSINGcXCJ | ||
32 | oEIgXTMyCILo34Fa/C6VCm2WBgz9zZO8/rHIiQm1J5zqz0DrDwKBUM9C | ||
33 | =LYpS | ||
34 | -----END PGP PUBLIC KEY BLOCK-----` | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go index 181ea1f..3a99289 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/meta_set.go | |||
@@ -63,7 +63,7 @@ func (s PluginMetaSet) WithName(name string) PluginMetaSet { | |||
63 | // WithVersion returns the subset of metas that have the given version. | 63 | // WithVersion returns the subset of metas that have the given version. |
64 | // | 64 | // |
65 | // This should be used only with the "valid" result from ValidateVersions; | 65 | // This should be used only with the "valid" result from ValidateVersions; |
66 | // it will ignore any plugin metas that have a invalid version strings. | 66 | // it will ignore any plugin metas that have invalid version strings. |
67 | func (s PluginMetaSet) WithVersion(version Version) PluginMetaSet { | 67 | func (s PluginMetaSet) WithVersion(version Version) PluginMetaSet { |
68 | ns := make(PluginMetaSet) | 68 | ns := make(PluginMetaSet) |
69 | for p := range s { | 69 | for p := range s { |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go index 75430fd..0466ab2 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/requirements.go | |||
@@ -4,6 +4,12 @@ import ( | |||
4 | "bytes" | 4 | "bytes" |
5 | ) | 5 | ) |
6 | 6 | ||
7 | // PluginInstallProtocolVersion is the protocol version TF-core | ||
8 | // supports to communicate with servers, and is used to resolve | ||
9 | // plugin discovery with terraform registry, in addition to | ||
10 | // any specified plugin version constraints | ||
11 | const PluginInstallProtocolVersion = 5 | ||
12 | |||
7 | // PluginRequirements describes a set of plugins (assumed to be of a consistent | 13 | // PluginRequirements describes a set of plugins (assumed to be of a consistent |
8 | // kind) that are required to exist and have versions within the given | 14 | // kind) that are required to exist and have versions within the given |
9 | // corresponding sets. | 15 | // corresponding sets. |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go index b6686a5..7bbae50 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/signature.go | |||
@@ -2,7 +2,6 @@ package discovery | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | 4 | "bytes" |
5 | "log" | ||
6 | "strings" | 5 | "strings" |
7 | 6 | ||
8 | "golang.org/x/crypto/openpgp" | 7 | "golang.org/x/crypto/openpgp" |
@@ -10,44 +9,11 @@ import ( | |||
10 | 9 | ||
11 | // Verify the data using the provided openpgp detached signature and the | 10 | // Verify the data using the provided openpgp detached signature and the |
12 | // embedded hashicorp public key. | 11 | // embedded hashicorp public key. |
13 | func verifySig(data, sig []byte) error { | 12 | func verifySig(data, sig []byte, armor string) (*openpgp.Entity, error) { |
14 | el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(hashiPublicKey)) | 13 | el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armor)) |
15 | if err != nil { | 14 | if err != nil { |
16 | log.Fatal(err) | 15 | return nil, err |
17 | } | 16 | } |
18 | 17 | ||
19 | _, err = openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig)) | 18 | return openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig)) |
20 | return err | ||
21 | } | 19 | } |
22 | |||
23 | // this is the public key that signs the checksums file for releases. | ||
24 | const hashiPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- | ||
25 | Version: GnuPG v1 | ||
26 | |||
27 | mQENBFMORM0BCADBRyKO1MhCirazOSVwcfTr1xUxjPvfxD3hjUwHtjsOy/bT6p9f | ||
28 | W2mRPfwnq2JB5As+paL3UGDsSRDnK9KAxQb0NNF4+eVhr/EJ18s3wwXXDMjpIifq | ||
29 | fIm2WyH3G+aRLTLPIpscUNKDyxFOUbsmgXAmJ46Re1fn8uKxKRHbfa39aeuEYWFA | ||
30 | 3drdL1WoUngvED7f+RnKBK2G6ZEpO+LDovQk19xGjiMTtPJrjMjZJ3QXqPvx5wca | ||
31 | KSZLr4lMTuoTI/ZXyZy5bD4tShiZz6KcyX27cD70q2iRcEZ0poLKHyEIDAi3TM5k | ||
32 | SwbbWBFd5RNPOR0qzrb/0p9ksKK48IIfH2FvABEBAAG0K0hhc2hpQ29ycCBTZWN1 | ||
33 | cml0eSA8c2VjdXJpdHlAaGFzaGljb3JwLmNvbT6JATgEEwECACIFAlMORM0CGwMG | ||
34 | CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFGFLYc0j/xMyWIIAIPhcVqiQ59n | ||
35 | Jc07gjUX0SWBJAxEG1lKxfzS4Xp+57h2xxTpdotGQ1fZwsihaIqow337YHQI3q0i | ||
36 | SqV534Ms+j/tU7X8sq11xFJIeEVG8PASRCwmryUwghFKPlHETQ8jJ+Y8+1asRydi | ||
37 | psP3B/5Mjhqv/uOK+Vy3zAyIpyDOMtIpOVfjSpCplVRdtSTFWBu9Em7j5I2HMn1w | ||
38 | sJZnJgXKpybpibGiiTtmnFLOwibmprSu04rsnP4ncdC2XRD4wIjoyA+4PKgX3sCO | ||
39 | klEzKryWYBmLkJOMDdo52LttP3279s7XrkLEE7ia0fXa2c12EQ0f0DQ1tGUvyVEW | ||
40 | WmJVccm5bq25AQ0EUw5EzQEIANaPUY04/g7AmYkOMjaCZ6iTp9hB5Rsj/4ee/ln9 | ||
41 | wArzRO9+3eejLWh53FoN1rO+su7tiXJA5YAzVy6tuolrqjM8DBztPxdLBbEi4V+j | ||
42 | 2tK0dATdBQBHEh3OJApO2UBtcjaZBT31zrG9K55D+CrcgIVEHAKY8Cb4kLBkb5wM | ||
43 | skn+DrASKU0BNIV1qRsxfiUdQHZfSqtp004nrql1lbFMLFEuiY8FZrkkQ9qduixo | ||
44 | mTT6f34/oiY+Jam3zCK7RDN/OjuWheIPGj/Qbx9JuNiwgX6yRj7OE1tjUx6d8g9y | ||
45 | 0H1fmLJbb3WZZbuuGFnK6qrE3bGeY8+AWaJAZ37wpWh1p0cAEQEAAYkBHwQYAQIA | ||
46 | CQUCUw5EzQIbDAAKCRBRhS2HNI/8TJntCAClU7TOO/X053eKF1jqNW4A1qpxctVc | ||
47 | z8eTcY8Om5O4f6a/rfxfNFKn9Qyja/OG1xWNobETy7MiMXYjaa8uUx5iFy6kMVaP | ||
48 | 0BXJ59NLZjMARGw6lVTYDTIvzqqqwLxgliSDfSnqUhubGwvykANPO+93BBx89MRG | ||
49 | unNoYGXtPlhNFrAsB1VR8+EyKLv2HQtGCPSFBhrjuzH3gxGibNDDdFQLxxuJWepJ | ||
50 | EK1UbTS4ms0NgZ2Uknqn1WRU1Ki7rE4sTy68iZtWpKQXZEJa0IGnuI2sSINGcXCJ | ||
51 | oEIgXTMyCILo34Fa/C6VCm2WBgz9zZO8/rHIiQm1J5zqz0DrDwKBUM9C | ||
52 | =LYpS | ||
53 | -----END PGP PUBLIC KEY BLOCK-----` | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go index 8fad58d..4311d51 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/version.go | |||
@@ -55,6 +55,11 @@ func (v Version) Equal(other Version) bool { | |||
55 | return v.raw.Equal(other.raw) | 55 | return v.raw.Equal(other.raw) |
56 | } | 56 | } |
57 | 57 | ||
58 | // IsPrerelease determines if version is a prerelease | ||
59 | func (v Version) IsPrerelease() bool { | ||
60 | return v.raw.Prerelease() != "" | ||
61 | } | ||
62 | |||
58 | // MinorUpgradeConstraintStr returns a ConstraintStr that would permit | 63 | // MinorUpgradeConstraintStr returns a ConstraintStr that would permit |
59 | // minor upgrades relative to the receiving version. | 64 | // minor upgrades relative to the receiving version. |
60 | func (v Version) MinorUpgradeConstraintStr() ConstraintStr { | 65 | func (v Version) MinorUpgradeConstraintStr() ConstraintStr { |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go b/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go index 0aefd75..de02f5e 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go +++ b/vendor/github.com/hashicorp/terraform/plugin/discovery/version_set.go | |||
@@ -36,6 +36,11 @@ type Constraints struct { | |||
36 | raw version.Constraints | 36 | raw version.Constraints |
37 | } | 37 | } |
38 | 38 | ||
39 | // NewConstraints creates a Constraints based on a version.Constraints. | ||
40 | func NewConstraints(c version.Constraints) Constraints { | ||
41 | return Constraints{c} | ||
42 | } | ||
43 | |||
39 | // AllVersions is a Constraints containing all versions | 44 | // AllVersions is a Constraints containing all versions |
40 | var AllVersions Constraints | 45 | var AllVersions Constraints |
41 | 46 | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/grpc_provider.go b/vendor/github.com/hashicorp/terraform/plugin/grpc_provider.go new file mode 100644 index 0000000..ae9a400 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/grpc_provider.go | |||
@@ -0,0 +1,562 @@ | |||
1 | package plugin | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "errors" | ||
6 | "log" | ||
7 | "sync" | ||
8 | |||
9 | "github.com/zclconf/go-cty/cty" | ||
10 | |||
11 | plugin "github.com/hashicorp/go-plugin" | ||
12 | proto "github.com/hashicorp/terraform/internal/tfplugin5" | ||
13 | "github.com/hashicorp/terraform/plugin/convert" | ||
14 | "github.com/hashicorp/terraform/providers" | ||
15 | "github.com/hashicorp/terraform/version" | ||
16 | "github.com/zclconf/go-cty/cty/msgpack" | ||
17 | "google.golang.org/grpc" | ||
18 | ) | ||
19 | |||
20 | // GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package. | ||
21 | type GRPCProviderPlugin struct { | ||
22 | plugin.Plugin | ||
23 | GRPCProvider func() proto.ProviderServer | ||
24 | } | ||
25 | |||
26 | func (p *GRPCProviderPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { | ||
27 | return &GRPCProvider{ | ||
28 | client: proto.NewProviderClient(c), | ||
29 | ctx: ctx, | ||
30 | }, nil | ||
31 | } | ||
32 | |||
33 | func (p *GRPCProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { | ||
34 | proto.RegisterProviderServer(s, p.GRPCProvider()) | ||
35 | return nil | ||
36 | } | ||
37 | |||
38 | // GRPCProvider handles the client, or core side of the plugin rpc connection. | ||
39 | // The GRPCProvider methods are mostly a translation layer between the | ||
40 | // terraform provioders types and the grpc proto types, directly converting | ||
41 | // between the two. | ||
42 | type GRPCProvider struct { | ||
43 | // PluginClient provides a reference to the plugin.Client which controls the plugin process. | ||
44 | // This allows the GRPCProvider a way to shutdown the plugin process. | ||
45 | PluginClient *plugin.Client | ||
46 | |||
47 | // TestServer contains a grpc.Server to close when the GRPCProvider is being | ||
48 | // used in an end to end test of a provider. | ||
49 | TestServer *grpc.Server | ||
50 | |||
51 | // Proto client use to make the grpc service calls. | ||
52 | client proto.ProviderClient | ||
53 | |||
54 | // this context is created by the plugin package, and is canceled when the | ||
55 | // plugin process ends. | ||
56 | ctx context.Context | ||
57 | |||
58 | // schema stores the schema for this provider. This is used to properly | ||
59 | // serialize the state for requests. | ||
60 | mu sync.Mutex | ||
61 | schemas providers.GetSchemaResponse | ||
62 | } | ||
63 | |||
64 | // getSchema is used internally to get the saved provider schema. The schema | ||
65 | // should have already been fetched from the provider, but we have to | ||
66 | // synchronize access to avoid being called concurrently with GetSchema. | ||
67 | func (p *GRPCProvider) getSchema() providers.GetSchemaResponse { | ||
68 | p.mu.Lock() | ||
69 | // unlock inline in case GetSchema needs to be called | ||
70 | if p.schemas.Provider.Block != nil { | ||
71 | p.mu.Unlock() | ||
72 | return p.schemas | ||
73 | } | ||
74 | p.mu.Unlock() | ||
75 | |||
76 | // the schema should have been fetched already, but give it another shot | ||
77 | // just in case things are being called out of order. This may happen for | ||
78 | // tests. | ||
79 | schemas := p.GetSchema() | ||
80 | if schemas.Diagnostics.HasErrors() { | ||
81 | panic(schemas.Diagnostics.Err()) | ||
82 | } | ||
83 | |||
84 | return schemas | ||
85 | } | ||
86 | |||
87 | // getResourceSchema is a helper to extract the schema for a resource, and | ||
88 | // panics if the schema is not available. | ||
89 | func (p *GRPCProvider) getResourceSchema(name string) providers.Schema { | ||
90 | schema := p.getSchema() | ||
91 | resSchema, ok := schema.ResourceTypes[name] | ||
92 | if !ok { | ||
93 | panic("unknown resource type " + name) | ||
94 | } | ||
95 | return resSchema | ||
96 | } | ||
97 | |||
98 | // gettDatasourceSchema is a helper to extract the schema for a datasource, and | ||
99 | // panics if that schema is not available. | ||
100 | func (p *GRPCProvider) getDatasourceSchema(name string) providers.Schema { | ||
101 | schema := p.getSchema() | ||
102 | dataSchema, ok := schema.DataSources[name] | ||
103 | if !ok { | ||
104 | panic("unknown data source " + name) | ||
105 | } | ||
106 | return dataSchema | ||
107 | } | ||
108 | |||
109 | func (p *GRPCProvider) GetSchema() (resp providers.GetSchemaResponse) { | ||
110 | log.Printf("[TRACE] GRPCProvider: GetSchema") | ||
111 | p.mu.Lock() | ||
112 | defer p.mu.Unlock() | ||
113 | |||
114 | if p.schemas.Provider.Block != nil { | ||
115 | return p.schemas | ||
116 | } | ||
117 | |||
118 | resp.ResourceTypes = make(map[string]providers.Schema) | ||
119 | resp.DataSources = make(map[string]providers.Schema) | ||
120 | |||
121 | // Some providers may generate quite large schemas, and the internal default | ||
122 | // grpc response size limit is 4MB. 64MB should cover most any use case, and | ||
123 | // if we get providers nearing that we may want to consider a finer-grained | ||
124 | // API to fetch individual resource schemas. | ||
125 | // Note: this option is marked as EXPERIMENTAL in the grpc API. | ||
126 | const maxRecvSize = 64 << 20 | ||
127 | protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize}) | ||
128 | if err != nil { | ||
129 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
130 | return resp | ||
131 | } | ||
132 | |||
133 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
134 | |||
135 | if protoResp.Provider == nil { | ||
136 | resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provider schema")) | ||
137 | return resp | ||
138 | } | ||
139 | |||
140 | resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider) | ||
141 | |||
142 | for name, res := range protoResp.ResourceSchemas { | ||
143 | resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res) | ||
144 | } | ||
145 | |||
146 | for name, data := range protoResp.DataSourceSchemas { | ||
147 | resp.DataSources[name] = convert.ProtoToProviderSchema(data) | ||
148 | } | ||
149 | |||
150 | p.schemas = resp | ||
151 | |||
152 | return resp | ||
153 | } | ||
154 | |||
155 | func (p *GRPCProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) (resp providers.PrepareProviderConfigResponse) { | ||
156 | log.Printf("[TRACE] GRPCProvider: PrepareProviderConfig") | ||
157 | |||
158 | schema := p.getSchema() | ||
159 | ty := schema.Provider.Block.ImpliedType() | ||
160 | |||
161 | mp, err := msgpack.Marshal(r.Config, ty) | ||
162 | if err != nil { | ||
163 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
164 | return resp | ||
165 | } | ||
166 | |||
167 | protoReq := &proto.PrepareProviderConfig_Request{ | ||
168 | Config: &proto.DynamicValue{Msgpack: mp}, | ||
169 | } | ||
170 | |||
171 | protoResp, err := p.client.PrepareProviderConfig(p.ctx, protoReq) | ||
172 | if err != nil { | ||
173 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
174 | return resp | ||
175 | } | ||
176 | |||
177 | config := cty.NullVal(ty) | ||
178 | if protoResp.PreparedConfig != nil { | ||
179 | config, err = msgpack.Unmarshal(protoResp.PreparedConfig.Msgpack, ty) | ||
180 | if err != nil { | ||
181 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
182 | return resp | ||
183 | } | ||
184 | } | ||
185 | resp.PreparedConfig = config | ||
186 | |||
187 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
188 | return resp | ||
189 | } | ||
190 | |||
191 | func (p *GRPCProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) { | ||
192 | log.Printf("[TRACE] GRPCProvider: ValidateResourceTypeConfig") | ||
193 | resourceSchema := p.getResourceSchema(r.TypeName) | ||
194 | |||
195 | mp, err := msgpack.Marshal(r.Config, resourceSchema.Block.ImpliedType()) | ||
196 | if err != nil { | ||
197 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
198 | return resp | ||
199 | } | ||
200 | |||
201 | protoReq := &proto.ValidateResourceTypeConfig_Request{ | ||
202 | TypeName: r.TypeName, | ||
203 | Config: &proto.DynamicValue{Msgpack: mp}, | ||
204 | } | ||
205 | |||
206 | protoResp, err := p.client.ValidateResourceTypeConfig(p.ctx, protoReq) | ||
207 | if err != nil { | ||
208 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
209 | return resp | ||
210 | } | ||
211 | |||
212 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
213 | return resp | ||
214 | } | ||
215 | |||
216 | func (p *GRPCProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) { | ||
217 | log.Printf("[TRACE] GRPCProvider: ValidateDataSourceConfig") | ||
218 | |||
219 | dataSchema := p.getDatasourceSchema(r.TypeName) | ||
220 | |||
221 | mp, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType()) | ||
222 | if err != nil { | ||
223 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
224 | return resp | ||
225 | } | ||
226 | |||
227 | protoReq := &proto.ValidateDataSourceConfig_Request{ | ||
228 | TypeName: r.TypeName, | ||
229 | Config: &proto.DynamicValue{Msgpack: mp}, | ||
230 | } | ||
231 | |||
232 | protoResp, err := p.client.ValidateDataSourceConfig(p.ctx, protoReq) | ||
233 | if err != nil { | ||
234 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
235 | return resp | ||
236 | } | ||
237 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
238 | return resp | ||
239 | } | ||
240 | |||
241 | func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) { | ||
242 | log.Printf("[TRACE] GRPCProvider: UpgradeResourceState") | ||
243 | |||
244 | resSchema := p.getResourceSchema(r.TypeName) | ||
245 | |||
246 | protoReq := &proto.UpgradeResourceState_Request{ | ||
247 | TypeName: r.TypeName, | ||
248 | Version: int64(r.Version), | ||
249 | RawState: &proto.RawState{ | ||
250 | Json: r.RawStateJSON, | ||
251 | Flatmap: r.RawStateFlatmap, | ||
252 | }, | ||
253 | } | ||
254 | |||
255 | protoResp, err := p.client.UpgradeResourceState(p.ctx, protoReq) | ||
256 | if err != nil { | ||
257 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
258 | return resp | ||
259 | } | ||
260 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
261 | |||
262 | state := cty.NullVal(resSchema.Block.ImpliedType()) | ||
263 | if protoResp.UpgradedState != nil { | ||
264 | state, err = msgpack.Unmarshal(protoResp.UpgradedState.Msgpack, resSchema.Block.ImpliedType()) | ||
265 | if err != nil { | ||
266 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
267 | return resp | ||
268 | } | ||
269 | } | ||
270 | |||
271 | resp.UpgradedState = state | ||
272 | return resp | ||
273 | } | ||
274 | |||
275 | func (p *GRPCProvider) Configure(r providers.ConfigureRequest) (resp providers.ConfigureResponse) { | ||
276 | log.Printf("[TRACE] GRPCProvider: Configure") | ||
277 | |||
278 | schema := p.getSchema() | ||
279 | |||
280 | var mp []byte | ||
281 | |||
282 | // we don't have anything to marshal if there's no config | ||
283 | mp, err := msgpack.Marshal(r.Config, schema.Provider.Block.ImpliedType()) | ||
284 | if err != nil { | ||
285 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
286 | return resp | ||
287 | } | ||
288 | |||
289 | protoReq := &proto.Configure_Request{ | ||
290 | TerraformVersion: version.Version, | ||
291 | Config: &proto.DynamicValue{ | ||
292 | Msgpack: mp, | ||
293 | }, | ||
294 | } | ||
295 | |||
296 | protoResp, err := p.client.Configure(p.ctx, protoReq) | ||
297 | if err != nil { | ||
298 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
299 | return resp | ||
300 | } | ||
301 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
302 | return resp | ||
303 | } | ||
304 | |||
305 | func (p *GRPCProvider) Stop() error { | ||
306 | log.Printf("[TRACE] GRPCProvider: Stop") | ||
307 | |||
308 | resp, err := p.client.Stop(p.ctx, new(proto.Stop_Request)) | ||
309 | if err != nil { | ||
310 | return err | ||
311 | } | ||
312 | |||
313 | if resp.Error != "" { | ||
314 | return errors.New(resp.Error) | ||
315 | } | ||
316 | return nil | ||
317 | } | ||
318 | |||
319 | func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { | ||
320 | log.Printf("[TRACE] GRPCProvider: ReadResource") | ||
321 | |||
322 | resSchema := p.getResourceSchema(r.TypeName) | ||
323 | |||
324 | mp, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType()) | ||
325 | if err != nil { | ||
326 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
327 | return resp | ||
328 | } | ||
329 | |||
330 | protoReq := &proto.ReadResource_Request{ | ||
331 | TypeName: r.TypeName, | ||
332 | CurrentState: &proto.DynamicValue{Msgpack: mp}, | ||
333 | } | ||
334 | |||
335 | protoResp, err := p.client.ReadResource(p.ctx, protoReq) | ||
336 | if err != nil { | ||
337 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
338 | return resp | ||
339 | } | ||
340 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
341 | |||
342 | state := cty.NullVal(resSchema.Block.ImpliedType()) | ||
343 | if protoResp.NewState != nil { | ||
344 | state, err = msgpack.Unmarshal(protoResp.NewState.Msgpack, resSchema.Block.ImpliedType()) | ||
345 | if err != nil { | ||
346 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
347 | return resp | ||
348 | } | ||
349 | } | ||
350 | resp.NewState = state | ||
351 | |||
352 | return resp | ||
353 | } | ||
354 | |||
355 | func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { | ||
356 | log.Printf("[TRACE] GRPCProvider: PlanResourceChange") | ||
357 | |||
358 | resSchema := p.getResourceSchema(r.TypeName) | ||
359 | |||
360 | priorMP, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType()) | ||
361 | if err != nil { | ||
362 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
363 | return resp | ||
364 | } | ||
365 | |||
366 | configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType()) | ||
367 | if err != nil { | ||
368 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
369 | return resp | ||
370 | } | ||
371 | |||
372 | propMP, err := msgpack.Marshal(r.ProposedNewState, resSchema.Block.ImpliedType()) | ||
373 | if err != nil { | ||
374 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
375 | return resp | ||
376 | } | ||
377 | |||
378 | protoReq := &proto.PlanResourceChange_Request{ | ||
379 | TypeName: r.TypeName, | ||
380 | PriorState: &proto.DynamicValue{Msgpack: priorMP}, | ||
381 | Config: &proto.DynamicValue{Msgpack: configMP}, | ||
382 | ProposedNewState: &proto.DynamicValue{Msgpack: propMP}, | ||
383 | PriorPrivate: r.PriorPrivate, | ||
384 | } | ||
385 | |||
386 | protoResp, err := p.client.PlanResourceChange(p.ctx, protoReq) | ||
387 | if err != nil { | ||
388 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
389 | return resp | ||
390 | } | ||
391 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
392 | |||
393 | state := cty.NullVal(resSchema.Block.ImpliedType()) | ||
394 | if protoResp.PlannedState != nil { | ||
395 | state, err = msgpack.Unmarshal(protoResp.PlannedState.Msgpack, resSchema.Block.ImpliedType()) | ||
396 | if err != nil { | ||
397 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
398 | return resp | ||
399 | } | ||
400 | } | ||
401 | resp.PlannedState = state | ||
402 | |||
403 | for _, p := range protoResp.RequiresReplace { | ||
404 | resp.RequiresReplace = append(resp.RequiresReplace, convert.AttributePathToPath(p)) | ||
405 | } | ||
406 | |||
407 | resp.PlannedPrivate = protoResp.PlannedPrivate | ||
408 | |||
409 | resp.LegacyTypeSystem = protoResp.LegacyTypeSystem | ||
410 | |||
411 | return resp | ||
412 | } | ||
413 | |||
414 | func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { | ||
415 | log.Printf("[TRACE] GRPCProvider: ApplyResourceChange") | ||
416 | |||
417 | resSchema := p.getResourceSchema(r.TypeName) | ||
418 | |||
419 | priorMP, err := msgpack.Marshal(r.PriorState, resSchema.Block.ImpliedType()) | ||
420 | if err != nil { | ||
421 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
422 | return resp | ||
423 | } | ||
424 | plannedMP, err := msgpack.Marshal(r.PlannedState, resSchema.Block.ImpliedType()) | ||
425 | if err != nil { | ||
426 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
427 | return resp | ||
428 | } | ||
429 | configMP, err := msgpack.Marshal(r.Config, resSchema.Block.ImpliedType()) | ||
430 | if err != nil { | ||
431 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
432 | return resp | ||
433 | } | ||
434 | |||
435 | protoReq := &proto.ApplyResourceChange_Request{ | ||
436 | TypeName: r.TypeName, | ||
437 | PriorState: &proto.DynamicValue{Msgpack: priorMP}, | ||
438 | PlannedState: &proto.DynamicValue{Msgpack: plannedMP}, | ||
439 | Config: &proto.DynamicValue{Msgpack: configMP}, | ||
440 | PlannedPrivate: r.PlannedPrivate, | ||
441 | } | ||
442 | |||
443 | protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq) | ||
444 | if err != nil { | ||
445 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
446 | return resp | ||
447 | } | ||
448 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
449 | |||
450 | resp.Private = protoResp.Private | ||
451 | |||
452 | state := cty.NullVal(resSchema.Block.ImpliedType()) | ||
453 | if protoResp.NewState != nil { | ||
454 | state, err = msgpack.Unmarshal(protoResp.NewState.Msgpack, resSchema.Block.ImpliedType()) | ||
455 | if err != nil { | ||
456 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
457 | return resp | ||
458 | } | ||
459 | } | ||
460 | resp.NewState = state | ||
461 | |||
462 | resp.LegacyTypeSystem = protoResp.LegacyTypeSystem | ||
463 | |||
464 | return resp | ||
465 | } | ||
466 | |||
467 | func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { | ||
468 | log.Printf("[TRACE] GRPCProvider: ImportResourceState") | ||
469 | |||
470 | protoReq := &proto.ImportResourceState_Request{ | ||
471 | TypeName: r.TypeName, | ||
472 | Id: r.ID, | ||
473 | } | ||
474 | |||
475 | protoResp, err := p.client.ImportResourceState(p.ctx, protoReq) | ||
476 | if err != nil { | ||
477 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
478 | return resp | ||
479 | } | ||
480 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
481 | |||
482 | for _, imported := range protoResp.ImportedResources { | ||
483 | resource := providers.ImportedResource{ | ||
484 | TypeName: imported.TypeName, | ||
485 | Private: imported.Private, | ||
486 | } | ||
487 | |||
488 | resSchema := p.getResourceSchema(resource.TypeName) | ||
489 | state := cty.NullVal(resSchema.Block.ImpliedType()) | ||
490 | if imported.State != nil { | ||
491 | state, err = msgpack.Unmarshal(imported.State.Msgpack, resSchema.Block.ImpliedType()) | ||
492 | if err != nil { | ||
493 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
494 | return resp | ||
495 | } | ||
496 | } | ||
497 | resource.State = state | ||
498 | resp.ImportedResources = append(resp.ImportedResources, resource) | ||
499 | } | ||
500 | |||
501 | return resp | ||
502 | } | ||
503 | |||
504 | func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { | ||
505 | log.Printf("[TRACE] GRPCProvider: ReadDataSource") | ||
506 | |||
507 | dataSchema := p.getDatasourceSchema(r.TypeName) | ||
508 | |||
509 | config, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType()) | ||
510 | if err != nil { | ||
511 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
512 | return resp | ||
513 | } | ||
514 | |||
515 | protoReq := &proto.ReadDataSource_Request{ | ||
516 | TypeName: r.TypeName, | ||
517 | Config: &proto.DynamicValue{ | ||
518 | Msgpack: config, | ||
519 | }, | ||
520 | } | ||
521 | |||
522 | protoResp, err := p.client.ReadDataSource(p.ctx, protoReq) | ||
523 | if err != nil { | ||
524 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
525 | return resp | ||
526 | } | ||
527 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
528 | |||
529 | state := cty.NullVal(dataSchema.Block.ImpliedType()) | ||
530 | if protoResp.State != nil { | ||
531 | state, err = msgpack.Unmarshal(protoResp.State.Msgpack, dataSchema.Block.ImpliedType()) | ||
532 | if err != nil { | ||
533 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
534 | return resp | ||
535 | } | ||
536 | } | ||
537 | resp.State = state | ||
538 | |||
539 | return resp | ||
540 | } | ||
541 | |||
542 | // closing the grpc connection is final, and terraform will call it at the end of every phase. | ||
543 | func (p *GRPCProvider) Close() error { | ||
544 | log.Printf("[TRACE] GRPCProvider: Close") | ||
545 | |||
546 | // Make sure to stop the server if we're not running within go-plugin. | ||
547 | if p.TestServer != nil { | ||
548 | p.TestServer.Stop() | ||
549 | } | ||
550 | |||
551 | // Check this since it's not automatically inserted during plugin creation. | ||
552 | // It's currently only inserted by the command package, because that is | ||
553 | // where the factory is built and is the only point with access to the | ||
554 | // plugin.Client. | ||
555 | if p.PluginClient == nil { | ||
556 | log.Println("[DEBUG] provider has no plugin.Client") | ||
557 | return nil | ||
558 | } | ||
559 | |||
560 | p.PluginClient.Kill() | ||
561 | return nil | ||
562 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/grpc_provisioner.go b/vendor/github.com/hashicorp/terraform/plugin/grpc_provisioner.go new file mode 100644 index 0000000..136c88d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plugin/grpc_provisioner.go | |||
@@ -0,0 +1,178 @@ | |||
1 | package plugin | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "errors" | ||
6 | "io" | ||
7 | "log" | ||
8 | "sync" | ||
9 | |||
10 | plugin "github.com/hashicorp/go-plugin" | ||
11 | "github.com/hashicorp/terraform/configs/configschema" | ||
12 | proto "github.com/hashicorp/terraform/internal/tfplugin5" | ||
13 | "github.com/hashicorp/terraform/plugin/convert" | ||
14 | "github.com/hashicorp/terraform/provisioners" | ||
15 | "github.com/zclconf/go-cty/cty" | ||
16 | "github.com/zclconf/go-cty/cty/msgpack" | ||
17 | "google.golang.org/grpc" | ||
18 | ) | ||
19 | |||
20 | // GRPCProvisionerPlugin is the plugin.GRPCPlugin implementation. | ||
21 | type GRPCProvisionerPlugin struct { | ||
22 | plugin.Plugin | ||
23 | GRPCProvisioner func() proto.ProvisionerServer | ||
24 | } | ||
25 | |||
26 | func (p *GRPCProvisionerPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { | ||
27 | return &GRPCProvisioner{ | ||
28 | client: proto.NewProvisionerClient(c), | ||
29 | ctx: ctx, | ||
30 | }, nil | ||
31 | } | ||
32 | |||
33 | func (p *GRPCProvisionerPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { | ||
34 | proto.RegisterProvisionerServer(s, p.GRPCProvisioner()) | ||
35 | return nil | ||
36 | } | ||
37 | |||
38 | // provisioners.Interface grpc implementation | ||
39 | type GRPCProvisioner struct { | ||
40 | // PluginClient provides a reference to the plugin.Client which controls the plugin process. | ||
41 | // This allows the GRPCProvider a way to shutdown the plugin process. | ||
42 | PluginClient *plugin.Client | ||
43 | |||
44 | client proto.ProvisionerClient | ||
45 | ctx context.Context | ||
46 | |||
47 | // Cache the schema since we need it for serialization in each method call. | ||
48 | mu sync.Mutex | ||
49 | schema *configschema.Block | ||
50 | } | ||
51 | |||
52 | func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) { | ||
53 | p.mu.Lock() | ||
54 | defer p.mu.Unlock() | ||
55 | |||
56 | if p.schema != nil { | ||
57 | return provisioners.GetSchemaResponse{ | ||
58 | Provisioner: p.schema, | ||
59 | } | ||
60 | } | ||
61 | |||
62 | protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request)) | ||
63 | if err != nil { | ||
64 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
65 | return resp | ||
66 | } | ||
67 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
68 | |||
69 | if protoResp.Provisioner == nil { | ||
70 | resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provisioner schema")) | ||
71 | return resp | ||
72 | } | ||
73 | |||
74 | resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block) | ||
75 | |||
76 | p.schema = resp.Provisioner | ||
77 | |||
78 | return resp | ||
79 | } | ||
80 | |||
81 | func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { | ||
82 | schema := p.GetSchema() | ||
83 | if schema.Diagnostics.HasErrors() { | ||
84 | resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) | ||
85 | return resp | ||
86 | } | ||
87 | |||
88 | mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) | ||
89 | if err != nil { | ||
90 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
91 | return resp | ||
92 | } | ||
93 | |||
94 | protoReq := &proto.ValidateProvisionerConfig_Request{ | ||
95 | Config: &proto.DynamicValue{Msgpack: mp}, | ||
96 | } | ||
97 | protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq) | ||
98 | if err != nil { | ||
99 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
100 | return resp | ||
101 | } | ||
102 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) | ||
103 | return resp | ||
104 | } | ||
105 | |||
106 | func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { | ||
107 | schema := p.GetSchema() | ||
108 | if schema.Diagnostics.HasErrors() { | ||
109 | resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) | ||
110 | return resp | ||
111 | } | ||
112 | |||
113 | mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) | ||
114 | if err != nil { | ||
115 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
116 | return resp | ||
117 | } | ||
118 | |||
119 | // connection is always assumed to be a simple string map | ||
120 | connMP, err := msgpack.Marshal(r.Connection, cty.Map(cty.String)) | ||
121 | if err != nil { | ||
122 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
123 | return resp | ||
124 | } | ||
125 | |||
126 | protoReq := &proto.ProvisionResource_Request{ | ||
127 | Config: &proto.DynamicValue{Msgpack: mp}, | ||
128 | Connection: &proto.DynamicValue{Msgpack: connMP}, | ||
129 | } | ||
130 | |||
131 | outputClient, err := p.client.ProvisionResource(p.ctx, protoReq) | ||
132 | if err != nil { | ||
133 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
134 | return resp | ||
135 | } | ||
136 | |||
137 | for { | ||
138 | rcv, err := outputClient.Recv() | ||
139 | if rcv != nil { | ||
140 | r.UIOutput.Output(rcv.Output) | ||
141 | } | ||
142 | if err != nil { | ||
143 | if err != io.EOF { | ||
144 | resp.Diagnostics = resp.Diagnostics.Append(err) | ||
145 | } | ||
146 | break | ||
147 | } | ||
148 | |||
149 | if len(rcv.Diagnostics) > 0 { | ||
150 | resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics)) | ||
151 | break | ||
152 | } | ||
153 | } | ||
154 | |||
155 | return resp | ||
156 | } | ||
157 | |||
158 | func (p *GRPCProvisioner) Stop() error { | ||
159 | protoResp, err := p.client.Stop(p.ctx, &proto.Stop_Request{}) | ||
160 | if err != nil { | ||
161 | return err | ||
162 | } | ||
163 | if protoResp.Error != "" { | ||
164 | return errors.New(protoResp.Error) | ||
165 | } | ||
166 | return nil | ||
167 | } | ||
168 | |||
169 | func (p *GRPCProvisioner) Close() error { | ||
170 | // check this since it's not automatically inserted during plugin creation | ||
171 | if p.PluginClient == nil { | ||
172 | log.Println("[DEBUG] provider has no plugin.Client") | ||
173 | return nil | ||
174 | } | ||
175 | |||
176 | p.PluginClient.Kill() | ||
177 | return nil | ||
178 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/plugin/plugin.go b/vendor/github.com/hashicorp/terraform/plugin/plugin.go index 00fa7b2..e4fb577 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/plugin.go +++ b/vendor/github.com/hashicorp/terraform/plugin/plugin.go | |||
@@ -6,8 +6,9 @@ import ( | |||
6 | 6 | ||
7 | // See serve.go for serving plugins | 7 | // See serve.go for serving plugins |
8 | 8 | ||
9 | // PluginMap should be used by clients for the map of plugins. | 9 | var VersionedPlugins = map[int]plugin.PluginSet{ |
10 | var PluginMap = map[string]plugin.Plugin{ | 10 | 5: { |
11 | "provider": &ResourceProviderPlugin{}, | 11 | "provider": &GRPCProviderPlugin{}, |
12 | "provisioner": &ResourceProvisionerPlugin{}, | 12 | "provisioner": &GRPCProvisionerPlugin{}, |
13 | }, | ||
13 | } | 14 | } |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/resource_provider.go b/vendor/github.com/hashicorp/terraform/plugin/resource_provider.go index d6a433c..459661a 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/resource_provider.go +++ b/vendor/github.com/hashicorp/terraform/plugin/resource_provider.go | |||
@@ -9,11 +9,14 @@ import ( | |||
9 | 9 | ||
10 | // ResourceProviderPlugin is the plugin.Plugin implementation. | 10 | // ResourceProviderPlugin is the plugin.Plugin implementation. |
11 | type ResourceProviderPlugin struct { | 11 | type ResourceProviderPlugin struct { |
12 | F func() terraform.ResourceProvider | 12 | ResourceProvider func() terraform.ResourceProvider |
13 | } | 13 | } |
14 | 14 | ||
15 | func (p *ResourceProviderPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { | 15 | func (p *ResourceProviderPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { |
16 | return &ResourceProviderServer{Broker: b, Provider: p.F()}, nil | 16 | return &ResourceProviderServer{ |
17 | Broker: b, | ||
18 | Provider: p.ResourceProvider(), | ||
19 | }, nil | ||
17 | } | 20 | } |
18 | 21 | ||
19 | func (p *ResourceProviderPlugin) Client( | 22 | func (p *ResourceProviderPlugin) Client( |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/resource_provisioner.go b/vendor/github.com/hashicorp/terraform/plugin/resource_provisioner.go index 8fce9d8..f0cc341 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/resource_provisioner.go +++ b/vendor/github.com/hashicorp/terraform/plugin/resource_provisioner.go | |||
@@ -4,16 +4,20 @@ import ( | |||
4 | "net/rpc" | 4 | "net/rpc" |
5 | 5 | ||
6 | "github.com/hashicorp/go-plugin" | 6 | "github.com/hashicorp/go-plugin" |
7 | "github.com/hashicorp/terraform/configs/configschema" | ||
7 | "github.com/hashicorp/terraform/terraform" | 8 | "github.com/hashicorp/terraform/terraform" |
8 | ) | 9 | ) |
9 | 10 | ||
10 | // ResourceProvisionerPlugin is the plugin.Plugin implementation. | 11 | // ResourceProvisionerPlugin is the plugin.Plugin implementation. |
11 | type ResourceProvisionerPlugin struct { | 12 | type ResourceProvisionerPlugin struct { |
12 | F func() terraform.ResourceProvisioner | 13 | ResourceProvisioner func() terraform.ResourceProvisioner |
13 | } | 14 | } |
14 | 15 | ||
15 | func (p *ResourceProvisionerPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { | 16 | func (p *ResourceProvisionerPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { |
16 | return &ResourceProvisionerServer{Broker: b, Provisioner: p.F()}, nil | 17 | return &ResourceProvisionerServer{ |
18 | Broker: b, | ||
19 | Provisioner: p.ResourceProvisioner(), | ||
20 | }, nil | ||
17 | } | 21 | } |
18 | 22 | ||
19 | func (p *ResourceProvisionerPlugin) Client( | 23 | func (p *ResourceProvisionerPlugin) Client( |
@@ -28,6 +32,11 @@ type ResourceProvisioner struct { | |||
28 | Client *rpc.Client | 32 | Client *rpc.Client |
29 | } | 33 | } |
30 | 34 | ||
35 | func (p *ResourceProvisioner) GetConfigSchema() (*configschema.Block, error) { | ||
36 | panic("not implemented") | ||
37 | return nil, nil | ||
38 | } | ||
39 | |||
31 | func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { | 40 | func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { |
32 | var resp ResourceProvisionerValidateResponse | 41 | var resp ResourceProvisionerValidateResponse |
33 | args := ResourceProvisionerValidateArgs{ | 42 | args := ResourceProvisionerValidateArgs{ |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/serve.go b/vendor/github.com/hashicorp/terraform/plugin/serve.go index 2028a61..8d056c5 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/serve.go +++ b/vendor/github.com/hashicorp/terraform/plugin/serve.go | |||
@@ -2,14 +2,23 @@ package plugin | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "github.com/hashicorp/go-plugin" | 4 | "github.com/hashicorp/go-plugin" |
5 | grpcplugin "github.com/hashicorp/terraform/helper/plugin" | ||
6 | proto "github.com/hashicorp/terraform/internal/tfplugin5" | ||
5 | "github.com/hashicorp/terraform/terraform" | 7 | "github.com/hashicorp/terraform/terraform" |
6 | ) | 8 | ) |
7 | 9 | ||
8 | // The constants below are the names of the plugins that can be dispensed | ||
9 | // from the plugin server. | ||
10 | const ( | 10 | const ( |
11 | // The constants below are the names of the plugins that can be dispensed | ||
12 | // from the plugin server. | ||
11 | ProviderPluginName = "provider" | 13 | ProviderPluginName = "provider" |
12 | ProvisionerPluginName = "provisioner" | 14 | ProvisionerPluginName = "provisioner" |
15 | |||
16 | // DefaultProtocolVersion is the protocol version assumed for legacy clients that don't specify | ||
17 | // a particular version during their handshake. This is the version used when Terraform 0.10 | ||
18 | // and 0.11 launch plugins that were built with support for both versions 4 and 5, and must | ||
19 | // stay unchanged at 4 until we intentionally build plugins that are not compatible with 0.10 and | ||
20 | // 0.11. | ||
21 | DefaultProtocolVersion = 4 | ||
13 | ) | 22 | ) |
14 | 23 | ||
15 | // Handshake is the HandshakeConfig used to configure clients and servers. | 24 | // Handshake is the HandshakeConfig used to configure clients and servers. |
@@ -19,7 +28,7 @@ var Handshake = plugin.HandshakeConfig{ | |||
19 | // one or the other that makes it so that they can't safely communicate. | 28 | // one or the other that makes it so that they can't safely communicate. |
20 | // This could be adding a new interface value, it could be how | 29 | // This could be adding a new interface value, it could be how |
21 | // helper/schema computes diffs, etc. | 30 | // helper/schema computes diffs, etc. |
22 | ProtocolVersion: 4, | 31 | ProtocolVersion: DefaultProtocolVersion, |
23 | 32 | ||
24 | // The magic cookie values should NEVER be changed. | 33 | // The magic cookie values should NEVER be changed. |
25 | MagicCookieKey: "TF_PLUGIN_MAGIC_COOKIE", | 34 | MagicCookieKey: "TF_PLUGIN_MAGIC_COOKIE", |
@@ -28,27 +37,85 @@ var Handshake = plugin.HandshakeConfig{ | |||
28 | 37 | ||
29 | type ProviderFunc func() terraform.ResourceProvider | 38 | type ProviderFunc func() terraform.ResourceProvider |
30 | type ProvisionerFunc func() terraform.ResourceProvisioner | 39 | type ProvisionerFunc func() terraform.ResourceProvisioner |
40 | type GRPCProviderFunc func() proto.ProviderServer | ||
41 | type GRPCProvisionerFunc func() proto.ProvisionerServer | ||
31 | 42 | ||
32 | // ServeOpts are the configurations to serve a plugin. | 43 | // ServeOpts are the configurations to serve a plugin. |
33 | type ServeOpts struct { | 44 | type ServeOpts struct { |
34 | ProviderFunc ProviderFunc | 45 | ProviderFunc ProviderFunc |
35 | ProvisionerFunc ProvisionerFunc | 46 | ProvisionerFunc ProvisionerFunc |
47 | |||
48 | // Wrapped versions of the above plugins will automatically shimmed and | ||
49 | // added to the GRPC functions when possible. | ||
50 | GRPCProviderFunc GRPCProviderFunc | ||
51 | GRPCProvisionerFunc GRPCProvisionerFunc | ||
36 | } | 52 | } |
37 | 53 | ||
38 | // Serve serves a plugin. This function never returns and should be the final | 54 | // Serve serves a plugin. This function never returns and should be the final |
39 | // function called in the main function of the plugin. | 55 | // function called in the main function of the plugin. |
40 | func Serve(opts *ServeOpts) { | 56 | func Serve(opts *ServeOpts) { |
57 | // since the plugins may not yet be aware of the new protocol, we | ||
58 | // automatically wrap the plugins in the grpc shims. | ||
59 | if opts.GRPCProviderFunc == nil && opts.ProviderFunc != nil { | ||
60 | provider := grpcplugin.NewGRPCProviderServerShim(opts.ProviderFunc()) | ||
61 | // this is almost always going to be a *schema.Provider, but check that | ||
62 | // we got back a valid provider just in case. | ||
63 | if provider != nil { | ||
64 | opts.GRPCProviderFunc = func() proto.ProviderServer { | ||
65 | return provider | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | if opts.GRPCProvisionerFunc == nil && opts.ProvisionerFunc != nil { | ||
70 | provisioner := grpcplugin.NewGRPCProvisionerServerShim(opts.ProvisionerFunc()) | ||
71 | if provisioner != nil { | ||
72 | opts.GRPCProvisionerFunc = func() proto.ProvisionerServer { | ||
73 | return provisioner | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
41 | plugin.Serve(&plugin.ServeConfig{ | 78 | plugin.Serve(&plugin.ServeConfig{ |
42 | HandshakeConfig: Handshake, | 79 | HandshakeConfig: Handshake, |
43 | Plugins: pluginMap(opts), | 80 | VersionedPlugins: pluginSet(opts), |
81 | GRPCServer: plugin.DefaultGRPCServer, | ||
44 | }) | 82 | }) |
45 | } | 83 | } |
46 | 84 | ||
47 | // pluginMap returns the map[string]plugin.Plugin to use for configuring a plugin | 85 | // pluginMap returns the legacy map[string]plugin.Plugin to use for configuring |
48 | // server or client. | 86 | // a plugin server or client. |
49 | func pluginMap(opts *ServeOpts) map[string]plugin.Plugin { | 87 | func legacyPluginMap(opts *ServeOpts) map[string]plugin.Plugin { |
50 | return map[string]plugin.Plugin{ | 88 | return map[string]plugin.Plugin{ |
51 | "provider": &ResourceProviderPlugin{F: opts.ProviderFunc}, | 89 | "provider": &ResourceProviderPlugin{ |
52 | "provisioner": &ResourceProvisionerPlugin{F: opts.ProvisionerFunc}, | 90 | ResourceProvider: opts.ProviderFunc, |
91 | }, | ||
92 | "provisioner": &ResourceProvisionerPlugin{ | ||
93 | ResourceProvisioner: opts.ProvisionerFunc, | ||
94 | }, | ||
95 | } | ||
96 | } | ||
97 | |||
98 | func pluginSet(opts *ServeOpts) map[int]plugin.PluginSet { | ||
99 | // Set the legacy netrpc plugins at version 4. | ||
100 | // The oldest version is returned in when executed by a legacy go-plugin | ||
101 | // client. | ||
102 | plugins := map[int]plugin.PluginSet{ | ||
103 | 4: legacyPluginMap(opts), | ||
104 | } | ||
105 | |||
106 | // add the new protocol versions if they're configured | ||
107 | if opts.GRPCProviderFunc != nil || opts.GRPCProvisionerFunc != nil { | ||
108 | plugins[5] = plugin.PluginSet{} | ||
109 | if opts.GRPCProviderFunc != nil { | ||
110 | plugins[5]["provider"] = &GRPCProviderPlugin{ | ||
111 | GRPCProvider: opts.GRPCProviderFunc, | ||
112 | } | ||
113 | } | ||
114 | if opts.GRPCProvisionerFunc != nil { | ||
115 | plugins[5]["provisioner"] = &GRPCProvisionerPlugin{ | ||
116 | GRPCProvisioner: opts.GRPCProvisionerFunc, | ||
117 | } | ||
118 | } | ||
53 | } | 119 | } |
120 | return plugins | ||
54 | } | 121 | } |
diff --git a/vendor/github.com/hashicorp/terraform/plugin/ui_input.go b/vendor/github.com/hashicorp/terraform/plugin/ui_input.go index 493efc0..3469e6a 100644 --- a/vendor/github.com/hashicorp/terraform/plugin/ui_input.go +++ b/vendor/github.com/hashicorp/terraform/plugin/ui_input.go | |||
@@ -1,19 +1,20 @@ | |||
1 | package plugin | 1 | package plugin |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "context" | ||
4 | "net/rpc" | 5 | "net/rpc" |
5 | 6 | ||
6 | "github.com/hashicorp/go-plugin" | 7 | "github.com/hashicorp/go-plugin" |
7 | "github.com/hashicorp/terraform/terraform" | 8 | "github.com/hashicorp/terraform/terraform" |
8 | ) | 9 | ) |
9 | 10 | ||
10 | // UIInput is an implementatin of terraform.UIInput that communicates | 11 | // UIInput is an implementation of terraform.UIInput that communicates |
11 | // over RPC. | 12 | // over RPC. |
12 | type UIInput struct { | 13 | type UIInput struct { |
13 | Client *rpc.Client | 14 | Client *rpc.Client |
14 | } | 15 | } |
15 | 16 | ||
16 | func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { | 17 | func (i *UIInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { |
17 | var resp UIInputInputResponse | 18 | var resp UIInputInputResponse |
18 | err := i.Client.Call("Plugin.Input", opts, &resp) | 19 | err := i.Client.Call("Plugin.Input", opts, &resp) |
19 | if err != nil { | 20 | if err != nil { |
@@ -41,7 +42,7 @@ type UIInputServer struct { | |||
41 | func (s *UIInputServer) Input( | 42 | func (s *UIInputServer) Input( |
42 | opts *terraform.InputOpts, | 43 | opts *terraform.InputOpts, |
43 | reply *UIInputInputResponse) error { | 44 | reply *UIInputInputResponse) error { |
44 | value, err := s.UIInput.Input(opts) | 45 | value, err := s.UIInput.Input(context.Background(), opts) |
45 | *reply = UIInputInputResponse{ | 46 | *reply = UIInputInputResponse{ |
46 | Value: value, | 47 | Value: value, |
47 | Error: plugin.NewBasicError(err), | 48 | Error: plugin.NewBasicError(err), |