diff --git a/cmd/ack-generate/command/webhook.go b/cmd/ack-generate/command/webhook.go new file mode 100644 index 00000000..3044ea05 --- /dev/null +++ b/cmd/ack-generate/command/webhook.go @@ -0,0 +1,126 @@ +// Copyright Amazon.com Inc. or its affiliates. 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. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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 command + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/aws-controllers-k8s/code-generator/pkg/generate/ack" + ackgenerate "github.com/aws-controllers-k8s/code-generator/pkg/generate/ack" + "github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion" +) + +var ( + optHubVersion string + optDeprecatedVersions []string + optEnableConversionWebhook bool +) + +var webhooksCmd = &cobra.Command{ + Use: "webhooks ", + Short: "Generates Go files containing conversion, validation and defaulting webhooks.", + RunE: generateWebhooks, +} + +func init() { + webhooksCmd.PersistentFlags().StringVar( + &optHubVersion, "hub-version", "", "the hub version for conversion webhooks", + ) + webhooksCmd.PersistentFlags().BoolVar( + &optEnableConversionWebhook, "conversion", false, "enable conversion webhooks generation", + ) + rootCmd.AddCommand(webhooksCmd) +} + +// generateWebhooks generates the Go files for conversion, defaulting and validating webhooks. +func generateWebhooks(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("please specify the service alias for the AWS service API to generate") + } + svcAlias := strings.ToLower(args[0]) + if optOutputPath == "" { + optOutputPath = filepath.Join(optServicesDir, svcAlias) + } + + apisVersionPath = filepath.Join(optOutputPath, "apis") + files, err := ioutil.ReadDir(apisVersionPath) + if err != nil { + return err + } + + apiInfos := map[string]multiversion.APIInfo{} + for _, f := range files { + metadata, err := ack.LoadGenerationMetadata(apisVersionPath, f.Name()) + if err != nil { + return err + } + apiInfos[f.Name()] = multiversion.APIInfo{ + Status: multiversion.APIStatusUnknown, + AWSSDKVersion: metadata.AWSSDKGoVersion, + GeneratorConfigPath: filepath.Join(apisVersionPath, f.Name(), metadata.GeneratorConfigInfo.OriginalFileName), + } + } + + if optHubVersion == "" { + latestAPIVersion, err := getLatestAPIVersion() + if err != nil { + return err + } + optHubVersion = latestAPIVersion + } + + mgr, err := multiversion.NewAPIVersionManager( + optCacheDir, + svcAlias, + optHubVersion, + apiInfos, + ack.DefaultConfig, + ) + if err != nil { + return err + } + + if optEnableConversionWebhook { + ts, err := ackgenerate.ConversionWebhooks(mgr, optTemplateDirs) + if err != nil { + return err + } + + if err = ts.Execute(); err != nil { + return err + } + + for path, contents := range ts.Executed() { + if optDryRun { + fmt.Printf("============================= %s ======================================\n", path) + fmt.Println(strings.TrimSpace(contents.String())) + continue + } + outPath := filepath.Join(optOutputPath, path) + outDir := filepath.Dir(outPath) + if _, err := ensureDir(outDir); err != nil { + return err + } + if err = ioutil.WriteFile(outPath, contents.Bytes(), 0666); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/generate/ack/webhook.go b/pkg/generate/ack/webhook.go new file mode 100644 index 00000000..5bbf1b34 --- /dev/null +++ b/pkg/generate/ack/webhook.go @@ -0,0 +1,124 @@ +// Copyright Amazon.com Inc. or its affiliates. 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. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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 ack + +import ( + "fmt" + ttpl "text/template" + + "github.com/aws-controllers-k8s/code-generator/pkg/generate/code" + "github.com/aws-controllers-k8s/code-generator/pkg/generate/templateset" + ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model" + "github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion" +) + +var ( + webhooksIncludePaths = []string{ + "boilerplate.go.tpl", + "apis/webhooks/conversion.go.tpl", + } + webhookCopyPaths = []string{} + webhooksFuncMap = ttpl.FuncMap{ + "GoCodeConvert": func( + src *ackmodel.CRD, + dst *ackmodel.CRD, + convertingToHub bool, + hubImportPath string, + sourceVarName string, + targetVarName string, + indentLevel int, + ) string { + return code.Convert(src, dst, convertingToHub, hubImportPath, sourceVarName, targetVarName, indentLevel) + }, + } +) + +// ConversionWebhooks returns a pointer to a TemplateSet containing all the templates +// for generating conversion webhooks. +func ConversionWebhooks( + mgr *multiversion.APIVersionManager, + templateBasePaths []string, +) (*templateset.TemplateSet, error) { + ts := templateset.New( + templateBasePaths, + webhooksIncludePaths, + webhookCopyPaths, + webhooksFuncMap, + ) + hubVersion := mgr.GetHubVersion() + hubModel, err := mgr.GetModel(hubVersion) + if err != nil { + return nil, err + } + + hubMetaVars := hubModel.MetaVars() + hubCRDs, err := hubModel.GetCRDs() + if err != nil { + return nil, err + } + + for _, crd := range hubCRDs { + convertVars := conversionVars{ + MetaVars: hubMetaVars, + SourceCRD: crd, + IsHub: true, + } + // Add the hub version template + target := fmt.Sprintf("apis/%s/%s_conversion.go", hubVersion, crd.Names.Snake) + if err = ts.Add(target, "apis/webhooks/conversion.go.tpl", convertVars); err != nil { + return nil, err + } + } + + // Add spoke version templates + for _, spokeVersion := range mgr.GetSpokeVersions() { + model, err := mgr.GetModel(spokeVersion) + if err != nil { + return nil, err + } + + metaVars := model.MetaVars() + crds, err := model.GetCRDs() + if err != nil { + return nil, err + } + + for i, crd := range crds { + convertVars := conversionVars{ + MetaVars: metaVars, + SourceCRD: crd, + DestCRD: hubCRDs[i], + IsHub: false, + HubVersion: hubVersion, + } + + target := fmt.Sprintf("apis/%s/%s_conversion.go", spokeVersion, crd.Names.Snake) + if err = ts.Add(target, "apis/webhooks/conversion.go.tpl", convertVars); err != nil { + return nil, err + } + } + } + + return ts, nil +} + +// conversionVars contains template variables for templates that output +// Go conversion functions. +type conversionVars struct { + templateset.MetaVars + SourceCRD *ackmodel.CRD + DestCRD *ackmodel.CRD + HubVersion string + IsHub bool +} diff --git a/pkg/generate/code/convert.go b/pkg/generate/code/convert.go new file mode 100644 index 00000000..7d32c73f --- /dev/null +++ b/pkg/generate/code/convert.go @@ -0,0 +1,109 @@ +// Copyright Amazon.com Inc. or its affiliates. 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. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package code + +import ( + "fmt" + "strings" + + "github.com/aws-controllers-k8s/code-generator/pkg/generate/code/converter" + "github.com/aws-controllers-k8s/code-generator/pkg/model" + "github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion" +) + +var ( + builtinStatuses = []string{"ACKResourceMetadata", "Conditions"} +) + +// Convert outputs Go code that convert a source CRD into a destination crd. +func Convert( + // source and destination CRDs + src, dst *model.CRD, + // boolean instructing `Convert` on whether to output Go code for ConvertTo + // or ConvertFrom + isConvertingToHub bool, + // the hub import alias. Generally it's the hub version. + hubImportAlias string, + // the source variable name. + srcVarName string, + // the destination raw variable name + dstRawVarName string, + // indentation level + indentLevel int, +) string { + out := "\n" + indent := strings.Repeat("\t", indentLevel) + + copyFromVarName := "dst" + copyFromRawVarName := "dstRaw" + if !isConvertingToHub { + copyFromVarName = "src" + copyFromRawVarName = "srcRaw" + } + + // cast srcRaw/dstRaw type + // dst := dstRaw.(*v1.Repository) || src := srcRaw.(*v1.Repository) + out += fmt.Sprintf( + "%s%s := %s.(*%s.%s)\n", + indent, + copyFromVarName, + copyFromRawVarName, + hubImportAlias, + dst.Names.Camel, + ) + + deltas, err := multiversion.ComputeCRDFieldDeltas(src, dst) + if err != nil { + msg := fmt.Sprintf("delta computation error: %v", err) + panic(msg) + } + + c := converter.New( + hubImportAlias, + src.SDKAPIPackageName(), + isConvertingToHub, + src.TypeRenames(), + builtinStatuses, + indentLevel, + ) + + // generate spec conversion code + toVarName := "dst.Spec" + fromVarName := "src.Spec" + out += c. + WithVariables(fromVarName, toVarName). + GenerateFieldsDeltasCode(deltas.SpecDeltas) + + // generate status conversion code + toVarName = "dst.Status" + fromVarName = "src.Status" + out += c. + WithVariables(fromVarName, toVarName). + GenerateFieldsDeltasCode(deltas.StatusDeltas) + + for _, status := range builtinStatuses { + // dst.Status.Conditions = src.Status.Conditions + out += fmt.Sprintf("%s%s.%s = %s.%s\n", indent, toVarName, status, fromVarName, status) + } + + out += "\n" + // dst.ObjectMeta = src.ObjectMeta + out += fmt.Sprintf("%sdst.ObjectMeta = src.ObjectMeta\n", indent) + // return nil + out += fmt.Sprintf("%sreturn nil", indent) + + return out +} diff --git a/pkg/generate/code/convert_test.go b/pkg/generate/code/convert_test.go new file mode 100644 index 00000000..b23c4a93 --- /dev/null +++ b/pkg/generate/code/convert_test.go @@ -0,0 +1,603 @@ +// Copyright Amazon.com Inc. or its affiliates. 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. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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 code_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aws-controllers-k8s/code-generator/pkg/generate/code" + "github.com/aws-controllers-k8s/code-generator/pkg/testutil" +) + +func TestConvertResource_ECR_Repository_v1alpha1_v1alpha2(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + srcModel := testutil.NewModelForServiceWithOptions(t, "ecr", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-v1alpha1.yaml", + }) + dstModel := testutil.NewModelForServiceWithOptions(t, "ecr", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-v1alpha2.yaml", + }) + hubCRDs, err := srcModel.GetCRDs() + require.Nil(err) + spokeCRDs, err := dstModel.GetCRDs() + require.Nil(err) + require.Len(hubCRDs, 1) + require.Len(spokeCRDs, 1) + + hubCRD := hubCRDs[0] + spokeCRD := spokeCRDs[0] + + expectedConvertTo := ` + dst := dstRaw.(*v1alpha2.Repository) + if src.Spec.ScanConfig != nil { + imageScanningConfigurationCopy := &v1alpha2.ImageScanningConfiguration{} + imageScanningConfigurationCopy.ScanOnPush = src.Spec.ScanConfig.ScanOnPush + dst.Spec.ImageScanningConfiguration = imageScanningConfigurationCopy + } + + dst.Spec.ImageTagMutability = src.Spec.ImageTagMutability + dst.Spec.RepositoryName = src.Spec.Name + if src.Spec.Tags != nil { + tagListCopy := make([]*v1alpha2.Tag, 0, len(src.Spec.Tags)) + for i, element := range src.Spec.Tags { + _ = i // non-used value guard. + elementCopy := &v1alpha2.Tag{} + if element != nil { + tagCopy := &v1alpha2.Tag{} + tagCopy.Key = element.Key + tagCopy.Value = element.Value + elementCopy = tagCopy + } + + tagListCopy = append(tagListCopy, elementCopy) + } + dst.Spec.Tags = tagListCopy + } + + dst.Status.CreatedAt = src.Status.CreatedAt + dst.Status.RegistryID = src.Status.RegistryID + dst.Status.RepositoryURI = src.Status.RepositoryURI + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertTo, + code.Convert( + spokeCRD, hubCRD, true, "v1alpha2", "src", "dstRaw", 1, + ), + ) + + expectedConvertFrom := ` + src := srcRaw.(*v1alpha2.Repository) + if src.Spec.ImageScanningConfiguration != nil { + imageScanningConfigurationCopy := &ImageScanningConfiguration{} + imageScanningConfigurationCopy.ScanOnPush = src.Spec.ImageScanningConfiguration.ScanOnPush + dst.Spec.ScanConfig = imageScanningConfigurationCopy + } + + dst.Spec.ImageTagMutability = src.Spec.ImageTagMutability + dst.Spec.Name = src.Spec.RepositoryName + if src.Spec.Tags != nil { + tagListCopy := make([]*Tag, 0, len(src.Spec.Tags)) + for i, element := range src.Spec.Tags { + _ = i // non-used value guard. + elementCopy := &Tag{} + if element != nil { + tagCopy := &Tag{} + tagCopy.Key = element.Key + tagCopy.Value = element.Value + elementCopy = tagCopy + } + + tagListCopy = append(tagListCopy, elementCopy) + } + dst.Spec.Tags = tagListCopy + } + + dst.Status.CreatedAt = src.Status.CreatedAt + dst.Status.RegistryID = src.Status.RegistryID + dst.Status.RepositoryURI = src.Status.RepositoryURI + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertFrom, + code.Convert( + spokeCRD, hubCRD, false, "v1alpha2", "src", "dstRaw", 1, + ), + ) +} + +func TestConvertResource_ECR_Repository_v1beta1_v1beta2(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + srcModel := testutil.NewModelForServiceWithOptions(t, "ecr", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-v1beta1.yaml", + ServiceAPIVersion: "0000-00-01", + }) + dstModel := testutil.NewModelForServiceWithOptions(t, "ecr", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-v1beta2.yaml", + ServiceAPIVersion: "0000-00-01", + }) + + hubCRDs, err := srcModel.GetCRDs() + require.Nil(err) + spokeCRDs, err := dstModel.GetCRDs() + require.Nil(err) + require.Len(hubCRDs, 1) + require.Len(spokeCRDs, 1) + + hubCRD := hubCRDs[0] + spokeCRD := spokeCRDs[0] + + expectedConvertTo := ` + dst := dstRaw.(*v1beta2.Repository) + if src.Spec.EncryptionConfiguration != nil { + encryptionConfigurationCopy := &v1beta2.EncryptionConfiguration{} + encryptionConfigurationCopy.EncryptionType = src.Spec.EncryptionConfiguration.EncryptionType + encryptionConfigurationCopy.KMSKey = src.Spec.EncryptionConfiguration.KMSKey + dst.Spec.EncryptionConfiguration = encryptionConfigurationCopy + } + + dst.Spec.ImageTagMutability = src.Spec.ImageTagMutability + dst.Spec.Name = src.Spec.Name + if src.Spec.ScanConfig != nil { + imageScanningConfigurationCopy := &v1beta2.ImageScanningConfiguration{} + imageScanningConfigurationCopy.ScanOnPush = src.Spec.ScanConfig.ScanOnPush + dst.Spec.ScanConfig = imageScanningConfigurationCopy + } + + if src.Spec.Tags != nil { + tagListCopy := make([]*v1beta2.Tag, 0, len(src.Spec.Tags)) + for i, element := range src.Spec.Tags { + _ = i // non-used value guard. + elementCopy := &v1beta2.Tag{} + if element != nil { + tagCopy := &v1beta2.Tag{} + tagCopy.Key = element.Key + tagCopy.Value = element.Value + elementCopy = tagCopy + } + + tagListCopy = append(tagListCopy, elementCopy) + } + dst.Spec.Tags = tagListCopy + } + + dst.Status.CreatedAt = src.Status.CreatedAt + dst.Status.RegistryID = src.Status.RegistryID + dst.Status.RepositoryURI = src.Status.RepositoryURI + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertTo, + code.Convert( + spokeCRD, hubCRD, true, "v1beta2", "src", "dstRaw", 1, + ), + ) + + expectedConvertFrom := ` + src := srcRaw.(*v1beta2.Repository) + if src.Spec.EncryptionConfiguration != nil { + encryptionConfigurationCopy := &EncryptionConfiguration{} + encryptionConfigurationCopy.EncryptionType = src.Spec.EncryptionConfiguration.EncryptionType + encryptionConfigurationCopy.KMSKey = src.Spec.EncryptionConfiguration.KMSKey + dst.Spec.EncryptionConfiguration = encryptionConfigurationCopy + } + + dst.Spec.ImageTagMutability = src.Spec.ImageTagMutability + dst.Spec.Name = src.Spec.Name + if src.Spec.ScanConfig != nil { + imageScanningConfigurationCopy := &ImageScanningConfiguration{} + imageScanningConfigurationCopy.ScanOnPush = src.Spec.ScanConfig.ScanOnPush + dst.Spec.ScanConfig = imageScanningConfigurationCopy + } + + if src.Spec.Tags != nil { + tagListCopy := make([]*Tag, 0, len(src.Spec.Tags)) + for i, element := range src.Spec.Tags { + _ = i // non-used value guard. + elementCopy := &Tag{} + if element != nil { + tagCopy := &Tag{} + tagCopy.Key = element.Key + tagCopy.Value = element.Value + elementCopy = tagCopy + } + + tagListCopy = append(tagListCopy, elementCopy) + } + dst.Spec.Tags = tagListCopy + } + + dst.Status.CreatedAt = src.Status.CreatedAt + dst.Status.RegistryID = src.Status.RegistryID + dst.Status.RepositoryURI = src.Status.RepositoryURI + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertFrom, + code.Convert( + spokeCRD, hubCRD, false, "v1beta2", "src", "dstRaw", 1, + ), + ) +} + +func TestConvertResource_APIGateway_Route_v1alpha1_v1alpha1(t *testing.T) { + assert := assert.New(t) + + srcModel := testutil.NewModelForServiceWithOptions(t, "apigatewayv2", &testutil.TestingModelOptions{}) + crd := testutil.GetCRDByName(t, srcModel, "Route") + + expectedConvertTo := ` + dst := dstRaw.(*v1alpha2.Route) + dst.Spec.APIID = src.Spec.APIID + dst.Spec.APIKeyRequired = src.Spec.APIKeyRequired + src.Spec.AuthorizationScopes = dst.Spec.AuthorizationScopes + dst.Spec.AuthorizationType = src.Spec.AuthorizationType + dst.Spec.AuthorizerID = src.Spec.AuthorizerID + dst.Spec.ModelSelectionExpression = src.Spec.ModelSelectionExpression + dst.Spec.OperationName = src.Spec.OperationName + dst.Spec.RequestModels = src.Spec.RequestModels + if src.Spec.RequestParameters != nil { + routeParametersCopy := make(map[string]*v1alpha2.ParameterConstraints, len(src.Spec.RequestParameters)) + for k, v := range src.Spec.RequestParameters { + elementCopy := &v1alpha2.ParameterConstraints{} + if v != nil { + parameterConstraintsCopy := &v1alpha2.ParameterConstraints{} + parameterConstraintsCopy.Required = v.Required + elementCopy = parameterConstraintsCopy + } + + routeParametersCopy[k] = elementCopy + } + dst.Spec.RequestParameters = routeParametersCopy + } + + dst.Spec.RouteKey = src.Spec.RouteKey + dst.Spec.RouteResponseSelectionExpression = src.Spec.RouteResponseSelectionExpression + dst.Spec.Target = src.Spec.Target + dst.Status.APIGatewayManaged = src.Status.APIGatewayManaged + dst.Status.RouteID = src.Status.RouteID + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertTo, + code.Convert( + crd, crd, true, "v1alpha2", "src", "dstRaw", 1, + ), + ) + + expectedConvertFrom := ` + src := srcRaw.(*v1alpha2.Route) + dst.Spec.APIID = src.Spec.APIID + dst.Spec.APIKeyRequired = src.Spec.APIKeyRequired + src.Spec.AuthorizationScopes = dst.Spec.AuthorizationScopes + dst.Spec.AuthorizationType = src.Spec.AuthorizationType + dst.Spec.AuthorizerID = src.Spec.AuthorizerID + dst.Spec.ModelSelectionExpression = src.Spec.ModelSelectionExpression + dst.Spec.OperationName = src.Spec.OperationName + dst.Spec.RequestModels = src.Spec.RequestModels + if src.Spec.RequestParameters != nil { + routeParametersCopy := make(map[string]*ParameterConstraints, len(src.Spec.RequestParameters)) + for k, v := range src.Spec.RequestParameters { + elementCopy := &ParameterConstraints{} + if v != nil { + parameterConstraintsCopy := &ParameterConstraints{} + parameterConstraintsCopy.Required = v.Required + elementCopy = parameterConstraintsCopy + } + + routeParametersCopy[k] = elementCopy + } + dst.Spec.RequestParameters = routeParametersCopy + } + + dst.Spec.RouteKey = src.Spec.RouteKey + dst.Spec.RouteResponseSelectionExpression = src.Spec.RouteResponseSelectionExpression + dst.Spec.Target = src.Spec.Target + dst.Status.APIGatewayManaged = src.Status.APIGatewayManaged + dst.Status.RouteID = src.Status.RouteID + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertFrom, + code.Convert( + crd, crd, false, "v1alpha2", "src", "dstRaw", 1, + ), + ) +} + +func TestConvertResource_CodeDeploy_Deployment_v1alpha1_v1alpha1(t *testing.T) { + assert := assert.New(t) + + srcModel := testutil.NewModelForServiceWithOptions(t, "codedeploy", &testutil.TestingModelOptions{}) + crd := testutil.GetCRDByName(t, srcModel, "Deployment") + + expectedConvertTo := ` + dst := dstRaw.(*v1alpha2.Deployment) + dst.Spec.ApplicationName = src.Spec.ApplicationName + if src.Spec.AutoRollbackConfiguration != nil { + autoRollbackConfigurationCopy := &v1alpha2.AutoRollbackConfiguration{} + autoRollbackConfigurationCopy.Enabled = src.Spec.AutoRollbackConfiguration.Enabled + src.Spec.AutoRollbackConfiguration.Events = autoRollbackConfigurationCopy.Events + dst.Spec.AutoRollbackConfiguration = autoRollbackConfigurationCopy + } + + dst.Spec.DeploymentConfigName = src.Spec.DeploymentConfigName + dst.Spec.DeploymentGroupName = src.Spec.DeploymentGroupName + dst.Spec.Description = src.Spec.Description + dst.Spec.FileExistsBehavior = src.Spec.FileExistsBehavior + dst.Spec.IgnoreApplicationStopFailures = src.Spec.IgnoreApplicationStopFailures + if src.Spec.Revision != nil { + revisionLocationCopy := &v1alpha2.RevisionLocation{} + if src.Spec.Revision.AppSpecContent != nil { + appSpecContentCopy := &v1alpha2.AppSpecContent{} + appSpecContentCopy.Content = src.Spec.Revision.AppSpecContent.Content + appSpecContentCopy.SHA256 = src.Spec.Revision.AppSpecContent.SHA256 + revisionLocationCopy.AppSpecContent = appSpecContentCopy + } + + if src.Spec.Revision.GitHubLocation != nil { + gitHubLocationCopy := &v1alpha2.GitHubLocation{} + gitHubLocationCopy.CommitID = src.Spec.Revision.GitHubLocation.CommitID + gitHubLocationCopy.Repository = src.Spec.Revision.GitHubLocation.Repository + revisionLocationCopy.GitHubLocation = gitHubLocationCopy + } + + revisionLocationCopy.RevisionType = src.Spec.Revision.RevisionType + if src.Spec.Revision.S3Location != nil { + s3LocationCopy := &v1alpha2.S3Location{} + s3LocationCopy.Bucket = src.Spec.Revision.S3Location.Bucket + s3LocationCopy.BundleType = src.Spec.Revision.S3Location.BundleType + s3LocationCopy.ETag = src.Spec.Revision.S3Location.ETag + s3LocationCopy.Key = src.Spec.Revision.S3Location.Key + s3LocationCopy.Version = src.Spec.Revision.S3Location.Version + revisionLocationCopy.S3Location = s3LocationCopy + } + + if src.Spec.Revision.String != nil { + rawStringCopy := &v1alpha2.RawString{} + rawStringCopy.Content = src.Spec.Revision.String.Content + rawStringCopy.SHA256 = src.Spec.Revision.String.SHA256 + revisionLocationCopy.String = rawStringCopy + } + + dst.Spec.Revision = revisionLocationCopy + } + + if src.Spec.TargetInstances != nil { + targetInstancesCopy := &v1alpha2.TargetInstances{} + src.Spec.TargetInstances.AutoScalingGroups = targetInstancesCopy.AutoScalingGroups + if src.Spec.TargetInstances.EC2TagSet != nil { + ec2TagSetCopy := &v1alpha2.EC2TagSet{} + if src.Spec.TargetInstances.EC2TagSet.EC2TagSetList != nil { + ec2TagSetListCopy := make([][]*v1alpha2.EC2TagFilter, 0, len(src.Spec.TargetInstances.EC2TagSet.EC2TagSetList)) + for i, element := range src.Spec.TargetInstances.EC2TagSet.EC2TagSetList { + _ = i // non-used value guard. + elementCopy := make([]*v1alpha2.EC2TagFilter, 0, len(element)) + if element != nil { + ec2TagFilterListCopy := make([]*v1alpha2.EC2TagFilter, 0, len(element)) + for i, element := range element { + _ = i // non-used value guard. + elementCopy := &v1alpha2.EC2TagFilter{} + if element != nil { + ec2TagFilterCopy := &v1alpha2.EC2TagFilter{} + ec2TagFilterCopy.Key = element.Key + ec2TagFilterCopy.Type = element.Type + ec2TagFilterCopy.Value = element.Value + elementCopy = ec2TagFilterCopy + } + + ec2TagFilterListCopy = append(ec2TagFilterListCopy, elementCopy) + } + elementCopy = ec2TagFilterListCopy + } + + ec2TagSetListCopy = append(ec2TagSetListCopy, elementCopy) + } + ec2TagSetCopy.EC2TagSetList = ec2TagSetListCopy + } + + targetInstancesCopy.EC2TagSet = ec2TagSetCopy + } + + if src.Spec.TargetInstances.TagFilters != nil { + ec2TagFilterListCopy := make([]*v1alpha2.EC2TagFilter, 0, len(src.Spec.TargetInstances.TagFilters)) + for i, element := range src.Spec.TargetInstances.TagFilters { + _ = i // non-used value guard. + elementCopy := &v1alpha2.EC2TagFilter{} + if element != nil { + ec2TagFilterCopy := &v1alpha2.EC2TagFilter{} + ec2TagFilterCopy.Key = element.Key + ec2TagFilterCopy.Type = element.Type + ec2TagFilterCopy.Value = element.Value + elementCopy = ec2TagFilterCopy + } + + ec2TagFilterListCopy = append(ec2TagFilterListCopy, elementCopy) + } + targetInstancesCopy.TagFilters = ec2TagFilterListCopy + } + + dst.Spec.TargetInstances = targetInstancesCopy + } + + dst.Spec.UpdateOutdatedInstancesOnly = src.Spec.UpdateOutdatedInstancesOnly + dst.Status.DeploymentID = src.Status.DeploymentID + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertTo, + code.Convert( + crd, crd, true, "v1alpha2", "src", "dstRaw", 1, + ), + ) + + expectedConvertFrom := ` + src := srcRaw.(*v1alpha2.Deployment) + dst.Spec.ApplicationName = src.Spec.ApplicationName + if src.Spec.AutoRollbackConfiguration != nil { + autoRollbackConfigurationCopy := &AutoRollbackConfiguration{} + autoRollbackConfigurationCopy.Enabled = src.Spec.AutoRollbackConfiguration.Enabled + src.Spec.AutoRollbackConfiguration.Events = autoRollbackConfigurationCopy.Events + dst.Spec.AutoRollbackConfiguration = autoRollbackConfigurationCopy + } + + dst.Spec.DeploymentConfigName = src.Spec.DeploymentConfigName + dst.Spec.DeploymentGroupName = src.Spec.DeploymentGroupName + dst.Spec.Description = src.Spec.Description + dst.Spec.FileExistsBehavior = src.Spec.FileExistsBehavior + dst.Spec.IgnoreApplicationStopFailures = src.Spec.IgnoreApplicationStopFailures + if src.Spec.Revision != nil { + revisionLocationCopy := &RevisionLocation{} + if src.Spec.Revision.AppSpecContent != nil { + appSpecContentCopy := &AppSpecContent{} + appSpecContentCopy.Content = src.Spec.Revision.AppSpecContent.Content + appSpecContentCopy.SHA256 = src.Spec.Revision.AppSpecContent.SHA256 + revisionLocationCopy.AppSpecContent = appSpecContentCopy + } + + if src.Spec.Revision.GitHubLocation != nil { + gitHubLocationCopy := &GitHubLocation{} + gitHubLocationCopy.CommitID = src.Spec.Revision.GitHubLocation.CommitID + gitHubLocationCopy.Repository = src.Spec.Revision.GitHubLocation.Repository + revisionLocationCopy.GitHubLocation = gitHubLocationCopy + } + + revisionLocationCopy.RevisionType = src.Spec.Revision.RevisionType + if src.Spec.Revision.S3Location != nil { + s3LocationCopy := &S3Location{} + s3LocationCopy.Bucket = src.Spec.Revision.S3Location.Bucket + s3LocationCopy.BundleType = src.Spec.Revision.S3Location.BundleType + s3LocationCopy.ETag = src.Spec.Revision.S3Location.ETag + s3LocationCopy.Key = src.Spec.Revision.S3Location.Key + s3LocationCopy.Version = src.Spec.Revision.S3Location.Version + revisionLocationCopy.S3Location = s3LocationCopy + } + + if src.Spec.Revision.String != nil { + rawStringCopy := &RawString{} + rawStringCopy.Content = src.Spec.Revision.String.Content + rawStringCopy.SHA256 = src.Spec.Revision.String.SHA256 + revisionLocationCopy.String = rawStringCopy + } + + dst.Spec.Revision = revisionLocationCopy + } + + if src.Spec.TargetInstances != nil { + targetInstancesCopy := &TargetInstances{} + src.Spec.TargetInstances.AutoScalingGroups = targetInstancesCopy.AutoScalingGroups + if src.Spec.TargetInstances.EC2TagSet != nil { + ec2TagSetCopy := &EC2TagSet{} + if src.Spec.TargetInstances.EC2TagSet.EC2TagSetList != nil { + ec2TagSetListCopy := make([][]*EC2TagFilter, 0, len(src.Spec.TargetInstances.EC2TagSet.EC2TagSetList)) + for i, element := range src.Spec.TargetInstances.EC2TagSet.EC2TagSetList { + _ = i // non-used value guard. + elementCopy := make([]*EC2TagFilter, 0, len(element)) + if element != nil { + ec2TagFilterListCopy := make([]*EC2TagFilter, 0, len(element)) + for i, element := range element { + _ = i // non-used value guard. + elementCopy := &EC2TagFilter{} + if element != nil { + ec2TagFilterCopy := &EC2TagFilter{} + ec2TagFilterCopy.Key = element.Key + ec2TagFilterCopy.Type = element.Type + ec2TagFilterCopy.Value = element.Value + elementCopy = ec2TagFilterCopy + } + + ec2TagFilterListCopy = append(ec2TagFilterListCopy, elementCopy) + } + elementCopy = ec2TagFilterListCopy + } + + ec2TagSetListCopy = append(ec2TagSetListCopy, elementCopy) + } + ec2TagSetCopy.EC2TagSetList = ec2TagSetListCopy + } + + targetInstancesCopy.EC2TagSet = ec2TagSetCopy + } + + if src.Spec.TargetInstances.TagFilters != nil { + ec2TagFilterListCopy := make([]*EC2TagFilter, 0, len(src.Spec.TargetInstances.TagFilters)) + for i, element := range src.Spec.TargetInstances.TagFilters { + _ = i // non-used value guard. + elementCopy := &EC2TagFilter{} + if element != nil { + ec2TagFilterCopy := &EC2TagFilter{} + ec2TagFilterCopy.Key = element.Key + ec2TagFilterCopy.Type = element.Type + ec2TagFilterCopy.Value = element.Value + elementCopy = ec2TagFilterCopy + } + + ec2TagFilterListCopy = append(ec2TagFilterListCopy, elementCopy) + } + targetInstancesCopy.TagFilters = ec2TagFilterListCopy + } + + dst.Spec.TargetInstances = targetInstancesCopy + } + + dst.Spec.UpdateOutdatedInstancesOnly = src.Spec.UpdateOutdatedInstancesOnly + dst.Status.DeploymentID = src.Status.DeploymentID + dst.Status.ACKResourceMetadata = src.Status.ACKResourceMetadata + dst.Status.Conditions = src.Status.Conditions + + dst.ObjectMeta = src.ObjectMeta + return nil` + + assert.Equal( + expectedConvertFrom, + code.Convert( + crd, crd, false, "v1alpha2", "src", "dstRaw", 1, + ), + ) +} diff --git a/pkg/generate/code/converter/converter.go b/pkg/generate/code/converter/converter.go new file mode 100644 index 00000000..28773e44 --- /dev/null +++ b/pkg/generate/code/converter/converter.go @@ -0,0 +1,718 @@ +// Copyright Amazon.com Inc. or its affiliates. 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. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file 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. + +// Code generated by ack-generate. DO NOT EDIT. + +package converter + +import ( + "fmt" + "strings" + + awssdkmodel "github.com/aws/aws-sdk-go/private/model/api" + + "github.com/aws-controllers-k8s/code-generator/pkg/model" + ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model" + "github.com/aws-controllers-k8s/code-generator/pkg/model/multiversion" + "github.com/aws-controllers-k8s/code-generator/pkg/names" +) + +// New initialise and returns a new converter. +func New( + hubImportAlias string, + sdkPackageName string, + isConvertingToHub bool, + typeRenames map[string]string, + builtinStatuses []string, + indentLevel int, +) converter { + return converter{ + hubImportAlias: hubImportAlias, + sdkPackageName: sdkPackageName, + isConvertingToHub: isConvertingToHub, + typeRenames: typeRenames, + indentLevel: indentLevel, + builtinStatuses: builtinStatuses, + } +} + +// converter is a private structure responsible of generating go code from +// multiversion.FieldDelta's +type converter struct { + // The hub import alias. Generally the hub version. + hubImportAlias string + // The sdk import alias. Generally the service alias. + sdkPackageName string + // Whether we're generating `ConvertTo` or `ConvertFrom` + isConvertingToHub bool + // indentation level + indentLevel int + // go type renames + typeRenames map[string]string + // the source and destination variable names + srcVar, dstVar string + // builtin statuses + builtinStatuses []string +} + +// WithVariables overrides the srcVar and dstVar fields and returns a copy +// of converter +func (c converter) WithVariables(srcVar, dstVar string) converter { + c.srcVar = srcVar + c.dstVar = dstVar + return c +} + +// withIndentLevel overrides the indentLevel field and returns a copy of +// converter +func (c converter) withIndentLevel(indentLevel int) converter { + c.indentLevel = indentLevel + return c +} + +// GenerateFieldsDeltasCode translates FieldDeltas into Go code that +// converts a CRD to another. +func (c converter) GenerateFieldsDeltasCode( + deltas []multiversion.FieldDelta, +) string { + out := "" + for _, delta := range deltas { + from := delta.Source + to := delta.Destination + + switch delta.ChangeType { + case multiversion.FieldChangeTypeNone: + out += c.copyField(from, to) + case multiversion.FieldChangeTypeRenamed: + out += c.copyField(from, to) + case multiversion.FieldChangeTypeAdded, + multiversion.FieldChangeTypeRemoved, + multiversion.FieldChangeTypeShapeChanged, + multiversion.FieldChangeTypeShapeChangedFromStringToSecret, + multiversion.FieldChangeTypeShapeChangedFromSecretToString: + fmt.Println("Not implemented ChangeType in generate.code.generateFieldsDeltasCode") + case multiversion.FieldChangeTypeUnknown: + panic("Received unknown ChangeType in generate.code.generateFieldsDeltasCode") + default: + panic("Unsupported ChangeType in generate.code.generateFieldsDeltasCode") + } + } + return out +} + +func (c converter) getGoType(original string) string { + goType := original + // first try to find a rename + rename, ok := c.typeRenames[original] + if ok { + goType = rename + } + // then transform to camel if name dosen't have an '_SDK' suffix + if !strings.HasSuffix(goType, "_SDK") { + goType = names.New(goType).Camel + } + return goType +} + +// copyStruct outputs Go code that converts a struct to another. +// +// Output code will look something like this: +// +// elementCopy := &v1alpha2.WebhookFilter{} +// if element != nil { +// webhookFilterCopy := &v1alpha2.WebhookFilter{} +// webhookFilterCopy.ExcludeMatchedPattern = element.ExcludeMatchedPattern +// webhookFilterCopy.Pattern = element.Pattern +// webhookFilterCopy.Type = element.Type +// elementCopy = webhookFilterCopy +// } +func (c converter) copyStruct(shape *awssdkmodel.Shape) string { + out := "" + indent := strings.Repeat("\t", c.indentLevel) + //TODO(a-hilaly): use ackcompare.HasNilDifference + // if src.Spec.Tags != nil { + out += fmt.Sprintf( + "%sif %s != nil {\n", + indent, + c.srcVar, + ) + + structShapeName := names.New(shape.ShapeName) + varStructCopy := structShapeName.CamelLower + "Copy" + // webhookFilterCopy := &v1alpha2.WebhookFilter{} + out += c. + withIndentLevel(c.indentLevel+1). + newShapeTypeInstance( + shape, + varStructCopy, + c.srcVar, + true, + ) + + // copy struct fields + for _, memberName := range shape.MemberNames() { + memberShapeRef := shape.MemberRefs[memberName] + memberShape := memberShapeRef.Shape + memberNames := names.New(memberName) + + switch memberShape.Type { + case "structure": + out += c. + withIndentLevel(c.indentLevel+1). + WithVariables( + c.srcVar+"."+memberNames.Camel, + varStructCopy+"."+memberNames.Camel, + ). + copyStruct( + memberShape, + ) + case "list": + out += c. + withIndentLevel(c.indentLevel+1). + WithVariables( + c.srcVar+"."+memberNames.Camel, + varStructCopy+"."+memberNames.Camel, + ). + copyList( + memberShape, + ) + case "map": + out += c. + withIndentLevel(c.indentLevel+1). + WithVariables( + c.srcVar+"."+memberNames.Camel, + varStructCopy+"."+memberNames.Camel, + ). + copyMap( + memberShape, + ) + default: + out += c. + withIndentLevel(c.indentLevel+1). + WithVariables( + c.srcVar+"."+memberNames.Camel, + varStructCopy+"."+memberNames.Camel, + ). + copyScalar( + memberShape, + ) + } + } + // elementCopy = webhookFilterCopy + out += storeVariableIn( + varStructCopy, + c.dstVar, + false, + c.indentLevel+1, + ) + out += fmt.Sprintf( + "%s}\n", indent, + ) + return out + "\n" +} + +// copyScalar outputs Go code that converts a CRD field +// that is a scalar. +// +// Output code will look something like this: +// +// dst.Status.CreatedAt = src.Status.CreatedAt +func (c converter) copyScalar(shape *awssdkmodel.Shape) string { + out := "" + indent := strings.Repeat("\t", c.indentLevel) + + switch shape.Type { + case "boolean", "string", "character", "byte", "short", + "integer", "long", "float", "double", "timestamp": + out += fmt.Sprintf( + // dst.Spec.Conditions = src.Spec.Conditions + "%s%s = %s\n", + indent, c.dstVar, c.srcVar, + ) + default: + panic("Unsupported shape type: " + shape.Type) + } + return out +} + +// copyList outputs Go code that copies one array to another. +// +// Output code will look something like this: +// +// if src.Spec.APIStages != nil { +// listOfAPIStageCopy := make([]*APIStage, 0, len(src.Spec.APIStages)) +// for i, element := range src.Spec.APIStages { +// _ = i // non-used value guard. +// elementCopy := &APIStage{} +// if element != nil { +// apiStageCopy := &APIStage{} +// apiStageCopy.APIID = element.APIID +// apiStageCopy.Stage = element.Stage +// if element.Throttle != nil { +// mapOfAPIStageThrottleSettingsCopy := make(map[string]*ThrottleSettings, len(element.Throttle)) +// for k, v := range element.Throttle { +// elementCopy := &ThrottleSettings{} +// if v != nil { +// throttleSettingsCopy := &ThrottleSettings{} +// throttleSettingsCopy.BurstLimit = v.BurstLimit +// throttleSettingsCopy.RateLimit = v.RateLimit +// elementCopy = throttleSettingsCopy +// } +// +// mapOfAPIStageThrottleSettingsCopy[k] = elementCopy +// } +// apiStageCopy.Throttle = mapOfAPIStageThrottleSettingsCopy +// } +// +// elementCopy = apiStageCopy +// } +// +// listOfAPIStageCopy = append(listOfAPIStageCopy, elementCopy) +// } +// dst.Spec.APIStages = listOfAPIStageCopy +// } +func (c converter) copyList(shape *awssdkmodel.Shape) string { + indent := strings.Repeat("\t", c.indentLevel) + // if a slice is only made of builtin types we just copy src to dst + if isMadeOfBuiltinTypes(shape) { + return fmt.Sprintf( + "%s%s = %s\n", + indent, + c.srcVar, + c.dstVar, + ) + } + + out := "" + // if err != nil { + out += fmt.Sprintf( + "%sif %s != nil {\n", + indent, + c.srcVar, + ) + + structShapeName := names.New(shape.ShapeName) + varStructCopy := structShapeName.CamelLower + "Copy" + + // filterGroupsCopy := make([][]*v1alpha2.WebhookFilter, 0, len(src.Spec.FilterGroups)) + out += c.withIndentLevel(c.indentLevel+1).newShapeTypeInstance( + shape, + varStructCopy, + c.srcVar, + false, + ) + + varIndex := "i" + varElement := "element" + // for i, element := range src.Spec.FilterGroups { + out += fmt.Sprintf( + "%s\tfor %s, %s := range %s {\n", + indent, + varIndex, + varElement, + c.srcVar, + ) + + // _ = i // non-used value guard. + out += fmt.Sprintf( + "%s\t\t_ = %s // non-used value guard.\n", + indent, + varIndex, + ) + + memberShapeRef := shape.MemberRef + memberShape := memberShapeRef.Shape + // elementCopy := make([]*v1alpha2.WebhookFilter, 0, len(element)) + varElementCopy := "element" + "Copy" + out += c.withIndentLevel(c.indentLevel+2).newShapeTypeInstance( + memberShape, + varElementCopy, + varElement, + true, + ) + + switch memberShape.Type { + case "structure": + out += c. + withIndentLevel(c.indentLevel+2). + WithVariables(varElement, varElementCopy). + copyStruct( + memberShape, + ) + case "list": + if isMadeOfBuiltinTypes(memberShape) { + out += fmt.Sprintf( + "%s%s = %s\n", + indent, + c.srcVar, + c.dstVar, + ) + } else { + out += c. + withIndentLevel(c.indentLevel+2). + WithVariables(varElement, varElementCopy). + copyList( + memberShape, + ) + } + case "map": + if isMadeOfBuiltinTypes(memberShape) { + out += fmt.Sprintf( + "%s%s = %s\n", + indent, + c.srcVar, + c.dstVar, + ) + } else { + out += c.withIndentLevel(c.indentLevel+2). + WithVariables(varElement, varElementCopy). + copyMap( + memberShape, + ) + } + default: + panic(fmt.Sprintf("Unsupported shape type in generate.code.copyMap")) + } + + // filterGroupCopy = append(filterGroupCopy, elementCopy) + out += fmt.Sprintf( + "%s\t\t%s = append(%s, %s)\n", + indent, + varStructCopy, + varStructCopy, + varElementCopy, + ) + + // closing loop + out += fmt.Sprintf( + "%s\t}\n", indent, + ) + + // dst.Spec.FilterGroups = filterGroupsCopy + out += storeVariableIn(varStructCopy, c.dstVar, false, c.indentLevel+1) + out += fmt.Sprintf( + "%s}\n", indent, + ) + return out + "\n" +} + +// copyMap outputs Go code that copies one map to another. +// +// Output code will look something like this: +// +// if src.Spec.RouteSettings != nil { +// routeSettingsMapCopy := make(map[string]*v1alpha2.RouteSettings, len(src.Spec.RouteSettings)) +// for k, v := range src.Spec.RouteSettings { +// elementCopy := &v1alpha2.RouteSettings{} +// if v != nil { +// routeSettingsCopy := &v1alpha2.RouteSettings{} +// routeSettingsCopy.DataTraceEnabled = v.DataTraceEnabled +// routeSettingsCopy.DetailedMetricsEnabled = v.DetailedMetricsEnabled +// routeSettingsCopy.LoggingLevel = v.LoggingLevel +// routeSettingsCopy.ThrottlingBurstLimit = v.ThrottlingBurstLimit +// routeSettingsCopy.ThrottlingRateLimit = v.ThrottlingRateLimit +// elementCopy = routeSettingsCopy +// } +// +// routeSettingsMapCopy[k] = elementCopy +// } +// dst.Spec.RouteSettings = routeSettingsMapCopy +// } +func (c converter) copyMap(shape *awssdkmodel.Shape) string { + indent := strings.Repeat("\t", c.indentLevel) + // if a map is only made of builtin types we just copy src to dst + if isMadeOfBuiltinTypes(shape) { + return storeVariableIn( + c.srcVar, + c.dstVar, + false, + c.indentLevel, + ) + } + + // if err != nil + out := fmt.Sprintf( + "%sif %s != nil {\n", + indent, + c.srcVar, + ) + + // routeSettingsCopy := &v1alpha2.RouteSettings{} + structShapeName := names.New(shape.ShapeName) + varStructCopy := structShapeName.CamelLower + "Copy" + out += c. + withIndentLevel(c.indentLevel+1). + newShapeTypeInstance( + shape, + varStructCopy, + c.srcVar, + false, + ) + + keyVarName := "k" + valueVarName := "v" + // for k, v := range src.Spec.RouteSettings { + out += fmt.Sprintf( + "%s\tfor %s, %s := range %s {\n", + indent, + keyVarName, + valueVarName, + c.srcVar, + ) + + memberShape := shape.ValueRef.Shape + varElementCopy := "element" + "Copy" + // elementCopy := &v1alpha2.RouteSettings{} + out += c. + withIndentLevel(c.indentLevel+2). + newShapeTypeInstance( + memberShape, + varElementCopy, + valueVarName, + true, + ) + + switch memberShape.Type { + case "structure": + out += c. + withIndentLevel(c.indentLevel+2). + WithVariables(valueVarName, varElementCopy). + copyStruct( + memberShape, + ) + case "list": + if isMadeOfBuiltinTypes(memberShape) { + out += fmt.Sprintf( + "%s%s = %s\n", + indent, + c.srcVar, + c.dstVar, + ) + } else { + out += c.withIndentLevel(c.indentLevel+2). + WithVariables(valueVarName, varElementCopy). + copyMap( + memberShape, + ) + } + case "map": + if isMadeOfBuiltinTypes(memberShape) { + out += fmt.Sprintf( + "%s%s = %s\n", + indent, + c.srcVar, + c.dstVar, + ) + } else { + out += c.withIndentLevel(c.indentLevel+2). + WithVariables(valueVarName, varElementCopy). + copyMap( + memberShape, + ) + } + default: + msg := fmt.Sprintf("Unsupported shape type in generate.code.copyMap %s", memberShape.Type) + panic(msg) + } + + // routeSettingsMapCopy[k] = elementCopy + out += fmt.Sprintf( + "%s\t\t%s[%s] = %s\n", + indent, + varStructCopy, + keyVarName, + varElementCopy, + ) + out += fmt.Sprintf( + "%s\t}\n", indent, + ) + + // dst.Spec.RouteSettings = routeSettingsMapCopy + out += storeVariableIn(varStructCopy, c.dstVar, false, c.indentLevel+1) + + // attach the copy struct to the dst variable + out += fmt.Sprintf( + "%s}\n", indent, + ) + + return out + "\n" +} + +// copyField returns go code that copies a src field to destination field. Typically +// field have similar Go structures but imported from different packages. +// +// Output code that looks like this: +// +// dst.Spec.ProjectName = src.Spec.ProjectName +func (c converter) copyField(from, to *model.Field) string { + // if a field is not renamed, from and to have the same name + // so the name doesn't impact much the code generation + varFromPath := c.srcVar + "." + from.Names.Camel + varToPath := c.dstVar + "." + to.Names.Camel + + // however in case of renames we should correctly invert from/to field + // paths. Only when we are convert to hub (not from hub). + if !c.isConvertingToHub { + varFromPath = c.srcVar + "." + to.Names.Camel + varToPath = c.dstVar + "." + from.Names.Camel + } + + switch from.ShapeRef.Shape.Type { + case "structure": + return c. + WithVariables(varFromPath, varToPath). + copyStruct(from.ShapeRef.Shape) + case "list": + return c. + WithVariables(varFromPath, varToPath). + copyList(from.ShapeRef.Shape) + case "map": + return c. + WithVariables(varFromPath, varToPath). + copyMap(from.ShapeRef.Shape) + default: + return c. + WithVariables(varFromPath, varToPath). + copyScalar(from.ShapeRef.Shape) + } +} + +// newShapeTypeInstance returns Go code that instanciate a new shape type. +// +// Output code will look something like this: +// +// imageScanningConfigurationCopy := &v2.ImageScanningConfiguration{} +func (c converter) newShapeTypeInstance( + shape *awssdkmodel.Shape, + allocationVarName string, + fromVar string, + isPointer bool, +) string { + out := "" + indent := strings.Repeat("\t", c.indentLevel) + + switch shape.Type { + case "structure": + goType := shape.GoTypeElem() + goType = c.getGoType(goType) + if c.isConvertingToHub { + goType = c.hubImportAlias + "." + goType + } + if isPointer { + goType = "&" + goType + } + out += fmt.Sprintf( + "%s%s := %s{}\n", + indent, + allocationVarName, + goType, + ) + case "list": + goType := shape.GoTypeWithPkgName() + if c.isConvertingToHub { + goType = ackmodel.ReplacePkgName( + goType, + c.sdkPackageName, + c.hubImportAlias, + true, + ) + } else { + goType = ackmodel.ReplacePkgName( + goType, + c.sdkPackageName, + "", + true, + ) + } + out += fmt.Sprintf( + "%s%s := make(%s, 0, len(%s))\n", + indent, + allocationVarName, + goType, + fromVar, + ) + case "map": + goType := shape.GoTypeWithPkgName() + if c.isConvertingToHub { + goType = ackmodel.ReplacePkgName( + goType, + c.sdkPackageName, + c.hubImportAlias, + true, + ) + } else { + goType = ackmodel.ReplacePkgName( + goType, + c.sdkPackageName, + "", + true, + ) + } + out += fmt.Sprintf( + "%s%s := make(%s, len(%s))\n", + indent, + allocationVarName, + goType, + fromVar, + ) + default: + msg := fmt.Sprintf("Unsupported shape type in generate.code.newShapeTypeInstance %s", shape.Type) + panic(msg) + } + + return out +} + +// storeVariableIn retruns go code that stores a value in a given variable. +func storeVariableIn( + // the value name to store + from string, + // the target name to store in + target string, + // whether to allocate the target variable + allocate bool, + // indentation level. + indentLevel int, +) string { + out := "" + indent := strings.Repeat("\t", indentLevel) + assignValue := "=" + if allocate { + assignValue = ":" + assignValue + } + out += fmt.Sprintf( + "%s%s %s %s\n", + indent, + target, + assignValue, + from, + ) + return out +} + +// isMadeOfBuiltinTypes returns true if a given shape is fully made of Go +// builtin types. Knowning such information allows us to directly copy a +// variable into another without having to minutely walk and copy the elements. +func isMadeOfBuiltinTypes(shape *awssdkmodel.Shape) bool { + switch shape.Type { + case "boolean", "string", "character", "byte", "short", + "integer", "long", "float", "double", "timestamp": + return true + case "list": + return isMadeOfBuiltinTypes(shape.MemberRef.Shape) + case "map": + return isMadeOfBuiltinTypes(shape.ValueRef.Shape) + default: + return false + } +} diff --git a/pkg/metadata/generation_metadata.go b/pkg/metadata/generation_metadata.go index 6699bf97..dcb565c4 100644 --- a/pkg/metadata/generation_metadata.go +++ b/pkg/metadata/generation_metadata.go @@ -145,6 +145,23 @@ func CreateGenerationMetadata( return nil } +// LoadGenerationMetadata read the generation metadata for a given api version and +// apis path. +func LoadGenerationMetadata(apisPath, apiVersion string) (*GenerationMetadata, error) { + filePath := filepath.Join(apisPath, apiVersion, outputFileName) + b, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + + var generationMetadata GenerationMetadata + err = yaml.Unmarshal(b, &generationMetadata) + if err != nil { + return nil, err + } + return &generationMetadata, nil +} + // hashDirectoryContent returns the sha1 checksum of a given directory. It will walk // the file tree of a directory and combine and the file contents before hashing it. func hashDirectoryContent(directory string) (string, error) { diff --git a/pkg/model/types.go b/pkg/model/types.go index a23ec8f3..2e3cf9d6 100644 --- a/pkg/model/types.go +++ b/pkg/model/types.go @@ -139,11 +139,15 @@ func ReplacePkgName( // alias the Kubernetes API types for the service API with that if strings.Contains(memberType, ".") { pkgName := strings.Split(memberType, ".")[0] - typeName := strings.Split(memberType, ".")[1] + memberType = strings.Split(memberType, ".")[1] if pkgName == apiPkgName { - memberType = replacePkgAlias + "." + typeName + if replacePkgAlias != "" { + memberType = replacePkgAlias + "." + memberType + } } else { - memberType = pkgName + "." + typeName + if replacePkgAlias != "" { + memberType = pkgName + "." + memberType + } } } if isPointerType && keepPointer { diff --git a/pkg/model/types_test.go b/pkg/model/types_test.go index f2adb17d..c029fbcd 100644 --- a/pkg/model/types_test.go +++ b/pkg/model/types_test.go @@ -66,6 +66,13 @@ func TestReplacePkgName(t *testing.T) { true, "[][]*svcsdk.EC2TagFilter", }, + { // empty replacePkgAlias + "*ecr.Repository", + "ecr", + "", + true, + "*Repository", + }, } for _, tc := range testCases { diff --git a/templates/apis/webhooks/conversion.go.tpl b/templates/apis/webhooks/conversion.go.tpl new file mode 100644 index 00000000..f0aa2e10 --- /dev/null +++ b/templates/apis/webhooks/conversion.go.tpl @@ -0,0 +1,62 @@ +{{- template "boilerplate" }} + +{{ $hubImportAlias := .HubVersion }} +package {{ .APIVersion }} + +import ( + "encoding/json" + "fmt" + + ctrlrtconversion "sigs.k8s.io/controller-runtime/pkg/conversion" +{{- if not .IsHub }} + ctrlrt "sigs.k8s.io/controller-runtime" + ackrtwh "github.com/aws-controllers-k8s/runtime/pkg/webhook" + + {{ $hubImportAlias }} "github.com/aws-controllers-k8s/{{ .ServiceIDClean }}-controller/apis/{{ .HubVersion }}" +{{- end }} +) + +var ( + _ = fmt.Printf + _ = json.Marshal +) + +{{- if .IsHub }} +// Assert hub interface implementation {{ .SourceCRD.Names.Camel }} +var _ ctrlrtconversion.Hub = &{{ .SourceCRD.Names.Camel }}{} + +// Hub marks this type as conversion hub. +func (*{{ .SourceCRD.Kind }}) Hub() {} +{{ else }} + +func init() { + webhook := ackrtwh.New( + "conversion", + "{{ .SourceCRD.Names.Camel }}", + "{{ .APIVersion }}", + func(mgr ctrlrt.Manager) error { + return ctrlrt.NewWebhookManagedBy(mgr). + For(&{{ .SourceCRD.Names.Camel }}{}). + Complete() + }, + ) + if err := ackrtwh.RegisterWebhook(webhook); err != nil { + msg := fmt.Sprintf("cannot register webhook: %v", err) + panic(msg) + } +} + +// Assert convertible interface implementation {{ .SourceCRD.Names.Camel }} +var _ ctrlrtconversion.Convertible = &{{ .SourceCRD.Names.Camel }}{} + +// ConvertTo converts this {{ .SourceCRD.Kind }} to the Hub version ({{ .HubVersion }}). +func (src *{{ .SourceCRD.Kind }}) ConvertTo(dstRaw ctrlrtconversion.Hub) error { +{{- GoCodeConvert .SourceCRD .DestCRD true $hubImportAlias "src" "dstRaw" 1}} +} + +// ConvertFrom converts the Hub version ({{ .HubVersion }}) to this {{ .SourceCRD.Kind }}. +func (dst *{{ .SourceCRD.Kind }}) ConvertFrom(srcRaw ctrlrtconversion.Hub) error { +{{- GoCodeConvert .SourceCRD .DestCRD false $hubImportAlias "dst" "srcRaw" 1}} +} + +{{- end }} \ No newline at end of file