Skip to content

Commit 7d026a8

Browse files
committed
implement landscaper v2 bridge
1 parent 1098c56 commit 7d026a8

File tree

7 files changed

+145
-34
lines changed

7 files changed

+145
-34
lines changed

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ require (
1717
github.com/openmcp-project/control-plane-operator v0.1.10
1818
github.com/openmcp-project/controller-utils v0.13.1
1919
github.com/openmcp-project/mcp-operator/api v0.33.1
20-
github.com/openmcp-project/openmcp-operator/api v0.7.0
21-
github.com/openmcp-project/openmcp-operator/lib v0.8.3
20+
github.com/openmcp-project/openmcp-operator/api v0.9.0
21+
github.com/openmcp-project/openmcp-operator/lib v0.9.0
22+
github.com/openmcp-project/service-provider-landscaper v0.4.0
2223
github.com/spf13/cobra v1.9.1
2324
github.com/spf13/pflag v1.0.7
2425
github.com/stretchr/testify v1.10.0
@@ -85,7 +86,7 @@ require (
8586
go.uber.org/zap v1.27.0 // indirect
8687
go.yaml.in/yaml/v2 v2.4.2 // indirect
8788
go.yaml.in/yaml/v3 v3.0.3 // indirect
88-
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
89+
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
8990
golang.org/x/mod v0.26.0 // indirect
9091
golang.org/x/net v0.42.0 // indirect
9192
golang.org/x/oauth2 v0.29.0 // indirect

go.sum

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,12 @@ github.com/openmcp-project/control-plane-operator v0.1.10 h1:5ticEP3llBmIHQkBzkZ
115115
github.com/openmcp-project/control-plane-operator v0.1.10/go.mod h1:GNu9LBTPWoE3dKsBo2kS+SeKSLU2qLtu3VjpaznsB2o=
116116
github.com/openmcp-project/controller-utils v0.13.1 h1:+06c0bs1BIO+hBsTcuiEK5y8vpDFoZPml59WNm8fagM=
117117
github.com/openmcp-project/controller-utils v0.13.1/go.mod h1:Z1ytVshYcgJq3VQVGqkuZsjO/BCr4UYAaVpHl6JSIMI=
118-
github.com/openmcp-project/openmcp-operator/api v0.7.0 h1:DvaMS3xtAvahGOQm9sI26aotupa8XkwZP52HfOhZ9K0=
119-
github.com/openmcp-project/openmcp-operator/api v0.7.0/go.mod h1:TuAq8Fbrzuykxw/h589M8+QfHotwero5MPWVzdFAqkw=
120-
github.com/openmcp-project/openmcp-operator/lib v0.8.3 h1:2bb1zbP6Si7/fUfXT9M5B0xQnd7O4zGJXaUoe9pDmcA=
121-
github.com/openmcp-project/openmcp-operator/lib v0.8.3/go.mod h1:oydIXRZoNDxtI4DI/JBUB08UPzvfdaKLqHvC4S4HXHQ=
118+
github.com/openmcp-project/openmcp-operator/api v0.9.0 h1:Ss2XTHci3QDwtVQ9QCpAez68WPJNHpabpwMTpFYn0NE=
119+
github.com/openmcp-project/openmcp-operator/api v0.9.0/go.mod h1:cqSmw3+8QJ2Va15aeCmISKIqcWnTpPSb8kch6eyfa6U=
120+
github.com/openmcp-project/openmcp-operator/lib v0.9.0 h1:jbuuZ2HlR0T1wO+Ty8AEaswNeoUK7eGUIw+8zEaicJo=
121+
github.com/openmcp-project/openmcp-operator/lib v0.9.0/go.mod h1:ILD0kCivVMZM5G38GJXflN1AXuQZX4wfH1FgNAcrXo0=
122+
github.com/openmcp-project/service-provider-landscaper v0.4.0 h1:H55q5whlcb1fyhY7/dkeigcp9UPXZQj+WDI57XVY4vY=
123+
github.com/openmcp-project/service-provider-landscaper v0.4.0/go.mod h1:5VU8eJ5nA2Kz0Kc4qbmw6ocMNCgLKzDu5mX6w5hOHa8=
122124
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
123125
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
124126
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -181,8 +183,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
181183
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
182184
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
183185
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
184-
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
185-
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
186+
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
187+
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
186188
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
187189
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
188190
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=

internal/controller/core/apiserver/controller_test.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"github.com/openmcp-project/controller-utils/pkg/testing"
2929
clustersv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1"
30+
commonapi "github.com/openmcp-project/openmcp-operator/api/common"
3031
openmcpclusterutils "github.com/openmcp-project/openmcp-operator/lib/utils"
3132

3233
gardenv1beta1 "github.com/openmcp-project/mcp-operator/api/external/gardener/pkg/apis/core/v1beta1"
@@ -383,10 +384,8 @@ var _ = Describe("CO-1153 APIServer Controller", func() {
383384
Expect(env.Client(testutils.LaaSCoreCluster).Create(env.Ctx, cluster)).To(Succeed())
384385

385386
cr.Status.Phase = clustersv1alpha1.REQUEST_GRANTED
386-
cr.Status.Cluster = &clustersv1alpha1.NamespacedObjectReference{
387-
ObjectReference: clustersv1alpha1.ObjectReference{
388-
Name: cluster.Name,
389-
},
387+
cr.Status.Cluster = &commonapi.ObjectReference{
388+
Name: cluster.Name,
390389
Namespace: cluster.Namespace,
391390
}
392391
Expect(env.Client(testutils.LaaSCoreCluster).Status().Update(env.Ctx, cr)).To(Succeed())
@@ -426,11 +425,11 @@ var _ = Describe("CO-1153 APIServer Controller", func() {
426425
dummyShootJson, err := json.Marshal(dummyShoot)
427426
Expect(err).NotTo(HaveOccurred())
428427
cluster.Status = clustersv1alpha1.ClusterStatus{
429-
Phase: clustersv1alpha1.CLUSTER_PHASE_READY,
430428
ProviderStatus: &runtime.RawExtension{
431429
Raw: dummyShootJson,
432430
},
433431
}
432+
cluster.Status.Phase = clustersv1alpha1.CLUSTER_PHASE_READY
434433
Expect(env.Client(testutils.LaaSCoreCluster).Status().Update(env.Ctx, cluster)).To(Succeed())
435434

436435
// reconcile again, should now get further
@@ -472,10 +471,8 @@ var _ = Describe("CO-1153 APIServer Controller", func() {
472471
Expect(env.Client(testutils.LaaSCoreCluster).Create(env.Ctx, access)).To(Succeed())
473472

474473
ar.Status.Phase = clustersv1alpha1.REQUEST_GRANTED
475-
ar.Status.SecretRef = &clustersv1alpha1.NamespacedObjectReference{
476-
ObjectReference: clustersv1alpha1.ObjectReference{
477-
Name: access.Name,
478-
},
474+
ar.Status.SecretRef = &commonapi.ObjectReference{
475+
Name: access.Name,
479476
Namespace: access.Namespace,
480477
}
481478
Expect(env.Client(testutils.LaaSCoreCluster).Status().Update(env.Ctx, ar)).To(Succeed())

internal/controller/core/apiserver/v2.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"context"
55
"fmt"
66
"strconv"
7+
"strings"
78
"time"
89

10+
"github.com/openmcp-project/controller-utils/pkg/collections"
911
"github.com/openmcp-project/controller-utils/pkg/logging"
1012
corev1 "k8s.io/api/core/v1"
1113
rbacv1 "k8s.io/api/rbac/v1"
@@ -22,6 +24,7 @@ import (
2224
gcpv1alpha1 "github.com/openmcp-project/cluster-provider-gardener/api/core/v1alpha1"
2325
clustersv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1"
2426
clustersconst "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1/constants"
27+
commonapi "github.com/openmcp-project/openmcp-operator/api/common"
2528
openmcpclusterutils "github.com/openmcp-project/openmcp-operator/lib/utils"
2629

2730
gardenv1beta1 "github.com/openmcp-project/mcp-operator/api/external/gardener/pkg/apis/core/v1beta1"
@@ -107,7 +110,9 @@ func v2HandleCreateOrUpdate(ctx context.Context, as *openmcpv1alpha1.APIServer,
107110
clusterReadyCon.Status = openmcpv1alpha1.ComponentConditionStatusFromBool(cluster.Status.Phase == clustersv1alpha1.CLUSTER_PHASE_READY)
108111
if clusterReadyCon.Status != openmcpv1alpha1.ComponentConditionStatusTrue {
109112
clusterReadyCon.Reason = cconst.ReasonClusterNotReady
110-
clusterReadyCon.Message = cluster.Status.Message
113+
clusterReadyCon.Message = strings.Join(collections.ProjectSlice(cluster.Status.Conditions, func(con metav1.Condition) string {
114+
return fmt.Sprintf("[%s] %s", con.Reason, con.Message)
115+
}), "\n")
111116
if clusterReadyCon.Message == "" {
112117
clusterReadyCon.Message = "Cluster is not ready yet, no further information available"
113118
}
@@ -160,11 +165,10 @@ func v2HandleCreateOrUpdate(ctx context.Context, as *openmcpv1alpha1.APIServer,
160165
} else {
161166
clusterRequestGrantedCon.Status = openmcpv1alpha1.ComponentConditionStatusFalse
162167
clusterRequestGrantedCon.Reason = cconst.ReasonClusterRequestNotGranted
163-
crReason := cr.Status.Reason
164-
crMessage := cr.Status.Message
165-
if crReason == "" {
166-
crReason = "<NoReason>"
167-
}
168+
crReason := cconst.ReasonClusterRequestNotGranted
169+
crMessage := strings.Join(collections.ProjectSlice(cr.Status.Conditions, func(con metav1.Condition) string {
170+
return fmt.Sprintf("[%s] %s", con.Reason, con.Message)
171+
}), "\n")
168172
if crMessage == "" {
169173
crMessage = "<NoMessage>"
170174
}
@@ -228,11 +232,10 @@ func v2HandleCreateOrUpdate(ctx context.Context, as *openmcpv1alpha1.APIServer,
228232
if ar.Status.Phase != clustersv1alpha1.REQUEST_GRANTED && ar.Status.SecretRef == nil {
229233
accessRequestGrantedCon.Status = openmcpv1alpha1.ComponentConditionStatusFalse
230234
accessRequestGrantedCon.Reason = cconst.ReasonAccessRequestNotGranted
231-
arReason := cr.Status.Reason
232-
arMessage := cr.Status.Message
233-
if arReason == "" {
234-
arReason = "<NoReason>"
235-
}
235+
arReason := cconst.ReasonAccessRequestNotGranted
236+
arMessage := strings.Join(collections.ProjectSlice(ar.Status.Conditions, func(con metav1.Condition) string {
237+
return fmt.Sprintf("[%s] %s", con.Reason, con.Message)
238+
}), "\n")
236239
if arMessage == "" {
237240
arMessage = "<NoMessage>"
238241
}
@@ -472,11 +475,11 @@ func (m *AccessRequestMutator) MetadataMutator() resources.MetadataMutator {
472475
// Mutate implements resources.Mutator.
473476
func (m *AccessRequestMutator) Mutate(r *clustersv1alpha1.AccessRequest) error {
474477
if m.isClusterRef && r.Spec.ClusterRef == nil {
475-
r.Spec.ClusterRef = &clustersv1alpha1.NamespacedObjectReference{}
478+
r.Spec.ClusterRef = &commonapi.ObjectReference{}
476479
r.Spec.ClusterRef.Name = m.refName
477480
r.Spec.ClusterRef.Namespace = m.refNamespace
478481
} else if !m.isClusterRef && r.Spec.RequestRef == nil {
479-
r.Spec.RequestRef = &clustersv1alpha1.NamespacedObjectReference{}
482+
r.Spec.RequestRef = &commonapi.ObjectReference{}
480483
r.Spec.RequestRef.Name = m.refName
481484
r.Spec.RequestRef.Namespace = m.refNamespace
482485
}

internal/controller/core/landscaper/controller.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/openmcp-project/mcp-operator/internal/utils/apiserver"
1212
"github.com/openmcp-project/mcp-operator/internal/utils/components"
1313

14+
mcpocfg "github.com/openmcp-project/mcp-operator/internal/config"
1415
"github.com/openmcp-project/mcp-operator/internal/controller/core/landscaper/conversion"
1516
lsutils "github.com/openmcp-project/mcp-operator/internal/controller/core/landscaper/utils"
1617

@@ -181,12 +182,27 @@ func (r *LandscaperConnector) reconcile(ctx context.Context, req ctrl.Request) c
181182
var res ctrl.Result
182183
var ready bool
183184
var reason string
185+
var v2cons []openmcpv1alpha1.ComponentCondition
184186
var errr openmcperrors.ReasonableError
185187
old := ls.DeepCopy()
186-
if deleteLandscaper {
187-
res, ready, reason, errr = r.handleDelete(ctx, ls, ld)
188+
if mcpocfg.Config.Architecture.DecideVersion(ls) == openmcpv1alpha1.ArchitectureV2 {
189+
// v2 logic
190+
log.Info("Using v2 logic for APIServer")
191+
if deleteLandscaper {
192+
res, ready, v2cons, errr = r.v2HandleDelete(ctx, ls)
193+
} else {
194+
res, ready, v2cons, errr = r.v2HandleCreateOrUpdate(ctx, ls)
195+
}
196+
if !ready {
197+
reason = cconst.ReasonWaitingForLaaS
198+
}
188199
} else {
189-
res, ready, reason, errr = r.handleCreateOrUpdate(ctx, ls, ld, as)
200+
// v1 logic
201+
if deleteLandscaper {
202+
res, ready, reason, errr = r.handleDelete(ctx, ls, ld)
203+
} else {
204+
res, ready, reason, errr = r.handleCreateOrUpdate(ctx, ls, ld, as)
205+
}
190206
}
191207
errs := openmcperrors.NewReasonableErrorList(errr)
192208

@@ -221,6 +237,7 @@ func (r *LandscaperConnector) reconcile(ctx context.Context, req ctrl.Request) c
221237
cons[0].Message = fmt.Sprintf("[%s] %s - %s", ld.Status.LastError.Operation, ld.Status.LastError.Reason, ld.Status.LastError.Message)
222238
}
223239
}
240+
cons = append(cons, v2cons...)
224241
return components.ReconcileResult[*openmcpv1alpha1.Landscaper]{OldComponent: old, Component: ls, Result: res, Reason: reason, ReconcileError: errs.Aggregate(), Conditions: cons}
225242
}
226243

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package landscaper
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/openmcp-project/controller-utils/pkg/collections"
9+
"github.com/openmcp-project/controller-utils/pkg/logging"
10+
11+
commonapi "github.com/openmcp-project/openmcp-operator/api/common"
12+
openmcpls "github.com/openmcp-project/service-provider-landscaper/api/v1alpha1"
13+
14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
ctrl "sigs.k8s.io/controller-runtime"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
19+
cconst "github.com/openmcp-project/mcp-operator/api/constants"
20+
openmcpv1alpha1 "github.com/openmcp-project/mcp-operator/api/core/v1alpha1"
21+
openmcperrors "github.com/openmcp-project/mcp-operator/api/errors"
22+
"github.com/openmcp-project/mcp-operator/internal/utils/components"
23+
)
24+
25+
func (r *LandscaperConnector) v2HandleCreateOrUpdate(ctx context.Context, ls *openmcpv1alpha1.Landscaper) (ctrl.Result, bool, []openmcpv1alpha1.ComponentCondition, openmcperrors.ReasonableError) {
26+
lsv2 := &openmcpls.Landscaper{}
27+
lsv2.SetName(ls.Name)
28+
lsv2.SetNamespace(ls.Namespace)
29+
if _, err := ctrl.CreateOrUpdate(ctx, r.CrateClient, lsv2, func() error {
30+
if lsv2.Labels == nil {
31+
lsv2.Labels = map[string]string{}
32+
}
33+
lsv2.Labels[openmcpv1alpha1.V1MCPReferenceLabelName] = ls.Name
34+
lsv2.Labels[openmcpv1alpha1.V1MCPReferenceLabelNamespace] = ls.Namespace
35+
36+
return nil
37+
}); err != nil {
38+
return ctrl.Result{}, false, nil, openmcperrors.WithReason(fmt.Errorf("error creating or updating Landscaper v2 resource: %w", err), cconst.ReasonCrateClusterInteractionProblem)
39+
}
40+
41+
ready := lsv2.Status.Phase == commonapi.StatusPhaseReady && lsv2.Status.ObservedGeneration == lsv2.Generation
42+
cons := collections.ProjectSlice(lsv2.Status.Conditions, func(v2con metav1.Condition) openmcpv1alpha1.ComponentCondition {
43+
return components.NewCondition("LSv2_"+v2con.Type, components.ComponentConditionStatusFromMetav1ConditionStatus(v2con.Status), v2con.Reason, v2con.Message)
44+
})
45+
46+
return ctrl.Result{}, ready, cons, nil
47+
}
48+
49+
func (r *LandscaperConnector) v2HandleDelete(ctx context.Context, ls *openmcpv1alpha1.Landscaper) (ctrl.Result, bool, []openmcpv1alpha1.ComponentCondition, openmcperrors.ReasonableError) {
50+
log := logging.FromContextOrPanic(ctx)
51+
52+
lsv2 := &openmcpls.Landscaper{}
53+
lsv2.SetName(ls.Name)
54+
lsv2.SetNamespace(ls.Namespace)
55+
if err := r.CrateClient.Get(ctx, client.ObjectKeyFromObject(lsv2), lsv2); err != nil {
56+
if !apierrors.IsNotFound(err) {
57+
return ctrl.Result{}, false, nil, openmcperrors.WithReason(fmt.Errorf("error getting Landscaper v2 resource: %w", err), cconst.ReasonCrateClusterInteractionProblem)
58+
}
59+
lsv2 = nil
60+
}
61+
62+
if lsv2 != nil {
63+
if lsv2.DeletionTimestamp.IsZero() {
64+
log.Info("Deleting Landscaper v2 resource", "resourceName", lsv2.Name, "resourceNamespace", lsv2.Namespace)
65+
if err := r.CrateClient.Delete(ctx, lsv2); err != nil {
66+
return ctrl.Result{}, false, nil, openmcperrors.WithReason(fmt.Errorf("error deleting Landscaper v2 resource: %w", err), cconst.ReasonCrateClusterInteractionProblem)
67+
}
68+
} else {
69+
log.Info("Waiting for Landscaper v2 resource to be deleted", "resourceName", lsv2.Name, "resourceNamespace", lsv2.Namespace)
70+
}
71+
72+
cons := collections.ProjectSlice(lsv2.Status.Conditions, func(v2con metav1.Condition) openmcpv1alpha1.ComponentCondition {
73+
return components.NewCondition("LSv2_"+v2con.Type, components.ComponentConditionStatusFromMetav1ConditionStatus(v2con.Status), v2con.Reason, v2con.Message)
74+
})
75+
return ctrl.Result{RequeueAfter: 30 * time.Second}, false, cons, nil
76+
}
77+
78+
log.Info("Landscaper v2 resource deleted", "resourceName", ls.Name, "resourceNamespace", ls.Namespace)
79+
return ctrl.Result{}, true, nil, nil
80+
}

internal/utils/components/conditions.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ func NewCondition(conType string, status openmcpv1alpha1.ComponentConditionStatu
118118
}
119119
}
120120

121+
// ComponentConditionStatusFromMetav1ConditionStatus maps a metav1.ConditionStatus to a ComponentConditionStatus.
122+
func ComponentConditionStatusFromMetav1ConditionStatus(status metav1.ConditionStatus) openmcpv1alpha1.ComponentConditionStatus {
123+
switch status {
124+
case metav1.ConditionTrue:
125+
return openmcpv1alpha1.ComponentConditionStatusTrue
126+
case metav1.ConditionFalse:
127+
return openmcpv1alpha1.ComponentConditionStatusFalse
128+
}
129+
return openmcpv1alpha1.ComponentConditionStatusUnknown
130+
}
131+
121132
// IsComponentReady returns true if the component's observedGenerations are up-to-date and all of its relevant conditions are "True".
122133
// If relevantConditions is empty, all of the component's conditions are deemed relevant.
123134
// Condition types in relevantConditions for which no condition exists on the component are considered "Unknown" and cause the method to return false.

0 commit comments

Comments
 (0)