tpgtools/override.go (160 lines of code) (raw):

// Copyright 2021 Google LLC. All Rights Reserved. // // 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 main import ( "fmt" "github.com/golang/glog" "gopkg.in/yaml.v2" ) // When adding a new override, write a doc about it using go/tpgtools-new-feature and add it to go/tpgtools-overrides. type OverrideType string // enum // Product-level Overrides const ( ProductBasePath OverrideType = "PRODUCT_BASE_PATH" ProductTitle OverrideType = "PRODUCT_TITLE" ProductDocsSection OverrideType = "PRODUCT_DOCS_SECTION" ) // Resource-level Overrides const ( VirtualField OverrideType = "VIRTUAL_FIELD" CustomID = "CUSTOM_ID" CustomizeDiff = "CUSTOMIZE_DIFF" ImportFormat = "IMPORT_FORMAT" AppendToBasePath = "APPEND_TO_BASE_PATH" ReplaceInBasePath = "REPLACE_IN_BASE_PATH" Mutex = "MUTEX" PreCreate = "PRE_CREATE_FUNCTION" PostCreate = "POST_CREATE_FUNCTION" PreDelete = "PRE_DELETE_FUNCTION" SkipInProvider = "SKIP_IN_PROVIDER" CustomResourceName = "CUSTOM_RESOURCE_NAME" NoSweeper = "NO_SWEEPER" CustomImport = "CUSTOM_IMPORT_FUNCTION" CustomCreateDirective = "CUSTOM_CREATE_DIRECTIVE_FUNCTION" SkipDeleteFunction = "SKIP_DELETE_FUNCTION" SerializationOnly = "SERIALIZATION_ONLY" CustomSerializer = "CUSTOM_SERIALIZER" TerraformProductName = "CUSTOM_TERRAFORM_PRODUCT_NAME" CustomTimeout = "CUSTOM_TIMEOUT" StateUpgrade = "STATE_UPGRADE" GenerateLongFormTests = "GENERATE_LONG_FORM_TESTS" ) // Field-level Overrides const ( CustomConfigMode OverrideType = "CUSTOM_CONFIG_MODE" CustomDescription = "CUSTOM_DESCRIPTION" NamePrefix = "NAME_PREFIX" CustomName = "CUSTOM_NAME" CustomStateGetter = "CUSTOM_STATE_GETTER" CustomStateSetter = "CUSTOM_STATE_SETTER" CustomValidation = "CUSTOM_VALIDATION" Deprecated = "DEPRECATED" DiffSuppressFunc = "DIFF_SUPPRESS_FUNC" EnumBool = "ENUM_BOOL" Exclude = "EXCLUDE" CustomIdentityGetter = "CUSTOM_IDENTITY_GETTER" Removed = "REMOVED" SetHashFunc = "SET_HASH_FUNC" CollapsedObject = "COLLAPSED_OBJECT" IgnoreRead = "IGNORE_READ" GenerateIfNotSet = "GENERATE_IF_NOT_SET" CustomListSize = "CUSTOM_LIST_SIZE_CONSTRAINT" CustomDefault = "CUSTOM_DEFAULT" CustomSchemaValues = "CUSTOM_SCHEMA_VALUES" ComplexMapKey = "COMPLEX_MAP_KEY_NAME" ) // Overrides represents the type a resource's override file can be marshalled // into. type Overrides []Override // Overrides handle minor quirks in behaviour for a resource (or one of its // fields) by injecting modifications into the generated code for the resource. // Every Override will have a Type; Overrides with a Field defined apply to that // field in the resource and overrives without apply to the resource; Overrides // may contain Details with structured metadata. type Override struct { Type OverrideType Field *string // may be nil Details interface{} // may be nil Location *string // may be nil } // ResourceOverride returns whether a single override with a single OverrideType // is present on the resource. func (o Overrides) ResourceOverride(typ OverrideType, location string) bool { found := false for _, v := range o { if v.Field == nil && v.Type == typ && compareLocation(v.Location, location) { if found { glog.Fatalf("found duplicate override of type %v", typ) } found = true } } return found } // ResourceOverrideWithDetails returns whether a single OverrideType is present // on the resource, and includes the override's Details in the i interface if so. func (o Overrides) ResourceOverrideWithDetails(typ OverrideType, i interface{}, location string) (bool, error) { found := false for _, v := range o { if v.Field == nil && v.Type == typ && compareLocation(v.Location, location) { if found { return false, fmt.Errorf("found duplicate override of type %v", typ) } found = true if err := convert(v.Details, i); err != nil { return false, fmt.Errorf("error converting type: %v", err) } } } return found, nil } // ResourceOverridesWithDetails returns all OverrideTypes of a given type on the // resource, returning their details as yaml in an array. // TODO: make this generic when Go supports generics // ResourceOverrideWithDetails (the singular variant) is preferred when multiple // overrides of a given type are not expected. func (o Overrides) ResourceOverridesWithDetails(typ OverrideType, location string) (overrides []interface{}) { for _, v := range o { if v.Field == nil && v.Type == typ && compareLocation(v.Location, location) { overrides = append(overrides, v.Details) } } return overrides } // PropertyOverride returns whether a single override with a single OverrideType // is present on a given property. func (o Overrides) PropertyOverride(typ OverrideType, p Property, location string) bool { found := false for _, v := range o { if v.Field != nil && *v.Field == p.overridePath() && v.Type == typ && compareLocation(v.Location, location) { if found { glog.Fatalf("found duplicate override of type %v", typ) } found = true } } return found } // PropertyOverrideWithDetails returns whether a single OverrideType is present // on a property, and includes the override's Details in the i interface if so. func (o Overrides) PropertyOverrideWithDetails(typ OverrideType, p Property, i interface{}, location string) (bool, error) { found := false for _, v := range o { if v.Field != nil && *v.Field == p.overridePath() && v.Type == typ && compareLocation(v.Location, location) { if found { return false, fmt.Errorf("found duplicate override of type %v", typ) } found = true if err := convert(v.Details, i); err != nil { return false, fmt.Errorf("error converting type: %v", err) } } } return found, nil } // ProductWithDetails returns whether a single OverrideType is present // on the resource, and includes the override's Details in the i interface if so. func (o Overrides) ProductOverrideWithDetails(typ OverrideType, i interface{}) (bool, error) { found := false for _, v := range o { if v.Field == nil && v.Type == typ { if found { return false, fmt.Errorf("found duplicate override of type %v", typ) } found = true if err := convert(v.Details, i); err != nil { return false, fmt.Errorf("error converting type: %v", err) } } } return found, nil } func compareLocation(overrideLocation *string, objectLocation string) bool { // Overrides without a location field are considered valid for every location if overrideLocation == nil { return true } return *overrideLocation == objectLocation } func convert(item, out interface{}) error { bytes, err := yaml.Marshal(item) if err != nil { return fmt.Errorf("failed to marshal: %v", err) } err = yaml.Unmarshal(bytes, out) if err != nil { return err } return nil }