Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/constants/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ package constants
const (
// ConditionMCPSuccessful is an aggregated condition showing whether all component resources could be reconciled successfully.
ConditionMCPSuccessful = "MCPSuccessful"

ConditionClusterRequestGranted = "ClusterRequestGranted"
ConditionClusterReady = "ClusterReady"
ConditionAccessRequestGranted = "AccessRequestGranted"
ConditionAccessRequestDeleted = "AccessRequestDeleted"
ConditionClusterRequestDeleted = "ClusterRequestDeleted"
)
8 changes: 8 additions & 0 deletions api/constants/reasons.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,11 @@ const (
// ReasonNotAllComponentsReconciledSuccessfully indicates that not all components have been reconciled successfully.
ReasonNotAllComponentsReconciledSuccessfully = "NotAllComponentsReconciledSuccessfully"
)

const (
ReasonClusterRequestNotGranted = "ClusterRequestNotGranted"
ReasonClusterNotReady = "ClusterNotReady"
ReasonAccessRequestNotGranted = "AccessRequestNotGranted"
ReasonAccessRequestNotDeleted = "AccessRequestNotDeleted"
ReasonClusterRequestNotDeleted = "ClusterRequestNotDeleted"
)
12 changes: 12 additions & 0 deletions api/core/v1alpha1/component_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,15 @@ func (ct ComponentType) ReconciliationCondition() string {
func (ct ComponentType) HealthyCondition() string {
return fmt.Sprintf("%sHealthy", string(ct))
}

// ArchitectureLabelPrefix returns the component-specific architecture label prefix.
// Note that this label is only used on the MCP resource itself, on the component resources, the static ArchitectureLabelPrefix is used.
func (ct ComponentType) ArchitectureLabelPrefix() string {
return fmt.Sprintf("%s.%s", strings.ToLower(string(ct)), ArchitectureLabelPrefix)
}

// ArchitectureVersionLabel returns the component-specific architecture version label.
// Note that this label is only used on the MCP resource itself, on the component resources, the static ArchitectureVersionLabel is used.
func (ct ComponentType) ArchitectureVersionLabel() string {
return fmt.Sprintf("%s%s", ct.ArchitectureLabelPrefix(), "version")
}
13 changes: 13 additions & 0 deletions api/core/v1alpha1/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const (
// ManagedByLabel is added to resources created by the operator.
ManagedByLabel = BaseDomain + "/managed-by"

// ManagedPurposeLabel holds the purpose of a managed resource.
ManagedPurposeLabel = "managed." + BaseDomain + "/purpose"
// ManagedPurposeArchitectureImmutability is the value of the managed purpose label for resources that are used to enforce architecture immutability.
ManagedPurposeArchitectureImmutability = "architecture-immutability"

CreatedByAnnotation = BaseDomain + "/created-by"

DisplayNameAnnotation = BaseDomain + "/display-name"
Expand Down Expand Up @@ -76,4 +81,12 @@ const (
APIServerDomain = "apiserver." + BaseDomain

ManagedByAPIServerLabel = APIServerDomain + "/managed"

// Architecture Switch Labels
ArchitectureLabelPrefix = "architecture." + BaseDomain + "/"
ArchitectureVersionLabel = ArchitectureLabelPrefix + "version"
ArchitectureV1 = "v1"
ArchitectureV2 = "v2"
V1MCPReferenceLabelName = "v1." + BaseDomain + "/mcp-name"
V1MCPReferenceLabelNamespace = "v1." + BaseDomain + "/mcp-namespace"
)
20 changes: 20 additions & 0 deletions charts/mcp-operator/templates/configmap-mcp-operator-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mcp-operator-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "mcp-operator.labels" . | nindent 4 }}
data:
config.yaml: |
architecture:
{{- if and .Values.architecture .Values.architecture.immutability }}
immutability:
{{- .Values.architecture.immutability | toYaml | nindent 8 }}
{{- end }}
{{- if and .Values.apiServer .Values.apiServer.architecture }}
apiServer:
version: {{ .Values.apiServer.architecture.version | default "v1" }}
allowOverride: {{ .Values.apiServer.architecture.allowOverride | default false }}
{{- end }}
10 changes: 10 additions & 0 deletions charts/mcp-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ spec:
checksum/co-clusters: {{ include (print $.Template.BasePath "/secrets-cloudorchestrator-clusters.yaml") . | sha256sum }}
checksum/auth-config: {{ include (print $.Template.BasePath "/secret-auth-config.yaml") . | sha256sum }}
checksum/authz-config: {{ include (print $.Template.BasePath "/secret-authz-config.yaml") . | sha256sum }}
checksum/mcp-operator-config: {{ include (print $.Template.BasePath "/configmap-mcp-operator-config.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand All @@ -50,6 +51,7 @@ spec:
command:
- /mcp-operator
- --controllers={{ include "mcp-operator.activeControllersString" .Values }}
- --config=/etc/config/mcp-operator/config.yaml
{{- if .Values.deployment.leaderElection.enabled }}
- --leader-elect
- --lease-namespace={{ .Values.deployment.leaderElection.leaseNamespace }}
Expand Down Expand Up @@ -140,6 +142,9 @@ spec:
mountPath: /tmp/k8s-webhook-server/serving-certs/
readOnly: true
{{- end }}
- name: mcp-operator-config
mountPath: /etc/config/mcp-operator
readOnly: true
- name: common
mountPath: /etc/config/common
readOnly: true
Expand Down Expand Up @@ -247,6 +252,11 @@ spec:
{{- end }}
{{- end }}
{{- end }}
- name: mcp-operator-config
projected:
sources:
- configMap:
name: mcp-operator-config
- name: common
projected:
sources:
Expand Down
8 changes: 7 additions & 1 deletion charts/mcp-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ clusters:
# caData: ...
# caConfigMapName: ...


# architecture: # architecture configuration
# immutability:
# policyName: mcp-architecture-immutability # name of the ValidatingAdmissionPolicy to enforce architecture immutability
# disabled: false # whether architecture immutability should be enforced (strongly recommended to leave this enabled)

crds:
manage: true
Expand All @@ -53,6 +56,9 @@ managedcontrolplane:

apiserver:
disabled: false
# architecture:
# version: v1
# allowOverride: false
worker:
maxWorkers: 10
intervalTime: 10s
Expand Down
136 changes: 135 additions & 1 deletion cmd/mcp-operator/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/openmcp-project/mcp-operator/internal/releasechannel"
"github.com/openmcp-project/mcp-operator/internal/utils/apiserver"

mcpocfg "github.com/openmcp-project/mcp-operator/internal/config"
apiservercontroller "github.com/openmcp-project/mcp-operator/internal/controller/core/apiserver"
authenticationcontroller "github.com/openmcp-project/mcp-operator/internal/controller/core/authentication"
authorizationcontroller "github.com/openmcp-project/mcp-operator/internal/controller/core/authorization"
Expand All @@ -17,12 +18,16 @@ import (
landscapercontroller "github.com/openmcp-project/mcp-operator/internal/controller/core/landscaper"
mcpcontroller "github.com/openmcp-project/mcp-operator/internal/controller/core/managedcontrolplane"

admissionv1 "k8s.io/api/admissionregistration/v1"
"sigs.k8s.io/controller-runtime/pkg/cluster"

v2install "github.com/openmcp-project/openmcp-operator/api/install"

laasinstall "github.com/gardener/landscaper-service/pkg/apis/core/install"
cocorev1beta1 "github.com/openmcp-project/control-plane-operator/api/v1beta1"
"github.com/openmcp-project/controller-utils/pkg/init/webhooks"
"github.com/openmcp-project/controller-utils/pkg/logging"
"github.com/openmcp-project/controller-utils/pkg/resources"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand All @@ -45,6 +50,8 @@ import (
openmcpinstall "github.com/openmcp-project/mcp-operator/api/install"
)

const OperatorName = "ManagedControlPlaneOperator"

func NewMCPOperatorCommand(ctx context.Context) *cobra.Command {
options := NewOptions()

Expand Down Expand Up @@ -143,6 +150,125 @@ func (o *Options) runInit(ctx context.Context) error {
}
}

// manage architecture immutability
labelSelector := client.MatchingLabels{
openmcpv1alpha1.ManagedByLabel: OperatorName,
openmcpv1alpha1.ManagedPurposeLabel: openmcpv1alpha1.ManagedPurposeArchitectureImmutability,
}
evapbs := &admissionv1.ValidatingAdmissionPolicyBindingList{}
if err := crateClient.List(ctx, evapbs, labelSelector); err != nil {
return fmt.Errorf("error listing ValidatingAdmissionPolicyBindings: %w", err)
}
for _, evapb := range evapbs.Items {
if mcpocfg.Config.Architecture.Immutability.Disabled || evapb.Name != mcpocfg.Config.Architecture.Immutability.PolicyName {
setupLog.Info("Deleting existing ValidatingAdmissionPolicyBinding with architecture immutability purpose", "name", evapb.Name)
if err := crateClient.Delete(ctx, &evapb); client.IgnoreNotFound(err) != nil {
return fmt.Errorf("error deleting ValidatingAdmissionPolicyBinding '%s': %w", evapb.Name, err)
}
}
}
evaps := &admissionv1.ValidatingAdmissionPolicyList{}
if err := crateClient.List(ctx, evaps, labelSelector); err != nil {
return fmt.Errorf("error listing ValidatingAdmissionPolicies: %w", err)
}
for _, evap := range evaps.Items {
if mcpocfg.Config.Architecture.Immutability.Disabled || evap.Name != mcpocfg.Config.Architecture.Immutability.PolicyName {
setupLog.Info("Deleting existing ValidatingAdmissionPolicy with architecture immutability purpose", "name", evap.Name)
if err := crateClient.Delete(ctx, &evap); client.IgnoreNotFound(err) != nil {
return fmt.Errorf("error deleting ValidatingAdmissionPolicy '%s': %w", evap.Name, err)
}
}
}
if !mcpocfg.Config.Architecture.Immutability.Disabled {
setupLog.Info("Architecture immutability validation enabled, creating/updating ValidatingAdmissionPolicies ...")
vapm := resources.NewValidatingAdmissionPolicyMutator(mcpocfg.Config.Architecture.Immutability.PolicyName, admissionv1.ValidatingAdmissionPolicySpec{
FailurePolicy: ptr.To(admissionv1.Fail),
MatchConstraints: &admissionv1.MatchResources{
ResourceRules: []admissionv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionv1.RuleWithOperations{
Operations: []admissionv1.OperationType{
admissionv1.Create,
admissionv1.Update,
},
Rule: admissionv1.Rule{ // match all resources, actual restriction happens in the binding
APIGroups: []string{"*"},
APIVersions: []string{"*"},
Resources: []string{"*"},
},
},
},
},
},
Variables: []admissionv1.Variable{
{
Name: "archLabel",
Expression: fmt.Sprintf(`(has(object.metadata.labels) && "%s" in object.metadata.labels) ? object.metadata.labels["%s"] : ""`, openmcpv1alpha1.ArchitectureVersionLabel, openmcpv1alpha1.ArchitectureVersionLabel),
},
{
Name: "oldArchLabel",
Expression: fmt.Sprintf(`(oldObject != null && has(oldObject.metadata.labels) && "%s" in oldObject.metadata.labels) ? oldObject.metadata.labels["%s"] : ""`, openmcpv1alpha1.ArchitectureVersionLabel, openmcpv1alpha1.ArchitectureVersionLabel),
},
},
Validations: []admissionv1.Validation{
{
Expression: fmt.Sprintf(`variables.archLabel == "%s" || variables.archLabel == "%s"`, openmcpv1alpha1.ArchitectureV1, openmcpv1alpha1.ArchitectureV2),
Message: fmt.Sprintf(`The label "%s" must be set and its value must be either "%s" or "%s".`, openmcpv1alpha1.ArchitectureVersionLabel, openmcpv1alpha1.ArchitectureV1, openmcpv1alpha1.ArchitectureV2),
},
{
Expression: fmt.Sprintf(`request.operation == "CREATE" || (variables.oldArchLabel == "" && variables.archLabel == "%s") || (variables.oldArchLabel == variables.archLabel)`, openmcpv1alpha1.ArchitectureV1),
Message: fmt.Sprintf(`The label "%s" is immutable, it may not be changed or removed once set. Adding it to existing resources is only allowed with "%s" as value.`, openmcpv1alpha1.ArchitectureVersionLabel, openmcpv1alpha1.ArchitectureV1),
},
},
})
vapm.MetadataMutator().WithLabels(map[string]string{
openmcpv1alpha1.ManagedByLabel: OperatorName,
openmcpv1alpha1.ManagedPurposeLabel: openmcpv1alpha1.ManagedPurposeArchitectureImmutability,
})
if err := resources.CreateOrUpdateResource(ctx, crateClient, vapm); err != nil {
return fmt.Errorf("error creating/updating ValidatingAdmissionPolicy for architecture immutability: %w", err)
}

vapbm := resources.NewValidatingAdmissionPolicyBindingMutator(mcpocfg.Config.Architecture.Immutability.PolicyName, admissionv1.ValidatingAdmissionPolicyBindingSpec{
PolicyName: mcpocfg.Config.Architecture.Immutability.PolicyName,
ValidationActions: []admissionv1.ValidationAction{
admissionv1.Deny,
},
MatchResources: &admissionv1.MatchResources{
ResourceRules: []admissionv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionv1.RuleWithOperations{
Operations: []admissionv1.OperationType{
admissionv1.Create,
admissionv1.Update,
},
Rule: admissionv1.Rule{
APIGroups: []string{openmcpv1alpha1.GroupVersion.Group},
APIVersions: []string{openmcpv1alpha1.GroupVersion.Version},
Resources: []string{
"apiservers",
"landscapers",
"cloudorchestrators",
"authentications",
"authorizations",
},
},
},
},
},
},
})
vapbm.MetadataMutator().WithLabels(map[string]string{
openmcpv1alpha1.ManagedByLabel: OperatorName,
openmcpv1alpha1.ManagedPurposeLabel: openmcpv1alpha1.ManagedPurposeArchitectureImmutability,
})
if err := resources.CreateOrUpdateResource(ctx, crateClient, vapbm); err != nil {
return fmt.Errorf("error creating/updating ValidatingAdmissionPolicyBinding for architecture immutability: %w", err)
}

setupLog.Info("ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding for architecture immutability created/updated")
}

