]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package schema |
2 | ||
3 | import ( | |
4 | "context" | |
5 | "errors" | |
6 | "fmt" | |
7 | "sync" | |
8 | ||
9 | "github.com/hashicorp/go-multierror" | |
10 | "github.com/hashicorp/terraform/config" | |
107c1cdb | 11 | "github.com/hashicorp/terraform/configs/configschema" |
bae9f6d2 JC |
12 | "github.com/hashicorp/terraform/terraform" |
13 | ) | |
14 | ||
15 | // Provisioner represents a resource provisioner in Terraform and properly | |
16 | // implements all of the ResourceProvisioner API. | |
17 | // | |
18 | // This higher level structure makes it much easier to implement a new or | |
19 | // custom provisioner for Terraform. | |
20 | // | |
21 | // The function callbacks for this structure are all passed a context object. | |
22 | // This context object has a number of pre-defined values that can be accessed | |
23 | // via the global functions defined in context.go. | |
24 | type Provisioner struct { | |
25 | // ConnSchema is the schema for the connection settings for this | |
26 | // provisioner. | |
27 | // | |
28 | // The keys of this map are the configuration keys, and the value is | |
29 | // the schema describing the value of the configuration. | |
30 | // | |
31 | // NOTE: The value of connection keys can only be strings for now. | |
32 | ConnSchema map[string]*Schema | |
33 | ||
34 | // Schema is the schema for the usage of this provisioner. | |
35 | // | |
36 | // The keys of this map are the configuration keys, and the value is | |
37 | // the schema describing the value of the configuration. | |
38 | Schema map[string]*Schema | |
39 | ||
40 | // ApplyFunc is the function for executing the provisioner. This is required. | |
41 | // It is given a context. See the Provisioner struct docs for more | |
42 | // information. | |
43 | ApplyFunc func(ctx context.Context) error | |
44 | ||
9b12e4fe JC |
45 | // ValidateFunc is a function for extended validation. This is optional |
46 | // and should be used when individual field validation is not enough. | |
c680a8e1 | 47 | ValidateFunc func(*terraform.ResourceConfig) ([]string, []error) |
9b12e4fe | 48 | |
bae9f6d2 JC |
49 | stopCtx context.Context |
50 | stopCtxCancel context.CancelFunc | |
51 | stopOnce sync.Once | |
52 | } | |
53 | ||
54 | // Keys that can be used to access data in the context parameters for | |
55 | // Provisioners. | |
56 | var ( | |
57 | connDataInvalid = contextKey("data invalid") | |
58 | ||
59 | // This returns a *ResourceData for the connection information. | |
60 | // Guaranteed to never be nil. | |
61 | ProvConnDataKey = contextKey("provider conn data") | |
62 | ||
63 | // This returns a *ResourceData for the config information. | |
64 | // Guaranteed to never be nil. | |
65 | ProvConfigDataKey = contextKey("provider config data") | |
66 | ||
67 | // This returns a terraform.UIOutput. Guaranteed to never be nil. | |
68 | ProvOutputKey = contextKey("provider output") | |
69 | ||
70 | // This returns the raw InstanceState passed to Apply. Guaranteed to | |
71 | // be set, but may be nil. | |
72 | ProvRawStateKey = contextKey("provider raw state") | |
73 | ) | |
74 | ||
75 | // InternalValidate should be called to validate the structure | |
76 | // of the provisioner. | |
77 | // | |
78 | // This should be called in a unit test to verify before release that this | |
79 | // structure is properly configured for use. | |
80 | func (p *Provisioner) InternalValidate() error { | |
81 | if p == nil { | |
82 | return errors.New("provisioner is nil") | |
83 | } | |
84 | ||
85 | var validationErrors error | |
86 | { | |
87 | sm := schemaMap(p.ConnSchema) | |
88 | if err := sm.InternalValidate(sm); err != nil { | |
89 | validationErrors = multierror.Append(validationErrors, err) | |
90 | } | |
91 | } | |
92 | ||
93 | { | |
94 | sm := schemaMap(p.Schema) | |
95 | if err := sm.InternalValidate(sm); err != nil { | |
96 | validationErrors = multierror.Append(validationErrors, err) | |
97 | } | |
98 | } | |
99 | ||
100 | if p.ApplyFunc == nil { | |
101 | validationErrors = multierror.Append(validationErrors, fmt.Errorf( | |
102 | "ApplyFunc must not be nil")) | |
103 | } | |
104 | ||
105 | return validationErrors | |
106 | } | |
107 | ||
108 | // StopContext returns a context that checks whether a provisioner is stopped. | |
109 | func (p *Provisioner) StopContext() context.Context { | |
110 | p.stopOnce.Do(p.stopInit) | |
111 | return p.stopCtx | |
112 | } | |
113 | ||
114 | func (p *Provisioner) stopInit() { | |
115 | p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) | |
116 | } | |
117 | ||
118 | // Stop implementation of terraform.ResourceProvisioner interface. | |
119 | func (p *Provisioner) Stop() error { | |
120 | p.stopOnce.Do(p.stopInit) | |
121 | p.stopCtxCancel() | |
122 | return nil | |
123 | } | |
124 | ||
107c1cdb ND |
125 | // GetConfigSchema implementation of terraform.ResourceProvisioner interface. |
126 | func (p *Provisioner) GetConfigSchema() (*configschema.Block, error) { | |
127 | return schemaMap(p.Schema).CoreConfigSchema(), nil | |
128 | } | |
129 | ||
bae9f6d2 JC |
130 | // Apply implementation of terraform.ResourceProvisioner interface. |
131 | func (p *Provisioner) Apply( | |
132 | o terraform.UIOutput, | |
133 | s *terraform.InstanceState, | |
134 | c *terraform.ResourceConfig) error { | |
135 | var connData, configData *ResourceData | |
136 | ||
137 | { | |
138 | // We first need to turn the connection information into a | |
139 | // terraform.ResourceConfig so that we can use that type to more | |
140 | // easily build a ResourceData structure. We do this by simply treating | |
141 | // the conn info as configuration input. | |
142 | raw := make(map[string]interface{}) | |
143 | if s != nil { | |
144 | for k, v := range s.Ephemeral.ConnInfo { | |
145 | raw[k] = v | |
146 | } | |
147 | } | |
148 | ||
149 | c, err := config.NewRawConfig(raw) | |
150 | if err != nil { | |
151 | return err | |
152 | } | |
153 | ||
154 | sm := schemaMap(p.ConnSchema) | |
107c1cdb | 155 | diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil, true) |
bae9f6d2 JC |
156 | if err != nil { |
157 | return err | |
158 | } | |
159 | connData, err = sm.Data(nil, diff) | |
160 | if err != nil { | |
161 | return err | |
162 | } | |
163 | } | |
164 | ||
165 | { | |
166 | // Build the configuration data. Doing this requires making a "diff" | |
167 | // even though that's never used. We use that just to get the correct types. | |
168 | configMap := schemaMap(p.Schema) | |
107c1cdb | 169 | diff, err := configMap.Diff(nil, c, nil, nil, true) |
bae9f6d2 JC |
170 | if err != nil { |
171 | return err | |
172 | } | |
173 | configData, err = configMap.Data(nil, diff) | |
174 | if err != nil { | |
175 | return err | |
176 | } | |
177 | } | |
178 | ||
179 | // Build the context and call the function | |
180 | ctx := p.StopContext() | |
181 | ctx = context.WithValue(ctx, ProvConnDataKey, connData) | |
182 | ctx = context.WithValue(ctx, ProvConfigDataKey, configData) | |
183 | ctx = context.WithValue(ctx, ProvOutputKey, o) | |
184 | ctx = context.WithValue(ctx, ProvRawStateKey, s) | |
185 | return p.ApplyFunc(ctx) | |
186 | } | |
c680a8e1 RS |
187 | |
188 | // Validate implements the terraform.ResourceProvisioner interface. | |
189 | func (p *Provisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { | |
190 | if err := p.InternalValidate(); err != nil { | |
191 | return nil, []error{fmt.Errorf( | |
192 | "Internal validation of the provisioner failed! This is always a bug\n"+ | |
193 | "with the provisioner itself, and not a user issue. Please report\n"+ | |
194 | "this bug:\n\n%s", err)} | |
195 | } | |
196 | ||
197 | if p.Schema != nil { | |
198 | w, e := schemaMap(p.Schema).Validate(c) | |
199 | ws = append(ws, w...) | |
200 | es = append(es, e...) | |
201 | } | |
202 | ||
203 | if p.ValidateFunc != nil { | |
204 | w, e := p.ValidateFunc(c) | |
205 | ws = append(ws, w...) | |
206 | es = append(es, e...) | |
207 | } | |
208 | ||
209 | return ws, es | |
210 | } |