Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/ack-generate/command/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func saveGeneratedMetadata(cmd *cobra.Command, args []string) error {
optGenVersion,
filepath.Join(optOutputPath, "apis"),
ackmetadata.UpdateReasonAPIGeneration,
optAWSSDKGoVersion,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing this PR against the s3-controller I see that after calling make build-controller SERVICE=s3 AWS_SERVICE_SDK_VERSION=v1.99.1 the ack-generate-metadata.yaml file looks like the below and includes both aws_sdk_go_version and aws_service_sdk_version. Should only one of these fields be emitted?

ack_generate_info:
  build_date: "2026-03-31T19:59:08Z"
  build_hash: a9e2ceaadfc00a742e2ea2b6d6c68348f03e52a5
  build_date: "2026-04-17T23:51:02Z"
  build_hash: 2f10e7b367cd01230a7916e4956c61f97be8cf13
  go_version: go1.26.1
  version: v0.58.0-3-ga9e2cea
api_directory_checksum: 379f9aa07359a58fabb591e795271b44a678b20c
  version: v0.58.0-5-g2f10e7b
api_directory_checksum: 29a8845f7c3bf83c6d20107a582b35d7a912d93a
api_version: v1alpha1
aws_sdk_go_version: v1.41.5
aws_service_sdk_version: v1.99.1
generator_config_info:
  file_checksum: 035386df2e486fac01a2dbedf247c36bbaeec7f3
  original_file_name: generator.yaml

sdkVersion,
svcSDKVersion,
optGeneratorConfigPath,
)
if err != nil {
Expand Down
54 changes: 50 additions & 4 deletions cmd/ack-generate/command/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package command
import (
"context"
"fmt"
"path/filepath"
"sort"
"strings"
"time"
Expand All @@ -31,6 +32,10 @@ import (
"github.com/aws-controllers-k8s/code-generator/pkg/util"
)

// svcSDKVersion holds the resolved per-service SDK version for use by
// saveGeneratedMetadata.
var svcSDKVersion string

// resolveModelName returns the SDK model name for a service, checking the
// generator config for an override.
func resolveModelName(svcAlias string, cfg ackgenconfig.Config) string {
Expand Down Expand Up @@ -115,24 +120,65 @@ func getServiceAccountName() (string, error) {
// setupGenerator loads the generator configuration, resolves the SDK version and fetches the
// model file
func setupGenerator(svcAlias string) (ackgenconfig.Config, error) {
var cfg ackgenconfig.Config

// Mutual exclusivity: both explicit CLI flags is an error
if optAWSSDKGoVersion != "" && optAWSServiceSDKVersion != "" {
return cfg, fmt.Errorf(
"--aws-sdk-go-version and --aws-service-sdk-version are mutually exclusive; provide only one",
)
}

// Load generator config to resolve model name before fetching
cfg, err := ackgenconfig.New(optGeneratorConfigPath, ackgenerate.DefaultConfig)
if err != nil {
return cfg, err
}

// Load existing generation metadata (used for both per-service and core
// version resolution fallbacks).
var metadataSvcSDKVersion string
var metadataCoreSDKVersion string
existingMetadata, err := ackmetadata.LoadGenerationMetadata(
filepath.Join(optOutputPath, "apis"), optGenVersion,
)
if err != nil {
return cfg, fmt.Errorf("cannot load existing generation metadata: %v", err)
}
if existingMetadata != nil {
metadataSvcSDKVersion = existingMetadata.AWSServiceSDKVersion
metadataCoreSDKVersion = existingMetadata.AWSSDKGoVersion
}

// Resolve per-service SDK version from priority chain:
// CLI flag → metadata YAML → empty
svcSDKVersion = sdk.GetServiceSDKVersion(optAWSServiceSDKVersion, metadataSvcSDKVersion)

// Resolve SDK version and fetch the model file
fetchStart := time.Now()
resolvedVersion, err := sdk.GetSDKVersion(optAWSSDKGoVersion, "", optOutputPath)

// When a per-service SDK version is set, the core version is still needed
// for the sdkVersion variable (used by metadata saving and other callers),
// but it is resolved from metadata/go.mod as a fallback — not as the
// primary fetch source. A resolution failure is non-fatal when the
// per-service version drives the EnsureModel fetch strategy.
resolvedVersion, err := sdk.GetSDKVersion(optAWSSDKGoVersion, metadataCoreSDKVersion, optOutputPath)
if err != nil {
return cfg, err
if svcSDKVersion == "" {
return cfg, err
}
// Per-service version is set; core version is best-effort.
resolvedVersion = ""
}
if resolvedVersion != "" {
resolvedVersion = sdk.EnsureSemverPrefix(resolvedVersion)
}
resolvedVersion = sdk.EnsureSemverPrefix(resolvedVersion)

modelName := resolveModelName(svcAlias, cfg)

ctx, cancel := sdk.ContextWithSigterm(context.Background())
defer cancel()
basePath, err := sdk.EnsureModel(ctx, optCacheDir, resolvedVersion, modelName)
basePath, err := sdk.EnsureModel(ctx, optCacheDir, resolvedVersion, modelName, svcSDKVersion)
if err != nil {
return cfg, err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ack-generate/command/crossplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func generateCrossplane(_ *cobra.Command, args []string) error {
modelName := resolveModelName(svcAlias, cfg)
ctx, cancel := sdk.ContextWithSigterm(context.Background())
defer cancel()
basePath, err := sdk.EnsureModel(ctx, optCacheDir, resolvedVersion, modelName)
basePath, err := sdk.EnsureModel(ctx, optCacheDir, resolvedVersion, modelName, "")
if err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/ack-generate/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
defaultCacheDir string
optCacheDir string
optAWSSDKGoVersion string
optAWSServiceSDKVersion string
defaultTemplateDirs []string
optTemplateDirs []string
defaultServicesDir string
Expand Down Expand Up @@ -124,6 +125,9 @@ func init() {
rootCmd.PersistentFlags().StringVar(
&optAWSSDKGoVersion, "aws-sdk-go-version", "", "Version of github.com/aws/aws-sdk-go-v2 used to generate apis and controllers files",
)
rootCmd.PersistentFlags().StringVar(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Should this flag be mutually exclusive with aws-sdk-go-version? What are the scenarios where we'd want both to be set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. If core sdk version is set at the same time, we can return an error

&optAWSServiceSDKVersion, "aws-service-sdk-version", "", "Per-service SDK version (e.g. v1.0.0) for fetching model from per-service tag",
)
rootCmd.PersistentFlags().StringVar(
&optServiceAccountName, "service-account-name", "", "The name of the ServiceAccount used for ACK service controller",
)
Expand Down
27 changes: 25 additions & 2 deletions pkg/metadata/generation_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ type GenerationMetadata struct {
// Last modification reason
LastModification lastModificationInfo `json:"last_modification"`
// AWS SDK Go version used generate the APIs
AWSSDKGoVersion string `json:"aws_sdk_go_version"`
AWSSDKGoVersion string `json:"aws_sdk_go_version,omitempty"`
// Per-service AWS SDK version used to fetch the model from a per-service tag
AWSServiceSDKVersion string `json:"aws_service_sdk_version,omitempty"`
// Information about the ack-generate binary used to generate the APIs
ACKGenerateInfo ackGenerateInfo `json:"ack_generate_info"`
// Information about the generator config file used to generate the APIs
Expand All @@ -86,13 +88,33 @@ type lastModificationInfo struct {
Reason UpdateReason `json:"reason"`
}

// LoadGenerationMetadata reads and parses an ack-generate-metadata.yaml file
// from the given API version directory. Returns nil (not error) when the file
// does not exist, since metadata may not yet have been generated.
func LoadGenerationMetadata(apisPath string, apiVersion string) (*GenerationMetadata, error) {
metadataPath := filepath.Join(apisPath, apiVersion, outputFileName)
content, err := ioutil.ReadFile(metadataPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
gm := &GenerationMetadata{}
if err = yaml.Unmarshal(content, gm); err != nil {
return nil, err
}
return gm, nil
}

// CreateGenerationMetadata gathers information about the generated code and save
// a yaml version in the API version directory
func CreateGenerationMetadata(
apiVersion string,
apisPath string,
modificationReason UpdateReason,
awsSDKGo string,
awsServiceSDKVersion string,
generatorFileName string,
) error {
filesDirectory := filepath.Join(apisPath, apiVersion)
Expand All @@ -112,7 +134,8 @@ func CreateGenerationMetadata(
LastModification: lastModificationInfo{
Reason: modificationReason,
},
AWSSDKGoVersion: awsSDKGo,
AWSSDKGoVersion: awsSDKGo,
AWSServiceSDKVersion: awsServiceSDKVersion,
ACKGenerateInfo: ackGenerateInfo{
Version: version.Version,
BuildDate: version.BuildDate,
Expand Down
124 changes: 124 additions & 0 deletions pkg/metadata/generation_metadata_test.go
Original file line number Diff line number Diff line change
@@ -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 metadata

import (
"strings"
"testing"

"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestGenerationMetadata_RoundTrip validates that serializing GenerationMetadata
// to YAML and deserializing back produces identical field values, including the
// aws_service_sdk_version field when present and its absence when empty.
func TestGenerationMetadata_RoundTrip(t *testing.T) {
tests := []struct {
name string
awsServiceSDKVersion string
expectFieldInYAML bool
}{
{
name: "with per-service SDK version",
awsServiceSDKVersion: "v1.0.0",
expectFieldInYAML: true,
},
{
name: "without per-service SDK version",
awsServiceSDKVersion: "",
expectFieldInYAML: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
original := GenerationMetadata{
APIVersion: "v1alpha1",
APIDirectoryChecksum: "abc123",
LastModification: lastModificationInfo{
Reason: UpdateReasonAPIGeneration,
},
AWSSDKGoVersion: "v1.41.5",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to also test that AWSSDKGoVersion is emitted as expected.

AWSServiceSDKVersion: tc.awsServiceSDKVersion,
ACKGenerateInfo: ackGenerateInfo{
Version: "v0.58.0",
GoVersion: "go1.25.3",
BuildDate: "2026-04-14T18:02:39Z",
BuildHash: "a9e2cea",
},
GeneratorConfigInfo: generatorConfigInfo{
OriginalFileName: "generator.yaml",
FileChecksum: "5b522171",
},
}

data, err := yaml.Marshal(original)
require.NoError(t, err)

// Verify the aws_service_sdk_version field presence/absence in raw YAML
yamlStr := string(data)
if tc.expectFieldInYAML {
assert.True(t, strings.Contains(yamlStr, "aws_service_sdk_version"),
"expected aws_service_sdk_version in YAML output")
} else {
assert.False(t, strings.Contains(yamlStr, "aws_service_sdk_version"),
"expected aws_service_sdk_version to be omitted from YAML output")
}

// Unmarshal back and verify round-trip equality
var restored GenerationMetadata
err = yaml.Unmarshal(data, &restored)
require.NoError(t, err)

assert.Equal(t, original.APIVersion, restored.APIVersion)
assert.Equal(t, original.APIDirectoryChecksum, restored.APIDirectoryChecksum)
assert.Equal(t, original.LastModification, restored.LastModification)
assert.Equal(t, original.AWSSDKGoVersion, restored.AWSSDKGoVersion)
assert.Equal(t, original.AWSServiceSDKVersion, restored.AWSServiceSDKVersion)
assert.Equal(t, original.ACKGenerateInfo, restored.ACKGenerateInfo)
assert.Equal(t, original.GeneratorConfigInfo, restored.GeneratorConfigInfo)
})
}
}

// TestGenerationMetadata_BackwardCompatibility verifies that YAML without the
// aws_service_sdk_version field deserializes without error and the field
// defaults to empty string (Requirement 4.3).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (Requirement 4.3). doesn't refer to anything.

Suggested change
// defaults to empty string (Requirement 4.3).
// defaults to empty string.

func TestGenerationMetadata_BackwardCompatibility(t *testing.T) {
// YAML that predates the aws_service_sdk_version field
oldYAML := `
api_version: v1alpha1
api_directory_checksum: abc123
aws_sdk_go_version: v1.41.5
last_modification:
reason: API generation
ack_generate_info:
version: v0.58.0
go_version: go1.25.3
build_date: "2026-04-14T18:02:39Z"
build_hash: a9e2cea
generator_config_info:
original_file_name: generator.yaml
file_checksum: "5b522171"
`
var gm GenerationMetadata
err := yaml.Unmarshal([]byte(oldYAML), &gm)
require.NoError(t, err)

assert.Equal(t, "v1alpha1", gm.APIVersion)
assert.Equal(t, "v1.41.5", gm.AWSSDKGoVersion)
assert.Equal(t, "", gm.AWSServiceSDKVersion, "missing field should default to empty string")
}
2 changes: 1 addition & 1 deletion pkg/model/multiversion/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func NewAPIVersionManager(
}
basePath, err := acksdk.EnsureModel(
context.Background(), sdkCacheDir,
acksdk.EnsureSemverPrefix(apiInfo.AWSSDKVersion), modelName,
acksdk.EnsureSemverPrefix(apiInfo.AWSSDKVersion), modelName, "",
)
if err != nil {
return nil, err
Expand Down
Loading