@@ -18,14 +18,31 @@ package cmd
18
18
19
19
import (
20
20
"context"
21
+ "fmt"
22
+ "os"
23
+ "path/filepath"
24
+ "strings"
21
25
22
- "github.com/go-errors/errors"
23
26
"github.com/spf13/cobra"
27
+
28
+ appsv1 "k8s.io/api/apps/v1"
29
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
30
+ clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
31
+ "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
32
+ configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
33
+ "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
34
+ "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
35
+ ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
36
+
37
+ operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
38
+ "sigs.k8s.io/cluster-api-operator/internal/controller/genericprovider"
39
+ "sigs.k8s.io/cluster-api-operator/util"
24
40
)
25
41
26
42
type initOptions struct {
27
43
kubeconfig string
28
44
kubeconfigContext string
45
+ operatorVersion string
29
46
coreProvider string
30
47
bootstrapProviders []string
31
48
controlPlaneProviders []string
@@ -39,6 +56,12 @@ type initOptions struct {
39
56
waitProviderTimeout int
40
57
}
41
58
59
+ const (
60
+ capiOperatorLatestVersion = "v0.7.0"
61
+ capiOperatorProviderName = "capi-operator"
62
+ capiOperatorManifestsURL = "https://github.com/kubernetes-sigs/cluster-api-operator/releases/latest/operator-components.yaml"
63
+ )
64
+
42
65
var initOpts = & initOptions {}
43
66
44
67
var initCmd = & cobra.Command {
@@ -79,7 +102,7 @@ var initCmd = &cobra.Command{
79
102
capioperator init --kubeconfig=foo.yaml --infrastructure=aws
80
103
81
104
# Initialize a management cluster with multiple infrastructure providers.
82
- capioperator init --infrastructure=aws; vsphere
105
+ capioperator init --infrastructure=aws --infrastructure= vsphere
83
106
84
107
# Initialize a management cluster with a custom target namespace for the operator.
85
108
capioperator init --infrastructure aws --target-namespace foo` ),
@@ -90,23 +113,25 @@ var initCmd = &cobra.Command{
90
113
}
91
114
92
115
func init () {
93
- initCmd .PersistentFlags ().StringVar (& initOpts .kubeconfig , "kubeconfig" , "" ,
116
+ initCmd .PersistentFlags ().StringVar (& initOpts .kubeconfig , "kubeconfig" , filepath . Join ( os . Getenv ( "HOME" ), ".kube" , "config" ) ,
94
117
"Path to the kubeconfig for the management cluster. If unspecified, default discovery rules apply." )
95
118
initCmd .PersistentFlags ().StringVar (& initOpts .kubeconfigContext , "kubeconfig-context" , "" ,
96
119
"Context to be used within the kubeconfig file. If empty, current context will be used." )
97
- initCmd .PersistentFlags ().StringVar (& initOpts .coreProvider , "core" , "" ,
120
+ initCmd .PersistentFlags ().StringVar (& initOpts .operatorVersion , "operator-version" , "" ,
121
+ "CAPI Operator version (e.g. v0.7.0) to install on the management cluster. If unspecified, the latest release is used." )
122
+ initCmd .PersistentFlags ().StringVar (& initOpts .coreProvider , "core" , "cluster-api" ,
98
123
"Core provider version (e.g. cluster-api:v1.1.5) to add to the management cluster. If unspecified, Cluster API's latest release is used." )
99
- initCmd .PersistentFlags ().StringSliceVarP (& initOpts .infrastructureProviders , "infrastructure" , "i" , nil ,
124
+ initCmd .PersistentFlags ().StringSliceVarP (& initOpts .infrastructureProviders , "infrastructure" , "i" , [] string {} ,
100
125
"Infrastructure providers and versions (e.g. aws:v0.5.0) to add to the management cluster." )
101
- initCmd .PersistentFlags ().StringSliceVarP (& initOpts .bootstrapProviders , "bootstrap" , "b" , nil ,
126
+ initCmd .PersistentFlags ().StringSliceVarP (& initOpts .bootstrapProviders , "bootstrap" , "b" , [] string { "kubeadm" } ,
102
127
"Bootstrap providers and versions (e.g. kubeadm:v1.1.5) to add to the management cluster. If unspecified, Kubeadm bootstrap provider's latest release is used." )
103
- initCmd .PersistentFlags ().StringSliceVarP (& initOpts .controlPlaneProviders , "control-plane" , "c" , nil ,
128
+ initCmd .PersistentFlags ().StringSliceVarP (& initOpts .controlPlaneProviders , "control-plane" , "c" , [] string { "kubeadm" } ,
104
129
"Control plane providers and versions (e.g. kubeadm:v1.1.5) to add to the management cluster. If unspecified, the Kubeadm control plane provider's latest release is used." )
105
130
// initCmd.PersistentFlags().StringSliceVar(&initOpts.ipamProviders, "ipam", nil,
106
131
// "IPAM providers and versions (e.g. infoblox:v0.0.1) to add to the management cluster.")
107
132
// initCmd.PersistentFlags().StringSliceVar(&initOpts.runtimeExtensionProviders, "runtime-extension", nil,
108
133
// "Runtime extension providers and versions (e.g. test:v0.0.1) to add to the management cluster.")
109
- initCmd .PersistentFlags ().StringSliceVar (& initOpts .addonProviders , "addon" , nil ,
134
+ initCmd .PersistentFlags ().StringSliceVar (& initOpts .addonProviders , "addon" , [] string {} ,
110
135
"Add-on providers and versions (e.g. helm:v0.1.0) to add to the management cluster." )
111
136
initCmd .Flags ().StringVarP (& initOpts .targetNamespace , "target-namespace" , "n" , "capi-operator-system" ,
112
137
"The target namespace where the operator should be deployed. If unspecified, the 'capi-operator-system' namespace is used." )
@@ -123,9 +148,214 @@ func init() {
123
148
func runInit () error {
124
149
ctx := context .Background ()
125
150
126
- return initProvider (ctx , initOpts )
151
+ return initProviders (ctx , initOpts )
152
+ }
153
+
154
+ func initProviders (ctx context.Context , opts * initOptions ) error {
155
+ client , err := CreateKubeClient (initOpts .kubeconfig , initOpts .kubeconfigContext )
156
+ if err != nil {
157
+ return fmt .Errorf ("cannot create a client: %w" , err )
158
+ }
159
+
160
+ // Checking if CAPI operator deployment exists.
161
+ deploymentExists , err := checkCAPIOpearatorAvailability (ctx , client )
162
+ if err != nil {
163
+ return fmt .Errorf ("cannot check CAPI operator availability: %w" , err )
164
+ }
165
+
166
+ if deploymentExists && initOpts .operatorVersion != "" {
167
+ return fmt .Errorf ("cannot specify an operator version when the CAPI operator is already installed" )
168
+ }
169
+
170
+ // Deploy CAPI operator if it doesn't exist.
171
+ if ! deploymentExists {
172
+ if err := deployCAPIOperator (opts ); err != nil {
173
+ return fmt .Errorf ("cannot deploy CAPI operator: %w" , err )
174
+ }
175
+ }
176
+
177
+ // Deploy Core Provider.
178
+ if err := createGenericProvider (ctx , client , newGenericProvider (clusterctlv1 .CoreProviderType ), opts .coreProvider , opts .targetNamespace ); err != nil {
179
+ return fmt .Errorf ("cannot create core provider: %w" , err )
180
+ }
181
+
182
+ // Deploy Bootstrap Providers.
183
+ for _ , bootstrapProvider := range opts .bootstrapProviders {
184
+ if err := createGenericProvider (ctx , client , newGenericProvider (clusterctlv1 .BootstrapProviderType ), bootstrapProvider , opts .targetNamespace ); err != nil {
185
+ return fmt .Errorf ("cannot create bootstrap provider: %w" , err )
186
+ }
187
+ }
188
+
189
+ // Deploy Infrastructure Providers.
190
+ for _ , infrastructureProvider := range opts .infrastructureProviders {
191
+ if err := createGenericProvider (ctx , client , newGenericProvider (clusterctlv1 .InfrastructureProviderType ), infrastructureProvider , opts .targetNamespace ); err != nil {
192
+ return fmt .Errorf ("cannot create infrastructure provider: %w" , err )
193
+ }
194
+ }
195
+
196
+ // Deploy Control Plane Providers.
197
+ for _ , controlPlaneProvider := range opts .controlPlaneProviders {
198
+ if err := createGenericProvider (ctx , client , newGenericProvider (clusterctlv1 .ControlPlaneProviderType ), controlPlaneProvider , opts .targetNamespace ); err != nil {
199
+ return fmt .Errorf ("cannot create control plane provider: %w" , err )
200
+ }
201
+ }
202
+
203
+ // Deploy Add-on Providers.
204
+ for _ , addonProvider := range opts .addonProviders {
205
+ if err := createGenericProvider (ctx , client , newGenericProvider (clusterctlv1 .AddonProviderType ), addonProvider , opts .targetNamespace ); err != nil {
206
+ return fmt .Errorf ("cannot create addon provider: %w" , err )
207
+ }
208
+ }
209
+
210
+ return nil
211
+ }
212
+
213
+ // checkCAPIOpearatorAvailability checks if the CAPI operator is available on the management cluster.
214
+ func checkCAPIOpearatorAvailability (ctx context.Context , client ctrlclient.Client ) (bool , error ) {
215
+ var deploymentList appsv1.DeploymentList
216
+
217
+ // Search deployments with desired labels in all namespaces.
218
+ capiOperatorLabels := map [string ]string {
219
+ "clusterctl.cluster.x-k8s.io/core" : "capi-operator" ,
220
+ "control-plane" : "controller-manager" ,
221
+ }
222
+
223
+ if err := client .List (ctx , & deploymentList , ctrlclient .MatchingLabels (capiOperatorLabels )); err != nil {
224
+ return false , fmt .Errorf ("cannot get a list of deployments from the server: %w" , err )
225
+ }
226
+
227
+ if len (deploymentList .Items ) > 1 {
228
+ return false , fmt .Errorf ("more than one CAPI Operator deployments were found" )
229
+ }
230
+
231
+ return len (deploymentList .Items ) == 1 , nil
232
+ }
233
+
234
+ // deployCAPIOperator deploys the CAPI operator on the management cluster.
235
+ func deployCAPIOperator (opts * initOptions ) error {
236
+ configClient , err := configclient .New ("" )
237
+ if err != nil {
238
+ return fmt .Errorf ("cannot create config client: %w" , err )
239
+ }
240
+
241
+ providerConfig := configclient .NewProvider (capiOperatorProviderName , capiOperatorManifestsURL , clusterctlv1 .ProviderTypeUnknown )
242
+
243
+ repo , err := util .RepositoryFactory (providerConfig , configClient .Variables ())
244
+ if err != nil {
245
+ return fmt .Errorf ("cannot create repository: %w" , err )
246
+ }
247
+
248
+ if opts .operatorVersion == "" {
249
+ // TODO(mfedosin): support DefaultVersion for the operator
250
+ // opts.operatorVersion = repo.DefaultVersion()
251
+ opts .operatorVersion = capiOperatorLatestVersion
252
+ }
253
+
254
+ componentsFile , err := repo .GetFile (opts .operatorVersion , repo .ComponentsPath ())
255
+ if err != nil {
256
+ return fmt .Errorf ("cannot get components file: %w" , err )
257
+ }
258
+
259
+ options := repository.ComponentsOptions {
260
+ TargetNamespace : opts .targetNamespace ,
261
+ SkipTemplateProcess : false ,
262
+ Version : opts .operatorVersion ,
263
+ }
264
+
265
+ components , err := repository .NewComponents (repository.ComponentsInput {
266
+ Provider : providerConfig ,
267
+ ConfigClient : configClient ,
268
+ Processor : yamlprocessor .NewSimpleProcessor (),
269
+ RawYaml : componentsFile ,
270
+ Options : options ,
271
+ })
272
+ if err != nil {
273
+ return fmt .Errorf ("cannot create CAPI operator components: %w" , err )
274
+ }
275
+
276
+ clusterKubeconfig := cluster.Kubeconfig {
277
+ Path : opts .kubeconfig ,
278
+ Context : opts .kubeconfigContext ,
279
+ }
280
+
281
+ clusterClient := cluster .New (clusterKubeconfig , configClient )
282
+
283
+ // Before installing the operator, ensure the cert-manager Webhook is in place.
284
+ certManager := clusterClient .CertManager ()
285
+ if err := certManager .EnsureInstalled (); err != nil {
286
+ return fmt .Errorf ("cannot install cert-manager Webhook: %w" , err )
287
+ }
288
+
289
+ // TODO(mfedosin): we have to ignore wait-providers and wait-providers-timeout options until we upgrade
290
+ // to CAPI v1.6.0 where clusterctl functions have Context support.
291
+
292
+ if err := clusterClient .ProviderComponents ().Create (components .Objs ()); err != nil {
293
+ return fmt .Errorf ("cannot create CAPI operator components: %w" , err )
294
+ }
295
+
296
+ return nil
297
+ }
298
+
299
+ func newGenericProvider (providerType clusterctlv1.ProviderType ) genericprovider.GenericProvider {
300
+ switch providerType {
301
+ case clusterctlv1 .CoreProviderType :
302
+ return & genericprovider.CoreProviderWrapper {CoreProvider : & operatorv1.CoreProvider {}}
303
+ case clusterctlv1 .BootstrapProviderType :
304
+ return & genericprovider.BootstrapProviderWrapper {BootstrapProvider : & operatorv1.BootstrapProvider {}}
305
+ case clusterctlv1 .ControlPlaneProviderType :
306
+ return & genericprovider.ControlPlaneProviderWrapper {ControlPlaneProvider : & operatorv1.ControlPlaneProvider {}}
307
+ case clusterctlv1 .InfrastructureProviderType :
308
+ return & genericprovider.InfrastructureProviderWrapper {InfrastructureProvider : & operatorv1.InfrastructureProvider {}}
309
+ case clusterctlv1 .AddonProviderType :
310
+ return & genericprovider.AddonProviderWrapper {AddonProvider : & operatorv1.AddonProvider {}}
311
+ case clusterctlv1 .IPAMProviderType , clusterctlv1 .RuntimeExtensionProviderType , clusterctlv1 .ProviderTypeUnknown :
312
+ panic (fmt .Sprintf ("unsupported provider type %s" , providerType ))
313
+ default :
314
+ panic (fmt .Sprintf ("unknown provider type %s" , providerType ))
315
+ }
127
316
}
128
317
129
- func initProvider (ctx context.Context , opts * initOptions ) error {
130
- return errors .New ("Not implemented" )
318
+ // createGenericProvider creates a generic provider.
319
+ func createGenericProvider (ctx context.Context , client ctrlclient.Client , provider genericprovider.GenericProvider , providerInput string , defaultNamespace string ) error {
320
+ // Parse the provider string
321
+ // Format is <optional-namespace>:<provider-name>:<optional-version>
322
+ var name , namespace , version string
323
+
324
+ parts := strings .Split (providerInput , ":" )
325
+ switch len (parts ) {
326
+ case 1 :
327
+ name = parts [0 ]
328
+ case 2 :
329
+ name = parts [0 ]
330
+ version = parts [1 ]
331
+ case 3 :
332
+ namespace = parts [0 ]
333
+ name = parts [1 ]
334
+ version = parts [2 ]
335
+ default :
336
+ return fmt .Errorf ("invalid provider format: %s" , provider )
337
+ }
338
+
339
+ // Set name and namespace
340
+ provider .SetName (name )
341
+
342
+ if namespace == "" {
343
+ namespace = defaultNamespace
344
+ }
345
+
346
+ provider .SetNamespace (namespace )
347
+
348
+ // Set version
349
+ if version != "" {
350
+ spec := provider .GetSpec ()
351
+ spec .Version = version
352
+ provider .SetSpec (spec )
353
+ }
354
+
355
+ // Create the provider
356
+ if err := client .Create (ctx , provider .GetObject ()); err != nil && ! apierrors .IsAlreadyExists (err ) {
357
+ return fmt .Errorf ("cannot create provider: %w" , err )
358
+ }
359
+
360
+ return nil
131
361
}
0 commit comments