pkg/platform/defaults.go (321 lines of code) (raw):

/* 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 ( "context" "runtime" "strings" "time" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" 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/kamelet/repository" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/log" "github.com/apache/camel-k/v2/pkg/util/openshift" ) const ( BuilderServiceAccount = "camel-k-builder" defaultBuildTimeout = 5 * time.Minute ) // ConfigureDefaults fills with default values all missing details about the integration platform. // Defaults are set in the status fields, not in the spec. func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPlatform, verbose bool) error { // Reset the state to initial values p.ResyncStatusFullConfig() // Set the operator version controlling this resource p.Status.Version = defaults.Version // Apply settings from global integration platform that is bound to this operator if err := applyGlobalPlatformDefaults(ctx, c, p); err != nil { return err } if p.Status.Build.RuntimeProvider == "" { p.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus log.Debugf("Integration Platform %s [%s]: setting default runtime provider %s", p.Name, p.Namespace, v1.RuntimeProviderQuarkus) } if p.Status.Build.RuntimeVersion == "" { p.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion log.Debugf("Integration Platform %s [%s]: setting default runtime version %s", p.Name, p.Namespace, p.Status.Build.PublishStrategy) } // update missing fields in the resource if p.Status.Cluster == "" { log.Debugf("Integration Platform %s [%s]: setting cluster status", p.Name, p.Namespace) // determine the kind of cluster the platform is installed into isOpenShift, err := openshift.IsOpenShift(c) switch { case err != nil: return err case isOpenShift: p.Status.Cluster = v1.IntegrationPlatformClusterOpenShift default: p.Status.Cluster = v1.IntegrationPlatformClusterKubernetes } } if p.Status.Build.PublishStrategy == "" { p.Status.Build.PublishStrategy = v1.IntegrationPlatformBuildPublishStrategyJib log.Debugf("Integration Platform %s [%s]: setting publishing strategy %s", p.Name, p.Namespace, p.Status.Build.PublishStrategy) } if p.Status.Build.BuildConfiguration.Strategy == "" { defaultStrategy := v1.BuildStrategyRoutine p.Status.Build.BuildConfiguration.Strategy = defaultStrategy log.Debugf("Integration Platform %s [%s]: setting build strategy %s", p.Name, p.Namespace, p.Status.Build.BuildConfiguration.Strategy) } if p.Status.Build.BuildConfiguration.OrderStrategy == "" { p.Status.Build.BuildConfiguration.OrderStrategy = v1.BuildOrderStrategyDependencies log.Debugf("Integration Platform %s [%s]: setting build order strategy %s", p.Name, p.Namespace, p.Status.Build.BuildConfiguration.OrderStrategy) } err := setPlatformDefaults(p, verbose) if err != nil { return err } err = configureRegistry(ctx, c, p) if err != nil { return err } if verbose && p.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategyS2I && p.Status.Build.Registry.Address == "" { log.Log.Info("No registry specified for publishing images") } if verbose && p.Status.Build.GetTimeout().Duration != 0 { log.Log.Infof("Maven Timeout set to %s", p.Status.Build.GetTimeout().Duration) } return nil } func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPlatform) error { if p.Status.Cluster == v1.IntegrationPlatformClusterOpenShift && p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyS2I && p.Status.Build.Registry.Address == "" { err := configureForOpenShiftS2i(ctx, c, p) if err != nil { return err } } log.Debugf("Final Registry Address: %s", p.Status.Build.Registry.Address) return nil } func configureForOpenShiftS2i(ctx context.Context, c client.Client, p *v1.IntegrationPlatform) error { log.Debugf("Integration Platform %s [%s]: setting registry address", p.Name, p.Namespace) // Default to using OpenShift internal container images registry when using a strategy other than S2I p.Status.Build.Registry.Address = defaults.OpenShiftRegistryAddress // OpenShift automatically injects the service CA certificate into the service-ca.crt key on the ConfigMap cm, err := createServiceCaBundleConfigMap(ctx, c, p) if err != nil { return err } log.Debugf("Integration Platform %s [%s]: setting registry certificate authority", p.Name, p.Namespace) p.Status.Build.Registry.CA = cm.Name // Default to using the registry secret that's configured for the builder service account if p.Status.Build.Registry.Secret == "" { log.Debugf("Integration Platform %s [%s]: setting registry secret", p.Name, p.Namespace) // Bind the required role to push images to the registry err := createBuilderRegistryRoleBinding(ctx, c, p) if err != nil { return err } sa := corev1.ServiceAccount{} err = c.Get(ctx, types.NamespacedName{Namespace: p.Namespace, Name: BuilderServiceAccount}, &sa) if err != nil { return err } // We may want to read the secret keys instead of relying on the secret name scheme for _, secret := range sa.Secrets { if strings.Contains(secret.Name, "camel-k-builder-dockercfg") { p.Status.Build.Registry.Secret = secret.Name break } } } return nil } func applyGlobalPlatformDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPlatform) error { operatorNamespace := GetOperatorNamespace() if operatorNamespace != "" { operatorID := defaults.OperatorID() if operatorNamespace != p.Namespace || (operatorID != "" && p.Name != operatorID) { if globalPlatform, err := findLocal(ctx, c, operatorNamespace); err != nil && !k8serrors.IsNotFound(err) { return err } else if globalPlatform != nil { applyPlatformSpec(globalPlatform, p) } } } return nil } func applyPlatformSpec(source *v1.IntegrationPlatform, target *v1.IntegrationPlatform) { if target.Status.Cluster == "" { target.Status.Cluster = source.Status.Cluster } if target.Status.Profile == "" { log.Debugf("Integration Platform %s [%s]: setting profile", target.Name, target.Namespace) target.Status.Profile = source.Status.Profile } if target.Status.Build.PublishStrategy == "" { target.Status.Build.PublishStrategy = source.Status.Build.PublishStrategy } if target.Status.Build.BuildConfiguration.Strategy == "" { target.Status.Build.BuildConfiguration.Strategy = source.Status.Build.BuildConfiguration.Strategy } if target.Status.Build.BuildConfiguration.OrderStrategy == "" { target.Status.Build.BuildConfiguration.OrderStrategy = source.Status.Build.BuildConfiguration.OrderStrategy } if target.Status.Build.RuntimeVersion == "" { log.Debugf("Integration Platform %s [%s]: setting runtime version", target.Name, target.Namespace) target.Status.Build.RuntimeVersion = source.Status.Build.RuntimeVersion } if target.Status.Build.BaseImage == "" { log.Debugf("Integration Platform %s [%s]: setting base image", target.Name, target.Namespace) target.Status.Build.BaseImage = source.Status.Build.BaseImage } if target.Status.Build.Maven.LocalRepository == "" { log.Debugf("Integration Platform %s [%s]: setting local repository", target.Name, target.Namespace) target.Status.Build.Maven.LocalRepository = source.Status.Build.Maven.LocalRepository } if len(source.Status.Build.Maven.CLIOptions) > 0 && len(target.Status.Build.Maven.CLIOptions) == 0 { log.Debugf("Integration Platform %s [%s]: setting CLI options", target.Name, target.Namespace) target.Status.Build.Maven.CLIOptions = make([]string, len(source.Status.Build.Maven.CLIOptions)) copy(target.Status.Build.Maven.CLIOptions, source.Status.Build.Maven.CLIOptions) } if len(source.Status.Build.Maven.Properties) > 0 { log.Debugf("Integration Platform %s [%s]: setting Maven properties", target.Name, target.Namespace) if len(target.Status.Build.Maven.Properties) == 0 { target.Status.Build.Maven.Properties = make(map[string]string, len(source.Status.Build.Maven.Properties)) } for key, val := range source.Status.Build.Maven.Properties { // only set unknown properties on target if _, ok := target.Status.Build.Maven.Properties[key]; !ok { target.Status.Build.Maven.Properties[key] = val } } } if len(source.Status.Build.Maven.Extension) > 0 && len(target.Status.Build.Maven.Extension) == 0 { log.Debugf("Integration Platform %s [%s]: setting Maven extensions", target.Name, target.Namespace) target.Status.Build.Maven.Extension = make([]v1.MavenArtifact, len(source.Status.Build.Maven.Extension)) copy(target.Status.Build.Maven.Extension, source.Status.Build.Maven.Extension) } if target.Status.Build.Registry.Address == "" && source.Status.Build.Registry.Address != "" { log.Debugf("Integration Platform %s [%s]: setting registry", target.Name, target.Namespace) source.Status.Build.Registry.DeepCopyInto(&target.Status.Build.Registry) } if err := target.Status.Traits.Merge(source.Status.Traits); err != nil { log.Errorf(err, "Integration Platform %s [%s]: failed to merge traits", target.Name, target.Namespace) } else if err := target.Status.Traits.Merge(target.Spec.Traits); err != nil { log.Errorf(err, "Integration Platform %s [%s]: failed to merge traits", target.Name, target.Namespace) } // Build timeout if target.Status.Build.Timeout == nil { log.Debugf("Integration Platform %s [%s]: setting build timeout", target.Name, target.Namespace) target.Status.Build.Timeout = source.Status.Build.Timeout } if target.Status.Build.MaxRunningBuilds <= 0 { log.Debugf("Integration Platform %s [%s]: setting max running builds", target.Name, target.Namespace) target.Status.Build.MaxRunningBuilds = source.Status.Build.MaxRunningBuilds } if len(target.Status.Kamelet.Repositories) == 0 { log.Debugf("Integration Platform %s [%s]: setting kamelet repositories", target.Name, target.Namespace) target.Status.Kamelet.Repositories = source.Status.Kamelet.Repositories } } func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error { if p.Status.Build.RuntimeVersion == "" { log.Debugf("Integration Platform %s [%s]: setting runtime version", p.Name, p.Namespace) p.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion } if p.Status.Build.BaseImage == "" { log.Debugf("Integration Platform %s [%s]: setting base image", p.Name, p.Namespace) p.Status.Build.BaseImage = defaults.BaseImage() } if p.Status.Build.Maven.LocalRepository == "" { log.Debugf("Integration Platform %s [%s]: setting local repository", p.Name, p.Namespace) p.Status.Build.Maven.LocalRepository = defaults.LocalRepository } if len(p.Status.Build.Maven.CLIOptions) == 0 { log.Debugf("Integration Platform %s [%s]: setting CLI options", p.Name, p.Namespace) p.Status.Build.Maven.CLIOptions = []string{ "-V", "--no-transfer-progress", "-Dstyle.color=never", } } // Build timeout if p.Status.Build.GetTimeout().Duration == 0 { p.Status.Build.Timeout = &metav1.Duration{ Duration: defaultBuildTimeout, } } else { d := p.Status.Build.GetTimeout().Duration.Truncate(time.Second) if verbose && p.Status.Build.GetTimeout().Duration != d { log.Log.Infof("Build timeout minimum unit is sec (configured: %s, truncated: %s)", p.Status.Build.GetTimeout().Duration, d) } log.Debugf("Integration Platform %s [%s]: setting build timeout", p.Name, p.Namespace) p.Status.Build.Timeout = &metav1.Duration{ Duration: d, } } if p.Status.Build.MaxRunningBuilds <= 0 { log.Debugf("Integration Platform %s [%s]: setting max running builds", p.Name, p.Namespace) if p.Status.Build.BuildConfiguration.Strategy == v1.BuildStrategyRoutine { p.Status.Build.MaxRunningBuilds = 3 } else if p.Status.Build.BuildConfiguration.Strategy == v1.BuildStrategyPod { p.Status.Build.MaxRunningBuilds = 10 } } if len(p.Status.Kamelet.Repositories) == 0 { log.Debugf("Integration Platform %s [%s]: setting kamelet repositories", p.Name, p.Namespace) p.Status.Kamelet.Repositories = append(p.Status.Kamelet.Repositories, v1.KameletRepositorySpec{ URI: repository.DefaultRemoteRepository, }) } setStatusAdditionalInfo(p) buildConfig := &p.Status.Build.BuildConfiguration if buildConfig.ImagePlatforms == nil { if runtime.GOARCH == "arm64" { buildConfig.ImagePlatforms = []string{"linux/arm64"} } } if verbose { log.Log.Infof("RuntimeVersion set to %s", p.Status.Build.RuntimeVersion) log.Log.Infof("BaseImage set to %s", p.Status.Build.BaseImage) log.Log.Infof("ImagePlatforms set to %s", buildConfig.ImagePlatforms) log.Log.Infof("LocalRepository set to %s", p.Status.Build.Maven.LocalRepository) log.Log.Infof("Timeout set to %s", p.Status.Build.GetTimeout()) } return nil } func setStatusAdditionalInfo(platform *v1.IntegrationPlatform) { platform.Status.Info = make(map[string]string) log.Debugf("Integration Platform %s [%s]: setting status info", platform.Name, platform.Namespace) platform.Status.Info["goVersion"] = runtime.Version() platform.Status.Info["goOS"] = runtime.GOOS platform.Status.Info["gitCommit"] = defaults.GitCommit } func createServiceCaBundleConfigMap(ctx context.Context, client client.Client, p *v1.IntegrationPlatform) (*corev1.ConfigMap, error) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: BuilderServiceAccount + "-ca", Namespace: p.Namespace, Annotations: map[string]string{ "service.beta.openshift.io/inject-cabundle": "true", }, }, } err := client.Create(ctx, cm) if err != nil && !k8serrors.IsAlreadyExists(err) { return nil, err } return cm, nil } func createBuilderRegistryRoleBinding(ctx context.Context, client client.Client, p *v1.IntegrationPlatform) error { rb := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: BuilderServiceAccount + "-registry", Namespace: p.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Name: BuilderServiceAccount, }, }, RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", APIGroup: "rbac.authorization.k8s.io", Name: "system:image-builder", }, } err := client.Create(ctx, rb) if err != nil { if k8serrors.IsForbidden(err) { log.Log.Infof("Cannot grant permission to push images to the registry. "+ "Run 'oc policy add-role-to-user system:image-builder system:serviceaccount:%s:%s' as a system admin.", p.Namespace, BuilderServiceAccount) } else if !k8serrors.IsAlreadyExists(err) { return err } } return nil }