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