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