This is an automated email from the ASF dual-hosted git repository.

cdeppisch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git


The following commit(s) were added to refs/heads/main by this push:
     new 4264d13b3 Create CamelCatalog on IntegrationPlatform controller
4264d13b3 is described below

commit 4264d13b39ff6fcbf1d4f6b9c41047a7cf73d32b
Author: Christoph Deppisch <cdeppi...@redhat.com>
AuthorDate: Mon Jan 22 15:18:59 2024 +0100

    Create CamelCatalog on IntegrationPlatform controller
    
    - Create CamelCatalog for new runtimeVersion that has been set on 
IntegrationPlatform
    - Improve CamelCatalog availability checks on IntegrationPlatform controller
    - Adds new condition on IntegrationPlatform representing the availability 
of the catalog
    - Adds new controller action and IntegrationPlatform phase "CreateCatalog" 
that handles the creation of the catalog
    - Sets IntegrationPlatform to error phase when catalog is not available
---
 pkg/apis/camel/v1/integrationplatform_types.go     |   9 +
 pkg/controller/integrationplatform/catalog.go      |  82 ++++++++
 pkg/controller/integrationplatform/catalog_test.go | 222 +++++++++++++++++++++
 .../integrationplatform_controller.go              |   1 +
 pkg/controller/integrationplatform/monitor.go      |  46 ++++-
 pkg/controller/integrationplatform/monitor_test.go | 191 ++++++++++++++++++
 pkg/platform/defaults.go                           |   2 +-
 pkg/trait/camel.go                                 |  54 +----
 pkg/trait/registry.go                              |   3 +-
 pkg/util/camel/camel_runtime.go                    |  62 ++++++
 pkg/util/defaults/defaults.go                      |   3 +
 pkg/util/maven/maven_command.go                    |  10 +-
 pkg/util/test/client.go                            |   7 +-
 pkg/util/util_test.go                              |   3 +-
 script/Makefile                                    |  10 +-
 15 files changed, 635 insertions(+), 70 deletions(-)

