This is an automated email from the ASF dual-hosted git repository. pcongiusti pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 084b771dbb53f26733dccd6e3498a2f3c9e5804e Author: Pasquale Congiusti <pasquale.congiu...@gmail.com> AuthorDate: Mon Sep 25 12:43:38 2023 +0200 chore(ctrl): get rid of dynamic builder image It was introduced to decouple the operator from the runtime. No longer needed as the native phase is executed using catalog tool image directly. --- .../catalog_builder_test.go | 7 - e2e/install/cli/global_test.go | 7 - pkg/apis/camel/v1/camelcatalog_types.go | 2 +- pkg/controller/catalog/initialize.go | 539 +-------------------- pkg/controller/integrationkit/build.go | 4 +- 5 files changed, 9 insertions(+), 550 deletions(-) diff --git a/e2e/commonwithcustominstall/catalog_builder_test.go b/e2e/commonwithcustominstall/catalog_builder_test.go index 0fe1061ed..436856c82 100644 --- a/e2e/commonwithcustominstall/catalog_builder_test.go +++ b/e2e/commonwithcustominstall/catalog_builder_test.go @@ -48,14 +48,7 @@ func TestCamelCatalogBuilder(t *testing.T) { Should(Equal(corev1.ConditionTrue)) catalogName := fmt.Sprintf("camel-catalog-%s", strings.ToLower(defaults.DefaultRuntimeVersion)) Eventually(CamelCatalog(ns, catalogName)).ShouldNot(BeNil()) - catalog := CamelCatalog(ns, catalogName)() - imageName := fmt.Sprintf("camel-k-runtime-%s-builder:%s", catalog.Spec.Runtime.Provider, strings.ToLower(catalog.Spec.Runtime.Version)) Eventually(CamelCatalogPhase(ns, catalogName), TestTimeoutMedium).Should(Equal(v1.CamelCatalogPhaseReady)) - Eventually(CamelCatalogImage(ns, catalogName), TestTimeoutMedium).Should(ContainSubstring(imageName)) - // The container may have been created by previous test - Eventually(CamelCatalogCondition(ns, catalogName, v1.CamelCatalogConditionReady)().Message).Should( - Or(Equal("Container image successfully built"), Equal("Container image exists on registry")), - ) // Run an integration with a catalog not compatible // The operator should create the catalog, but fail on reconciliation as it is not compatible diff --git a/e2e/install/cli/global_test.go b/e2e/install/cli/global_test.go index 479264255..27ad16159 100644 --- a/e2e/install/cli/global_test.go +++ b/e2e/install/cli/global_test.go @@ -66,14 +66,7 @@ func TestRunGlobalInstall(t *testing.T) { Should(Equal(corev1.ConditionTrue)) catalogName := fmt.Sprintf("camel-catalog-%s", strings.ToLower(defaults.DefaultRuntimeVersion)) Eventually(CamelCatalog(operatorNamespace, catalogName)).ShouldNot(BeNil()) - catalog := CamelCatalog(operatorNamespace, catalogName)() - imageName := fmt.Sprintf("camel-k-runtime-%s-builder:%s", catalog.Spec.Runtime.Provider, strings.ToLower(catalog.Spec.Runtime.Version)) Eventually(CamelCatalogPhase(operatorNamespace, catalogName), TestTimeoutMedium).Should(Equal(v1.CamelCatalogPhaseReady)) - Eventually(CamelCatalogImage(operatorNamespace, catalogName), TestTimeoutMedium).Should(ContainSubstring(imageName)) - // The container may have been created by previous test - Eventually(CamelCatalogCondition(operatorNamespace, catalogName, v1.CamelCatalogConditionReady)().Message).Should( - Or(Equal("Container image successfully built"), Equal("Container image exists on registry")), - ) }) t.Run("Global test on namespace with platform", func(t *testing.T) { diff --git a/pkg/apis/camel/v1/camelcatalog_types.go b/pkg/apis/camel/v1/camelcatalog_types.go index 9b38eb98b..5b83f378c 100644 --- a/pkg/apis/camel/v1/camelcatalog_types.go +++ b/pkg/apis/camel/v1/camelcatalog_types.go @@ -67,7 +67,7 @@ type CamelCatalogSpec struct { Loaders map[string]CamelLoader `json:"loaders" yaml:"loaders"` } -// CamelCatalogStatus defines the observed state of CamelCatalog. As the catalog is a static resource, we expect it to be empty. +// CamelCatalogStatus defines the observed state of CamelCatalog. type CamelCatalogStatus struct { // ObservedGeneration is the most recent generation observed for this Catalog. ObservedGeneration int64 `json:"observedGeneration,omitempty"` diff --git a/pkg/controller/catalog/initialize.go b/pkg/controller/catalog/initialize.go index a80712e65..20cd365d7 100644 --- a/pkg/controller/catalog/initialize.go +++ b/pkg/controller/catalog/initialize.go @@ -18,45 +18,10 @@ limitations under the License. package catalog import ( - "archive/tar" - "bufio" - "compress/gzip" "context" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strings" - "time" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/builder" - "github.com/apache/camel-k/v2/pkg/client" platformutil "github.com/apache/camel-k/v2/pkg/platform" - "github.com/apache/camel-k/v2/pkg/util" - "github.com/apache/camel-k/v2/pkg/util/defaults" - "github.com/apache/camel-k/v2/pkg/util/kubernetes" - "github.com/apache/camel-k/v2/pkg/util/openshift" - "github.com/apache/camel-k/v2/pkg/util/s2i" - - spectrum "github.com/container-tools/spectrum/pkg/builder" - - buildv1 "github.com/openshift/api/build/v1" - imagev1 "github.com/openshift/api/image/v1" - - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/client-go/rest" - - ctrl "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) // NewInitializeAction returns a action that initializes the catalog configuration when not provided by the user. @@ -90,509 +55,15 @@ func (action *initializeAction) Handle(ctx context.Context, catalog *v1.CamelCat return catalog, nil } - if platform.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyS2I { - return initializeS2i(ctx, action.client, platform, catalog) - } - // Default to spectrum - // Make basic options for building image in the registry - options, err := makeSpectrumOptions(ctx, action.client, platform.Namespace, platform.Status.Build.Registry) - if err != nil { - return catalog, err - } - return initializeSpectrum(options, platform, catalog) + return initialize(catalog) } -func initializeSpectrum(options spectrum.Options, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) { +func initialize(catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) { target := catalog.DeepCopy() - organization := ip.Status.Build.Registry.Organization - if organization == "" { - organization = catalog.Namespace - } - imageName := fmt.Sprintf( - "%s/%s/camel-k-runtime-%s-builder:%s", - ip.Status.Build.Registry.Address, - organization, - catalog.Spec.Runtime.Provider, - strings.ToLower(catalog.Spec.Runtime.Version), - ) - - newStdR, newStdW, pipeErr := os.Pipe() - defer util.CloseQuietly(newStdW) - - if pipeErr != nil { - // In the unlikely case of an error, use stdout instead of aborting - Log.Errorf(pipeErr, "Unable to remap I/O. Spectrum messages will be displayed on the stdout") - newStdW = os.Stdout - } - go readSpectrumLogs(newStdR) - - // We use the future target image as a base just for the sake of pulling and verify it exists - options.Base = imageName - options.Stderr = newStdW - options.Stdout = newStdW - - if !imageSnapshot(options.Base) && imageExistsSpectrum(options) { - target.Status.Phase = v1.CamelCatalogPhaseReady - target.Status.SetCondition( - v1.CamelCatalogConditionReady, - corev1.ConditionTrue, - "Builder Image", - "Container image exists on registry", - ) - target.Status.Image = imageName - - return target, nil - } - - // Now we properly set the base and the target image - options.Base = catalog.Spec.GetQuarkusToolingImage() - options.Target = imageName - - err := buildRuntimeBuilderWithTimeoutSpectrum(options, ip.Status.Build.GetBuildCatalogToolTimeout().Duration) - - if err != nil { - Log.Error(err, "Error trying to build Camel K builder container") - target.Status.Phase = v1.CamelCatalogPhaseError - target.Status.SetErrorCondition( - v1.CamelCatalogConditionReady, - "Builder Image", - err, - ) - } else { - target.Status.Phase = v1.CamelCatalogPhaseReady - target.Status.SetCondition( - v1.CamelCatalogConditionReady, - corev1.ConditionTrue, - "Builder Image", - "Container image successfully built", - ) - target.Status.Image = imageName - } - - return target, nil -} - -// nolint: maintidx // TODO: refactor the code -func initializeS2i(ctx context.Context, c client.Client, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) { - target := catalog.DeepCopy() - // No registry in s2i - imageName := fmt.Sprintf( - "camel-k-runtime-%s-builder", - catalog.Spec.Runtime.Provider, - ) - imageTag := strings.ToLower(catalog.Spec.Runtime.Version) - - uidStr := getS2iUserID(ctx, c, ip, catalog) - - // Dockerfile - dockerfile := ` - FROM ` + catalog.Spec.GetQuarkusToolingImage() + ` - USER ` + uidStr + `:0 - ADD --chown=` + uidStr + `:0 /usr/local/bin/kamel /usr/local/bin/kamel - ADD --chown=` + uidStr + `:0 /usr/share/maven/mvnw/ /usr/share/maven/mvnw/ - ` - if imageSnapshot(imageName + ":" + imageTag) { - dockerfile = dockerfile + ` - ADD --chown=` + uidStr + `:0 ` + defaults.LocalRepository + ` ` + defaults.LocalRepository + ` - ` - } - - owner := catalogReference(catalog) - - // BuildConfig - bc := &buildv1.BuildConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: buildv1.GroupVersion.String(), - Kind: "BuildConfig", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: imageName, - Namespace: ip.Namespace, - Labels: map[string]string{ - kubernetes.CamelCreatorLabelKind: v1.CamelCatalogKind, - kubernetes.CamelCreatorLabelName: catalog.Name, - kubernetes.CamelCreatorLabelNamespace: catalog.Namespace, - kubernetes.CamelCreatorLabelVersion: catalog.ResourceVersion, - "camel.apache.org/runtime.version": catalog.Spec.Runtime.Version, - "camel.apache.org/runtime.provider": string(catalog.Spec.Runtime.Provider), - }, - }, - Spec: buildv1.BuildConfigSpec{ - CommonSpec: buildv1.CommonSpec{ - Source: buildv1.BuildSource{ - Type: buildv1.BuildSourceBinary, - Dockerfile: &dockerfile, - }, - Strategy: buildv1.BuildStrategy{ - DockerStrategy: &buildv1.DockerBuildStrategy{}, - }, - Output: buildv1.BuildOutput{ - To: &corev1.ObjectReference{ - Kind: "ImageStreamTag", - Name: imageName + ":" + imageTag, - }, - }, - }, - }, - } - - // ImageStream - is := &imagev1.ImageStream{ - TypeMeta: metav1.TypeMeta{ - APIVersion: imagev1.GroupVersion.String(), - Kind: "ImageStream", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: bc.Name, - Namespace: bc.Namespace, - Labels: map[string]string{ - kubernetes.CamelCreatorLabelKind: v1.CamelCatalogKind, - kubernetes.CamelCreatorLabelName: catalog.Name, - kubernetes.CamelCreatorLabelNamespace: catalog.Namespace, - kubernetes.CamelCreatorLabelVersion: catalog.ResourceVersion, - "camel.apache.org/runtime.provider": string(catalog.Spec.Runtime.Provider), - }, - }, - Spec: imagev1.ImageStreamSpec{ - LookupPolicy: imagev1.ImageLookupPolicy{ - Local: true, - }, - }, - } - - if !imageSnapshot(imageName+":"+imageTag) && imageExistsS2i(ctx, c, is) { - target.Status.Phase = v1.CamelCatalogPhaseReady - target.Status.SetCondition( - v1.CamelCatalogConditionReady, - corev1.ConditionTrue, - "Builder Image", - "Container image exists on registry (later)", - ) - target.Status.Image = imageName - return target, nil - } - - err := s2i.BuildConfig(ctx, c, bc, owner) - if err != nil { - target.Status.Phase = v1.CamelCatalogPhaseError - target.Status.SetErrorCondition( - v1.CamelCatalogConditionReady, - "Builder Image", - err, - ) - return target, err - } - - err = s2i.ImageStream(ctx, c, is, owner) - if err != nil { - target.Status.Phase = v1.CamelCatalogPhaseError - target.Status.SetErrorCondition( - v1.CamelCatalogConditionReady, - "Builder Image", - err, - ) - return target, err - } - - err = util.WithTempDir(imageName+"-s2i-", func(tmpDir string) error { - archive := filepath.Join(tmpDir, "archive.tar.gz") - - archiveFile, err := os.Create(archive) - if err != nil { - return fmt.Errorf("cannot create tar archive: %w", err) - } - - directories := []string{ - "/usr/local/bin/kamel:/usr/local/bin/kamel", - "/usr/share/maven/mvnw/:/usr/share/maven/mvnw/", - } - if imageSnapshot(imageName + ":" + imageTag) { - directories = append(directories, defaults.LocalRepository+":"+defaults.LocalRepository) - } - - err = tarEntries(archiveFile, directories...) - if err != nil { - return fmt.Errorf("cannot tar path entry: %w", err) - } - - f, err := util.Open(archive) - if err != nil { - return err - } - httpCli, err := rest.HTTPClientFor(c.GetConfig()) - if err != nil { - return err - } - restClient, err := apiutil.RESTClientForGVK( - schema.GroupVersionKind{Group: "build.openshift.io", Version: "v1"}, false, - c.GetConfig(), serializer.NewCodecFactory(c.GetScheme()), httpCli) - if err != nil { - return err - } - - r := restClient.Post(). - Namespace(bc.Namespace). - Body(bufio.NewReader(f)). - Resource("buildconfigs"). - Name(bc.Name). - SubResource("instantiatebinary"). - Do(ctx) - - if r.Error() != nil { - return fmt.Errorf("cannot instantiate binary: %w", err) - } - - data, err := r.Raw() - if err != nil { - return fmt.Errorf("no raw data retrieved: %w", err) - } - - s2iBuild := buildv1.Build{} - err = json.Unmarshal(data, &s2iBuild) - if err != nil { - return fmt.Errorf("cannot unmarshal instantiated binary response: %w", err) - } - - err = s2i.WaitForS2iBuildCompletion(ctx, c, &s2iBuild) - if err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - //nolint: contextcheck - if err := s2i.CancelBuild(context.Background(), c, &s2iBuild); err != nil { - return fmt.Errorf("cannot cancel s2i Build: %s/%s", s2iBuild.Namespace, s2iBuild.Name) - } - } - return err - } - if s2iBuild.Status.Output.To != nil { - Log.Infof("Camel K builder container image %s:%s@%s created", imageName, imageTag, s2iBuild.Status.Output.To.ImageDigest) - } - - err = c.Get(ctx, ctrl.ObjectKeyFromObject(is), is) - if err != nil { - return err - } - - if is.Status.DockerImageRepository == "" { - return errors.New("dockerImageRepository not available in ImageStream") - } - - target.Status.Phase = v1.CamelCatalogPhaseReady - target.Status.SetCondition( - v1.CamelCatalogConditionReady, - corev1.ConditionTrue, - "Builder Image", - "Container image successfully built", - ) - target.Status.Image = is.Status.DockerImageRepository + ":" + imageTag - - return f.Close() - }) - - if err != nil { - target.Status.Phase = v1.CamelCatalogPhaseError - target.Status.SetErrorCondition( - v1.CamelCatalogConditionReady, - "Builder Image", - err, - ) - return target, err - } + // TODO - we may verify the existence of the catalog image (required by native build) + // or any other condition that may make a CamelCatalog to fail. + target.Status.Phase = v1.CamelCatalogPhaseReady return target, nil } - -func imageExistsSpectrum(options spectrum.Options) bool { - Log.Infof("Checking if Camel K builder container %s already exists...", options.Base) - ctrImg, err := spectrum.Pull(options) - // Ignore the error-indent-flow as we save the need to declare a dependency explicitly - // nolint: revive - if ctrImg != nil && err == nil { - if hash, err := ctrImg.Digest(); err != nil { - Log.Errorf(err, "Cannot calculate digest") - return false - } else { - Log.Infof("Found Camel K builder container with digest %s", hash.String()) - return true - } - } - - Log.Infof("Couldn't pull image due to %s", err.Error()) - return false -} - -func imageExistsS2i(ctx context.Context, c client.Client, is *imagev1.ImageStream) bool { - Log.Infof("Checking if Camel K builder container %s already exists...", is.Name) - key := ctrl.ObjectKey{ - Namespace: is.Namespace, - Name: is.Name, - } - - err := c.Get(ctx, key, is) - - if err != nil { - if !k8serrors.IsNotFound(err) { - Log.Infof("Couldn't pull image due to %s", err.Error()) - } - Log.Info("Could not find Camel K builder container") - return false - } - Log.Info("Found Camel K builder container ") - return true -} - -func imageSnapshot(imageName string) bool { - return strings.HasSuffix(imageName, "snapshot") -} - -func buildRuntimeBuilderWithTimeoutSpectrum(options spectrum.Options, timeout time.Duration) error { - // Backward compatibility with IP which had not a timeout field - if timeout == 0 { - return buildRuntimeBuilderImageSpectrum(options) - } - result := make(chan error, 1) - go func() { - result <- buildRuntimeBuilderImageSpectrum(options) - }() - select { - case <-time.After(timeout): - return fmt.Errorf("build timeout: %s", timeout.String()) - case result := <-result: - return result - } -} - -// This func will take care to dynamically build an image that will contain the tools required -// by the catalog build plus kamel binary and a maven wrapper required for the build. -func buildRuntimeBuilderImageSpectrum(options spectrum.Options) error { - if options.Base == "" { - return fmt.Errorf("missing base image, likely catalog is not compatible with this Camel K version") - } - Log.Infof("Making up Camel K builder container %s", options.Target) - - if jobs := runtime.GOMAXPROCS(0); jobs > 1 { - options.Jobs = jobs - } - - directories := []string{ - "/usr/local/bin/kamel:/usr/local/bin/", - "/usr/share/maven/mvnw/:/usr/share/maven/mvnw/", - } - if imageSnapshot(options.Target) { - directories = append(directories, defaults.LocalRepository+":"+defaults.LocalRepository) - } - - _, err := spectrum.Build(options, directories...) - if err != nil { - return err - } - - return nil -} - -func readSpectrumLogs(newStdOut io.Reader) { - scanner := bufio.NewScanner(newStdOut) - - for scanner.Scan() { - line := scanner.Text() - Log.Infof(line) - } -} - -func makeSpectrumOptions(ctx context.Context, c client.Client, platformNamespace string, registry v1.RegistrySpec) (spectrum.Options, error) { - options := spectrum.Options{} - var err error - registryConfigDir := "" - if registry.Secret != "" { - registryConfigDir, err = builder.MountSecret(ctx, c, platformNamespace, registry.Secret) - if err != nil { - return options, err - } - } - options.PullInsecure = registry.Insecure - options.PushInsecure = registry.Insecure - options.PullConfigDir = registryConfigDir - options.PushConfigDir = registryConfigDir - options.Recursive = true - - return options, nil -} - -// Add entries (files or folders) into tar with the possibility to change its path. -func tarEntries(writer io.Writer, files ...string) error { - - gzw := gzip.NewWriter(writer) - defer util.CloseQuietly(gzw) - - tw := tar.NewWriter(gzw) - defer util.CloseQuietly(tw) - - // Iterate over files and add them to the tar archive - for _, fileDetail := range files { - fileSource := strings.Split(fileDetail, ":")[0] - fileTarget := strings.Split(fileDetail, ":")[1] - // ensure the src actually exists before trying to tar it - if _, err := os.Stat(fileSource); err != nil { - return fmt.Errorf("unable to tar files: %w", err) - } - - if err := filepath.Walk(fileSource, func(file string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - - if !fi.Mode().IsRegular() { - return nil - } - - header, err := tar.FileInfoHeader(fi, fi.Name()) - if err != nil { - return err - } - - // update the name to correctly reflect the desired destination when un-taring - header.Name = strings.TrimPrefix(strings.ReplaceAll(file, fileSource, fileTarget), string(filepath.Separator)) - - if err := tw.WriteHeader(header); err != nil { - return err - } - - f, err := util.Open(file) - if err != nil { - return err - } - - if _, err := io.Copy(tw, f); err != nil { - return err - } - - return f.Close() - }); err != nil { - return fmt.Errorf("unable to tar: %w", err) - } - - } - return nil -} - -func catalogReference(catalog *v1.CamelCatalog) *unstructured.Unstructured { - owner := &unstructured.Unstructured{} - owner.SetName(catalog.Name) - owner.SetUID(catalog.UID) - owner.SetAPIVersion(catalog.APIVersion) - owner.SetKind(catalog.Kind) - return owner -} - -// get user id from security context constraint configuration in namespace if present. -func getS2iUserID(ctx context.Context, c client.Client, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) string { - ugfidStr := "1001" - if ip.Status.Cluster == v1.IntegrationPlatformClusterOpenShift { - uidStr, err := openshift.GetOpenshiftUser(ctx, c, catalog.GetNamespace()) - if err != nil { - Log.Error(err, "Unable to retieve an Openshift user and group Ids.") - } - return uidStr - } - return ugfidStr -} diff --git a/pkg/controller/integrationkit/build.go b/pkg/controller/integrationkit/build.go index d4b49fa97..faa32c72d 100644 --- a/pkg/controller/integrationkit/build.go +++ b/pkg/controller/integrationkit/build.go @@ -132,7 +132,9 @@ func (action *buildAction) handleBuildSubmitted(ctx context.Context, kit *v1.Int return nil, fmt.Errorf("error while creating Camel K Builder service account: %w", err) } } - buildConfig.ToolImage = env.CamelCatalog.Image + // The build operation, when executed as a Pod, should be executed by a container image containing the + // `kamel builder` command. Likely the same image running the operator should be fine. + buildConfig.ToolImage = defaults.OperatorImage() buildConfig.BuilderPodNamespace = operatorNamespace v1.SetBuilderConfigurationTasks(env.Pipeline, buildConfig)