diff --git a/cmd/main.go b/cmd/main.go index c13c18c932..27829ff115 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -104,26 +104,34 @@ func main() { var sgSetup bool var manageCRDs bool var preDelete bool - - flag.BoolVar(&enableLeaderElection, "enable-leader-election", true, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.StringVar(&urlOnlyKubeconfig, "url-only-kubeconfig", "", - "Path to a kubeconfig, but only for the apiserver url.") - flag.BoolVar(&showVersion, "version", false, - "Show version information") - flag.StringVar(&printImages, "print-images", "", - "Print the default images the operator could deploy and exit. Possible values: list") - flag.StringVar(&printCalicoCRDs, "print-calico-crds", "", - "Print the Calico CRDs the operator has bundled then exit. Possible values: all, . If a value other than 'all' is specified, the first CRD with a prefix of the specified value will be printed.") - flag.StringVar(&printEnterpriseCRDs, "print-enterprise-crds", "", - "Print the Enterprise CRDs the operator has bundled then exit. Possible values: all, . If a value other than 'all' is specified, the first CRD with a prefix of the specified value will be printed.") - flag.BoolVar(&sgSetup, "aws-sg-setup", false, - "Setup Security Groups in AWS (should only be used on OpenShift).") - flag.BoolVar(&manageCRDs, "manage-crds", false, - "Operator should manage the projectcalico.org and operator.tigera.io CRDs.") - flag.BoolVar(&preDelete, "pre-delete", false, - "Run helm pre-deletion hook logic, then exit.") + var variant string + + // bootstrapCRDs is a flag that can be used to install the CRDs and exit. This is useful for + // workflows that use an init container to install CustomResources prior to the operator starting. + var bootstrapCRDs bool + + flag.BoolVar( + &enableLeaderElection, "enable-leader-election", true, + "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.", + ) + flag.StringVar( + &printCalicoCRDs, "print-calico-crds", "", + `Print the Calico CRDs the operator has bundled then exit. Possible values: all, . +If a value other than 'all' is specified, the first CRD with a prefix of the specified value will be printed.`, + ) + flag.StringVar( + &printEnterpriseCRDs, "print-enterprise-crds", "", + `Print the Enterprise CRDs the operator has bundled then exit. Possible values: all, . +If a value other than 'all' is specified, the first CRD with a prefix of the specified value will be printed.`, + ) + flag.StringVar(&urlOnlyKubeconfig, "url-only-kubeconfig", "", "Path to a kubeconfig, but only for the apiserver url.") + flag.BoolVar(&showVersion, "version", false, "Show version information") + flag.StringVar(&printImages, "print-images", "", "Print the default images the operator could deploy and exit. Possible values: list") + flag.BoolVar(&sgSetup, "aws-sg-setup", false, "Setup Security Groups in AWS (should only be used on OpenShift).") + flag.BoolVar(&manageCRDs, "manage-crds", false, "Operator should manage the projectcalico.org and operator.tigera.io CRDs.") + flag.BoolVar(&preDelete, "pre-delete", false, "Run helm pre-deletion hook logic, then exit.") + flag.BoolVar(&bootstrapCRDs, "bootstrap-crds", false, "Install CRDs and exit") + flag.StringVar(&variant, "variant", string(operatortigeraiov1.Calico), "Default product variant to assume during boostrapping.") opts := zap.Options{} opts.BindFlags(flag.CommandLine) @@ -269,6 +277,20 @@ func main() { os.Exit(1) } + // If configured to manage CRDs, do a preliminary install of them here. The Installation controller + // will reconcile them as well, but we need to make sure they are installed before we start the rest of the controllers. + if bootstrapCRDs || manageCRDs { + if err := crds.Ensure(mgr.GetClient(), variant); err != nil { + setupLog.Error(err, "Failed to ensure CRDs are created") + os.Exit(1) + } + + if bootstrapCRDs { + setupLog.Info("CRDs installed successfully") + os.Exit(0) + } + } + // Start a goroutine to handle termination. go func() { // Cancel the main context when we are done. diff --git a/pkg/crds/crds.go b/pkg/crds/crds.go index efabdf0d39..08c0df72f7 100644 --- a/pkg/crds/crds.go +++ b/pkg/crds/crds.go @@ -15,14 +15,17 @@ package crds import ( + "context" "embed" "fmt" "path" "regexp" "strings" "sync" + "time" apiextenv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" // gopkg.in/yaml.v2 didn't parse all the fields but this package did @@ -184,3 +187,26 @@ func ToRuntimeObjects(crds ...*apiextenv1.CustomResourceDefinition) []client.Obj } return objs } + +// Ensure ensures that the CRDs necessary for bootstrapping exist in the cluster. +// Further reconciliation of the CRDs is handled by the core controller. +func Ensure(c client.Client, variant string) error { + // Ensure Calico CRDs exist, which will allow us to bootstrap. + for _, crd := range GetCRDs(opv1.ProductVariant(variant)) { + // Skip the Tenant CRD - this is only used in Calico Cloud. + if crd.Name == "tenants.operator.tigera.io" { + continue + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + if err := c.Create(ctx, crd); err != nil { + // Ignore if the CRD already exists + if !errors.IsAlreadyExists(err) { + cancel() + return fmt.Errorf("failed to create CustomResourceDefinition %s: %s", crd.Name, err) + } + } + cancel() + } + return nil +}