return nil
}

Expand Down Expand Up @@ -219,7 +345,15 @@ func (o *Options) run(ctx context.Context) error {

if o.ActiveControllers.Has(ControllerIDAPIServer) {
// APIServer controller
apiServerProvider, err := apiservercontroller.NewAPIServerProvider(ctx, mgr.GetClient(), o.APIServerConfig)
// build platform cluster client for v2 path
v2scheme := v2install.InstallOperatorAPIs(runtime.NewScheme())
platformClient, err := client.New(o.LaaSClusterConfig, client.Options{
Scheme: v2scheme,
})
if err != nil {
return fmt.Errorf("error creating platform cluster client: %w", err)
}
apiServerProvider, err := apiservercontroller.NewAPIServerProvider(ctx, mgr.GetClient(), platformClient, o.APIServerConfig)
if err != nil {
return fmt.Errorf("error creating %s: %w", apiservercontroller.ControllerName, err)
}
Expand Down
19 changes: 19 additions & 0 deletions cmd/mcp-operator/app/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/openmcp-project/mcp-operator/internal/components"

mcpocfg "github.com/openmcp-project/mcp-operator/internal/config"
"github.com/openmcp-project/mcp-operator/internal/controller/core/apiserver/config"
configauthn "github.com/openmcp-project/mcp-operator/internal/controller/core/authentication/config"
configauthz "github.com/openmcp-project/mcp-operator/internal/controller/core/authorization/config"
Expand Down Expand Up @@ -56,6 +57,7 @@ type rawOptions struct {

// raw options that need to be evaluated
APIServerConfigPath string `json:"apiServerConfigPath"`
MCPOConfigPath string `json:"configPath"`
LaaSClusterPath string `json:"laasClusterConfigPath"`
CrateClusterPath string `json:"crateClusterConfigPath"`
CloudOrchestratorClusterPath string `json:"cloudOrchestratorClusterConfigPath"`
Expand Down Expand Up @@ -131,6 +133,9 @@ func (o *Options) String(includeHeader bool, includeRawOptions bool) (string, er
// API server config
opts["apiServerConfig"] = o.APIServerConfig

// architecture config
opts["config"] = mcpocfg.Config

// clusters
opts["crateClusterHost"] = nil
if o.CrateClusterConfig != nil {
Expand Down Expand Up @@ -219,6 +224,7 @@ func (o *Options) AddFlags(fs *flag.FlagSet) {
fs.StringVar(&o.AuthzConfigPath, "authz-config", "", "Path to the authorization config file.")

// common
fs.StringVar(&o.MCPOConfigPath, "config", "", "Path to the MCP operator config file.")
fs.BoolVar(&o.DryRun, "dry-run", false, "If true, the CLI args are evaluated as usual, but the program exits before the controllers are started.")
fs.StringVar(&o.CrateClusterPath, "crate-cluster", "", "Path to the crate cluster kubeconfig file or directory containing either a kubeconfig or host, token, and ca file. Leave empty to use in-cluster config.")
fs.StringVar(&o.ControllerList, "controllers", strings.Join([]string{ControllerIDManagedControlPlane, ControllerIDAPIServer, ControllerIDLandscaper, ControllerIDCloudOrchestrator}, ","), "Comma-separated list of controllers that should be active.")
Expand Down Expand Up @@ -337,6 +343,19 @@ func (o *Options) Complete() error {
}
}

// load config
if o.MCPOConfigPath != "" {
cfg, err := mcpocfg.LoadConfig(o.MCPOConfigPath)
if err != nil {
return err
}
err = cfg.Validate().ToAggregate()
if err != nil {
return fmt.Errorf("invalid config: %w", err)
}
mcpocfg.Config = *cfg
}

// print options
optsString, err := o.String(true, false)
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<!-- Do not edit this file, as it is auto-generated!-->
# Documentation Index

## v2 Architecture

- [v2 Architecture Bridge](architecture-v2/bridge.md)

## Configuration

- [Configuration](config/config.md)

## Controllers

- [APIServer Controller](controllers/apiserver.md)
Expand Down
3 changes: 3 additions & 0 deletions docs/architecture-v2/.docnames
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"header": "v2 Architecture"
}
Loading