github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/provisioner.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package schema 7 8 import ( 9 "context" 10 "errors" 11 "fmt" 12 "sync" 13 14 "github.com/hashicorp/go-multierror" 15 "github.com/opentofu/opentofu/internal/configs/configschema" 16 "github.com/opentofu/opentofu/internal/legacy/tofu" 17 ) 18 19 // Provisioner represents a resource provisioner in OpenTofu and properly 20 // implements all of the ResourceProvisioner API. 21 // 22 // This higher level structure makes it much easier to implement a new or 23 // custom provisioner for OpenTofu. 24 // 25 // The function callbacks for this structure are all passed a context object. 26 // This context object has a number of pre-defined values that can be accessed 27 // via the global functions defined in context.go. 28 type Provisioner struct { 29 // ConnSchema is the schema for the connection settings for this 30 // provisioner. 31 // 32 // The keys of this map are the configuration keys, and the value is 33 // the schema describing the value of the configuration. 34 // 35 // NOTE: The value of connection keys can only be strings for now. 36 ConnSchema map[string]*Schema 37 38 // Schema is the schema for the usage of this provisioner. 39 // 40 // The keys of this map are the configuration keys, and the value is 41 // the schema describing the value of the configuration. 42 Schema map[string]*Schema 43 44 // ApplyFunc is the function for executing the provisioner. This is required. 45 // It is given a context. See the Provisioner struct docs for more 46 // information. 47 ApplyFunc func(ctx context.Context) error 48 49 // ValidateFunc is a function for extended validation. This is optional 50 // and should be used when individual field validation is not enough. 51 ValidateFunc func(*tofu.ResourceConfig) ([]string, []error) 52 53 stopCtx context.Context 54 stopCtxCancel context.CancelFunc 55 stopOnce sync.Once 56 } 57 58 // Keys that can be used to access data in the context parameters for 59 // Provisioners. 60 var ( 61 connDataInvalid = contextKey("data invalid") 62 63 // This returns a *ResourceData for the connection information. 64 // Guaranteed to never be nil. 65 ProvConnDataKey = contextKey("provider conn data") 66 67 // This returns a *ResourceData for the config information. 68 // Guaranteed to never be nil. 69 ProvConfigDataKey = contextKey("provider config data") 70 71 // This returns a tofu.UIOutput. Guaranteed to never be nil. 72 ProvOutputKey = contextKey("provider output") 73 74 // This returns the raw InstanceState passed to Apply. Guaranteed to 75 // be set, but may be nil. 76 ProvRawStateKey = contextKey("provider raw state") 77 ) 78 79 // InternalValidate should be called to validate the structure 80 // of the provisioner. 81 // 82 // This should be called in a unit test to verify before release that this 83 // structure is properly configured for use. 84 func (p *Provisioner) InternalValidate() error { 85 if p == nil { 86 return errors.New("provisioner is nil") 87 } 88 89 var validationErrors error 90 { 91 sm := schemaMap(p.ConnSchema) 92 if err := sm.InternalValidate(sm); err != nil { 93 validationErrors = multierror.Append(validationErrors, err) 94 } 95 } 96 97 { 98 sm := schemaMap(p.Schema) 99 if err := sm.InternalValidate(sm); err != nil { 100 validationErrors = multierror.Append(validationErrors, err) 101 } 102 } 103 104 if p.ApplyFunc == nil { 105 validationErrors = multierror.Append(validationErrors, fmt.Errorf( 106 "ApplyFunc must not be nil")) 107 } 108 109 return validationErrors 110 } 111 112 // StopContext returns a context that checks whether a provisioner is stopped. 113 func (p *Provisioner) StopContext() context.Context { 114 p.stopOnce.Do(p.stopInit) 115 return p.stopCtx 116 } 117 118 func (p *Provisioner) stopInit() { 119 p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) 120 } 121 122 // Stop implementation of tofu.ResourceProvisioner interface. 123 func (p *Provisioner) Stop() error { 124 p.stopOnce.Do(p.stopInit) 125 p.stopCtxCancel() 126 return nil 127 } 128 129 // GetConfigSchema implementation of tofu.ResourceProvisioner interface. 130 func (p *Provisioner) GetConfigSchema() (*configschema.Block, error) { 131 return schemaMap(p.Schema).CoreConfigSchema(), nil 132 } 133 134 // Apply implementation of tofu.ResourceProvisioner interface. 135 func (p *Provisioner) Apply( 136 o tofu.UIOutput, 137 s *tofu.InstanceState, 138 c *tofu.ResourceConfig) error { 139 var connData, configData *ResourceData 140 141 { 142 // We first need to turn the connection information into a 143 // tofu.ResourceConfig so that we can use that type to more 144 // easily build a ResourceData structure. We do this by simply treating 145 // the conn info as configuration input. 146 raw := make(map[string]interface{}) 147 if s != nil { 148 for k, v := range s.Ephemeral.ConnInfo { 149 raw[k] = v 150 } 151 } 152 153 c := tofu.NewResourceConfigRaw(raw) 154 sm := schemaMap(p.ConnSchema) 155 diff, err := sm.Diff(nil, c, nil, nil, true) 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) 169 diff, err := configMap.Diff(nil, c, nil, nil, true) 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 } 187 188 // Validate implements the tofu.ResourceProvisioner interface. 189 func (p *Provisioner) Validate(c *tofu.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%w", 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 }