| /* |
| * 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 builder |
| |
| import ( |
| "time" |
| |
| "github.com/apache/incubator-kie-kogito-serverless-operator/workflowproj" |
| |
| corev1 "k8s.io/api/core/v1" |
| |
| "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/cfg" |
| "k8s.io/klog/v2" |
| |
| "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflowdef" |
| |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/client-go/rest" |
| |
| operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" |
| clientr "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/client" |
| "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/platform" |
| "github.com/apache/incubator-kie-kogito-serverless-operator/utils" |
| |
| "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/api" |
| builder "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/builder/kubernetes" |
| "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/client" |
| "github.com/apache/incubator-kie-kogito-serverless-operator/log" |
| ) |
| |
| const ( |
| resourceDockerfile = "Dockerfile" |
| ) |
| |
| var _ BuildManager = &containerBuilderManager{} |
| |
| type kanikoBuildInput struct { |
| name string |
| task *api.KanikoTask |
| workflowDefinition []byte |
| workflow *operatorapi.SonataFlow |
| workflowProperties []operatorapi.ConfigMapWorkflowResource |
| dockerfile string |
| imageTag string |
| } |
| |
| type containerBuilderManager struct { |
| buildManagerContext |
| // needed for the internal container-builder |
| restConfig *rest.Config |
| } |
| |
| func (c *containerBuilderManager) Schedule(build *operatorapi.SonataFlowBuild) error { |
| kanikoTaskCache := api.KanikoTaskCache{} |
| if platform.IsKanikoCacheEnabled(c.platform) { |
| kanikoTaskCache.Enabled = utils.Pbool(true) |
| } |
| kanikoTask := &api.KanikoTask{ |
| ContainerBuildBaseTask: api.ContainerBuildBaseTask{ |
| Name: "kaniko", |
| BuildArgs: build.Spec.BuildArgs, |
| Envs: build.Spec.Envs, |
| Resources: build.Spec.Resources, |
| }, |
| PublishTask: api.PublishTask{}, |
| Cache: kanikoTaskCache, |
| AdditionalFlags: build.Spec.Arguments, |
| KanikoExecutorImage: cfg.GetCfg().KanikoExecutorImageTag, |
| } |
| var containerBuilder *api.ContainerBuild |
| var err error |
| if containerBuilder, err = c.scheduleNewKanikoBuildWithContainerFile(build, kanikoTask); err != nil { |
| return err |
| } |
| if containerBuilder == nil { |
| return nil |
| } |
| if err = build.Status.SetInnerBuild(containerBuilder); err != nil { |
| return err |
| } |
| build.Status.BuildPhase = operatorapi.BuildPhase(containerBuilder.Status.Phase) |
| if len(build.Status.BuildPhase) == 0 { |
| build.Status.BuildPhase = operatorapi.BuildPhaseInitialization |
| } |
| build.Status.Error = containerBuilder.Status.Error |
| return nil |
| } |
| |
| func (c *containerBuilderManager) Reconcile(build *operatorapi.SonataFlowBuild) error { |
| containerBuild := &api.ContainerBuild{} |
| if err := build.Status.GetInnerBuild(containerBuild); err != nil { |
| return err |
| } |
| containerCli, _ := clientr.FromCtrlClientSchemeAndConfig(c.client, c.client.Scheme(), c.restConfig) |
| containerBuild, err := c.reconcileBuild(containerBuild, containerCli) |
| if err != nil { |
| return err |
| } |
| build.Status.BuildPhase = operatorapi.BuildPhase(containerBuild.Status.Phase) |
| build.Status.Error = containerBuild.Status.Error |
| build.Status.ImageTag = containerBuild.Status.RepositoryImageTag |
| if err = build.Status.SetInnerBuild(containerBuild); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func newContainerBuilderManager(managerContext buildManagerContext, config *rest.Config) BuildManager { |
| return &containerBuilderManager{ |
| buildManagerContext: managerContext, |
| restConfig: config, |
| } |
| } |
| |
| func (c *containerBuilderManager) scheduleNewKanikoBuildWithContainerFile(build *operatorapi.SonataFlowBuild, task *api.KanikoTask) (*api.ContainerBuild, error) { |
| workflow, err := c.fetchWorkflowForBuild(build) |
| if err != nil { |
| return nil, err |
| } |
| workflowDef, err := workflowdef.GetJSONWorkflow(workflow, c.ctx) |
| if err != nil { |
| return nil, err |
| } |
| |
| buildInput := kanikoBuildInput{ |
| name: workflow.Name, |
| task: task, |
| workflowDefinition: workflowDef, |
| workflow: workflow, |
| workflowProperties: buildWorkflowPropertyResources(workflow), |
| dockerfile: platform.GetCustomizedBuilderDockerfile(c.builderConfigMap.Data[defaultBuilderResourceName], *c.platform), |
| imageTag: buildNamespacedImageTag(workflow), |
| } |
| |
| if c.platform.Spec.Build.Config.Timeout == nil { |
| c.platform.Spec.Build.Config.Timeout = &metav1.Duration{Duration: 5 * time.Minute} |
| } |
| return c.buildImage(buildInput) |
| } |
| |
| func (c *containerBuilderManager) reconcileBuild(build *api.ContainerBuild, cli client.Client) (*api.ContainerBuild, error) { |
| result, err := builder.FromBuild(build).WithClient(cli).Reconcile() |
| return result, err |
| } |
| |
| func (c *containerBuilderManager) buildImage(buildInput kanikoBuildInput) (*api.ContainerBuild, error) { |
| cli, err := client.FromCtrlClientSchemeAndConfig(c.client, c.client.Scheme(), c.restConfig) |
| plat := api.PlatformContainerBuild{ |
| ObjectReference: api.ObjectReference{ |
| Namespace: c.platform.Namespace, |
| Name: buildInput.name, |
| }, |
| Spec: api.PlatformContainerBuildSpec{ |
| BuildStrategy: api.ContainerBuildStrategyPod, |
| PublishStrategy: api.PlatformBuildPublishStrategyKaniko, |
| Registry: api.ContainerRegistrySpec{ |
| Insecure: c.platform.Spec.Build.Config.Registry.Insecure, |
| Address: c.platform.Spec.Build.Config.Registry.Address, |
| Secret: c.platform.Spec.Build.Config.Registry.Secret, |
| }, |
| Timeout: &metav1.Duration{ |
| Duration: c.platform.Spec.Build.Config.Timeout.Duration, |
| }, |
| }, |
| } |
| |
| build, err := newBuild(buildInput, plat, c.builderConfigMap.Data[configKeyDefaultExtension], cli) |
| if err != nil { |
| klog.V(log.E).ErrorS(err, "error during build Image") |
| return nil, err |
| } |
| return build, err |
| } |
| |
| // Helper function to create a new container-builder build and schedule it |
| func newBuild(buildInput kanikoBuildInput, platform api.PlatformContainerBuild, defaultExtension string, cli client.Client) (*api.ContainerBuild, error) { |
| buildInfo := builder.ContainerBuilderInfo{ |
| FinalImageName: buildInput.imageTag, |
| BuildUniqueName: buildInput.name, |
| Platform: platform, |
| ContainerBuilderImageTag: buildInput.task.KanikoExecutorImage, |
| } |
| |
| newBuilder := builder.NewBuild(buildInfo). |
| WithClient(cli). |
| AddResource(resourceDockerfile, []byte(buildInput.dockerfile)). |
| AddResource(buildInput.name+defaultExtension, buildInput.workflowDefinition) |
| for _, res := range buildInput.workflow.Spec.Resources.ConfigMaps { |
| newBuilder.AddConfigMapResource(res.ConfigMap, res.WorkflowPath) |
| } |
| |
| //make the workflow properties available to the kaniko build. |
| for _, props := range buildInput.workflowProperties { |
| newBuilder.AddConfigMapResource(props.ConfigMap, props.WorkflowPath) |
| } |
| |
| return newBuilder.Scheduler(). |
| WithAdditionalArgs(buildInput.task.AdditionalFlags). |
| WithResourceRequirements(buildInput.task.Resources). |
| WithBuildArgs(buildInput.task.BuildArgs). |
| WithEnvs(buildInput.task.Envs).Schedule() |
| } |
| |
| // buildNamespacedImageTag For the kaniko build we prepend the namespace to the calculated image name/tag to avoid potential |
| // collisions if the same workflows is deployed in a different namespace. In OpenShift this last is not needed since the |
| // ImageStreams are already namespaced. |
| func buildNamespacedImageTag(workflow *operatorapi.SonataFlow) string { |
| return workflow.Namespace + "/" + workflowdef.GetWorkflowAppImageNameTag(workflow) |
| } |
| |
| func buildWorkflowPropertyResources(workflow *operatorapi.SonataFlow) []operatorapi.ConfigMapWorkflowResource { |
| return []operatorapi.ConfigMapWorkflowResource{ |
| {ConfigMap: corev1.LocalObjectReference{Name: workflowproj.GetWorkflowUserPropertiesConfigMapName(workflow)}, WorkflowPath: ""}, |
| {ConfigMap: corev1.LocalObjectReference{Name: workflowproj.GetWorkflowManagedPropertiesConfigMapName(workflow)}, WorkflowPath: ""}, |
| } |
| } |