diff --git a/cloud/scope/vpc_cluster.go b/cloud/scope/vpc_cluster.go new file mode 100644 index 000000000..37d6f9b38 --- /dev/null +++ b/cloud/scope/vpc_cluster.go @@ -0,0 +1,168 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed 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 scope + +import ( + "context" + "errors" + "fmt" + + "github.com/go-logr/logr" + + "github.com/IBM/go-sdk-core/v5/core" + "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" + + "k8s.io/klog/v2/textlogger" + + "sigs.k8s.io/controller-runtime/pkg/client" + + capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/patch" + + infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/authenticator" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/cos" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcecontroller" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcemanager" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/vpc" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/endpoints" +) + +const ( + // LOGDEBUGLEVEL indicates the debug level of the logs. + LOGDEBUGLEVEL = 5 +) + +// VPCClusterScopeParams defines the input parameters used to create a new VPCClusterScope. +type VPCClusterScopeParams struct { + Client client.Client + Cluster *capiv1beta1.Cluster + IBMVPCCluster *infrav1beta2.IBMVPCCluster + Logger logr.Logger + ServiceEndpoint []endpoints.ServiceEndpoint + + IBMVPCClient vpc.Vpc +} + +// VPCClusterScope defines a scope defined around a VPC Cluster. +type VPCClusterScope struct { + logr.Logger + Client client.Client + patchHelper *patch.Helper + + COSClient cos.Cos + ResourceControllerClient resourcecontroller.ResourceController + ResourceManagerClient resourcemanager.ResourceManager + VPCClient vpc.Vpc + + Cluster *capiv1beta1.Cluster + IBMVPCCluster *infrav1beta2.IBMVPCCluster + ServiceEndpoint []endpoints.ServiceEndpoint +} + +// NewVPCClusterScope creates a new VPCClusterScope from the supplied parameters. +func NewVPCClusterScope(params VPCClusterScopeParams) (*VPCClusterScope, error) { + if params.Client == nil { + err := errors.New("error failed to generate new scope from nil Client") + return nil, err + } + if params.Cluster == nil { + err := errors.New("error failed to generate new scope from nil Cluster") + return nil, err + } + if params.IBMVPCCluster == nil { + err := errors.New("error failed to generate new scope from nil IBMVPCCluster") + return nil, err + } + if params.Logger == (logr.Logger{}) { + params.Logger = textlogger.NewLogger(textlogger.NewConfig()) + } + + helper, err := patch.NewHelper(params.IBMVPCCluster, params.Client) + if err != nil { + return nil, fmt.Errorf("error failed to init patch helper: %w", err) + } + + vpcEndpoint := endpoints.FetchVPCEndpoint(params.IBMVPCCluster.Spec.Region, params.ServiceEndpoint) + vpcClient, err := vpc.NewService(vpcEndpoint) + if err != nil { + return nil, fmt.Errorf("error failed to create IBM VPC client: %w", err) + } + + if params.IBMVPCCluster.Spec.Network == nil || params.IBMVPCCluster.Spec.Region == "" { + return nil, fmt.Errorf("error failed to generate vpc client as Network or Region is nil") + } + + if params.Logger.V(LOGDEBUGLEVEL).Enabled() { + core.SetLoggingLevel(core.LevelDebug) + } + + auth, err := authenticator.GetAuthenticator() + if err != nil { + return nil, fmt.Errorf("error failed to create authenticator: %w", err) + } + + // Create Global Tagging client. + // TODO(cjschaef): need service support. + + // Create Resource Controller client. + rcOptions := resourcecontroller.ServiceOptions{ + ResourceControllerV2Options: &resourcecontrollerv2.ResourceControllerV2Options{ + Authenticator: auth, + }, + } + // Fetch the resource controller endpoint. + rcEndpoint := endpoints.FetchEndpoints(string(endpoints.RC), params.ServiceEndpoint) + if rcEndpoint != "" { + rcOptions.URL = rcEndpoint + params.Logger.V(3).Info("Overriding the default resource controller endpoint", "ResourceControllerEndpoint", rcEndpoint) + } + resourceControllerClient, err := resourcecontroller.NewService(rcOptions) + if err != nil { + return nil, fmt.Errorf("error failed to create resource controller client: %w", err) + } + + // Create Resource Manager client. + // TODO(cjschaef): Need to extend ResourceManager service and endpoint support to add properly. + + clusterScope := &VPCClusterScope{ + Logger: params.Logger, + Client: params.Client, + patchHelper: helper, + Cluster: params.Cluster, + IBMVPCCluster: params.IBMVPCCluster, + ServiceEndpoint: params.ServiceEndpoint, + ResourceControllerClient: resourceControllerClient, + VPCClient: vpcClient, + } + return clusterScope, nil +} + +// PatchObject persists the cluster configuration and status. +func (s *VPCClusterScope) PatchObject() error { + return s.patchHelper.Patch(context.TODO(), s.IBMVPCCluster) +} + +// Close closes the current scope persisting the cluster configuration and status. +func (s *VPCClusterScope) Close() error { + return s.PatchObject() +} + +// Name returns the CAPI cluster name. +func (s *VPCClusterScope) Name() string { + return s.Cluster.Name +} diff --git a/controllers/ibmvpccluster_controller.go b/controllers/ibmvpccluster_controller.go index 066736d57..fe72ba047 100644 --- a/controllers/ibmvpccluster_controller.go +++ b/controllers/ibmvpccluster_controller.go @@ -71,6 +71,11 @@ func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } + // Determine whether the Cluster is designed for extended Infrastructure support, implemented in a separate path. + if ibmCluster.Spec.Network != nil { + return r.reconcileV2(ctx, req) + } + // Fetch the Cluster. cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) if err != nil { @@ -109,6 +114,57 @@ func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return r.reconcile(clusterScope) } +func (r *IBMVPCClusterReconciler) reconcileV2(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { + log := r.Log.WithValues("ibmvpccluster", req.NamespacedName) + + // Fetch the IBMVPCCluster instance. + ibmCluster := &infrav1beta2.IBMVPCCluster{} + err := r.Get(ctx, req.NamespacedName, ibmCluster) + if err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Fetch the Cluster. + cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) + if err != nil { + return ctrl.Result{}, err + } + if cluster == nil { + log.Info("Cluster Controller has not yet set OwnerRef") + return ctrl.Result{}, nil + } + + clusterScope, err := scope.NewVPCClusterScope(scope.VPCClusterScopeParams{ + Client: r.Client, + Logger: log, + Cluster: cluster, + IBMVPCCluster: ibmCluster, + ServiceEndpoint: r.ServiceEndpoint, + }) + + // Always close the scope when exiting this function so we can persist any IBMVPCCluster changes. + defer func() { + if clusterScope != nil { + if err := clusterScope.Close(); err != nil && reterr == nil { + reterr = err + } + } + }() + + // Handle deleted clusters. + if !ibmCluster.DeletionTimestamp.IsZero() { + return r.reconcileDeleteV2(clusterScope) + } + + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to create scope: %w", err) + } + return r.reconcileCluster(clusterScope) +} + func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (ctrl.Result, error) { // If the IBMVPCCluster doesn't have our finalizer, add it. if controllerutil.AddFinalizer(clusterScope.IBMVPCCluster, infrav1beta2.ClusterFinalizer) { @@ -173,6 +229,10 @@ func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (c return ctrl.Result{}, nil } +func (r *IBMVPCClusterReconciler) reconcileCluster(_ *scope.VPCClusterScope) (ctrl.Result, error) { + return ctrl.Result{}, fmt.Errorf("not implemented") +} + func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterScope) (ctrl.Result, error) { // check if still have existing VSIs. listVSIOpts := &vpcv1.ListInstancesOptions{ @@ -227,6 +287,10 @@ func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterSco return handleFinalizerRemoval(clusterScope) } +func (r *IBMVPCClusterReconciler) reconcileDeleteV2(_ *scope.VPCClusterScope) (ctrl.Result, error) { + return ctrl.Result{}, fmt.Errorf("not implemented") +} + func (r *IBMVPCClusterReconciler) getOrCreate(clusterScope *scope.ClusterScope) (*vpcv1.LoadBalancer, error) { loadBalancer, err := clusterScope.CreateLoadBalancer() return loadBalancer, err