@@ -20,90 +20,270 @@ import (
20
20
"os"
21
21
"path/filepath"
22
22
23
+ log "github.com/sirupsen/logrus"
24
+ "github.com/spf13/cobra"
25
+ "gopkg.in/yaml.v2"
26
+ "k8s.io/client-go/discovery"
27
+ "sigs.k8s.io/controller-runtime/pkg/client/config"
28
+
23
29
"github.com/operator-framework/operator-sdk/cmd/operator-sdk/internal/genutil"
24
- gencrd "github.com/operator-framework/operator-sdk/internal/generate/crd "
30
+ apiflags "github.com/operator-framework/operator-sdk/internal/flags/apiflags "
25
31
"github.com/operator-framework/operator-sdk/internal/scaffold"
32
+ "github.com/operator-framework/operator-sdk/internal/scaffold/ansible"
33
+ "github.com/operator-framework/operator-sdk/internal/scaffold/helm"
26
34
"github.com/operator-framework/operator-sdk/internal/scaffold/input"
27
35
"github.com/operator-framework/operator-sdk/internal/util/projutil"
28
-
29
- log "github.com/sirupsen/logrus"
30
- "github.com/spf13/cobra"
31
36
)
32
37
33
- var (
34
- apiVersion string
35
- kind string
36
- skipGeneration bool
37
- crdVersion string
38
- )
38
+ var apiFlags apiflags.APIFlags
39
39
40
40
func newAddAPICmd () * cobra.Command {
41
41
apiCmd := & cobra.Command {
42
42
Use : "api" ,
43
43
Short : "Adds a new api definition under pkg/apis" ,
44
- Long : `operator-sdk add api --kind=<kind> --api-version=<group/version> creates
45
- the api definition for a new custom resource under pkg/apis. This command
46
- must be run from the project root directory. If the api already exists at
47
- pkg/apis/<group>/<version> then the command will not overwrite and return
48
- an error.
49
-
50
- By default, this command runs Kubernetes deepcopy and CRD generators on
51
- tagged types in all paths under pkg/apis. Go code is generated under
52
- pkg/apis/<group>/<version>/zz_generated.deepcopy.go. CRD's are generated,
53
- or updated if they exist for a particular group + version + kind, under
44
+ Long : `operator-sdk add api --kind=<kind> --api-version<group/version>
45
+ creates an API definition for a new custom resource.
46
+ This command must be run from the project root directory.
47
+
48
+ For Go-based operators:
49
+
50
+ - Creates the api definition for a new custom resource under pkg/apis.
51
+ - By default, this command runs Kubernetes deepcopy and CRD generators on
52
+ tagged types in all paths under pkg/apis. Go code is generated under
53
+ pkg/apis/<group>/<version>/zz_generated.deepcopy.go. Generation can be disabled with the
54
+ --skip-generation flag for Go-based operators.
55
+
56
+ For Ansible-based operators:
57
+
58
+ - Creates resource folder under /roles.
59
+ - watches.yaml is updated with new resource.
60
+ - deploy/role.yaml will be updated with apiGroup for new API.
61
+
62
+ For Helm-based operators:
63
+ - Creates resource folder under /helm-charts.
64
+ - watches.yaml is updated with new resource.
65
+ - deploy/role.yaml will be updated to reflact new rules for the incoming API.
66
+
67
+ CRD's are generated, or updated if they exist for a particular group + version + kind, under
54
68
deploy/crds/<full group>_<resource>_crd.yaml; OpenAPI V3 validation YAML
55
- is generated as a 'validation' object. Generation can be disabled with the
56
- --skip-generation flag.
57
-
58
- Example:
59
-
60
- $ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=AppService
61
- $ tree pkg/apis
62
- pkg/apis/
63
- ├── addtoscheme_app_appservice.go
64
- ├── apis.go
65
- └── app
66
- └── v1alpha1
67
- ├── doc.go
68
- ├── register.go
69
- ├── appservice_types.go
70
- ├── zz_generated.deepcopy.go
71
- $ tree deploy/crds
72
- ├── deploy/crds/app.example.com_v1alpha1_appservice_cr.yaml
73
- ├── deploy/crds/app.example.com_appservices_crd.yaml
69
+ is generated as a 'validation' object.` ,
70
+ Example : ` # Create a new API, under an existing project. This command must be run from the project root directory.
71
+ # Go Example:
72
+ $ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=AppService
73
+
74
+ # Ansible Example
75
+ $ operator-sdk add api \
76
+ --api-version=app.example.com/v1alpha1 \
77
+ --kind=AppService
78
+
79
+ # Helm Example:
80
+ $ operator-sdk add api \
81
+ --api-version=app.example.com/v1alpha1 \
82
+ --kind=AppService
83
+
84
+ $ operator-sdk add api \
85
+ --api-version=app.example.com/v1alpha1 \
86
+ --kind=AppService
87
+ --helm-chart=myrepo/app
88
+
89
+ $ operator-sdk add api \
90
+ --helm-chart=myrepo/app
91
+
92
+ $ operator-sdk add api \
93
+ --helm-chart=myrepo/app \
94
+ --helm-chart-version=1.2.3
95
+
96
+ $ operator-sdk add api \
97
+ --helm-chart=app \
98
+ --helm-chart-repo=https://charts.mycompany.com/
99
+
100
+ $ operator-sdk add api \
101
+ --helm-chart=app \
102
+ --helm-chart-repo=https://charts.mycompany.com/ \
103
+ --helm-chart-version=1.2.3
104
+
105
+ $ operator-sdk add api \
106
+ --helm-chart=/path/to/local/chart-directories/app/
107
+
108
+ $ operator-sdk add api \
109
+ --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz
74
110
` ,
75
111
RunE : apiRun ,
76
112
}
77
113
78
- apiCmd .Flags ().StringVar (& apiVersion , "api-version" , "" ,
79
- "Kubernetes APIVersion that has a format of $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)" )
80
- if err := apiCmd .MarkFlagRequired ("api-version" ); err != nil {
81
- log .Fatalf ("Failed to mark `api-version` flag for `add api` subcommand as required" )
82
- }
83
- apiCmd .Flags ().StringVar (& kind , "kind" , "" , "Kubernetes resource Kind name. (e.g AppService)" )
84
- if err := apiCmd .MarkFlagRequired ("kind" ); err != nil {
85
- log .Fatalf ("Failed to mark `kind` flag for `add api` subcommand as required" )
86
- }
87
- apiCmd .Flags ().BoolVar (& skipGeneration , "skip-generation" , false ,
88
- "Skip generation of deepcopy and OpenAPI code and OpenAPI CRD specs" )
89
- apiCmd .Flags ().StringVar (& crdVersion , "crd-version" , gencrd .DefaultCRDVersion ,
90
- "CRD version to generate" )
114
+ // Initialize flagSet struct with command flags
115
+ apiFlags .AddTo (apiCmd .Flags ())
91
116
92
117
return apiCmd
93
118
}
94
119
95
120
func apiRun (cmd * cobra.Command , args []string ) error {
121
+
96
122
projutil .MustInProjectRoot ()
97
123
98
- // Only Go projects can add apis.
99
- if err := projutil .CheckGoProjectCmd (cmd ); err != nil {
124
+ operatorType := projutil .GetOperatorType ()
125
+ if operatorType == projutil .OperatorTypeUnknown {
126
+ return projutil.ErrUnknownOperatorType {}
127
+ }
128
+ // Verify the incoming flags.
129
+ if err := apiFlags .VerifyCommonFlags (operatorType ); err != nil {
100
130
return err
101
131
}
102
132
103
- log .Infof ("Generating api version %s for kind %s." , apiVersion , kind )
133
+ log .Infof ("Generating api version %s for kind %s." , apiFlags .APIVersion , apiFlags .Kind )
134
+
135
+ switch operatorType {
136
+ case projutil .OperatorTypeGo :
137
+ if err := doGoAPIScaffold (); err != nil {
138
+ return err
139
+ }
140
+ case projutil .OperatorTypeAnsible :
141
+ if err := doAnsibleAPIScaffold (); err != nil {
142
+ return err
143
+ }
144
+ case projutil .OperatorTypeHelm :
145
+ if err := doHelmAPIScaffold (); err != nil {
146
+ return err
147
+ }
148
+ }
149
+ log .Info ("API generation complete." )
150
+ return nil
151
+ }
152
+
153
+ // TODO
154
+ // Consolidate scaffold func to be used by both "new" and "add api" commands.
155
+ func doAnsibleAPIScaffold () error {
156
+ // Create and validate new resource.
157
+ r , err := scaffold .NewResource (apiFlags .APIVersion , apiFlags .Kind )
158
+ if err != nil {
159
+ return fmt .Errorf ("invalid apiVersion and kind: %v" , err )
160
+ }
161
+ absProjectPath := projutil .MustGetwd ()
162
+ cfg := & input.Config {
163
+ AbsProjectPath : absProjectPath ,
164
+ }
165
+ roleFiles := ansible.RolesFiles {Resource : * r }
166
+ roleTemplates := ansible.RolesTemplates {Resource : * r }
167
+
168
+ // update watch.yaml for the given resource r.
169
+ if err := ansible .UpdateAnsibleWatchForResource (r , absProjectPath ); err != nil {
170
+ return fmt .Errorf ("failed to update the Watch manifest for the resource (%v, %v): (%v)" ,
171
+ r .APIVersion , r .Kind , err )
172
+ }
173
+
174
+ s := & scaffold.Scaffold {}
175
+ err = s .Execute (cfg ,
176
+ & scaffold.CR {Resource : r },
177
+ & ansible.RolesReadme {Resource : * r },
178
+ & ansible.RolesMetaMain {Resource : * r },
179
+ & roleFiles ,
180
+ & roleTemplates ,
181
+ & ansible.RolesVarsMain {Resource : * r },
182
+ & ansible.RolesDefaultsMain {Resource : * r },
183
+ & ansible.RolesTasksMain {Resource : * r },
184
+ & ansible.RolesHandlersMain {Resource : * r },
185
+ )
186
+ if err != nil {
187
+ return fmt .Errorf ("new ansible api scaffold failed: %v" , err )
188
+ }
189
+ if err = genutil .GenerateCRDNonGo ("" , * r , apiFlags .CrdVersion ); err != nil {
190
+ return err
191
+ }
192
+
193
+ // Remove placeholders from empty directories
194
+ err = os .Remove (filepath .Join (s .AbsProjectPath , roleFiles .Path ))
195
+ if err != nil {
196
+ return fmt .Errorf ("new ansible api scaffold failed: %v" , err )
197
+ }
198
+ err = os .Remove (filepath .Join (s .AbsProjectPath , roleTemplates .Path ))
199
+ if err != nil {
200
+ return fmt .Errorf ("new ansible api scaffold failed: %v" , err )
201
+ }
202
+
203
+ // update deploy/role.yaml for the given resource r.
204
+ if err := scaffold .UpdateRoleForResource (r , absProjectPath ); err != nil {
205
+ return fmt .Errorf ("failed to update the RBAC manifest for the resource (%v, %v): (%v)" ,
206
+ r .APIVersion , r .Kind , err )
207
+ }
208
+ return nil
209
+ }
210
+
211
+ // TODO
212
+ // Consolidate scaffold func to be used by both "new" and "add api" commands.
213
+ func doHelmAPIScaffold () error {
214
+
215
+ absProjectPath := projutil .MustGetwd ()
216
+ projectName := filepath .Base (absProjectPath )
217
+ cfg := & input.Config {
218
+ AbsProjectPath : absProjectPath ,
219
+ ProjectName : projectName ,
220
+ }
221
+
222
+ createOpts := helm.CreateChartOptions {
223
+ ResourceAPIVersion : apiFlags .APIVersion ,
224
+ ResourceKind : apiFlags .Kind ,
225
+ Chart : apiFlags .HelmChartRef ,
226
+ Version : apiFlags .HelmChartVersion ,
227
+ Repo : apiFlags .HelmChartRepo ,
228
+ }
229
+
230
+ r , chart , err := helm .CreateChart (cfg .AbsProjectPath , createOpts )
231
+ if err != nil {
232
+ return fmt .Errorf ("failed to create helm chart: %v" , err )
233
+ }
234
+
235
+ valuesPath := filepath .Join ("<project_dir>" , helm .HelmChartsDir , chart .Name (), "values.yaml" )
236
+
237
+ rawValues , err := yaml .Marshal (chart .Values )
238
+ if err != nil {
239
+ return fmt .Errorf ("failed to get raw chart values: %v" , err )
240
+ }
241
+ crSpec := fmt .Sprintf ("# Default values copied from %s\n \n %s" , valuesPath , rawValues )
242
+
243
+ // update watch.yaml for the given resource r.
244
+ if err := helm .UpdateHelmWatchForResource (r , absProjectPath , chart .Name ()); err != nil {
245
+ return fmt .Errorf ("failed to update the Watch manifest for the resource (%v, %v): (%v)" ,
246
+ r .APIVersion , r .Kind , err )
247
+ }
248
+
249
+ s := & scaffold.Scaffold {}
250
+ err = s .Execute (cfg ,
251
+ & scaffold.CR {
252
+ Resource : r ,
253
+ Spec : crSpec ,
254
+ },
255
+ )
256
+ if err != nil {
257
+ log .Fatalf ("API scaffold failed: %v" , err )
258
+ }
259
+ if err = genutil .GenerateCRDNonGo ("" , * r , apiFlags .CrdVersion ); err != nil {
260
+ return err
261
+ }
262
+
263
+ roleScaffold := helm .DefaultRoleScaffold
264
+
265
+ if k8sCfg , err := config .GetConfig (); err != nil {
266
+ log .Warnf ("Using default RBAC rules: failed to get Kubernetes config: %s" , err )
267
+ } else if dc , err := discovery .NewDiscoveryClientForConfig (k8sCfg ); err != nil {
268
+ log .Warnf ("Using default RBAC rules: failed to create Kubernetes discovery client: %s" , err )
269
+ } else {
270
+ roleScaffold = helm .GenerateRoleScaffold (dc , chart )
271
+ }
272
+
273
+ if err = scaffold .MergeRoleForResource (r , absProjectPath , roleScaffold ); err != nil {
274
+ return fmt .Errorf ("failed to merge rules in the RBAC manifest for resource (%v, %v): %v" ,
275
+ r .APIVersion , r .Kind , err )
276
+ }
277
+
278
+ return nil
279
+ }
280
+
281
+ // TODO
282
+ // Consolidate scaffold func to be used by both "new" and "add api" commands.
283
+ func doGoAPIScaffold () error {
104
284
105
285
// Create and validate new resource.
106
- r , err := scaffold .NewResource (apiVersion , kind )
286
+ r , err := scaffold .NewResource (apiFlags . APIVersion , apiFlags . Kind )
107
287
if err != nil {
108
288
return err
109
289
}
@@ -140,14 +320,14 @@ func apiRun(cmd *cobra.Command, args []string) error {
140
320
r .APIVersion , r .Kind , err )
141
321
}
142
322
143
- if ! skipGeneration {
323
+ if ! apiFlags . SkipGeneration {
144
324
// Run k8s codegen for deepcopy
145
325
if err := genutil .K8sCodegen (); err != nil {
146
326
log .Fatal (err )
147
327
}
148
328
149
329
// Generate a validation spec for the new CRD.
150
- if err := genutil .CRDGen (crdVersion ); err != nil {
330
+ if err := genutil .CRDGen (apiFlags . CrdVersion ); err != nil {
151
331
log .Fatal (err )
152
332
}
153
333
}
0 commit comments