diff --git a/pkg/apis/camel/v1/integrationplatform_types.go 
b/pkg/apis/camel/v1/integrationplatform_types.go
index 62470188c..6e480d3fa 100644
--- a/pkg/apis/camel/v1/integrationplatform_types.go
+++ b/pkg/apis/camel/v1/integrationplatform_types.go
@@ -193,6 +193,8 @@ const (
        IntegrationPlatformPhaseReady IntegrationPlatformPhase = "Ready"
        // IntegrationPlatformPhaseError when the IntegrationPlatform had some 
error (see Conditions).
        IntegrationPlatformPhaseError IntegrationPlatformPhase = "Error"
+       // IntegrationPlatformPhaseCreateCatalog when the IntegrationPlatform 
creates a new CamelCatalog.
+       IntegrationPlatformPhaseCreateCatalog IntegrationPlatformPhase = 
"CreateCatalog"
        // IntegrationPlatformPhaseDuplicate when the IntegrationPlatform is 
duplicated.
        IntegrationPlatformPhaseDuplicate IntegrationPlatformPhase = "Duplicate"
 
@@ -205,8 +207,15 @@ const (
        // IntegrationPlatformConditionTypeRegistryAvailable is the condition 
for the availability of a container registry.
        IntegrationPlatformConditionTypeRegistryAvailable 
IntegrationPlatformConditionType = "RegistryAvailable"
 
+       // IntegrationPlatformConditionCamelCatalogAvailable is the condition 
for the availability of a container registry.
+       IntegrationPlatformConditionCamelCatalogAvailable 
IntegrationPlatformConditionType = "CamelCatalogAvailable"
+
        // IntegrationPlatformConditionCreatedReason represents the reason that 
the IntegrationPlatform is created.
        IntegrationPlatformConditionCreatedReason = "IntegrationPlatformCreated"
+       // IntegrationPlatformConditionTypeRegistryAvailableReason represents 
the reason that the IntegrationPlatform Registry is available.
+       IntegrationPlatformConditionTypeRegistryAvailableReason = 
"IntegrationPlatformRegistryAvailable"
+       // IntegrationPlatformConditionCamelCatalogAvailableReason represents 
the reason that the IntegrationPlatform is created.
+       IntegrationPlatformConditionCamelCatalogAvailableReason = 
"IntegrationPlatformCamelCatalogAvailable"
 )
 
 // IntegrationPlatformCondition describes the state of a resource at a certain 
point.
diff --git a/pkg/controller/integrationplatform/catalog.go 
b/pkg/controller/integrationplatform/catalog.go
new file mode 100644
index 000000000..91ab24c68
--- /dev/null
+++ b/pkg/controller/integrationplatform/catalog.go
@@ -0,0 +1,82 @@
+/*
+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 integrationplatform
+
+import (
+       "context"
+       "fmt"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/v2/pkg/util/camel"
+
+       corev1 "k8s.io/api/core/v1"
+)
+
+// NewCreateCatalogAction returns an action to create a new CamelCatalog.
+func NewCreateCatalogAction() Action {
+       return &catalogAction{}
+}
+
+type catalogAction struct {
+       baseAction
+}
+
+func (action *catalogAction) Name() string {
+       return "catalog"
+}
+
+func (action *catalogAction) CanHandle(platform *v1.IntegrationPlatform) bool {
+       return platform.Status.Phase == v1.IntegrationPlatformPhaseCreateCatalog
+}
+
+func (action *catalogAction) Handle(ctx context.Context, platform 
*v1.IntegrationPlatform) (*v1.IntegrationPlatform, error) {
+       // New runtime version set - check that catalog exists and create it if 
it does not exist
+       runtimeSpec := v1.RuntimeSpec{
+               Version:  platform.Status.Build.RuntimeVersion,
+               Provider: v1.RuntimeProviderQuarkus,
+       }
+
+       if catalog, err := camel.LoadCatalog(ctx, action.client, 
platform.Namespace, runtimeSpec); err != nil {
+               action.L.Error(err, "IntegrationPlatform unable to load Camel 
catalog",
+                       "runtime-version", runtimeSpec.Version, 
"runtime-provider", runtimeSpec.Provider)
+               return platform, nil
+       } else if catalog == nil {
+               if _, err = camel.CreateCatalog(ctx, action.client, 
platform.Namespace, platform, runtimeSpec); err != nil {
+                       action.L.Error(err, "IntegrationPlatform unable to 
create Camel catalog",
+                               "runtime-version", runtimeSpec.Version, 
"runtime-provider", runtimeSpec.Provider)
+
+                       platform.Status.Phase = v1.IntegrationPlatformPhaseError
+                       platform.Status.SetCondition(
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailable,
+                               corev1.ConditionFalse,
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailableReason,
+                               fmt.Sprintf("camel catalog %s not available, 
please review given runtime version", runtimeSpec.Version))
+
+                       return platform, nil
+               }
+       }
+
+       platform.Status.Phase = v1.IntegrationPlatformPhaseReady
+       platform.Status.SetCondition(
+               v1.IntegrationPlatformConditionCamelCatalogAvailable,
+               corev1.ConditionTrue,
+               v1.IntegrationPlatformConditionCamelCatalogAvailableReason,
+               fmt.Sprintf("camel catalog %s available", runtimeSpec.Version))
+
+       return platform, nil
+}
diff --git a/pkg/controller/integrationplatform/catalog_test.go 
b/pkg/controller/integrationplatform/catalog_test.go
new file mode 100644
index 000000000..e005f0e89
--- /dev/null
+++ b/pkg/controller/integrationplatform/catalog_test.go
@@ -0,0 +1,222 @@
+/*
+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 integrationplatform
+
+import (
+       "context"
+       "errors"
+       "fmt"
+       "os"
+       "strings"
+       "testing"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/v2/pkg/platform"
+       "github.com/apache/camel-k/v2/pkg/resources"
+       "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/test"
+       "github.com/rs/xid"
+       "github.com/stretchr/testify/assert"
+
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       k8stesting "k8s.io/client-go/testing"
+       k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func TestCanHandlePhaseCreateCatalog(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       action := NewCreateCatalogAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer := action.CanHandle(&ip)
+       assert.True(t, answer)
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseError
+       answer = action.CanHandle(&ip)
+       assert.False(t, answer)
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseReady
+       answer = action.CanHandle(&ip)
+       assert.False(t, answer)
+}
+
+func TestCreateCatalog(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog
+       ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       // use local Maven executable in tests
+       t.Setenv("MAVEN_WRAPPER", "false")
+       _, ok := os.LookupEnv("MAVEN_CMD")
+       if !ok {
+               t.Setenv("MAVEN_CMD", "mvn")
+       }
+
+       fakeClient := c.(*test.FakeClient) //nolint
+       fakeClient.AddReactor("create", "*", func(action k8stesting.Action) 
(bool, runtime.Object, error) {
+               createAction := action.(k8stesting.CreateAction) //nolint
+
+               assert.Equal(t, "ns", createAction.GetNamespace())
+
+               return true, createAction.GetObject(), nil
+       })
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewCreateCatalogAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+
+       list := v1.NewCamelCatalogList()
+       err = c.List(context.TODO(), &list, k8sclient.InNamespace(ip.Namespace))
+
+       assert.Nil(t, err)
+       assert.NotEmpty(t, list.Items)
+
+       items, err := resources.WithPrefix("/camel-catelog-")
+       assert.Nil(t, err)
+
+       for _, k := range items {
+               found := false
+
+               for _, c := range list.Items {
+                       n := strings.TrimSuffix(k, ".yaml")
+                       n = strings.ToLower(n)
+
+                       if c.Name == n {
+                               found = true
+                       }
+               }
+
+               assert.True(t, found)
+       }
+}
+
+func TestCatalogAlreadyPresent(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog
+
+       catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", 
defaults.DefaultRuntimeVersion))
+       catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion
+       catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus
+
+       c, err := test.NewFakeClient(&ip, &catalog)
+       assert.Nil(t, err)
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+}
+
+func TestCreateCatalogError(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog
+
+       // force catalog build to fail
+       ip.Spec.Build.RuntimeVersion = "0.0.0"
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       // use local Maven executable in tests
+       t.Setenv("MAVEN_WRAPPER", "false")
+       _, ok := os.LookupEnv("MAVEN_CMD")
+       if !ok {
+               t.Setenv("MAVEN_CMD", "mvn")
+       }
+
+       fakeClient := c.(*test.FakeClient) //nolint
+       fakeClient.AddReactor("create", "*", func(action k8stesting.Action) 
(bool, runtime.Object, error) {
+               createAction := action.(k8stesting.CreateAction) //nolint
+
+               assert.Equal(t, "ns", createAction.GetNamespace())
+
+               return true, nil, errors.New("failed to create catalog for some 
reason")
+       })
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewCreateCatalogAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionFalse, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+       assert.Equal(t, 
v1.IntegrationPlatformConditionCamelCatalogAvailableReason, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason)
+       assert.Equal(t, "camel catalog 0.0.0 not available, please review given 
runtime version", 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message)
+}
diff --git 
a/pkg/controller/integrationplatform/integrationplatform_controller.go 
b/pkg/controller/integrationplatform/integrationplatform_controller.go
index 2c714af8d..3ae36d8d6 100644
--- a/pkg/controller/integrationplatform/integrationplatform_controller.go
+++ b/pkg/controller/integrationplatform/integrationplatform_controller.go
@@ -157,6 +157,7 @@ func (r *reconcileIntegrationPlatform) Reconcile(ctx 
context.Context, request re
                NewInitializeAction(),
                NewCreateAction(),
                NewMonitorAction(),
+               NewCreateCatalogAction(),
        }
 
        var targetPhase v1.IntegrationPlatformPhase
diff --git a/pkg/controller/integrationplatform/monitor.go 
b/pkg/controller/integrationplatform/monitor.go
index cc849c162..a231cbf2e 100644
--- a/pkg/controller/integrationplatform/monitor.go
+++ b/pkg/controller/integrationplatform/monitor.go
@@ -23,6 +23,7 @@ import (
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        platformutil "github.com/apache/camel-k/v2/pkg/platform"
+       "github.com/apache/camel-k/v2/pkg/util/camel"
        "github.com/apache/camel-k/v2/pkg/util/defaults"
        "github.com/apache/camel-k/v2/pkg/util/openshift"
        corev1 "k8s.io/api/core/v1"
@@ -52,6 +53,8 @@ func (action *monitorAction) Handle(ctx context.Context, 
platform *v1.Integratio
                action.L.Info("IntegrationPlatform version updated", "version", 
platform.Status.Version)
        }
 
+       platformPhase := v1.IntegrationPlatformPhaseReady
+
        // Refresh applied configuration
        if err := platformutil.ConfigureDefaults(ctx, action.client, platform, 
false); err != nil {
                return nil, err
@@ -66,26 +69,59 @@ func (action *monitorAction) Handle(ctx context.Context, 
platform *v1.Integratio
                platform.Status.SetCondition(
                        v1.IntegrationPlatformConditionTypeRegistryAvailable,
                        corev1.ConditionFalse,
-                       "IntegrationPlatformRegistryAvailable",
+                       
v1.IntegrationPlatformConditionTypeRegistryAvailableReason,
                        "registry not available because provided by Openshift")
        } else {
                if platform.Status.Build.Registry.Address == "" {
                        // error, we need a registry if we're not on Openshift
-                       platform.Status.Phase = v1.IntegrationPlatformPhaseError
+                       platformPhase = v1.IntegrationPlatformPhaseError
                        platform.Status.SetCondition(
                                
v1.IntegrationPlatformConditionTypeRegistryAvailable,
                                corev1.ConditionFalse,
-                               "IntegrationPlatformRegistryAvailable",
+                               
v1.IntegrationPlatformConditionTypeRegistryAvailableReason,
                                "registry address not available, you need to 
set one")
                } else {
-                       platform.Status.Phase = v1.IntegrationPlatformPhaseReady
                        platform.Status.SetCondition(
                                
v1.IntegrationPlatformConditionTypeRegistryAvailable,
                                corev1.ConditionTrue,
-                               "IntegrationPlatformRegistryAvailable",
+                               
v1.IntegrationPlatformConditionTypeRegistryAvailableReason,
                                fmt.Sprintf("registry available at %s", 
platform.Status.Build.Registry.Address))
                }
        }
 
+       if platformPhase == v1.IntegrationPlatformPhaseReady {
+               // Camel catalog condition
+               runtimeSpec := v1.RuntimeSpec{
+                       Version:  platform.Status.Build.RuntimeVersion,
+                       Provider: v1.RuntimeProviderQuarkus,
+               }
+               if catalog, err := camel.LoadCatalog(ctx, action.client, 
platform.Namespace, runtimeSpec); err != nil {
+                       action.L.Error(err, "IntegrationPlatform unable to load 
Camel catalog",
+                               "runtime-version", runtimeSpec.Version, 
"runtime-provider", runtimeSpec.Provider)
+               } else if catalog == nil {
+                       if platform.Status.Phase != 
v1.IntegrationPlatformPhaseError {
+                               platformPhase = 
v1.IntegrationPlatformPhaseCreateCatalog
+                       } else {
+                               // IntegrationPlatform is in error phase for 
some reason - that error state must be resolved before we move into create 
catalog phase
+                               // avoids to run into endless loop of error and 
catalog creation phase ping pong
+                               platformPhase = v1.IntegrationPlatformPhaseError
+                       }
+
+                       platform.Status.SetCondition(
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailable,
+                               corev1.ConditionFalse,
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailableReason,
+                               fmt.Sprintf("camel catalog %s not available, 
please review given runtime version", runtimeSpec.Version))
+               } else {
+                       platform.Status.SetCondition(
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailable,
+                               corev1.ConditionTrue,
+                               
v1.IntegrationPlatformConditionCamelCatalogAvailableReason,
+                               fmt.Sprintf("camel catalog %s available", 
runtimeSpec.Version))
+               }
+       }
+
+       platform.Status.Phase = platformPhase
+
        return platform, nil
 }
diff --git a/pkg/controller/integrationplatform/monitor_test.go 
b/pkg/controller/integrationplatform/monitor_test.go
new file mode 100644
index 000000000..ee22a9496
--- /dev/null
+++ b/pkg/controller/integrationplatform/monitor_test.go
@@ -0,0 +1,191 @@
+/*
+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 integrationplatform
+
+import (
+       "context"
+       "fmt"
+       "testing"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "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/log"
+       "github.com/apache/camel-k/v2/pkg/util/test"
+       "github.com/rs/xid"
+       "github.com/stretchr/testify/assert"
+
+       corev1 "k8s.io/api/core/v1"
+)
+
+func TestCanHandlePhaseReadyOrError(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseReady
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer := action.CanHandle(&ip)
+       assert.True(t, answer)
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseError
+       answer = action.CanHandle(&ip)
+       assert.True(t, answer)
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog
+       answer = action.CanHandle(&ip)
+       assert.False(t, answer)
+}
+
+func TestMonitor(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", 
defaults.DefaultRuntimeVersion))
+       catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion
+       catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus
+
+       c, err := test.NewFakeClient(&ip, &catalog)
+       assert.Nil(t, err)
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+}
+
+func TestMonitorTransitionToCreateCatalog(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseCreateCatalog, 
answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status)
+       assert.Equal(t, corev1.ConditionFalse, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+       assert.Equal(t, 
v1.IntegrationPlatformConditionCamelCatalogAvailableReason, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason)
+       assert.Equal(t, fmt.Sprintf("camel catalog %s not available, please 
review given runtime version", defaults.DefaultRuntimeVersion), 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message)
+}
+
+func TestMonitorRetainErrorState(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+       ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress
+
+       ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+
+       ip.Status.Phase = v1.IntegrationPlatformPhaseError
+
+       c, err := test.NewFakeClient(&ip)
+       assert.Nil(t, err)
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionTrue, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status)
+       assert.Equal(t, corev1.ConditionFalse, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status)
+       assert.Equal(t, 
v1.IntegrationPlatformConditionCamelCatalogAvailableReason, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason)
+       assert.Equal(t, fmt.Sprintf("camel catalog %s not available, please 
review given runtime version", defaults.DefaultRuntimeVersion), 
answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message)
+}
+
+func TestMonitorMissingRegistryError(t *testing.T) {
+       ip := v1.IntegrationPlatform{}
+       ip.Namespace = "ns"
+       ip.Name = xid.New().String()
+       ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift
+       ip.Spec.Profile = v1.TraitProfileOpenShift
+
+       catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", 
defaults.DefaultRuntimeVersion))
+       catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion
+       catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus
+
+       c, err := test.NewFakeClient(&ip, &catalog)
+       assert.Nil(t, err)
+
+       err = platform.ConfigureDefaults(context.TODO(), c, &ip, false)
+       assert.Nil(t, err)
+
+       action := NewMonitorAction()
+       action.InjectLogger(log.Log)
+       action.InjectClient(c)
+
+       answer, err := action.Handle(context.TODO(), &ip)
+       assert.Nil(t, err)
+       assert.NotNil(t, answer)
+
+       assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase)
+       assert.Equal(t, corev1.ConditionFalse, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status)
+       assert.Equal(t, 
v1.IntegrationPlatformConditionTypeRegistryAvailableReason, 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Reason)
+       assert.Equal(t, "registry address not available, you need to set one", 
answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Message)
+}
diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go
index b8a87db2a..67e2d3415 100644
--- a/pkg/platform/defaults.go
+++ b/pkg/platform/defaults.go
@@ -140,7 +140,7 @@ func configureRegistry(ctx context.Context, c 
client.Client, p *v1.IntegrationPl
                p.Status.Build.Registry.Address == "" {
                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 = 
"image-registry.openshift-image-registry.svc:5000"
+               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)
diff --git a/pkg/trait/camel.go b/pkg/trait/camel.go
index 45e518954..237564077 100644
--- a/pkg/trait/camel.go
+++ b/pkg/trait/camel.go
@@ -18,23 +18,18 @@ limitations under the License.
 package trait
 
 import (
-       "context"
        "errors"
        "fmt"
        "strconv"
-       "strings"
 
        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/runtime/schema"
        ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
        "github.com/apache/camel-k/v2/pkg/util/camel"
        "github.com/apache/camel-k/v2/pkg/util/kubernetes"
-       "github.com/apache/camel-k/v2/pkg/util/maven"
        "github.com/apache/camel-k/v2/pkg/util/property"
 )
 
@@ -124,57 +119,10 @@ func (t *camelTrait) loadOrCreateCatalog(e *Environment, 
runtimeVersion string)
                // the required versions (camel and runtime) are not expressed 
as
                // semver constraints
                if exactVersionRegexp.MatchString(runtimeVersion) {
-                       ctx, cancel := context.WithTimeout(e.Ctx, 
e.Platform.Status.Build.GetTimeout().Duration)
-                       defer cancel()
-                       catalog, err = camel.GenerateCatalog(ctx, e.Client,
-                               catalogNamespace, 
e.Platform.Status.Build.Maven, runtime, []maven.Dependency{})
+                       catalog, err = camel.CreateCatalog(e.Ctx, e.Client, 
catalogNamespace, e.Platform, runtime)
                        if err != nil {
                                return err
                        }
-
-                       // sanitize catalog name
-                       catalogName := "camel-catalog-" + 
strings.ToLower(runtimeVersion)
-
-                       cx := v1.NewCamelCatalogWithSpecs(catalogNamespace, 
catalogName, catalog.CamelCatalogSpec)
-                       cx.Labels = make(map[string]string)
-                       cx.Labels["app"] = "camel-k"
-                       cx.Labels["camel.apache.org/runtime.version"] = 
runtime.Version
-                       cx.Labels["camel.apache.org/runtime.provider"] = 
string(runtime.Provider)
-                       cx.Labels["camel.apache.org/catalog.generated"] = True
-
-                       if err := e.Client.Create(e.Ctx, &cx); err != nil {
-                               if k8serrors.IsAlreadyExists(err) {
-                                       // It's still possible that catalog 
wasn't yet found at the time of loading
-                                       // but then created in the background 
before the client tries to create it.
-                                       // In this case, simply try loading 
again and reuse the existing catalog.
-                                       catalog, err = camel.LoadCatalog(e.Ctx, 
e.Client, catalogNamespace, runtime)
-                                       if err != nil {
-                                               // unexpected error
-                                               return fmt.Errorf("catalog %q 
already exists but unable to load: %w", catalogName, err)
-                                       }
-                               } else {
-                                       return fmt.Errorf("unable to create 
catalog runtime=%s, provider=%s, name=%s: %w",
-                                               runtime.Version,
-                                               runtime.Provider,
-                                               catalogName, err)
-
-                               }
-                       }
-
-                       // verify that the catalog has been generated
-                       ct, err := kubernetes.GetUnstructured(
-                               e.Ctx,
-                               e.Client,
-                               schema.GroupVersionKind{Group: 
"camel.apache.org", Version: "v1", Kind: "CamelCatalog"},
-                               catalogName,
-                               catalogNamespace,
-                       )
-                       if ct == nil || err != nil {
-                               return fmt.Errorf("unable to create catalog 
runtime=%s, provider=%s, name=%s: %w",
-                                       runtime.Version,
-                                       runtime.Provider,
-                                       catalogName, err)
-                       }
                }
        }
 
diff --git a/pkg/trait/registry.go b/pkg/trait/registry.go
index daa1bb8ff..3efd76510 100644
--- a/pkg/trait/registry.go
+++ b/pkg/trait/registry.go
@@ -29,6 +29,7 @@ import (
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
        "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"
        "github.com/apache/camel-k/v2/pkg/util/registry"
 
@@ -70,7 +71,7 @@ func (t *registryTrait) Configure(e *Environment) (bool, 
*TraitCondition, error)
 func (t *registryTrait) Apply(e *Environment) error {
        registryAddress := e.Platform.Status.Build.Registry.Address
        if registryAddress == "" && e.Platform.Status.Cluster == 
v1.IntegrationPlatformClusterOpenShift {
-               registryAddress = 
"image-registry.openshift-image-registry.svc:5000"
+               registryAddress = defaults.OpenShiftRegistryAddress
        }
        if registryAddress == "" {
                return errors.New("could not figure out Image Registry URL, 
please set it manually")
diff --git a/pkg/util/camel/camel_runtime.go b/pkg/util/camel/camel_runtime.go
index ed613690a..412875783 100644
--- a/pkg/util/camel/camel_runtime.go
+++ b/pkg/util/camel/camel_runtime.go
@@ -19,13 +19,75 @@ package camel
 
 import (
        "context"
+       "fmt"
+       "strings"
 
+       "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+       "github.com/apache/camel-k/v2/pkg/util/maven"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/runtime/schema"
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        "github.com/apache/camel-k/v2/pkg/client"
 )
 
+// CreateCatalog --.
+func CreateCatalog(ctx context.Context, client client.Client, namespace 
string, platform *v1.IntegrationPlatform, runtime v1.RuntimeSpec) 
(*RuntimeCatalog, error) {
+       ctx, cancel := context.WithTimeout(ctx, 
platform.Status.Build.GetTimeout().Duration)
+       defer cancel()
+       catalog, err := GenerateCatalog(ctx, client, namespace, 
platform.Status.Build.Maven, runtime, []maven.Dependency{})
+       if err != nil {
+               return nil, err
+       }
+
+       // sanitize catalog name
+       catalogName := "camel-catalog-" + strings.ToLower(runtime.Version)
+
+       cx := v1.NewCamelCatalogWithSpecs(namespace, catalogName, 
catalog.CamelCatalogSpec)
+       cx.Labels = make(map[string]string)
+       cx.Labels["app"] = "camel-k"
+       cx.Labels["camel.apache.org/runtime.version"] = runtime.Version
+       cx.Labels["camel.apache.org/runtime.provider"] = 
string(runtime.Provider)
+       cx.Labels["camel.apache.org/catalog.generated"] = "true"
+
+       if err := client.Create(ctx, &cx); err != nil {
+               if k8serrors.IsAlreadyExists(err) {
+                       // It's still possible that catalog wasn't yet found at 
the time of loading
+                       // but then created in the background before the client 
tries to create it.
+                       // In this case, simply try loading again and reuse the 
existing catalog.
+                       catalog, err = LoadCatalog(ctx, client, namespace, 
runtime)
+                       if err != nil {
+                               // unexpected error
+                               return nil, fmt.Errorf("catalog %q already 
exists but unable to load: %w", catalogName, err)
+                       }
+               } else {
+                       return nil, fmt.Errorf("unable to create catalog 
runtime=%s, provider=%s, name=%s: %w",
+                               runtime.Version,
+                               runtime.Provider,
+                               catalogName, err)
+
+               }
+       }
+
+       // verify that the catalog has been generated
+       ct, err := kubernetes.GetUnstructured(
+               ctx,
+               client,
+               schema.GroupVersionKind{Group: "camel.apache.org", Version: 
"v1", Kind: "CamelCatalog"},
+               catalogName,
+               namespace,
+       )
+       if ct == nil || err != nil {
+               return nil, fmt.Errorf("unable to create catalog runtime=%s, 
provider=%s, name=%s: %w",
+                       runtime.Version,
+                       runtime.Provider,
+                       catalogName, err)
+       }
+
+       return catalog, nil
+}
+
 // LoadCatalog --.
 func LoadCatalog(ctx context.Context, client client.Client, namespace string, 
runtime v1.RuntimeSpec) (*RuntimeCatalog, error) {
        options := []k8sclient.ListOption{
diff --git a/pkg/util/defaults/defaults.go b/pkg/util/defaults/defaults.go
index d1d6b84a8..d4bff2ec0 100644
--- a/pkg/util/defaults/defaults.go
+++ b/pkg/util/defaults/defaults.go
@@ -37,6 +37,9 @@ const (
        // ImageName --
        ImageName = "docker.io/apache/camel-k"
 
+       // OpenShiftRegistryAddress --
+       OpenShiftRegistryAddress = 
"image-registry.openshift-image-registry.svc:5000"
+
        // installDefaultKamelets --
        installDefaultKamelets = true
 )
diff --git a/pkg/util/maven/maven_command.go b/pkg/util/maven/maven_command.go
index 922421f98..fc4707573 100644
--- a/pkg/util/maven/maven_command.go
+++ b/pkg/util/maven/maven_command.go
@@ -46,10 +46,12 @@ func (c *Command) Do(ctx context.Context) error {
                return err
        }
 
-       // Prepare maven wrapper helps when running the builder as Pod as it 
makes
-       // the builder container, Maven agnostic
-       if err := c.prepareMavenWrapper(ctx); err != nil {
-               return err
+       if e, ok := os.LookupEnv("MAVEN_WRAPPER"); (ok && e == "true") || !ok {
+               // Prepare maven wrapper helps when running the builder as Pod 
as it makes
+               // the builder container, Maven agnostic
+               if err := c.prepareMavenWrapper(ctx); err != nil {
+                       return err
+               }
        }
 
        mvnCmd := "./mvnw"
diff --git a/pkg/util/test/client.go b/pkg/util/test/client.go
index 081a6733a..82657df94 100644
--- a/pkg/util/test/client.go
+++ b/pkg/util/test/client.go
@@ -25,7 +25,6 @@ import (
        "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"
-       camel 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned"
        fakecamelclientset 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned/fake"
        camelv1 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned/typed/camel/v1"
        camelv1alpha1 
"github.com/apache/camel-k/v2/pkg/client/camel/clientset/versioned/typed/camel/v1alpha1"
@@ -137,12 +136,16 @@ func filterObjects(scheme *runtime.Scheme, input 
[]runtime.Object, filter func(g
 type FakeClient struct {
        controller.Client
        kubernetes.Interface
-       camel            camel.Interface
+       camel            *fakecamelclientset.Clientset
        scales           *fakescale.FakeScaleClient
        disabledGroups   []string
        enabledOpenshift bool
 }
 
+func (c *FakeClient) AddReactor(verb, resource string, reaction 
testing.ReactionFunc) {
+       c.camel.AddReactor(verb, resource, reaction)
+}
+
 func (c *FakeClient) CamelV1() camelv1.CamelV1Interface {
        return c.camel.CamelV1()
 }
diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go
index 6a428b234..945b865a2 100644
--- a/pkg/util/util_test.go
+++ b/pkg/util/util_test.go
@@ -23,12 +23,13 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/apache/camel-k/v2/pkg/util/defaults"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
 )
 
 func TestStringContainsPrefix(t *testing.T) {
-       args := []string{"install", "--operator-image=xxx/yyy:zzz", 
"--registry", "image-registry.openshift-image-registry.svc:5000"}
+       args := []string{"install", "--operator-image=xxx/yyy:zzz", 
"--registry", defaults.OpenShiftRegistryAddress}
        assert.True(t, StringContainsPrefix(args, "--operator-image="))
        assert.False(t, StringContainsPrefix(args, "--olm"))
 }
diff --git a/script/Makefile b/script/Makefile
index 3d1adbcce..08b26559d 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -49,9 +49,10 @@ METADATA_IMAGE_NAME := $(CUSTOM_IMAGE)-metadata
 BUNDLE_IMAGE_NAME ?= $(CUSTOM_IMAGE)-bundle
 RELEASE_GIT_REMOTE := origin
 GIT_COMMIT := $(shell if [ -d .git ]; then git rev-list -1 HEAD; else echo 
"$(CUSTOM_VERSION)"; fi)
-LINT_GOGC := 10
+LINT_GOGC := 20
 LINT_DEADLINE := 10m
 DEBUG_MODE ?= false
+OPENSHIFT_REGISTRY := image-registry.openshift-image-registry.svc:5000
 
 # olm bundle vars
 MANAGER := config/manager
@@ -187,6 +188,9 @@ codegen:
        @echo "  // ImageName -- " >> $(VERSIONFILE)
        @echo "  ImageName = \"$(CUSTOM_IMAGE)\"" >> $(VERSIONFILE)
        @echo "" >> $(VERSIONFILE)
+       @echo "  // OpenShiftRegistryAddress -- " >> $(VERSIONFILE)
+       @echo "  OpenShiftRegistryAddress = \"$(OPENSHIFT_REGISTRY)\"" >> 
$(VERSIONFILE)
+       @echo "" >> $(VERSIONFILE)
        @echo "  // installDefaultKamelets -- " >> $(VERSIONFILE)
        @echo "  installDefaultKamelets = $(INSTALL_DEFAULT_KAMELETS)" >> 
$(VERSIONFILE)
        @echo ")" >> $(VERSIONFILE)
@@ -404,10 +408,10 @@ OS_LOWER := $(shell echo $(OS) | tr '[:upper:]' 
'[:lower:]')
 endif
 
 lint:
-       GOGC=$(LINT_GOGC) golangci-lint run --config .golangci.yml --out-format 
tab --deadline $(LINT_DEADLINE) --verbose
+       GOGC=$(LINT_GOGC) golangci-lint run --config .golangci.yml --out-format 
colored-tab --deadline $(LINT_DEADLINE) --verbose
 
 lint-fix:
-       GOGC=$(LINT_GOGC) golangci-lint run --config .golangci.yml --out-format 
tab --deadline $(LINT_DEADLINE) --fix
+       GOGC=$(LINT_GOGC) golangci-lint run --config .golangci.yml --out-format 
colored-tab --deadline $(LINT_DEADLINE) --fix
 
 dir-licenses:
        ./script/vendor-license-directory.sh


Reply via email to