]>
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 | ||
44 | stopCtx context.Context | |
45 | stopCtxCancel context.CancelFunc | |
46 | stopOnce sync.Once | |
47 | } | |
48 | ||
49 | // Keys that can be used to access data in the context parameters for | |
50 | // Provisioners. | |
51 | var ( | |
52 | connDataInvalid = contextKey("data invalid") | |
53 | ||
54 | // This returns a *ResourceData for the connection information. | |
55 | // Guaranteed to never be nil. | |
56 | ProvConnDataKey = contextKey("provider conn data") | |
57 | ||
58 | // This returns a *ResourceData for the config information. | |
59 | // Guaranteed to never be nil. | |
60 | ProvConfigDataKey = contextKey("provider config data") | |
61 | ||
62 | // This returns a terraform.UIOutput. Guaranteed to never be nil. | |
63 | ProvOutputKey = contextKey("provider output") | |
64 | ||
65 | // This returns the raw InstanceState passed to Apply. Guaranteed to | |
66 | // be set, but may be nil. | |
67 | ProvRawStateKey = contextKey("provider raw state") | |
68 | ) | |
69 | ||
70 | // InternalValidate should be called to validate the structure | |
71 | // of the provisioner. | |
72 | // | |
73 | // This should be called in a unit test to verify before release that this | |
74 | // structure is properly configured for use. | |
75 | func (p *Provisioner) InternalValidate() error { | |
76 | if p == nil { | |
77 | return errors.New("provisioner is nil") | |
78 | } | |
79 | ||
80 | var validationErrors error | |
81 | { | |
82 | sm := schemaMap(p.ConnSchema) | |
83 | if err := sm.InternalValidate(sm); err != nil { | |
84 | validationErrors = multierror.Append(validationErrors, err) | |
85 | } | |
86 | } | |
87 | ||
88 | { | |
89 | sm := schemaMap(p.Schema) | |
90 | if err := sm.InternalValidate(sm); err != nil { | |
91 | validationErrors = multierror.Append(validationErrors, err) | |
92 | } | |
93 | } | |
94 | ||
95 | if p.ApplyFunc == nil { | |
96 | validationErrors = multierror.Append(validationErrors, fmt.Errorf( | |
97 | "ApplyFunc must not be nil")) | |
98 | } | |
99 | ||
100 | return validationErrors | |
101 | } | |
102 | ||
103 | // StopContext returns a context that checks whether a provisioner is stopped. | |
104 | func (p *Provisioner) StopContext() context.Context { | |
105 | p.stopOnce.Do(p.stopInit) | |
106 | return p.stopCtx | |
107 | } | |
108 | ||
109 | func (p *Provisioner) stopInit() { | |
110 | p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) | |
111 | } | |
112 | ||
113 | // Stop implementation of terraform.ResourceProvisioner interface. | |
114 | func (p *Provisioner) Stop() error { | |
115 | p.stopOnce.Do(p.stopInit) | |
116 | p.stopCtxCancel() | |
117 | return nil | |
118 | } | |
119 | ||
120 | func (p *Provisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { | |
121 | return schemaMap(p.Schema).Validate(c) | |
122 | } | |
123 | ||
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 | } |