From 8cee9def02460e665c935ad7e5b2f56595a06b24 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Tue, 6 Jul 2021 16:43:59 +0200 Subject: [PATCH 1/3] Add `pkg/generate/code/convert.go` to help generating conversion functions This patch introduces a new function that generates conversion functions. It can be used to generate `ConvertTo` and `ConvertFrom` functions. --- pkg/generate/code/convert.go | 109 ++++ pkg/generate/code/convert_test.go | 603 +++++++++++++++++++ pkg/generate/code/converter/converter.go | 718 +++++++++++++++++++++++ 3 files changed, 1430 insertions(+) create mode 100644 pkg/generate/code/convert.go create mode 100644 pkg/generate/code/convert_test.go create mode 100644 pkg/generate/code/converter/converter.go 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 + } +} From c1145b45d980026dae47c791524b27c6e997d2cf Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Tue, 6 Jul 2021 16:46:09 +0200 Subject: [PATCH 2/3] Introduce `ack-generate webhooks` subcommand This patch brings in a new sub command that can be used to generate webhook functions. For now it can only generate conversion webhook functions, but it can be used to generate defaulting and validating functions. This patch also introduces a new function to load existing ack-generate metadata for a given api version. --- cmd/ack-generate/command/webhook.go | 126 ++++++++++++++++++++++ pkg/generate/ack/webhook.go | 124 +++++++++++++++++++++ pkg/metadata/generation_metadata.go | 17 +++ templates/apis/webhooks/conversion.go.tpl | 62 +++++++++++ 4 files changed, 329 insertions(+) create mode 100644 cmd/ack-generate/command/webhook.go create mode 100644 pkg/generate/ack/webhook.go create mode 100644 templates/apis/webhooks/conversion.go.tpl 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/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/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 From 3a3f2f07a0ad89e75f4d95bcbd4fedb414c2a788 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Thu, 15 Jul 2021 16:03:25 +0200 Subject: [PATCH 3/3] prepend the `replacePkgAlias` in `model.ReplacePkgName` only when it's not empty --- pkg/model/types.go | 10 +++++++--- pkg/model/types_test.go | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) 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 {