@@ -16,6 +16,7 @@ package command
1616import (
1717 "fmt"
1818 "io/ioutil"
19+ "os"
1920 "path/filepath"
2021 "strings"
2122 "time"
@@ -29,28 +30,44 @@ import (
2930)
3031
3132var (
32- cmdControllerPath string
33- pkgResourcePath string
34- latestAPIVersion string
33+ cmdControllerPath string
34+ pkgResourcePath string
35+ latestAPIVersion string
36+ optRunFromController bool
3537)
3638
3739var 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
4349func 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).
4860func 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+ }
0 commit comments