terraformutils/resource.go (148 lines of code) (raw):
// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package terraformutils
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper"
"github.com/hashicorp/terraform/terraform"
"github.com/zclconf/go-cty/cty"
)
type Resource struct {
InstanceInfo *terraform.InstanceInfo
InstanceState *terraform.InstanceState
Outputs map[string]*terraform.OutputState `json:",omitempty"`
ResourceName string
Provider string
Item map[string]interface{} `json:",omitempty"`
IgnoreKeys []string `json:",omitempty"`
AllowEmptyValues []string `json:",omitempty"`
AdditionalFields map[string]interface{} `json:",omitempty"`
SlowQueryRequired bool
DataFiles map[string][]byte
}
type ApplicableFilter interface {
IsApplicable(resourceName string) bool
}
type ResourceFilter struct {
ApplicableFilter
ServiceName string
FieldPath string
AcceptableValues []string
}
func (rf *ResourceFilter) Filter(resource Resource) bool {
if !rf.IsApplicable(strings.TrimPrefix(resource.InstanceInfo.Type, resource.Provider+"_")) {
return true
}
var vals []interface{}
switch {
case rf.FieldPath == "id":
vals = []interface{}{resource.InstanceState.ID}
case rf.AcceptableValues == nil:
var hasField = WalkAndCheckField(rf.FieldPath, resource.InstanceState.Attributes)
if hasField {
return true
}
return WalkAndCheckField(rf.FieldPath, resource.Item)
default:
vals = WalkAndGet(rf.FieldPath, resource.InstanceState.Attributes)
if len(vals) == 0 {
vals = WalkAndGet(rf.FieldPath, resource.Item)
}
}
for _, val := range vals {
for _, acceptableValue := range rf.AcceptableValues {
if val == acceptableValue {
return true
}
}
}
return false
}
func (rf *ResourceFilter) IsApplicable(serviceName string) bool {
return rf.ServiceName == "" || rf.ServiceName == serviceName
}
func (rf *ResourceFilter) isInitial() bool {
return rf.FieldPath == "id"
}
func NewResource(id, resourceName, resourceType, provider string,
attributes map[string]string,
allowEmptyValues []string,
additionalFields map[string]interface{}) Resource {
return Resource{
ResourceName: TfSanitize(resourceName),
Item: nil,
Provider: provider,
InstanceState: &terraform.InstanceState{
ID: id,
Attributes: attributes,
},
InstanceInfo: &terraform.InstanceInfo{
Type: resourceType,
Id: fmt.Sprintf("%s.%s", resourceType, TfSanitize(resourceName)),
},
AdditionalFields: additionalFields,
AllowEmptyValues: allowEmptyValues,
}
}
func NewSimpleResource(id, resourceName, resourceType, provider string, allowEmptyValues []string) Resource {
return NewResource(
id,
resourceName,
resourceType,
provider,
map[string]string{},
allowEmptyValues,
map[string]interface{}{},
)
}
func (r *Resource) Refresh(provider *providerwrapper.ProviderWrapper) {
var err error
if r.SlowQueryRequired {
time.Sleep(200 * time.Millisecond)
}
r.InstanceState, err = provider.Refresh(r.InstanceInfo, r.InstanceState)
if err != nil {
log.Println(err)
}
}
func (r Resource) GetIDKey() string {
if _, exist := r.InstanceState.Attributes["self_link"]; exist {
return "self_link"
}
return "id"
}
func (r *Resource) ParseTFstate(parser Flatmapper, impliedType cty.Type) error {
attributes, err := parser.Parse(impliedType)
if err != nil {
return err
}
// add Additional Fields to resource
for key, value := range r.AdditionalFields {
attributes[key] = value
}
if attributes == nil {
attributes = map[string]interface{}{} // ensure HCL can represent empty resource correctly
}
r.Item = attributes
return nil
}
func (r *Resource) ConvertTFstate(provider *providerwrapper.ProviderWrapper) error {
ignoreKeys := []*regexp.Regexp{}
for _, pattern := range r.IgnoreKeys {
ignoreKeys = append(ignoreKeys, regexp.MustCompile(pattern))
}
allowEmptyValues := []*regexp.Regexp{}
for _, pattern := range r.AllowEmptyValues {
if pattern != "" {
allowEmptyValues = append(allowEmptyValues, regexp.MustCompile(pattern))
}
}
parser := NewFlatmapParser(r.InstanceState.Attributes, ignoreKeys, allowEmptyValues)
schema := provider.GetSchema()
impliedType := schema.ResourceTypes[r.InstanceInfo.Type].Block.ImpliedType()
return r.ParseTFstate(parser, impliedType)
}
func (r *Resource) ServiceName() string {
return strings.TrimPrefix(r.InstanceInfo.Type, r.Provider+"_")
}