package terraform import ( "fmt" "reflect" "strconv" "strings" "github.com/hashicorp/terraform/config" ) // ResourceProvider is an interface that must be implemented by any // resource provider: the thing that creates and manages the resources in // a Terraform configuration. type ResourceProvider interface { // Validate is called once at the beginning with the raw configuration // (no interpolation done) and can return a list of warnings and/or // errors. // // This should not assume that any values of the configurations are valid. // The primary use case of this call is to check that required keys are // set. Validate(*ResourceConfig) ([]string, []error) // Configure configures the provider itself with the configuration // given. This is useful for setting things like access keys. // // Configure returns an error if it occurred. Configure(*ResourceConfig) error // Resources returns all the available resource types that this provider // knows how to manage. Resources() []ResourceType // Apply applies a diff to a specific resource and returns the new // resource state along with an error. // // If the resource state given has an empty ID, then a new resource // is expected to be created. Apply( *ResourceState, *ResourceDiff) (*ResourceState, error) // Diff diffs a resource versus a desired state and returns // a diff. Diff( *ResourceState, *ResourceConfig) (*ResourceDiff, error) // Refresh refreshes a resource and updates all of its attributes // with the latest information. Refresh(*ResourceState) (*ResourceState, error) } // ResourceConfig holds the configuration given for a resource. This is // done instead of a raw `map[string]interface{}` type so that rich // methods can be added to it to make dealing with it easier. type ResourceConfig struct { ComputedKeys []string Raw map[string]interface{} Config map[string]interface{} } // ResourceType is a type of resource that a resource provider can manage. type ResourceType struct { Name string } // ResourceProviderFactory is a function type that creates a new instance // of a resource provider. type ResourceProviderFactory func() (ResourceProvider, error) // NewResourceConfig creates a new ResourceConfig from a config.RawConfig. func NewResourceConfig(c *config.RawConfig) *ResourceConfig { return &ResourceConfig{ ComputedKeys: c.UnknownKeys(), Raw: c.Raw, Config: c.Config(), } } // CheckSet checks that the given list of configuration keys is // properly set. If not, errors are returned for each unset key. // // This is useful to be called in the Validate method of a ResourceProvider. func (c *ResourceConfig) CheckSet(keys []string) []error { var errs []error for _, k := range keys { if !c.IsSet(k) { errs = append(errs, fmt.Errorf("%s must be set", k)) } } return errs } // Get looks up a configuration value by key and returns the value. // // The second return value is true if the get was successful. Get will // not succeed if the value is being computed. func (c *ResourceConfig) Get(k string) (interface{}, bool) { parts := strings.Split(k, ".") var current interface{} = c.Raw for _, part := range parts { if current == nil { return nil, false } cv := reflect.ValueOf(current) switch cv.Kind() { case reflect.Map: v := cv.MapIndex(reflect.ValueOf(part)) if !v.IsValid() { return nil, false } current = v.Interface() case reflect.Slice: i, err := strconv.ParseInt(part, 0, 0) if err != nil { return nil, false } current = cv.Index(int(i)).Interface() default: panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) } } return current, true } // IsSet checks if the key in the configuration is set. A key is set if // it has a value or the value is being computed (is unknown currently). // // This function should be used rather than checking the keys of the // raw configuration itself, since a key may be omitted from the raw // configuration if it is being computed. func (c *ResourceConfig) IsSet(k string) bool { if c == nil { return false } for _, ck := range c.ComputedKeys { if ck == k { return true } } if _, ok := c.Get(k); ok { return true } return false } func ProviderSatisfies(p ResourceProvider, n string) bool { for _, rt := range p.Resources() { if rt.Name == n { return true } } return false }