diff --git a/config/manager/platformcontroller-deployment.yaml b/config/manager/platformcontroller-deployment.yaml new file mode 100644 index 0000000000..04df9aba9b --- /dev/null +++ b/config/manager/platformcontroller-deployment.yaml @@ -0,0 +1,89 @@ +# --------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# --------------------------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camel-k-platformcontroller + labels: + app: "camel-k" + camel.apache.org/component: operator + name: camel-k-platformcontroller + app.kubernetes.io/component: operator + app.kubernetes.io/name: camel-k + app.kubernetes.io/version: "2.3.0-SNAPSHOT" +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + name: camel-k-platformcontroller + template: + metadata: + labels: + name: camel-k-platformcontroller + camel.apache.org/component: operator + app: "camel-k" + app.kubernetes.io/component: operator + app.kubernetes.io/name: camel-k + app.kubernetes.io/version: "2.3.0-SNAPSHOT" + spec: + serviceAccountName: camel-k-operator + containers: + - name: camel-k-platformcontroller + image: docker.io/apache/camel-k:2.3.0-SNAPSHOT + imagePullPolicy: IfNotPresent + command: + - kamel + - platformcontroller + ports: + - containerPort: 8080 + name: metrics + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "camel-k-platformcontroller" + - name: OPERATOR_ID + value: "camel-k" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # NAMESPACE is always the operator namespace, independently of WATCH_NAMESPACE + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 20 + periodSeconds: 10 + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 80a0f66fe5..97f3811171 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -270,6 +270,7 @@ func (o *installCmdOptions) setupEnvVars() { } } +// TODO: check this part to add platformcontroller installation func (o *installCmdOptions) tryInstallViaOLM( cmd *cobra.Command, clientProvider client.Provider, output *kubernetes.Collection, ) (bool, error) { diff --git a/pkg/cmd/platformcontroller.go b/pkg/cmd/platformcontroller.go new file mode 100644 index 0000000000..ec209dc91f --- /dev/null +++ b/pkg/cmd/platformcontroller.go @@ -0,0 +1,68 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "github.com/apache/camel-k/v2/pkg/cmd/platformcontroller" + "github.com/apache/camel-k/v2/pkg/platform" + "github.com/apache/camel-k/v2/pkg/util/defaults" + "github.com/spf13/cobra" +) + +const platformcontrollerCommand = "platformcontroller" + +func newCmdPlatformController() (*cobra.Command, *platformcontrollerCmdOptions) { + options := platformcontrollerCmdOptions{} + + cmd := cobra.Command{ + Use: "platformcontroller", + Short: "Run the Camel K platform controller", + Long: `Run the Camel K platform controller`, + Hidden: true, + PreRunE: decode(&options), + Run: options.run, + } + + cmd.Flags().Int32("health-port", 8081, "The port of the health endpoint") + cmd.Flags().Int32("monitoring-port", 8080, "The port of the metrics endpoint") + cmd.Flags().Bool("leader-election", true, "Use leader election") + cmd.Flags().String("leader-election-id", "", "Use the given ID as the leader election Lease name") + + return &cmd, &options +} + +type platformcontrollerCmdOptions struct { + HealthPort int32 `mapstructure:"health-port"` + MonitoringPort int32 `mapstructure:"monitoring-port"` + LeaderElection bool `mapstructure:"leader-election"` + LeaderElectionID string `mapstructure:"leader-election-id"` +} + +func (o *platformcontrollerCmdOptions) run(_ *cobra.Command, _ []string) { + + leaderElectionID := o.LeaderElectionID + if leaderElectionID == "" { + if defaults.OperatorID() != "" { + leaderElectionID = platform.GetPlatformControllerLockName(defaults.OperatorID()) + } else { + leaderElectionID = platform.PlatformControllerLockName + } + } + + platformcontroller.Run(o.HealthPort, o.MonitoringPort, o.LeaderElection, leaderElectionID) +} diff --git a/pkg/cmd/platformcontroller/platformcontroller.go b/pkg/cmd/platformcontroller/platformcontroller.go new file mode 100644 index 0000000000..6c50da9b18 --- /dev/null +++ b/pkg/cmd/platformcontroller/platformcontroller.go @@ -0,0 +1,272 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package platformcontroller + +import ( + "context" + "flag" + "fmt" + "os" + "reflect" + "runtime" + "strconv" + "strings" + "time" + + "k8s.io/klog/v2" + + "go.uber.org/automaxprocs/maxprocs" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + coordination "k8s.io/api/coordination/v1" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/cache" + ctrl "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/healthz" + logf "sigs.k8s.io/controller-runtime/pkg/log" + zapctrl "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" + + servingv1 "knative.dev/serving/pkg/apis/serving/v1" + + "github.com/apache/camel-k/v2/pkg/apis" + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/client" + "github.com/apache/camel-k/v2/pkg/controller" + "github.com/apache/camel-k/v2/pkg/event" + "github.com/apache/camel-k/v2/pkg/install" + "github.com/apache/camel-k/v2/pkg/platform" + "github.com/apache/camel-k/v2/pkg/util/defaults" + "github.com/apache/camel-k/v2/pkg/util/kubernetes" + logutil "github.com/apache/camel-k/v2/pkg/util/log" +) + +var log = logutil.Log.WithName("cmd") + +func printVersion() { + log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + log.Info(fmt.Sprintf("Camel K Platform Controller Version: %v", defaults.Version)) + log.Info(fmt.Sprintf("Camel K Default Runtime Version: %v", defaults.DefaultRuntimeVersion)) + log.Info(fmt.Sprintf("Camel K Git Commit: %v", defaults.GitCommit)) + log.Info(fmt.Sprintf("Camel K Platform Controller ID: %v", defaults.OperatorID())) + + // Will only appear if DEBUG level has been enabled using the env var LOG_LEVEL + log.Debug("*** DEBUG level messages will be logged ***") +} + +// Run starts the Camel K platform controller. +func Run(healthPort, monitoringPort int32, leaderElection bool, leaderElectionID string) { + + flag.Parse() + + // The logger instantiated here can be changed to any logger + // implementing the logr.Logger interface. This logger will + // be propagated through the whole operator, generating + // uniform and structured logs. + + // The constants specified here are zap specific + var logLevel zapcore.Level + logLevelVal, ok := os.LookupEnv("LOG_LEVEL") + if ok { + switch strings.ToLower(logLevelVal) { + case "error": + logLevel = zapcore.ErrorLevel + case "info": + logLevel = zapcore.InfoLevel + case "debug": + logLevel = zapcore.DebugLevel + default: + customLevel, err := strconv.Atoi(strings.ToLower(logLevelVal)) + exitOnError(err, "Invalid log-level") + // Need to multiply by -1 to turn logr expected level into zap level + logLevel = zapcore.Level(int8(customLevel) * -1) + } + } else { + logLevel = zapcore.InfoLevel + } + + // Use and set atomic level that all following log events are compared with + // in order to evaluate if a given log level on the event is enabled. + logf.SetLogger(zapctrl.New(func(o *zapctrl.Options) { + o.Development = false + o.Level = zap.NewAtomicLevelAt(logLevel) + })) + + klog.SetLogger(log.AsLogger()) + + _, err := maxprocs.Set(maxprocs.Logger(func(f string, a ...interface{}) { log.Info(fmt.Sprintf(f, a)) })) + if err != nil { + log.Error(err, "failed to set GOMAXPROCS from cgroups") + } + + printVersion() + + watchNamespace, err := getWatchNamespace() + exitOnError(err, "failed to get watch namespace") + + ctx := signals.SetupSignalHandler() + + cfg, err := config.GetConfig() + exitOnError(err, "cannot get client config") + // Increase maximum burst that is used by client-side throttling, + // to prevent the requests made to apply the bundled Kamelets + // from being throttled. + cfg.QPS = 20 + cfg.Burst = 200 + bootstrapClient, err := client.NewClientWithConfig(false, cfg) + exitOnError(err, "cannot initialize client") + + // We do not rely on the event broadcaster managed by controller runtime, + // so that we can check the operator has been granted permission to create + // Events. This is required for the operator to be installable by standard + // admin users, that are not granted create permission on Events by default. + broadcaster := record.NewBroadcaster() + defer broadcaster.Shutdown() + + if ok, err := kubernetes.CheckPermission(ctx, bootstrapClient, corev1.GroupName, "events", watchNamespace, "", "create"); err != nil || !ok { + // Do not sink Events to the server as they'll be rejected + broadcaster = event.NewSinkLessBroadcaster(broadcaster) + exitOnError(err, "cannot check permissions for creating Events") + log.Info("Event broadcasting is disabled because of missing permissions to create Events") + } + + platformcontrollerNamespace := platform.GetPlatformControllerNamespace() + if platformcontrollerNamespace == "" { + // Fallback to using the watch namespace when the platform controller is not in-cluster. + // It does not support local (off-cluster) platform controller watching resources globally, + // in which case it's not possible to determine a namespace. + platformcontrollerNamespace = watchNamespace + if platformcontrollerNamespace == "" { + leaderElection = false + log.Info("unable to determine namespace for leader election") + } + } + + // Set the platform controller container image if it runs in-container + platform.PlatformControllerImage, err = getPlatformControllerImage(ctx, bootstrapClient) + exitOnError(err, "cannot get platform controller container image") + + if ok, err := kubernetes.CheckPermission(ctx, bootstrapClient, coordination.GroupName, "leases", platformcontrollerNamespace, "", "create"); err != nil || !ok { + leaderElection = false + exitOnError(err, "cannot check permissions for creating Leases") + log.Info("The platform controller is not granted permissions to create Leases") + } + + if !leaderElection { + log.Info("Leader election is disabled!") + } + + //TODO: check if this part is actually needed from here. + hasIntegrationLabel, err := labels.NewRequirement(v1.IntegrationLabel, selection.Exists, []string{}) + exitOnError(err, "cannot create Integration label selector") + selector := labels.NewSelector().Add(*hasIntegrationLabel) + + selectors := map[ctrl.Object]cache.ByObject{ + &corev1.Pod{}: {Label: selector}, + &appsv1.Deployment{}: {Label: selector}, + &batchv1.Job{}: {Label: selector}, + &servingv1.Service{}: {Label: selector}, + } + + if ok, err := kubernetes.IsAPIResourceInstalled(bootstrapClient, batchv1.SchemeGroupVersion.String(), reflect.TypeOf(batchv1.CronJob{}).Name()); ok && err == nil { + selectors[&batchv1.CronJob{}] = cache.ByObject{ + Label: selector, + } + } + + options := cache.Options{ + ByObject: selectors, + Namespaces: []string{watchNamespace}, + } + //TODO: to here. + + mgr, err := manager.New(cfg, manager.Options{ + EventBroadcaster: broadcaster, + LeaderElection: leaderElection, + LeaderElectionNamespace: platformcontrollerNamespace, + LeaderElectionID: leaderElectionID, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + LeaderElectionReleaseOnCancel: true, + HealthProbeBindAddress: ":" + strconv.Itoa(int(healthPort)), + MetricsBindAddress: ":" + strconv.Itoa(int(monitoringPort)), + Cache: options, + }) + exitOnError(err, "") + + log.Info("Configuring manager") + exitOnError(mgr.AddHealthzCheck("health-probe", healthz.Ping), "Unable add liveness check") + exitOnError(apis.AddToScheme(mgr.GetScheme()), "") + ctrlClient, err := client.FromManager(mgr) + exitOnError(err, "") + exitOnError(controller.AddToPlatformManager(ctx, mgr, ctrlClient), "") + + log.Info("Installing platform manager resources") + installCtx, installCancel := context.WithTimeout(ctx, 1*time.Minute) + defer installCancel() + install.OperatorStartupOptionalTools(installCtx, bootstrapClient, watchNamespace, platformcontrollerNamespace, log) + + log.Info("Starting the platform manager") + exitOnError(mgr.Start(ctx), "platform manager exited non-zero") +} + +// getWatchNamespace returns the Namespace the platform controller should be watching for changes. +func getWatchNamespace() (string, error) { + ns, found := os.LookupEnv(platform.PlatformControllerWatchNamespaceEnvVariable) + if !found { + return "", fmt.Errorf("%s must be set", platform.PlatformControllerWatchNamespaceEnvVariable) + } + return ns, nil +} + +// getPlatformControllerImage returns the image currently used by the running platform controller if present (when running out of cluster, it may be absent). +func getPlatformControllerImage(ctx context.Context, c ctrl.Reader) (string, error) { + ns := platform.GetPlatformControllerNamespace() + name := platform.GetOperatorPodName() + if ns == "" || name == "" { + return "", nil + } + + pod := corev1.Pod{} + if err := c.Get(ctx, ctrl.ObjectKey{Namespace: ns, Name: name}, &pod); err != nil && k8serrors.IsNotFound(err) { + return "", nil + } else if err != nil { + return "", err + } + if len(pod.Spec.Containers) == 0 { + return "", fmt.Errorf("no containers found in platform controller pod") + } + return pod.Spec.Containers[0].Image, nil +} + +func exitOnError(err error, msg string) { + if err != nil { + log.Error(err, msg) + os.Exit(1) + } +} diff --git a/pkg/cmd/platformcontroller_test.go b/pkg/cmd/platformcontroller_test.go new file mode 100644 index 0000000000..d1558f601c --- /dev/null +++ b/pkg/cmd/platformcontroller_test.go @@ -0,0 +1,84 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "testing" + + "github.com/apache/camel-k/v2/pkg/util/test" + "github.com/spf13/cobra" + + "github.com/stretchr/testify/assert" +) + +const cmdPlatformcontroller = "platformcontroller" + +// nolint: unparam +func initializePlatformcontrollerCmdOptions(t *testing.T) (*platformcontrollerCmdOptions, *cobra.Command, RootCmdOptions) { + t.Helper() + + options, rootCmd := kamelTestPreAddCommandInit() + platformcontrollerCmdOptions := addTestPlatformcontrollerCmd(*options, rootCmd) + kamelTestPostAddCommandInit(t, rootCmd) + + return platformcontrollerCmdOptions, rootCmd, *options +} + +// nolint: unparam +func addTestPlatformcontrollerCmd(options RootCmdOptions, rootCmd *cobra.Command) *platformcontrollerCmdOptions { + // add a testing version of operator Command + platformcontrollerCmd, platformcontrollerOptions := newCmdPlatformController() + platformcontrollerCmd.RunE = func(c *cobra.Command, args []string) error { + return nil + } + platformcontrollerCmd.PostRunE = func(c *cobra.Command, args []string) error { + return nil + } + platformcontrollerCmd.Args = test.ArbitraryArgs + rootCmd.AddCommand(platformcontrollerCmd) + return platformcontrollerOptions +} + +func TestPlatformcontrollerNoFlag(t *testing.T) { + operatorCmdOptions, rootCmd, _ := initializePlatformcontrollerCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdPlatformcontroller) + assert.Nil(t, err) + // Check default expected values + assert.Equal(t, int32(8081), operatorCmdOptions.HealthPort) + assert.Equal(t, int32(8080), operatorCmdOptions.MonitoringPort) +} + +func TestPlatformcontrollerNonExistingFlag(t *testing.T) { + _, rootCmd, _ := initializePlatformcontrollerCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdPlatformcontroller, "--nonExistingFlag") + assert.NotNil(t, err) +} + +func TestPlatformcontrollerHealthPortFlag(t *testing.T) { + operatorCmdOptions, rootCmd, _ := initializePlatformcontrollerCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdPlatformcontroller, "--health-port", "7171") + assert.Nil(t, err) + assert.Equal(t, int32(7171), operatorCmdOptions.HealthPort) +} + +func TestPlatformcontrollerMonitoringPortFlag(t *testing.T) { + operatorCmdOptions, rootCmd, _ := initializePlatformcontrollerCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdPlatformcontroller, "--monitoring-port", "7172") + assert.Nil(t, err) + assert.Equal(t, int32(7172), operatorCmdOptions.MonitoringPort) +} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index a43e5a3491..f25c2270b1 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -144,6 +144,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) { cmd.AddCommand(newCmdDescribe(options)) cmd.AddCommand(cmdOnly(newCmdRebuild(options))) cmd.AddCommand(cmdOnly(newCmdOperator())) + cmd.AddCommand(cmdOnly(newCmdPlatformController())) cmd.AddCommand(cmdOnly(newCmdBuilder(options))) cmd.AddCommand(cmdOnly(newCmdDebug(options))) cmd.AddCommand(cmdOnly(newCmdDump(options))) @@ -198,7 +199,7 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error { // reconciled. Hence the compatibility check is skipped for the install and the operator command. // Furthermore, there can be any incompatibilities, as the install command deploys // the operator version it's compatible with. - if cmd.Use != builderCommand && cmd.Use != installCommand && cmd.Use != operatorCommand { + if cmd.Use != builderCommand && cmd.Use != installCommand && cmd.Use != operatorCommand && cmd.Use != platformcontrollerCommand { checkAndShowCompatibilityWarning(command.Context, cmd, c, command.Namespace) } } diff --git a/pkg/controller/add_integrationplatform.go b/pkg/controller/add_integrationplatform.go index 16b88f91aa..b0348bc525 100644 --- a/pkg/controller/add_integrationplatform.go +++ b/pkg/controller/add_integrationplatform.go @@ -22,5 +22,5 @@ import ( ) func init() { - addToManager = append(addToManager, integrationplatform.Add) + addToPlatformManager = append(addToPlatformManager, integrationplatform.Add) } diff --git a/pkg/controller/platformcontroller.go b/pkg/controller/platformcontroller.go new file mode 100644 index 0000000000..661e048bc6 --- /dev/null +++ b/pkg/controller/platformcontroller.go @@ -0,0 +1,39 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/apache/camel-k/v2/pkg/client" +) + +// addToPlatformManager is a list of functions to add Controllers to the PlatformManager. +var addToPlatformManager []func(context.Context, ctrl.Manager, client.Client) error + +// AddToPlatformManager adds Controllers to the PlatformManager. +func AddToPlatformManager(ctx context.Context, manager ctrl.Manager, client client.Client) error { + for _, f := range addToPlatformManager { + if err := f(ctx, manager, client); err != nil { + return err + } + } + return nil +} diff --git a/pkg/install/operator.go b/pkg/install/operator.go index 5681ca6469..1e65c0b9cb 100644 --- a/pkg/install/operator.go +++ b/pkg/install/operator.go @@ -271,6 +271,11 @@ func OperatorOrCollect(ctx context.Context, cmd *cobra.Command, c client.Client, return err } + // Deploy the platformcontroller + if err := installPlatformcontroller(ctx, c, cfg.Namespace, customizer, collection, force); err != nil { + return err + } + if err = installEvents(ctx, c, cfg.Namespace, customizer, collection, force, cfg.Global); err != nil { if k8serrors.IsAlreadyExists(err) { return err @@ -497,6 +502,12 @@ func installOperator(ctx context.Context, c client.Client, namespace string, cus ) } +func installPlatformcontroller(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection, force bool) error { + return ResourcesOrCollect(ctx, c, namespace, collection, force, customizer, + "/manager/platformcontroller-deployment.yaml", + ) +} + func installKnativeBindings(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection, force bool, global bool) error { if global { return ResourcesOrCollect(ctx, c, namespace, collection, force, customizer, diff --git a/pkg/platform/operator.go b/pkg/platform/operator.go index 16f6cb0dd4..e837f55d00 100644 --- a/pkg/platform/operator.go +++ b/pkg/platform/operator.go @@ -94,6 +94,14 @@ func GetOperatorNamespace() string { return "" } +// GetPlatformControllerNamespace returns the namespace where the current platform controller is located (if set). +func GetPlatformControllerNamespace() string { + if podNamespace, envSet := os.LookupEnv(platformControllerNamespaceEnvVariable); envSet { + return podNamespace + } + return "" +} + // GetOperatorPodName returns the pod that is running the current operator (if any). func GetOperatorPodName() string { if podName, envSet := os.LookupEnv(operatorPodNameEnvVariable); envSet { @@ -102,6 +110,14 @@ func GetOperatorPodName() string { return "" } +// GetPlatformControllerPodName returns the pod that is running the current platform controller (if any). +func GetPlatformControllerPodName() string { + if podName, envSet := os.LookupEnv(platformControllerPodNameEnvVariable); envSet { + return podName + } + return "" +} + // GetOperatorLockName returns the name of the lock lease that is electing a leader on the particular namespace. func GetOperatorLockName(operatorID string) string { return fmt.Sprintf("%s-lock", operatorID) diff --git a/pkg/platform/platformcontroller.go b/pkg/platform/platformcontroller.go new file mode 100644 index 0000000000..8ab6a47e82 --- /dev/null +++ b/pkg/platform/platformcontroller.go @@ -0,0 +1,35 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package platform + +import "fmt" + +const ( + PlatformControllerWatchNamespaceEnvVariable = "WATCH_NAMESPACE" + platformControllerNamespaceEnvVariable = "NAMESPACE" + platformControllerPodNameEnvVariable = "POD_NAME" +) + +const PlatformControllerLockName = "camel-k-platform-controller-lock" + +var PlatformControllerImage string + +// GetPlatformControllerLockName returns the name of the lock lease that is electing a leader on the particular namespace. +func GetPlatformControllerLockName(platformControllerID string) string { + return fmt.Sprintf("camel-k-platform-controller-%s-lock", platformControllerID) +} diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go index 09dc7fdcf5..2745284ef6 100644 --- a/pkg/resources/resources.go +++ b/pkg/resources/resources.go @@ -244,6 +244,13 @@ var assets = func() http.FileSystem { compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x53\x4d\x4f\xe3\x3a\x14\xdd\xfb\x57\x1c\x35\x0b\x40\x2a\x0d\x7a\xcb\xbe\x55\x1e\xb4\x7a\xd1\xa0\x54\x22\x65\x10\x4b\x37\xb9\x4d\xae\x26\xf1\xf5\xd8\x4e\x43\xff\xfd\xc8\x69\x3b\x80\x66\x37\xc2\x9b\xca\xea\xcd\xf9\xb8\xe7\x38\xc1\xed\xd7\x1d\x95\xe0\x91\x2b\x32\x9e\x6a\x04\x41\x68\x09\x99\xd5\x55\x4b\x28\x65\x1f\x46\xed\x08\x6b\x19\x4c\xad\x03\x8b\xc1\x75\x56\xae\x6f\x30\x98\x9a\x1c\xc4\x10\xc4\xa1\x17\x47\x2a\x41\x25\x26\x38\xde\x0d\x41\x1c\xba\x13\x20\x74\xe3\x88\x7a\x32\xc1\x2f\x80\x92\x68\x42\x2f\x36\xdb\xfc\x7e\x85\x3d\x77\x84\x9a\xfd\xe9\x23\xaa\x31\x72\x68\x55\x82\xd0\xb2\xc7\x28\xee\x07\xf6\xe2\xa0\xeb\x9a\x23\xb1\xee\xc0\x66\x2f\xae\x3f\xc9\x70\xd4\x68\x57\xb3\x69\x50\x89\x3d\x3a\x6e\xda\x00\x19\x0d\x39\xdf\xb2\x5d\xa8\x04\xdb\x68\xa3\x5c\x5f\x94\xf8\x13\xec\xc4\x19\x04\xaf\x32\x9c\x3d\x7c\xb0\x7b\xde\xc2\x1c\xdf\xc9\xf9\x48\xf2\xcf\xe2\x4e\x25\xb8\x8e\x23\xb3\xf3\x9f\xb3\x9b\x7f\x71\x94\x01\xbd\x3e\xc2\x48\xc0\xe0\xe9\x03\x32\xbd\x55\x64\x03\xd8\xa0\x92\xde\x76\xac\x4d\x45\xef\xb6\x7e\x33\x2c\x30\x09\x88\x18\xb2\x0b\x9a\x0d\xf4\x64\x03\xb2\xff\x38\x06\x1d\x54\xa2\x12\x4c\xa7\x0d\xc1\x2e\xd3\x74\x1c\xc7\x85\x9e\xe4\x2e\xc4\x35\xe9\xc5\x5d\xfa\x98\xdf\xaf\x8a\x72\x75\x3b\x49\x56\x09\x9e\x4d\x47\xde\xc3\xd1\xcf\x81\x1d\xd5\xd8\x1d\xa1\xad\xed\xb8\xd2\xbb\x8e\xd0\xe9\x31\x06\x37\xa5\x33\x85\xce\x06\xa3\xe3\xc0\xa6\x99\xc3\x9f\x53\x57\xc9\xa7\x74\xde\xd7\x75\x91\xc7\xfe\xd3\x80\x18\x68\x83\x59\x56\x22\x2f\x67\xf8\x2f\x2b\xf3\x72\xae\x12\xbc\xe4\xdb\xff\x37\xcf\x5b\xbc\x64\x4f\x4f\x59\xb1\xcd\x57\x25\x36\x4f\xb8\xdf\x14\x0f\xf9\x36\xdf\x14\x25\x36\x6b\x64\xc5\x2b\xbe\xe5\xc5\xc3\x1c\xc4\xa1\x25\x07\x7a\xb3\x2e\xea\x17\x07\x8e\x8b\xa4\x3a\x66\x7a\x29\xd0\x45\x40\xec\x47\xbc\x7b\x4b\x15\xef\xb9\x42\xa7\x4d\x33\xe8\x86\xd0\xc8\x81\x9c\x89\xf5\xb0\xe4\x7a\xf6\x31\x4e\x0f\x6d\x6a\x95\xa0\xe3\x9e\xc3\xd4\x22\xff\xa7\xa9\x48\xf3\x95\x6f\x4b\xdd\x42\xec\x12\x8e\x7a\x39\x90\x02\xac\x0e\xed\x12\x69\x54\x9c\x06\xea\x6d\xa7\x03\x9d\x6e\xf1\xf9\x68\x8e\x0d\x4e\xef\x52\x32\x87\xf4\x2e\x3d\xe8\x6e\xa0\xb5\x93\xfe\x8c\xa2\xeb\xfa\xaf\x20\x14\x30\xfd\x2e\x71\x35\x9b\x5d\xa9\x5f\x01\x00\x00\xff\xff\x83\x05\x78\x51\x3e\x04\x00\x00"), }, + "/manager/platformcontroller-deployment.yaml": &vfsgen۰CompressedFileInfo{ + name: "platformcontroller-deployment.yaml", + modTime: time.Time{}, + uncompressedSize: 3060, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x56\x41\x6f\xe2\x48\x13\xbd\xfb\x57\x94\xf0\x65\x46\x0a\x90\xe4\x34\xf2\x77\xf2\x17\xc8\x0e\xda\x2c\x20\xcc\xec\x68\x4e\xab\xa6\x5d\xd8\xa5\xb4\xbb\xbc\xdd\x65\x18\xf6\xd7\xaf\xda\x40\x82\x3d\x49\x94\x91\x46\x5a\x9f\xa0\xeb\xf5\xab\x57\xf5\xaa\xdd\x8e\x61\xf8\xeb\x9e\x28\x86\x07\xd2\x68\x3d\xe6\x20\x0c\x52\x22\xa4\xb5\xd2\x25\x42\xc6\x5b\xd9\x2b\x87\x70\xcf\x8d\xcd\x95\x10\x5b\xf8\x90\x66\xf7\x1f\xa1\xb1\x39\x3a\x60\x8b\xc0\x0e\x2a\x76\x18\xc5\xa0\xd9\x8a\xa3\x4d\x23\xec\xc0\x1c\x09\x41\x15\x0e\xb1\x42\x2b\x7e\x04\x90\x21\xb6\xec\xf3\xc5\x7a\x76\x37\x85\x2d\x19\x84\x9c\xfc\x71\x13\xe6\xb0\x27\x29\xa3\x18\xa4\x24\x0f\x7b\x76\x8f\xb0\x65\x07\x2a\xcf\x29\x24\x56\x06\xc8\x6e\xd9\x55\x47\x19\x0e\x0b\xe5\x72\xb2\x05\x68\xae\x0f\x8e\x8a\x52\x80\xf7\x16\x9d\x2f\xa9\x1e\x45\x31\xac\x43\x19\xd9\xfd\x59\x89\x3f\xd2\xb6\x39\x85\xe1\x1b\x37\xa7\x1a\x2e\xca\x3d\x75\xe1\x0a\xfe\x44\xe7\x43\x92\xdb\xd1\x75\x14\xc3\x87\x00\x19\x9c\x82\x83\x8f\xff\x83\x03\x37\x50\xa9\x03\x58\x16\x68\x3c\x5e\x30\xe3\x77\x8d\xb5\x00\x59\xd0\x5c\xd5\x86\x94\xd5\xf8\x5c\xd6\x53\x86\x11\xb4\x02\x02\x07\x6f\x44\x91\x05\xd5\x96\x01\xbc\xbd\x84\x81\x92\x28\x8e\x62\x68\x9f\x52\xa4\x4e\xc6\xe3\xfd\x7e\x3f\x52\xad\xdc\x11\xbb\x62\x7c\xae\x6e\xfc\x30\xbb\x9b\xce\xb3\xe9\xb0\x95\x1c\xc5\xf0\xc5\x1a\xf4\x1e\x1c\xfe\xdd\x90\xc3\x1c\x36\x07\x50\x75\x6d\x48\xab\x8d\x41\x30\x6a\x1f\x8c\x6b\xdd\x69\x4d\x27\x0b\x7b\x47\x42\xb6\xb8\x02\x7f\x72\x3d\x8a\x3b\xee\x3c\xb7\xeb\x2c\x8f\x7c\x07\xc0\x16\x94\x85\x41\x9a\xc1\x2c\x1b\xc0\xff\xd3\x6c\x96\x5d\x45\x31\x7c\x9d\xad\x3f\x2f\xbe\xac\xe1\x6b\xba\x5a\xa5\xf3\xf5\x6c\x9a\xc1\x62\x05\x77\x8b\xf9\x64\xb6\x9e\x2d\xe6\x19\x2c\xee\x21\x9d\x7f\x83\xdf\x67\xf3\xc9\x15\x20\x49\x89\x0e\xf0\x7b\xed\x82\x7e\x76\x40\xa1\x91\x98\x07\x4f\xcf\x03\x74\x16\x10\xe6\x23\xfc\xf7\x35\x6a\xda\x92\x06\xa3\x6c\xd1\xa8\x02\xa1\xe0\x1d\x3a\x1b\xc6\xa3\x46\x57\x91\x0f\x76\x7a\x50\x36\x8f\x62\x30\x54\x91\xb4\x53\xe4\x7f\x2c\x2a\xa4\xf9\x95\x67\x2b\x52\x35\x9d\xc6\x29\x09\x0e\xf8\xf1\xee\x26\x7a\x24\x9b\x27\x30\xc1\xda\xf0\x21\x1c\x8e\xa8\x42\x51\xb9\x12\x95\x44\x00\x56\x55\x98\x80\x56\x15\x9a\xe1\xe3\xb0\x36\x4a\xc2\xd4\xb7\x87\x8b\x8d\x41\x17\x01\x18\xb5\x41\xe3\x03\x18\x02\x67\x02\x83\x13\x7c\xd0\x2e\xb5\x7f\x2e\xa7\x24\x0c\x23\x5b\xb4\x92\x00\xd7\xe8\x94\xb0\x6b\x81\xef\x48\xd5\x26\x18\x3d\x36\x1b\x74\x16\x05\xfd\x88\xf8\x55\xba\x1f\x91\x9d\x04\xaf\x60\x76\xe7\xee\x0c\x6e\x47\xb7\xa3\xeb\x61\x36\x4f\x97\xd9\xe7\xc5\x7a\x10\x05\x5b\x43\x91\x0e\xdb\xc1\xf5\x09\xdc\x44\x00\x5e\x9c\x12\x2c\x0e\xc7\xf2\xe5\x50\x63\x02\x2b\xd4\x0e\x95\x60\x08\xa3\x41\x2d\xec\x8e\xe1\x4a\x89\x2e\x1f\x2e\xda\xf5\xae\xa2\x05\xab\xb0\x8e\x27\x8e\x0b\x73\xc2\x63\x3a\x74\xef\xec\xe2\x4f\xf9\xf2\x8a\xaf\x3f\xe7\xc6\x7b\x1d\xf9\x59\x57\x02\xfe\xec\x4c\xfb\x1b\xdd\x8e\x34\xa6\x5a\x73\x63\x65\xde\x69\x46\x4f\x50\xe8\x89\xa2\xf0\x92\x7e\xee\xde\xf0\xfd\xfd\x03\xa0\x4a\x15\x98\x40\xce\xfa\x11\x5d\x90\x79\x6c\xe6\xf8\xb4\x39\xe9\x6a\xed\x6f\x5c\x36\xc6\x2c\xd9\x90\x3e\x24\x30\xdb\xce\x59\x96\x0e\x7d\x38\x7e\xcf\x38\xcd\x55\xa5\x6c\x9e\x5c\x2c\x05\x89\x8f\x81\xbf\xb7\xf6\xa6\xd0\x9a\x9d\xf8\x3e\xcb\x53\xf9\x4b\x76\x92\xc0\xa7\xeb\x4f\xd7\x1d\xc4\x79\x94\x2a\x14\x47\xda\x5f\xc4\xd0\xee\xfa\x64\x47\xe8\xd7\x74\x7d\xf7\xf9\xaf\x79\xfa\xc7\x34\x5b\xa6\x77\xd3\x1e\xdd\x4e\x99\x06\xef\x1d\x57\x49\x2f\x00\xb0\x25\x34\xf9\x0a\xb7\x3f\x46\x4e\xb1\xa5\x92\x32\x79\x9a\xfd\x51\x48\xe7\x6b\xa5\xf1\x45\x19\x8b\xe5\x74\x95\xae\x17\xab\x56\xc9\x4b\x22\x9e\x47\xf9\x05\x83\x07\x6f\x73\xce\x26\x6f\x33\xbe\xbc\x7d\xb9\x98\xbc\xaa\xe6\xd7\xb5\xa4\x03\x8d\xe1\xc9\x88\x70\x37\x2a\xb3\x57\x07\xdf\x5e\x2e\xe7\x73\x00\x4f\x6d\xbc\x02\xb2\x39\xd6\x68\x73\xb4\x62\xda\x9b\xff\x2d\x2f\xcf\x55\xfd\x47\x4e\x1b\xda\xa1\x45\xef\x97\x8e\x37\xd8\x25\x0a\xdf\x25\xbf\xa1\xf4\xd9\xeb\x96\x74\x5c\xa2\x32\x52\xfe\xd3\x0f\x9e\xe7\xff\xa6\x13\x20\x4b\x42\xca\x4c\xd0\xa8\x43\x86\x9a\x6d\xee\x13\xb8\xed\x9e\x91\x1a\x1d\x71\xfe\x14\xbd\xb9\x8c\x7a\xd4\x8d\x23\x39\xdc\xb1\x15\xfc\xde\x93\xe4\x1a\x9b\xfa\x39\xdb\x15\xb3\x24\x20\xae\xe9\x7a\xe7\x51\x87\x17\xe9\xd2\x71\xf8\x90\xeb\x57\x73\xba\x65\x1a\x2b\x54\xe1\x04\xb7\xaa\x31\xd2\x81\x28\x63\x78\xbf\x74\xb4\x23\x83\x05\x4e\xbd\x56\xa6\xfd\xc2\x48\x60\xab\x8c\xef\xa6\xd2\xaa\x56\x1b\x32\x24\x84\xbe\x9f\x28\x77\x5c\xf7\xd7\x86\x90\x3e\x3c\x44\xd1\xbf\x01\x00\x00\xff\xff\x00\xbd\x54\x08\xf4\x0b\x00\x00"), + }, "/prometheus": &vfsgen۰DirInfo{ name: "prometheus", modTime: time.Time{}, @@ -810,6 +817,7 @@ var assets = func() http.FileSystem { fs["/manager/patch-resource-requirements.yaml"].(os.FileInfo), fs["/manager/patch-toleration.yaml"].(os.FileInfo), fs["/manager/patch-watch-namespace-global.yaml"].(os.FileInfo), + fs["/manager/platformcontroller-deployment.yaml"].(os.FileInfo), } fs["/prometheus"].(*vfsgen۰DirInfo).entries = []os.FileInfo{ fs["/prometheus/operator-pod-monitor.yaml"].(os.FileInfo),