Giter Club home page Giter Club logo

Comments (2)

bflad avatar bflad commented on June 23, 2024

I can provide the initial SDK for handling CloudFormation Resource Schema in Go and an example method for Terraform Plugin SDK version 2 schema from that SDK. We will likely be targeting a different Terraform Plugin SDK, which will likely hold up the implementation for generated provider resources.

from terraform-provider-awscc.

bflad avatar bflad commented on June 23, 2024

The experimental code for Terraform Plugin SDK v2 schema generation is shown below.

err := cfResource.Expand()

if err != nil {
	return err
}

for propertyName, property := range cfResource.Properties {
	log.Printf("[DEBUG] Found schema property (%s):\n%s", propertyName, property)
	log.Printf("[DEBUG] Generated schema for property (%s): %s", propertyName, RootPropertySchema(cfResource, propertyName))
}
func RootPropertySchema(r *cfschema.Resource, name string) string {
	if r == nil {
		return ""
	}

	property, ok := r.Properties[name]

	if !ok || property == nil {
		return ""
	}

	return PropertySchema(r, []string{}, name, property)
}

func PropertySchema(r *cfschema.Resource, pathPrefix []string, name string, property *Property) string {
	if r == nil {
		return ""
	}

	var b strings.Builder
	path := append(pathPrefix, name)
	indentation := strings.Repeat("\t", len(path))

	createOnly := r.CreateOnlyProperties.ContainsPath(path)
	readOnly := r.ReadOnlyProperties.ContainsPath(path)
	required := r.IsRequired(name)

	if name != "" {
		fmt.Fprintf(&b, "\n\n%s\"%s\": {", indentation, ToSnakeCase(name))
	}

	switch property.Type.String() {
	default:
		fmt.Fprintf(&b, "\n%sType: UNKNOWN,", indentation)
	case cfschema.PropertyTypeArray:
		if property.InsertionOrder != nil && *property.InsertionOrder {
			fmt.Fprintf(&b, "\n%sType: TypeList,", indentation)
		} else {
			fmt.Fprintf(&b, "\n%sType: TypeSet,", indentation)
		}

		if property.MaxItems != nil {
			fmt.Fprintf(&b, "\n%sMaxItems: %d,", indentation, *property.MaxItems)
		}

		if property.MinItems != nil {
			fmt.Fprintf(&b, "\n%sMinItems: %d,", indentation, *property.MinItems)
		}

		if property.Items != nil {
			if property.Items.Type.String() == cfschema.PropertyTypeObject {
				fmt.Fprintf(&b, "\n%sElem: &schema.Resource{", indentation)
				fmt.Fprintf(&b, "\n%s\tSchema: map[string]schema.Schema{", indentation)
			} else {
				fmt.Fprintf(&b, "\n%sElem: &schema.Schema{", indentation)
			}

			fmt.Fprintf(&b, PropertySchema(r, path, "", property.Items))

			if property.Items.Type.String() == cfschema.PropertyTypeObject {
				fmt.Fprintf(&b, "\n%s\t},", indentation)
			}

			fmt.Fprintf(&b, "\n%s},", indentation)
		}
	case cfschema.PropertyTypeBoolean:
		fmt.Fprintf(&b, "\n%sType: TypeBool,", indentation)
	case cfschema.PropertyTypeInteger:
		fmt.Fprintf(&b, "\n%sType: TypeInt,", indentation)

		if len(property.Enum) == 0 {
			break
		}

		fmt.Fprintf(&b, "\n%sValidateFunc: validation.All(", indentation)

		if len(property.Enum) > 0 {
			fmt.Fprintf(&b, "\n%s\tvalidation.IntInSlice([]int{", indentation)
			for _, enumItem := range property.Enum {
				switch enumItem.(type) {
				case float64:
					fmt.Fprintf(&b, "\n%s\t\t%d,", indentation, int(enumItem.(float64)))
				case int64:
					fmt.Fprintf(&b, "\n%s\t\t%d,", indentation, enumItem)
				}
			}

			fmt.Fprintf(&b, "\n%s\t}),", indentation)
		}

		fmt.Fprintf(&b, "\n%s),", indentation)
	case cfschema.PropertyTypeNumber:
		fmt.Fprintf(&b, "\n%sType: TypeFloat,", indentation)
	case cfschema.PropertyTypeObject:
		// If there are no underlying Properties, the schema is not defined.
		// CloudFormation documentation denotes these as Json or Object.
		if len(property.Properties) == 0 {
			fmt.Fprintf(&b, "\n%sType: TypeDynamic,", indentation)
			break
		}

		fmt.Fprintf(&b, "\n%sType: TypeList,", indentation)
		fmt.Fprintf(&b, "\n%sMaxItems: 1,", indentation)
		fmt.Fprintf(&b, "\n%sElem: &schema.Resource{", indentation)
		fmt.Fprintf(&b, "\n%s\tSchema: map[string]schema.Schema{", indentation)
		for objPropertyName, objProperty := range property.Properties {
			fmt.Fprintf(&b, PropertySchema(r, path, objPropertyName, objProperty))
		}
		fmt.Fprintf(&b, "\n%s\t},", indentation)
		fmt.Fprintf(&b, "\n%s},", indentation)
	case cfschema.PropertyTypeString:
		fmt.Fprintf(&b, "\n%sType: TypeString,", indentation)

		if len(property.Enum) == 0 && property.MaxLength == nil && property.MinLength == nil && property.Pattern == nil {
			break
		}

		fmt.Fprintf(&b, "\n%sValidateFunc: validation.All(", indentation)

		if len(property.Enum) > 0 {
			fmt.Fprintf(&b, "\n%s\tvalidation.StringInSlice([]string{", indentation)
			for _, enumItem := range property.Enum {
				fmt.Fprintf(&b, "\n%s\t\t%q,", indentation, enumItem)
			}
			fmt.Fprintf(&b, "\n%s\t}, false),", indentation)
		}

		if property.MaxLength != nil && property.MinLength != nil {
			fmt.Fprintf(&b, "\n%s\tvalidation.StringLenBetween(%d, %d),", indentation, *property.MinLength, *property.MaxLength)
		} else if property.MaxLength != nil {
			fmt.Fprintf(&b, "\n%s\tvalidation.StringLenBetween(0, %d),", indentation, *property.MaxLength)
		}

		if property.Pattern != nil {
			fmt.Fprintf(&b, "\n%s\tvalidation.StringMatch(`%s`, \"\"),", indentation, *property.Pattern)
		}

		fmt.Fprintf(&b, "\n%s),", indentation)
	}

	// Array items
	if name == "" {
		fmt.Fprintf(&b, "\n%s},", indentation)
		return b.String()
	}

	if required {
		fmt.Fprintf(&b, "\n%sRequired: true,", indentation)
	} else if !readOnly {
		fmt.Fprintf(&b, "\n%sOptional: true,", indentation)
	}

	if readOnly && !required {
		fmt.Fprintf(&b, "\n%sComputed: true,", indentation)
	}

	if createOnly {
		fmt.Fprintf(&b, "\n%sForceNew: true,", indentation)
	}

	if property.Description != nil {
		fmt.Fprintf(&b, "\n%sDescription: `%s`,", indentation, *property.Description)
	}

	fmt.Fprintf(&b, "\n%s},", indentation)

	return b.String()
}

// ToSnakeCase converts a string to snake case.
//
// For example, AWS schema property names are in PascalCase,
// while Terraform schema attribute names are in snake_case.
func ToSnakeCase(str string) string {
	result := regexp.MustCompile("(.)([A-Z][a-z]+)").ReplaceAllString(str, "${1}_${2}")
	result = regexp.MustCompile("([a-z0-9])([A-Z])").ReplaceAllString(result, "${1}_${2}")
	return strings.ToLower(result)
}

from terraform-provider-awscc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.