Skip to content

Commit 8e82f0a

Browse files
authored
Add a cobra PostRunE to save generation metadata (#90)
Issue aws-controllers-k8s/community#809 Description of changes: This patch adds a cobra `PostRunE` function that writes generator parameters, output metadata and generated files checksum, to a file within the API versions directory. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 878da89 commit 8e82f0a

File tree

7 files changed

+255
-27
lines changed

7 files changed

+255
-27
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ AWS_SERVICE=$(shell echo $(SERVICE) | tr '[:upper:]' '[:lower:]')
99
VERSION ?= "v0.0.0"
1010
GITCOMMIT=$(shell git rev-parse HEAD)
1111
BUILDDATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
12-
GO_LDFLAGS=-ldflags "-X main.version=$(VERSION) \
13-
-X main.buildHash=$(GITCOMMIT) \
14-
-X main.buildDate=$(BUILDDATE)"
12+
IMPORT_PATH=github.com/aws-controllers-k8s/code-generator
13+
GO_LDFLAGS=-ldflags "-X $(IMPORT_PATH)/pkg/version.Version=$(VERSION) \
14+
-X $(IMPORT_PATH)/pkg/version.BuildHash=$(GITCOMMIT) \
15+
-X $(IMPORT_PATH)/pkg/version.BuildDate=$(BUILDDATE)"
1516

1617
# We need to use the codegen tag when building and testing because the
1718
# aws-sdk-go/private/model/api package is gated behind a build tag "codegen"...

cmd/ack-generate/command/apis.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ var (
4242

4343
// apiCmd is the command that generates service API types
4444
var apisCmd = &cobra.Command{
45-
Use: "apis <service>",
46-
Short: "Generate Kubernetes API type definitions for an AWS service API",
47-
RunE: generateAPIs,
45+
Use: "apis <service>",
46+
Short: "Generate Kubernetes API type definitions for an AWS service API",
47+
RunE: generateAPIs,
48+
PostRunE: saveGeneratedMetadata,
4849
}
4950

5051
func init() {
@@ -54,6 +55,19 @@ func init() {
5455
rootCmd.AddCommand(apisCmd)
5556
}
5657

58+
// saveGeneratedMetadata saves the parameters used to generate APIs and checksum
59+
// of the generated code.
60+
func saveGeneratedMetadata(cmd *cobra.Command, args []string) error {
61+
err := ackgenerate.CreateGenerationMetadata(
62+
optGenVersion,
63+
filepath.Join(optOutputPath, "apis"),
64+
ackgenerate.UpdateReasonAPIGeneration,
65+
optAWSSDKGoVersion,
66+
optGeneratorConfigPath,
67+
)
68+
return err
69+
}
70+
5771
// generateAPIs generates the Go files for each resource in the AWS service
5872
// API.
5973
func generateAPIs(cmd *cobra.Command, args []string) error {

cmd/ack-generate/command/root.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ A tool to generate AWS service controller code`
3030
)
3131

3232
var (
33-
version string
34-
buildHash string
35-
buildDate string
3633
defaultCacheDir string
3734
optCacheDir string
3835
optRefreshCache bool
@@ -125,11 +122,7 @@ func init() {
125122
// Execute adds all child commands to the root command and sets flags
126123
// appropriately. This is called by main.main(). It only needs to happen once
127124
// to the rootCmd.
128-
func Execute(v string, bh string, bd string) {
129-
version = v
130-
buildHash = bh
131-
buildDate = bd
132-
125+
func Execute() {
133126
if err := rootCmd.Execute(); err != nil {
134127
os.Exit(1)
135128
}

cmd/ack-generate/command/version.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ package command
1515

1616
import (
1717
"fmt"
18-
"runtime"
1918

2019
"github.com/spf13/cobra"
20+
21+
"github.com/aws-controllers-k8s/code-generator/pkg/version"
2122
)
2223

2324
const debugHeader = `Date: %s
@@ -31,8 +32,7 @@ var versionCmd = &cobra.Command{
3132
Use: "version",
3233
Short: "Display the version of " + appName,
3334
Run: func(cmd *cobra.Command, args []string) {
34-
goVersion := fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)
35-
fmt.Printf(debugHeader, buildDate, goVersion, version, buildHash)
35+
fmt.Printf(debugHeader, version.BuildDate, version.GoVersion, version.Version, version.BuildHash)
3636
},
3737
}
3838

cmd/ack-generate/main.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@ import (
1717
"github.com/aws-controllers-k8s/code-generator/cmd/ack-generate/command"
1818
)
1919

20-
var (
21-
// version of application at compile time (-X 'main.version=$(VERSION)').
22-
version = "(Unknown Version)"
23-
// buildHash GIT hash of application at compile time (-X 'main.buildHash=$(GITCOMMIT)').
24-
buildHash = "No Git-hash Provided."
25-
// buildDate of application at compile time (-X 'main.buildDate=$(BUILDDATE)').
26-
buildDate = "No Build Date Provided."
27-
)
28-
2920
func main() {
30-
command.Execute(version, buildHash, buildDate)
21+
command.Execute()
3122
}

pkg/generate/ack/output.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package ack
15+
16+
import (
17+
"crypto/sha1"
18+
"encoding/hex"
19+
"io"
20+
"io/ioutil"
21+
"os"
22+
"path/filepath"
23+
"time"
24+
25+
"github.com/ghodss/yaml"
26+
27+
"github.com/aws-controllers-k8s/code-generator/pkg/version"
28+
)
29+
30+
const (
31+
outputFileName = "ack-generate-metadata.yaml"
32+
)
33+
34+
// UpdateReason is the reason a package got modified.
35+
type UpdateReason string
36+
37+
const (
38+
// UpdateReasonAPIGeneration should be used when an API package
39+
// is modified by the APIs generator (ack-generate apis).
40+
UpdateReasonAPIGeneration UpdateReason = "API generation"
41+
42+
// UpdateReasonConversionFunctionsGeneration Should be used when
43+
// an API package is modified by conversion functions generator.
44+
// TODO(hilalymh) ack-generate conversion-functions
45+
UpdateReasonConversionFunctionsGeneration UpdateReason = "Conversion functions generation"
46+
)
47+
48+
// GenerationMetadata represents the parameters used to generate/update the
49+
// API version directory.
50+
//
51+
// This type is public because soon it will be used by conversion generators
52+
// to load APIs generation metadata.
53+
// TODO(hilalymh) Add functions to load/edit metadata files.
54+
type GenerationMetadata struct {
55+
// The APIs version e.g v1alpha2
56+
APIVersion string `json:"api_version"`
57+
// The checksum of all the combined files generated within the APIs directory
58+
APIDirectoryChecksum string `json:"api_directory_checksum"`
59+
// Last modification reason
60+
LastModification lastModificationInfo `json:"last_modification"`
61+
// AWS SDK Go version used generate the APIs
62+
AWSSDKGoVersion string `json:"aws_sdk_go_version"`
63+
// Informatiom about the ack-generate binary used to generate the APIs
64+
ACKGenerateInfo ackGenerateInfo `json:"ack_generate_info"`
65+
// Information about the generator config file used to generate the APIs
66+
GeneratorConfigInfo generatorConfigInfo `json:"generator_config_info"`
67+
}
68+
69+
// ack-generate binary information
70+
type ackGenerateInfo struct {
71+
Version string `json:"version"`
72+
GoVersion string `json:"go_version"`
73+
BuildDate string `json:"build_date"`
74+
BuildHash string `json:"build_hash"`
75+
}
76+
77+
// generator.yaml information
78+
type generatorConfigInfo struct {
79+
OriginalFileName string `json:"original_file_name"`
80+
FileChecksum string `json:"file_checksum"`
81+
}
82+
83+
// last modification information
84+
type lastModificationInfo struct {
85+
// UTC Timestamp
86+
Timestamp string `json:"timestamp"`
87+
// Modification reason
88+
Reason UpdateReason `json:"reason"`
89+
}
90+
91+
// CreateGenerationMetadata gathers information about the generated code and save
92+
// a yaml version in the API version directory
93+
func CreateGenerationMetadata(
94+
apiVersion string,
95+
apisPath string,
96+
modificationReason UpdateReason,
97+
awsSDKGo string,
98+
generatorFileName string,
99+
) error {
100+
filesDirectory := filepath.Join(apisPath, apiVersion)
101+
hash, err := hashDirectoryContent(filesDirectory)
102+
if err != nil {
103+
return err
104+
}
105+
106+
generatorFileHash, err := hashFile(generatorFileName)
107+
if err != nil {
108+
return err
109+
}
110+
111+
generationMetadata := &GenerationMetadata{
112+
APIVersion: apiVersion,
113+
APIDirectoryChecksum: hash,
114+
LastModification: lastModificationInfo{
115+
Timestamp: time.Now().UTC().String(),
116+
Reason: modificationReason,
117+
},
118+
AWSSDKGoVersion: awsSDKGo,
119+
ACKGenerateInfo: ackGenerateInfo{
120+
Version: version.Version,
121+
BuildDate: version.BuildDate,
122+
BuildHash: version.BuildHash,
123+
GoVersion: version.GoVersion,
124+
},
125+
GeneratorConfigInfo: generatorConfigInfo{
126+
OriginalFileName: filepath.Base(generatorFileName),
127+
FileChecksum: generatorFileHash,
128+
},
129+
}
130+
131+
data, err := yaml.Marshal(generationMetadata)
132+
if err != nil {
133+
return err
134+
}
135+
136+
outputFileName := filepath.Join(filesDirectory, outputFileName)
137+
err = ioutil.WriteFile(
138+
outputFileName,
139+
data,
140+
os.ModePerm,
141+
)
142+
if err != nil {
143+
return err
144+
}
145+
return nil
146+
}
147+
148+
// hashDirectoryContent returns the sha1 checksum of a given directory. It will walk
149+
// the file tree of a directory and combine and the file contents before hashing it.
150+
func hashDirectoryContent(directory string) (string, error) {
151+
h := sha1.New()
152+
err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
153+
if err != nil {
154+
return err
155+
}
156+
if !info.IsDir() {
157+
// ignore yaml files (output.yaml and generator.yaml)
158+
fileExtension := filepath.Ext(info.Name())
159+
if fileExtension == ".yaml" {
160+
return nil
161+
}
162+
163+
fileReader, err := os.Open(path)
164+
if err != nil {
165+
return err
166+
}
167+
_, err = io.Copy(h, fileReader)
168+
if err != nil {
169+
return err
170+
}
171+
}
172+
return nil
173+
})
174+
if err != nil {
175+
return "", err
176+
}
177+
178+
hash := hex.EncodeToString(h.Sum(nil))
179+
return hash, nil
180+
}
181+
182+
// hashFile returns the sha1 hash of a given file
183+
func hashFile(filename string) (string, error) {
184+
h := sha1.New()
185+
fileReader, err := os.Open(filename)
186+
if err != nil {
187+
return "", err
188+
}
189+
_, err = io.Copy(h, fileReader)
190+
if err != nil {
191+
return "", err
192+
}
193+
hash := hex.EncodeToString(h.Sum(nil))
194+
return hash, nil
195+
}

pkg/version/version.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package version
15+
16+
import (
17+
"fmt"
18+
"runtime"
19+
)
20+
21+
var (
22+
// BuildDate of application at compile time (-X 'main.buildDate=$(BUILDDATE)').
23+
BuildDate string = "No Build Date Provided."
24+
// Version of application at compile time (-X 'main.version=$(VERSION)').
25+
Version string = "(Unknown Version)"
26+
// BuildHash is the GIT hash of application at compile time (-X 'main.buildHash=$(GITCOMMIT)').
27+
BuildHash string = "No Git-hash Provided."
28+
// GoVersion is the Go compiler version used to compile this binary
29+
GoVersion string
30+
)
31+
32+
func init() {
33+
GoVersion = fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)
34+
}

0 commit comments

Comments
 (0)