Skip to content

Commit a93f9d9

Browse files
committed
feat: add --from-controller flag for self-contained code generation
Adds `ack-generate controller <service> --from-controller` and `ack-generate release <service> <version> --from-controller`, which run the full generation pipeline from within a controller repo without needing shell scripts or a local code-generator checkout. Usage (from a controller repo): ack-generate controller s3 --from-controller ack-generate release s3 v1.2.3 --from-controller The generated Makefile wires these up: make generate # installs ack-generate, runs controller --from-controller make release V=v1.2.3 # installs ack-generate, runs release --from-controller Without --from-controller, both commands behave exactly as before — existing generation via build-controller.sh and build-controller-release.sh is unaffected.
1 parent a433c69 commit a93f9d9

16 files changed

Lines changed: 1016 additions & 53 deletions

File tree

cmd/ack-generate/command/apis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func generateAPIs(cmd *cobra.Command, args []string) error {
112112
util.Tracef("loadModel: %s\n", time.Since(modelStart))
113113

114114
apisStart := time.Now()
115-
ts, err := ackgenerate.APIs(m, optTemplateDirs)
115+
ts, err := ackgenerate.APIs(m, optTemplateDirs, embeddedTemplatesFS)
116116
if err != nil {
117117
return err
118118
}

cmd/ack-generate/command/controller.go

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package command
1616
import (
1717
"fmt"
1818
"io/ioutil"
19+
"os"
1920
"path/filepath"
2021
"strings"
2122
"time"
@@ -29,28 +30,44 @@ import (
2930
)
3031

3132
var (
32-
cmdControllerPath string
33-
pkgResourcePath string
34-
latestAPIVersion string
33+
cmdControllerPath string
34+
pkgResourcePath string
35+
latestAPIVersion string
36+
optRunFromController bool
3537
)
3638

3739
var controllerCmd = &cobra.Command{
3840
Use: "controller <service>",
3941
Short: "Generates Go files containing service controller implementation for a given service",
40-
RunE: generateController,
42+
Long: `When --from-controller is set, runs the full generation pipeline from the
43+
current working directory (which must be a controller repo). This includes
44+
APIs, deepcopy, CRDs, controller code, RBAC, formatting, and boilerplate
45+
file copying. Without it, only generates the controller Go code.`,
46+
RunE: generateController,
4147
}
4248

4349
func init() {
50+
controllerCmd.PersistentFlags().BoolVar(
51+
&optRunFromController, "from-controller", false,
52+
"Run the full generation pipeline from the current directory (must be a controller repo).",
53+
)
4454
rootCmd.AddCommand(controllerCmd)
4555
}
4656

47-
// generateController generates the Go files for a service controller
57+
// generateController generates the Go files for a service controller.
58+
// When --controller-source-path is provided, it runs the full pipeline
59+
// (APIs + deepcopy + CRDs + controller code + RBAC + formatting + boilerplate).
4860
func generateController(cmd *cobra.Command, args []string) error {
4961
cmdStart := time.Now()
5062
if len(args) != 1 {
5163
return fmt.Errorf("please specify the service alias for the AWS service API to generate")
5264
}
5365
svcAlias := strings.ToLower(args[0])
66+
67+
if optRunFromController {
68+
return generateControllerFullPipeline(svcAlias)
69+
}
70+
5471
if optOutputPath == "" {
5572
optOutputPath = filepath.Join(optServicesDir, svcAlias)
5673
}
@@ -78,7 +95,7 @@ func generateController(cmd *cobra.Command, args []string) error {
7895
}
7996

8097
ctrlStart := time.Now()
81-
ts, err := ackgenerate.Controller(m, optTemplateDirs, serviceAccountName)
98+
ts, err := ackgenerate.Controller(m, optTemplateDirs, serviceAccountName, embeddedTemplatesFS)
8299
if err != nil {
83100
return err
84101
}
@@ -110,3 +127,148 @@ func generateController(cmd *cobra.Command, args []string) error {
110127
util.Tracef("generateController total: %s\n", time.Since(cmdStart))
111128
return nil
112129
}
130+
131+
// generateControllerFullPipeline runs the complete generation pipeline when
132+
// --controller-source-path is provided.
133+
func generateControllerFullPipeline(svcAlias string) error {
134+
controllerPath, err := os.Getwd()
135+
if err != nil {
136+
return fmt.Errorf("determining current directory: %w", err)
137+
}
138+
139+
// Resolve config paths from the controller repo if not explicitly set
140+
resolved := ackgenerate.ResolveConfigPaths(controllerPath)
141+
if optGeneratorConfigPath == "" {
142+
optGeneratorConfigPath = resolved.GeneratorConfigPath
143+
}
144+
if optMetadataConfigPath == "" {
145+
optMetadataConfigPath = resolved.MetadataConfigPath
146+
}
147+
if optDocumentationConfigPath == "" {
148+
optDocumentationConfigPath = resolved.DocumentationConfigPath
149+
}
150+
151+
if optOutputPath == "" {
152+
optOutputPath = controllerPath
153+
}
154+
155+
rbacRoleName := fmt.Sprintf("ack-%s-controller", svcAlias)
156+
157+
if optServiceAccountName == "" {
158+
optServiceAccountName = fmt.Sprintf("ack-%s-controller", svcAlias)
159+
}
160+
161+
runtimeVersion, err := ackgenerate.ResolveRuntimeVersion(controllerPath)
162+
if err != nil {
163+
return fmt.Errorf("resolving runtime version: %w", err)
164+
}
165+
166+
// Detect template overrides in the controller repo
167+
svcTemplatesDir := filepath.Join(controllerPath, "templates")
168+
if fi, err := os.Stat(svcTemplatesDir); err == nil && fi.IsDir() {
169+
optTemplateDirs = append([]string{svcTemplatesDir}, optTemplateDirs...)
170+
}
171+
172+
// Load generator config to resolve model name before fetching
173+
cfg, err := setupGenerator(svcAlias)
174+
if err != nil {
175+
return err
176+
}
177+
178+
metadata, err := ackmetadata.NewServiceMetadata(optMetadataConfigPath)
179+
if err != nil {
180+
return err
181+
}
182+
m, err := loadModelWithLatestAPIVersion(svcAlias, metadata, cfg)
183+
if err != nil {
184+
return err
185+
}
186+
187+
apiVersion := "v1alpha1"
188+
if len(metadata.APIVersions) > 0 {
189+
av, err := getLatestAPIVersion(metadata.APIVersions)
190+
if err == nil {
191+
apiVersion = av
192+
}
193+
}
194+
195+
// Step 1: Runtime CRDs + controller-gen steps (pre-codegen)
196+
pipelineOpts := ackgenerate.BuildControllerOptions{
197+
SvcAlias: svcAlias,
198+
ControllerSourcePath: controllerPath,
199+
APIVersion: apiVersion,
200+
RBACRoleName: rbacRoleName,
201+
RuntimeVersion: runtimeVersion,
202+
BoilerplateFS: embeddedBoilerplateFS,
203+
TemplatesFS: embeddedTemplatesFS,
204+
}
205+
206+
// Step 2: Generate APIs
207+
fmt.Printf("Building Kubernetes API objects for %s\n", svcAlias)
208+
apisTS, err := ackgenerate.APIs(m, optTemplateDirs, embeddedTemplatesFS)
209+
if err != nil {
210+
return err
211+
}
212+
if err = apisTS.Execute(); err != nil {
213+
return err
214+
}
215+
apisVersionPath := filepath.Join(optOutputPath, "apis", apiVersion)
216+
for path, contents := range apisTS.Executed() {
217+
outPath := filepath.Join(apisVersionPath, path)
218+
outDir := filepath.Dir(outPath)
219+
if _, err := sdk.EnsureDir(outDir); err != nil {
220+
return err
221+
}
222+
if err = ioutil.WriteFile(outPath, contents.Bytes(), 0666); err != nil {
223+
return err
224+
}
225+
}
226+
227+
// Save generation metadata (ack-generate-metadata.yaml) and copy generator.yaml
228+
apisPath := filepath.Join(optOutputPath, "apis")
229+
if err = ackmetadata.CreateGenerationMetadata(
230+
apiVersion,
231+
apisPath,
232+
ackmetadata.UpdateReasonAPIGeneration,
233+
sdkVersion,
234+
optGeneratorConfigPath,
235+
); err != nil {
236+
return fmt.Errorf("creating generation metadata: %w", err)
237+
}
238+
generatorCopyDest := filepath.Join(apisPath, apiVersion, "generator.yaml")
239+
if err = util.CopyFile(optGeneratorConfigPath, generatorCopyDest); err != nil {
240+
return fmt.Errorf("copying generator.yaml: %w", err)
241+
}
242+
243+
// Pre-codegen pipeline steps (runtime CRDs, deepcopy, CRDs)
244+
if err := ackgenerate.BuildControllerPreCodegen(pipelineOpts); err != nil {
245+
return err
246+
}
247+
248+
// Step 5: Generate controller code
249+
fmt.Printf("Building service controller for %s\n", svcAlias)
250+
serviceAccountName, err := getServiceAccountName()
251+
if err != nil {
252+
return err
253+
}
254+
ctrlTS, err := ackgenerate.Controller(m, optTemplateDirs, serviceAccountName, embeddedTemplatesFS)
255+
if err != nil {
256+
return err
257+
}
258+
if err = ctrlTS.Execute(); err != nil {
259+
return err
260+
}
261+
for path, contents := range ctrlTS.Executed() {
262+
outPath := filepath.Join(optOutputPath, path)
263+
outDir := filepath.Dir(outPath)
264+
if _, err := sdk.EnsureDir(outDir); err != nil {
265+
return err
266+
}
267+
if err = ioutil.WriteFile(outPath, contents.Bytes(), 0666); err != nil {
268+
return err
269+
}
270+
}
271+
272+
// Post-codegen pipeline steps (go mod tidy, RBAC, formatting, boilerplate)
273+
return ackgenerate.BuildControllerPostCodegen(pipelineOpts)
274+
}

cmd/ack-generate/command/olm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func generateOLMAssets(cmd *cobra.Command, args []string) error {
127127
}
128128

129129
// generate templates
130-
ts, err := olmgenerate.BundleAssets(m, commonMeta, svcConf, version, optImageRepository, optTemplateDirs)
130+
ts, err := olmgenerate.BundleAssets(m, commonMeta, svcConf, version, optImageRepository, optTemplateDirs, embeddedTemplatesFS)
131131
if err != nil {
132132
return err
133133
}

cmd/ack-generate/command/release.go

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package command
1616
import (
1717
"fmt"
1818
"io/ioutil"
19+
"os"
1920
"path/filepath"
2021
"strings"
2122

@@ -26,39 +27,77 @@ import (
2627
"github.com/aws-controllers-k8s/code-generator/pkg/sdk"
2728
)
2829

29-
var optReleaseOutputPath string
30+
var (
31+
optReleaseOutputPath string
32+
optRelRunFromController bool
33+
)
3034

3135
var releaseCmd = &cobra.Command{
3236
Use: "release <service> <release_version>",
3337
Short: "Generates release artifacts for a specific service controller and release version",
34-
RunE: generateRelease,
38+
Long: `When --from-controller is set, runs the full release pipeline from the current
39+
working directory (which must be a controller repo). This includes template
40+
generation, CRDs, RBAC, and Helm template post-processing. Without it, only
41+
generates the release templates.`,
42+
RunE: generateRelease,
3543
}
3644

3745
func init() {
3846
releaseCmd.PersistentFlags().StringVarP(
3947
&optReleaseOutputPath, "output", "o", "", "path to root directory to create generated files. Defaults to "+optServicesDir+"/$service",
4048
)
49+
releaseCmd.PersistentFlags().BoolVar(
50+
&optRelRunFromController, "from-controller", false,
51+
"Run the full release pipeline from the current directory (must be a controller repo).",
52+
)
4153
rootCmd.AddCommand(releaseCmd)
4254
}
4355

4456
// generateRelease generates the Helm charts and other release artifacts for a
45-
// service controller and release version
57+
// service controller and release version. When --controller-source-path is
58+
// provided, it also runs the post-processing pipeline (CRDs, RBAC, Helm
59+
// template patching).
4660
func generateRelease(cmd *cobra.Command, args []string) error {
4761
if len(args) != 2 {
4862
return fmt.Errorf("please specify the service alias and the release version to generate release artifacts for")
4963
}
5064
svcAlias := strings.ToLower(args[0])
65+
releaseVersion := strings.ToLower(args[1])
66+
67+
var controllerPath string
68+
if optRelRunFromController {
69+
var err error
70+
controllerPath, err = os.Getwd()
71+
if err != nil {
72+
return fmt.Errorf("determining current directory: %w", err)
73+
}
74+
75+
resolved := ackgenerate.ResolveConfigPaths(controllerPath)
76+
if optGeneratorConfigPath == "" {
77+
optGeneratorConfigPath = resolved.GeneratorConfigPath
78+
}
79+
if optMetadataConfigPath == "" {
80+
optMetadataConfigPath = resolved.MetadataConfigPath
81+
}
82+
if optDocumentationConfigPath == "" {
83+
optDocumentationConfigPath = resolved.DocumentationConfigPath
84+
}
85+
}
86+
5187
if optReleaseOutputPath == "" {
52-
optReleaseOutputPath = filepath.Join(optServicesDir, svcAlias)
88+
if controllerPath != "" {
89+
optReleaseOutputPath = controllerPath
90+
} else {
91+
optReleaseOutputPath = filepath.Join(optServicesDir, svcAlias)
92+
}
5393
}
5494
if optImageRepository == "" {
5595
optImageRepository = fmt.Sprintf("public.ecr.aws/aws-controllers-k8s/%s-controller", svcAlias)
5696
}
57-
// TODO(jaypipes): We could do some git-fu here to verify that the release
58-
// version supplied hasn't been used (as a Git tag) before...
59-
releaseVersion := strings.ToLower(args[1])
97+
if optServiceAccountName == "" {
98+
optServiceAccountName = fmt.Sprintf("ack-%s-controller", svcAlias)
99+
}
60100

61-
// Load generator config to resolve model name before fetching
62101
cfg, err := setupGenerator(svcAlias)
63102
if err != nil {
64103
return err
@@ -74,9 +113,11 @@ func generateRelease(cmd *cobra.Command, args []string) error {
74113
return err
75114
}
76115

116+
fmt.Printf("Building release artifacts for %s-%s\n", svcAlias, releaseVersion)
77117
ts, err := ackgenerate.Release(
78118
m, metadata, optTemplateDirs,
79119
releaseVersion, optImageRepository, optServiceAccountName,
120+
embeddedTemplatesFS,
80121
)
81122
if err != nil {
82123
return err
@@ -101,5 +142,32 @@ func generateRelease(cmd *cobra.Command, args []string) error {
101142
return err
102143
}
103144
}
145+
146+
if optRelRunFromController {
147+
apiVersion := "v1alpha1"
148+
if len(metadata.APIVersions) > 0 {
149+
av, err := getLatestAPIVersion(metadata.APIVersions)
150+
if err == nil {
151+
apiVersion = av
152+
}
153+
}
154+
155+
runtimeVersion, err := ackgenerate.ResolveRuntimeVersion(controllerPath)
156+
if err != nil {
157+
return fmt.Errorf("resolving runtime version: %w", err)
158+
}
159+
160+
releaseOpts := ackgenerate.BuildReleaseOptions{
161+
SvcAlias: svcAlias,
162+
ControllerSourcePath: controllerPath,
163+
APIVersion: apiVersion,
164+
RBACRoleName: fmt.Sprintf("ack-%s-controller", svcAlias),
165+
RuntimeVersion: runtimeVersion,
166+
}
167+
if err := ackgenerate.BuildRelease(releaseOpts); err != nil {
168+
return err
169+
}
170+
}
171+
104172
return nil
105173
}

0 commit comments

Comments
 (0)