github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/helper/schema/resource.go (about) 1 package schema 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 8 "github.com/hashicorp/terraform/terraform" 9 ) 10 11 // Resource represents a thing in Terraform that has a set of configurable 12 // attributes and a lifecycle (create, read, update, delete). 13 // 14 // The Resource schema is an abstraction that allows provider writers to 15 // worry only about CRUD operations while off-loading validation, diff 16 // generation, etc. to this higher level library. 17 type Resource struct { 18 // Schema is the schema for the configuration of this resource. 19 // 20 // The keys of this map are the configuration keys, and the values 21 // describe the schema of the configuration value. 22 // 23 // The schema is used to represent both configurable data as well 24 // as data that might be computed in the process of creating this 25 // resource. 26 Schema map[string]*Schema 27 28 // SchemaVersion is the version number for this resource's Schema 29 // definition. The current SchemaVersion stored in the state for each 30 // resource. Provider authors can increment this version number 31 // when Schema semantics change. If the State's SchemaVersion is less than 32 // the current SchemaVersion, the InstanceState is yielded to the 33 // MigrateState callback, where the provider can make whatever changes it 34 // needs to update the state to be compatible to the latest version of the 35 // Schema. 36 // 37 // When unset, SchemaVersion defaults to 0, so provider authors can start 38 // their Versioning at any integer >= 1 39 SchemaVersion int 40 41 // MigrateState is responsible for updating an InstanceState with an old 42 // version to the format expected by the current version of the Schema. 43 // 44 // It is called during Refresh if the State's stored SchemaVersion is less 45 // than the current SchemaVersion of the Resource. 46 // 47 // The function is yielded the state's stored SchemaVersion and a pointer to 48 // the InstanceState that needs updating, as well as the configured 49 // provider's configured meta interface{}, in case the migration process 50 // needs to make any remote API calls. 51 MigrateState StateMigrateFunc 52 53 // The functions below are the CRUD operations for this resource. 54 // 55 // The only optional operation is Update. If Update is not implemented, 56 // then updates will not be supported for this resource. 57 // 58 // The ResourceData parameter in the functions below are used to 59 // query configuration and changes for the resource as well as to set 60 // the ID, computed data, etc. 61 // 62 // The interface{} parameter is the result of the ConfigureFunc in 63 // the provider for this resource. If the provider does not define 64 // a ConfigureFunc, this will be nil. This parameter should be used 65 // to store API clients, configuration structures, etc. 66 // 67 // If any errors occur during each of the operation, an error should be 68 // returned. If a resource was partially updated, be careful to enable 69 // partial state mode for ResourceData and use it accordingly. 70 // 71 // Exists is a function that is called to check if a resource still 72 // exists. If this returns false, then this will affect the diff 73 // accordingly. If this function isn't set, it will not be called. It 74 // is highly recommended to set it. The *ResourceData passed to Exists 75 // should _not_ be modified. 76 Create CreateFunc 77 Read ReadFunc 78 Update UpdateFunc 79 Delete DeleteFunc 80 Exists ExistsFunc 81 } 82 83 // See Resource documentation. 84 type CreateFunc func(*ResourceData, interface{}) error 85 86 // See Resource documentation. 87 type ReadFunc func(*ResourceData, interface{}) error 88 89 // See Resource documentation. 90 type UpdateFunc func(*ResourceData, interface{}) error 91 92 // See Resource documentation. 93 type DeleteFunc func(*ResourceData, interface{}) error 94 95 // See Resource documentation. 96 type ExistsFunc func(*ResourceData, interface{}) (bool, error) 97 98 // See Resource documentation. 99 type StateMigrateFunc func( 100 int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) 101 102 // Apply creates, updates, and/or deletes a resource. 103 func (r *Resource) Apply( 104 s *terraform.InstanceState, 105 d *terraform.InstanceDiff, 106 meta interface{}) (*terraform.InstanceState, error) { 107 data, err := schemaMap(r.Schema).Data(s, d) 108 if err != nil { 109 return s, err 110 } 111 112 if s == nil { 113 // The Terraform API dictates that this should never happen, but 114 // it doesn't hurt to be safe in this case. 115 s = new(terraform.InstanceState) 116 } 117 118 if d.Destroy || d.RequiresNew() { 119 if s.ID != "" { 120 // Destroy the resource since it is created 121 if err := r.Delete(data, meta); err != nil { 122 return data.State(), err 123 } 124 125 // Make sure the ID is gone. 126 data.SetId("") 127 } 128 129 // If we're only destroying, and not creating, then return 130 // now since we're done! 131 if !d.RequiresNew() { 132 return nil, nil 133 } 134 135 // Reset the data to be stateless since we just destroyed 136 data, err = schemaMap(r.Schema).Data(nil, d) 137 if err != nil { 138 return nil, err 139 } 140 } 141 142 err = nil 143 if data.Id() == "" { 144 // We're creating, it is a new resource. 145 err = r.Create(data, meta) 146 } else { 147 if r.Update == nil { 148 return s, fmt.Errorf("doesn't support update") 149 } 150 151 err = r.Update(data, meta) 152 } 153 154 return r.recordCurrentSchemaVersion(data.State()), err 155 } 156 157 // Diff returns a diff of this resource and is API compatible with the 158 // ResourceProvider interface. 159 func (r *Resource) Diff( 160 s *terraform.InstanceState, 161 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 162 return schemaMap(r.Schema).Diff(s, c) 163 } 164 165 // Validate validates the resource configuration against the schema. 166 func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { 167 return schemaMap(r.Schema).Validate(c) 168 } 169 170 // Refresh refreshes the state of the resource. 171 func (r *Resource) Refresh( 172 s *terraform.InstanceState, 173 meta interface{}) (*terraform.InstanceState, error) { 174 if r.Exists != nil { 175 // Make a copy of data so that if it is modified it doesn't 176 // affect our Read later. 177 data, err := schemaMap(r.Schema).Data(s, nil) 178 if err != nil { 179 return s, err 180 } 181 182 exists, err := r.Exists(data, meta) 183 if err != nil { 184 return s, err 185 } 186 if !exists { 187 return nil, nil 188 } 189 } 190 191 needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) 192 if needsMigration && r.MigrateState != nil { 193 s, err := r.MigrateState(stateSchemaVersion, s, meta) 194 if err != nil { 195 return s, err 196 } 197 } 198 199 data, err := schemaMap(r.Schema).Data(s, nil) 200 if err != nil { 201 return s, err 202 } 203 204 err = r.Read(data, meta) 205 state := data.State() 206 if state != nil && state.ID == "" { 207 state = nil 208 } 209 210 return r.recordCurrentSchemaVersion(state), err 211 } 212 213 // InternalValidate should be called to validate the structure 214 // of the resource. 215 // 216 // This should be called in a unit test for any resource to verify 217 // before release that a resource is properly configured for use with 218 // this library. 219 // 220 // Provider.InternalValidate() will automatically call this for all of 221 // the resources it manages, so you don't need to call this manually if it 222 // is part of a Provider. 223 func (r *Resource) InternalValidate() error { 224 if r == nil { 225 return errors.New("resource is nil") 226 } 227 228 return schemaMap(r.Schema).InternalValidate() 229 } 230 231 // Determines if a given InstanceState needs to be migrated by checking the 232 // stored version number with the current SchemaVersion 233 func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { 234 stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"]) 235 return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion 236 } 237 238 func (r *Resource) recordCurrentSchemaVersion( 239 state *terraform.InstanceState) *terraform.InstanceState { 240 if state != nil && r.SchemaVersion > 0 { 241 if state.Meta == nil { 242 state.Meta = make(map[string]string) 243 } 244 state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) 245 } 246 return state 247 }