From d73c367b94d828251b93cbf4960cf47294e53fa1 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Fri, 31 May 2024 10:28:13 +1000 Subject: [PATCH 1/5] added ocm support for geneva actions --- cmd/aro/rp.go | 2 +- .../admin_hive_clusterdeployment_get_test.go | 4 +- .../admin_openshiftcluster_approvecsr_test.go | 2 +- .../admin_openshiftcluster_cordonnode_test.go | 2 +- ...hiftcluster_delete_managedresource_test.go | 2 +- .../admin_openshiftcluster_drainnode_test.go | 2 +- ...nshiftcluster_etcdcertificaterenew_test.go | 2 + ...dmin_openshiftcluster_etcdrecovery_test.go | 1 + ...openshiftcluster_kubernetesobjects_test.go | 4 +- ...enshiftcluster_kubernetespods_logs_test.go | 2 +- .../admin_openshiftcluster_list_test.go | 2 +- .../admin_openshiftcluster_ocm_operations.go | 149 +++++++++++ ...in_openshiftcluster_ocm_operations_test.go | 234 ++++++++++++++++++ .../admin_openshiftcluster_redeployvm_test.go | 3 +- ...in_openshiftcluster_resources_list_test.go | 2 +- .../admin_openshiftcluster_startvm_test.go | 2 +- .../admin_openshiftcluster_stopvm_test.go | 2 +- .../admin_openshiftcluster_vmresize_test.go | 2 +- .../admin_openshiftcluster_vmsizelist_test.go | 2 +- .../admin_openshiftversion_list_test.go | 2 +- .../admin_openshiftversion_put_test.go | 2 +- pkg/frontend/adminactions/generate.go | 2 +- pkg/frontend/adminactions/ocmactions.go | 33 +++ pkg/frontend/asyncoperationresult_get_test.go | 2 +- .../asyncoperationsstatus_get_test.go | 2 +- pkg/frontend/clustermanager_delete_test.go | 2 +- pkg/frontend/clustermanager_get_test.go | 2 +- .../clustermanager_putorpatch_test.go | 2 +- pkg/frontend/fixetcd_test.go | 1 + pkg/frontend/frontend.go | 9 + .../openshiftcluster_applensdetectors_test.go | 2 +- pkg/frontend/openshiftcluster_delete_test.go | 2 +- pkg/frontend/openshiftcluster_get_test.go | 2 +- pkg/frontend/openshiftcluster_list_test.go | 2 +- ...enshiftcluster_preflightvalidation_test.go | 2 +- .../openshiftcluster_putorpatch_test.go | 6 +- .../openshiftclustercredentials_post_test.go | 2 +- ...tclusterkubeconfigcredentials_post_test.go | 2 +- pkg/frontend/openshiftversions_list_test.go | 2 +- pkg/frontend/security_test.go | 2 +- pkg/frontend/subscriptions_put_test.go | 2 +- pkg/util/mocks/adminactions/adminactions.go | 57 ++++- pkg/util/ocm/api/api.go | 198 +++++++++++++++ pkg/util/ocm/api/api_test.go | 159 ++++++++++++ pkg/util/ocm/api/auth.go | 21 ++ pkg/util/ocm/api/auth_test.go | 30 +++ pkg/util/ocm/api/endpoints.go | 36 +++ pkg/util/ocm/api/endpoints_test.go | 50 ++++ pkg/util/ocm/api/mock/api.go | 97 ++++++++ pkg/util/ocm/api/models.go | 113 +++++++++ pkg/util/ocm/api/requestbuilder.go | 73 ++++++ pkg/util/ocm/api/requestbuilder_test.go | 81 ++++++ pkg/util/ocm/api/transport.go | 29 +++ pkg/util/ocm/api/transport_test.go | 43 ++++ pkg/util/ocm/generate.go | 7 + pkg/util/ocm/ocm.go | 52 ++++ pkg/util/ocm/ocm_test.go | 117 +++++++++ 57 files changed, 1630 insertions(+), 39 deletions(-) create mode 100644 pkg/frontend/admin_openshiftcluster_ocm_operations.go create mode 100644 pkg/frontend/admin_openshiftcluster_ocm_operations_test.go create mode 100644 pkg/frontend/adminactions/ocmactions.go create mode 100644 pkg/util/ocm/api/api.go create mode 100644 pkg/util/ocm/api/api_test.go create mode 100644 pkg/util/ocm/api/auth.go create mode 100644 pkg/util/ocm/api/auth_test.go create mode 100644 pkg/util/ocm/api/endpoints.go create mode 100644 pkg/util/ocm/api/endpoints_test.go create mode 100644 pkg/util/ocm/api/mock/api.go create mode 100644 pkg/util/ocm/api/models.go create mode 100644 pkg/util/ocm/api/requestbuilder.go create mode 100644 pkg/util/ocm/api/requestbuilder_test.go create mode 100644 pkg/util/ocm/api/transport.go create mode 100644 pkg/util/ocm/api/transport_test.go create mode 100644 pkg/util/ocm/generate.go create mode 100644 pkg/util/ocm/ocm.go create mode 100644 pkg/util/ocm/ocm_test.go diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go index 5dfd3eb7f2d..a4b532c4cdc 100644 --- a/cmd/aro/rp.go +++ b/cmd/aro/rp.go @@ -173,7 +173,7 @@ func rp(ctx context.Context, log, audit *logrus.Entry) error { if err != nil { return err } - f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, clusterdata.NewParallelEnricher(metrics, _env)) + f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, adminactions.NewOCMActions, clusterdata.NewParallelEnricher(metrics, _env)) if err != nil { return err } diff --git a/pkg/frontend/admin_hive_clusterdeployment_get_test.go b/pkg/frontend/admin_hive_clusterdeployment_get_test.go index cbaa7063fa6..ec0da49e1ad 100644 --- a/pkg/frontend/admin_hive_clusterdeployment_get_test.go +++ b/pkg/frontend/admin_hive_clusterdeployment_get_test.go @@ -90,10 +90,10 @@ func Test_getAdminHiveClusterDeployment(t *testing.T) { clusterManager := mock_hive.NewMockClusterManager(controller) clusterManager.EXPECT().GetClusterDeployment(gomock.Any(), gomock.Any()).Return(&clusterDeployment, nil).Times(tt.expectedGetClusterDeploymentCallCount) f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil) + ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil, nil) } else { f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) } if err != nil { diff --git a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go index 3bef4853bad..a5133279e5f 100644 --- a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go +++ b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go @@ -93,7 +93,7 @@ func TestAdminApproveCSR(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go index d524af4a757..6bafe5a8638 100644 --- a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go @@ -154,7 +154,7 @@ func TestAdminCordonUncordonNode(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go index 4215af7ceaa..766fec544bc 100644 --- a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go +++ b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go @@ -115,7 +115,7 @@ func TestAdminDeleteManagedResource(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_drainnode_test.go b/pkg/frontend/admin_openshiftcluster_drainnode_test.go index b7d94da3587..414d1cdad65 100644 --- a/pkg/frontend/admin_openshiftcluster_drainnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_drainnode_test.go @@ -86,7 +86,7 @@ func TestAdminDrainNode(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go index a26cf2d1d2d..d454645bc53 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go @@ -545,6 +545,7 @@ func TestAdminEtcdCertificateRenew(t *testing.T) { return k, nil }, nil, + nil, nil) if err != nil { t.Fatal(err) @@ -762,6 +763,7 @@ func TestAdminEtcdCertificateRecovery(t *testing.T) { return k, nil }, nil, + nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go index f2f405340ef..30c99aa3f57 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go @@ -174,6 +174,7 @@ func TestAdminEtcdRecovery(t *testing.T) { nil, kubeActionsFactory, nil, + nil, ti.enricher) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go index 604f064b31c..2349076cf64 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go @@ -264,7 +264,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -413,7 +413,7 @@ func TestAdminPostKubernetesObjects(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go index 91b84279719..63a312bd08f 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go @@ -127,7 +127,7 @@ func TestAdminKubernetesGetPodLogs(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go index c39934e30ef..2e086a5ae46 100644 --- a/pkg/frontend/admin_openshiftcluster_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_list_test.go @@ -124,7 +124,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.throwsError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations.go b/pkg/frontend/admin_openshiftcluster_ocm_operations.go new file mode 100644 index 00000000000..08415d351df --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations.go @@ -0,0 +1,149 @@ +package frontend + +import ( + "context" + "encoding/json" + "fmt" + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" + ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "github.com/Azure/ARO-RP/pkg/util/pullsecret" + "github.com/ghodss/yaml" + "github.com/go-chi/chi/v5" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kruntime "k8s.io/apimachinery/pkg/runtime" + "net/http" + "path/filepath" + "strings" +) + +func (f *frontend) getOCMClusterInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + + ocmActions, err := f.getOCMActions(ctx, r, log) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + clusterInfo, err := ocmActions.GetClusterInfoWithUpgradePolicies(ctx) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + clusterInfoBytes, err := json.Marshal(clusterInfo) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + adminReply(log, w, nil, clusterInfoBytes, nil) +} + +func (f *frontend) postAdminOCMCancelUpgradePolicy(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + policyID := r.URL.Query().Get("policyID") + + ocmActions, err := f.getOCMActions(ctx, r, log) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + response, err := ocmActions.CancelClusterUpgradePolicy(ctx, policyID) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + responseBytes, err := json.Marshal(response) + if err != nil { + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%v", err) + return + } + + adminReply(log, w, nil, responseBytes, nil) +} + +func (f *frontend) getOCMActions(ctx context.Context, r *http.Request, log *logrus.Entry) (adminactions.OCMActions, error) { + resType, resName, resGroupName := chi.URLParam(r, "resourceType"), chi.URLParam(r, "resourceName"), chi.URLParam(r, "resourceGroupName") + + r.URL.Path = filepath.Dir(r.URL.Path) + resourceID := strings.TrimPrefix(r.URL.Path, "/admin") + doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID) + switch { + case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound): + return nil, api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", resType, resName, resGroupName) + case err != nil: + return nil, err + } + + clusterID := doc.ID + k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster) + if err != nil { + return nil, err + } + + ps, err := k.KubeGet(ctx, "Secret", "openshift-config", "pull-secret") + if err != nil { + return nil, err + } + + var u unstructured.Unstructured + if err := json.Unmarshal(ps, &u); err != nil { + return nil, err + } + var secret corev1.Secret + if err := kruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &secret); err != nil { + return nil, err + } + + psKeys, err := pullsecret.UnmarshalSecretData(&secret) + if err != nil { + return nil, err + } + + token, ok := psKeys["cloud.openshift.com"] + if !ok { + return nil, fmt.Errorf("token not found in pull secret") + } + + cm, err := k.KubeGet(ctx, "ConfigMap", "openshift-managed-upgrade-operator", "managed-upgrade-operator-config") + if err != nil { + return nil, err + } + + if err := json.Unmarshal(cm, &u); err != nil { + return nil, err + } + var configMap corev1.ConfigMap + if err := kruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &configMap); err != nil { + return nil, err + } + + configYaml, ok := configMap.Data["config.yaml"] + if !ok { + return nil, fmt.Errorf("config.yaml not found") + } + + var config ocmapi.Config + err = yaml.Unmarshal([]byte(configYaml), &config) + if err != nil { + return nil, err + } + + // default OCM base URL jic pending upgrade exists + ocmBaseUrl := "https://api.openshift.com" + if config.ConfigManager.Source == "OCM" { + ocmBaseUrl = config.ConfigManager.OcmBaseURL + } + + return f.ocmActionsFactory(clusterID, ocmBaseUrl, token), nil +} diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go new file mode 100644 index 00000000000..477022d7b85 --- /dev/null +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go @@ -0,0 +1,234 @@ +package frontend + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" + testdatabase "github.com/Azure/ARO-RP/test/database" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "net/http" + "strings" + "testing" + "time" + + "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + "github.com/golang/mock/gomock" + "github.com/sirupsen/logrus" +) + +func createSecretBytesWithAuths(authsJson string) []byte { + secret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + Data: map[string][]byte{ + ".dockerconfigjson": []byte(authsJson), + }, + } + secretBytes, _ := json.Marshal(secret) + return secretBytes +} + +func createConfigMapBytesWithConfigYaml(configYaml string) []byte { + configMap := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + Data: map[string]string{ + "config.yaml": configYaml, + }, + } + configMapBytes, _ := json.Marshal(configMap) + return configMapBytes +} + +func TestOCMOperations(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + mockTenantID := "00000000-0000-0000-0000-000000000000" + clusterInfo := &ocmapi.ClusterInfo{ + Id: "testClusterID", + Name: "testClusterName", + ExternalID: "testExternalID", + DisplayName: "Test Cluster", + CreationTimestamp: time.Now(), + ActivityTimestamp: time.Now(), + OpenshiftVersion: "4.8.0", + Version: ocmapi.ClusterVersion{ + Id: "4.8.0", + ChannelGroup: "stable", + AvailableUpgrades: []string{"4.9.0", "4.10.0"}, + EndOfLifeTimestamp: time.Now().AddDate(1, 0, 0), // One year from now + }, + NodeDrainGracePeriod: ocmapi.NodeDrainGracePeriod{ + Value: 60, + Unit: "minutes", + }, + UpgradePolicies: []ocmapi.UpgradePolicy{ + { + Id: "testPolicyID", + UpgradePolicyStatus: ocmapi.UpgradePolicyStatus{ + State: "scheduled", + Description: "Upgrade scheduled", + }, + }, + { + Id: "testPolicyID2", + UpgradePolicyStatus: ocmapi.UpgradePolicyStatus{ + State: "running", + Description: "Upgrade is running", + }, + }, + }, + } + clusterInfoBytes, _ := json.Marshal(clusterInfo) + + cancelUpgradeReply := &ocmapi.CancelUpgradeResponse{ + Kind: "CancelUpgradeResponse", + Value: "cancelled", + Description: "Manually cancelled by SRE", + } + cancelUpgradeReplyBytes, _ := json.Marshal(cancelUpgradeReply) + + ctx := context.Background() + + testCases := []struct { + name string + resourceID string + fixture func(*testdatabase.Fixture) + mocks func(*mock_adminactions.MockOCMActions, *mock_adminactions.MockKubeActions) + endpoint string + httpMethod string + wantStatusCode int + wantResponse []byte + wantError string + }{ + { + name: "Get Cluster Info", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(ocmActions *mock_adminactions.MockOCMActions, kubeActions *mock_adminactions.MockKubeActions) { + kubeActions.EXPECT().KubeGet(gomock.Any(), "Secret", "openshift-config", "pull-secret").Return(createSecretBytesWithAuths(`{"auths":{"cloud.openshift.com":{"auth":"mock-token"}}}`), nil) + kubeActions.EXPECT().KubeGet(gomock.Any(), "ConfigMap", "openshift-managed-upgrade-operator", "managed-upgrade-operator-config").Return(createConfigMapBytesWithConfigYaml(`configManager: + source: LOCAL + localConfigName: managed-upgrade-config`), nil) + ocmActions.EXPECT().GetClusterInfoWithUpgradePolicies(gomock.Any()).Return(clusterInfo, nil) + }, + endpoint: "/getocmclusterinfowithupgradepolicies", + httpMethod: http.MethodGet, + wantStatusCode: http.StatusOK, + wantResponse: clusterInfoBytes, + }, + { + name: "Cancel Cluster Upgrade Policy", + resourceID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + fixture: func(f *testdatabase.Fixture) { + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: fmt.Sprintf("/subscriptions/%s/resourceGroups/test-cluster", mockSubID), + }, + }, + }, + }) + + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + Properties: &api.SubscriptionProperties{ + TenantID: mockTenantID, + }, + }, + }) + }, + mocks: func(ocmActions *mock_adminactions.MockOCMActions, kubeActions *mock_adminactions.MockKubeActions) { + kubeActions.EXPECT().KubeGet(gomock.Any(), "Secret", "openshift-config", "pull-secret").Return(createSecretBytesWithAuths(`{"auths":{"cloud.openshift.com":{"auth":"mock-token"}}}`), nil) + kubeActions.EXPECT().KubeGet(gomock.Any(), "ConfigMap", "openshift-managed-upgrade-operator", "managed-upgrade-operator-config").Return(createConfigMapBytesWithConfigYaml(`configManager: + source: LOCAL + localConfigName: managed-upgrade-config`), nil) + ocmActions.EXPECT().CancelClusterUpgradePolicy(gomock.Any(), clusterInfo.UpgradePolicies[0].Id).Return(cancelUpgradeReply, nil) + }, + endpoint: fmt.Sprintf("/cancelocmupgradepolicy?policyID=%s", clusterInfo.UpgradePolicies[0].Id), + httpMethod: http.MethodPost, + wantStatusCode: http.StatusOK, + wantResponse: cancelUpgradeReplyBytes, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions() + defer ti.done() + + ocmActions := mock_adminactions.NewMockOCMActions(ti.controller) + kubeActions := mock_adminactions.NewMockKubeActions(ti.controller) + tc.mocks(ocmActions, kubeActions) + + err := ti.buildFixtures(tc.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + return kubeActions, nil + }, nil, func(clusterID, ocmBaseUrl, token string) adminactions.OCMActions { + return ocmActions + }, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(tc.httpMethod, + fmt.Sprintf("https://server/admin%s%s", tc.resourceID, tc.endpoint), + nil, nil) + if err != nil { + t.Error(err) + } + + // remove trailing newline added by reply method in frontend pkg. + b = bytes.TrimSuffix(b, []byte("\n")) + + err = validateResponse(resp, b, tc.wantStatusCode, tc.wantError, tc.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go index fef1bdcf051..2b1f4825323 100644 --- a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go @@ -86,7 +86,8 @@ func TestAdminRedeployVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, + nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_resources_list_test.go b/pkg/frontend/admin_openshiftcluster_resources_list_test.go index 0fdc9a64f8f..4b9b42d4310 100644 --- a/pkg/frontend/admin_openshiftcluster_resources_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_resources_list_test.go @@ -95,7 +95,7 @@ func TestAdminListResourcesList(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) mockResponder := mock_frontend.NewMockStreamResponder(ti.controller) mockResponder.EXPECT().AdminReplyStream(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) f.streamResponder = mockResponder diff --git a/pkg/frontend/admin_openshiftcluster_startvm_test.go b/pkg/frontend/admin_openshiftcluster_startvm_test.go index cf0eadb9f01..3c1f8b7696e 100644 --- a/pkg/frontend/admin_openshiftcluster_startvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_startvm_test.go @@ -86,7 +86,7 @@ func TestAdminStartVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_stopvm_test.go b/pkg/frontend/admin_openshiftcluster_stopvm_test.go index 77fcd351d28..ad7fd6cf033 100644 --- a/pkg/frontend/admin_openshiftcluster_stopvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_stopvm_test.go @@ -88,7 +88,7 @@ func TestAdminStopVM(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_vmresize_test.go b/pkg/frontend/admin_openshiftcluster_vmresize_test.go index 0d5946f31eb..ac9fd4a3315 100644 --- a/pkg/frontend/admin_openshiftcluster_vmresize_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmresize_test.go @@ -205,7 +205,7 @@ func TestAdminVMResize(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go index 77fa4ea51b8..591b0e43ffc 100644 --- a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go @@ -138,7 +138,7 @@ func TestAdminListVMSizeList(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftversion_list_test.go b/pkg/frontend/admin_openshiftversion_list_test.go index c851c806bb4..46a2473b618 100644 --- a/pkg/frontend/admin_openshiftversion_list_test.go +++ b/pkg/frontend/admin_openshiftversion_list_test.go @@ -110,7 +110,7 @@ func TestOpenShiftVersionList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftversion_put_test.go b/pkg/frontend/admin_openshiftversion_put_test.go index e92e10cb909..11b5d45c0c0 100644 --- a/pkg/frontend/admin_openshiftversion_put_test.go +++ b/pkg/frontend/admin_openshiftversion_put_test.go @@ -266,7 +266,7 @@ func TestOpenShiftVersionPut(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/adminactions/generate.go b/pkg/frontend/adminactions/generate.go index 0462f2241e3..d5dde3f8781 100644 --- a/pkg/frontend/adminactions/generate.go +++ b/pkg/frontend/adminactions/generate.go @@ -4,5 +4,5 @@ package adminactions // Licensed under the Apache License 2.0. //go:generate rm -rf ../../util/mocks/$GOPACKAGE -//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE KubeActions,AzureActions +//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE KubeActions,AzureActions,OCMActions //go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../util/mocks/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/frontend/adminactions/ocmactions.go b/pkg/frontend/adminactions/ocmactions.go new file mode 100644 index 00000000000..1fb6a0066b8 --- /dev/null +++ b/pkg/frontend/adminactions/ocmactions.go @@ -0,0 +1,33 @@ +package adminactions + +import ( + "context" + "github.com/Azure/ARO-RP/pkg/util/ocm" + "github.com/Azure/ARO-RP/pkg/util/ocm/api" +) + +type OCMActions interface { + GetClusterInfoWithUpgradePolicies(ctx context.Context) (*api.ClusterInfo, error) + CancelClusterUpgradePolicy(ctx context.Context, policyID string) (*api.CancelUpgradeResponse, error) +} + +type ocmActions struct { + clusterID string + apiInstance api.API +} + +func NewOCMActions(clusterID, baseURL, token string) OCMActions { + apiInstance := api.NewClient(clusterID, baseURL, token) + return &ocmActions{ + clusterID: clusterID, + apiInstance: apiInstance, + } +} + +func (o *ocmActions) GetClusterInfoWithUpgradePolicies(ctx context.Context) (*api.ClusterInfo, error) { + return ocm.GetClusterInfoWithUpgradePolices(ctx, o.apiInstance) +} + +func (o *ocmActions) CancelClusterUpgradePolicy(ctx context.Context, policyID string) (*api.CancelUpgradeResponse, error) { + return ocm.CancelClusterUpgradePolicy(ctx, o.apiInstance, policyID) +} diff --git a/pkg/frontend/asyncoperationresult_get_test.go b/pkg/frontend/asyncoperationresult_get_test.go index 516fcac902a..d14dd6ed0fb 100644 --- a/pkg/frontend/asyncoperationresult_get_test.go +++ b/pkg/frontend/asyncoperationresult_get_test.go @@ -136,7 +136,7 @@ func TestGetAsyncOperationResult(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/asyncoperationsstatus_get_test.go b/pkg/frontend/asyncoperationsstatus_get_test.go index f6a6d3a9a1f..4d83986078e 100644 --- a/pkg/frontend/asyncoperationsstatus_get_test.go +++ b/pkg/frontend/asyncoperationsstatus_get_test.go @@ -183,7 +183,7 @@ func TestGetAsyncOperationsStatus(t *testing.T) { ti.asyncOperationsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_delete_test.go b/pkg/frontend/clustermanager_delete_test.go index 7c85ef3e453..d08abccb2ac 100644 --- a/pkg/frontend/clustermanager_delete_test.go +++ b/pkg/frontend/clustermanager_delete_test.go @@ -120,7 +120,7 @@ func TestDeleteClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_get_test.go b/pkg/frontend/clustermanager_get_test.go index eaf73d78d01..5365befe5e7 100644 --- a/pkg/frontend/clustermanager_get_test.go +++ b/pkg/frontend/clustermanager_get_test.go @@ -123,7 +123,7 @@ func TestGetClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_putorpatch_test.go b/pkg/frontend/clustermanager_putorpatch_test.go index 7708c7e459e..24f3ce5cdb2 100644 --- a/pkg/frontend/clustermanager_putorpatch_test.go +++ b/pkg/frontend/clustermanager_putorpatch_test.go @@ -201,7 +201,7 @@ func TestPutOrPatchClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/fixetcd_test.go b/pkg/frontend/fixetcd_test.go index b25b9a482b1..82a96edd383 100644 --- a/pkg/frontend/fixetcd_test.go +++ b/pkg/frontend/fixetcd_test.go @@ -530,6 +530,7 @@ func TestFixEtcd(t *testing.T) { nil, nil, nil, + nil, ti.enricher) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index 5d300085111..c63e35101ee 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -45,6 +45,8 @@ type kubeActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster type azureActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) +type ocmActionsFactory func(clusterID, ocmBaseUrl, token string) adminactions.OCMActions + type frontend struct { auditLog *logrus.Entry baseLog *logrus.Entry @@ -75,6 +77,7 @@ type frontend struct { hiveClusterManager hive.ClusterManager kubeActionsFactory kubeActionsFactory azureActionsFactory azureActionsFactory + ocmActionsFactory ocmActionsFactory skuValidator SkuValidator quotaValidator QuotaValidator @@ -124,6 +127,7 @@ func NewFrontend(ctx context.Context, hiveClusterManager hive.ClusterManager, kubeActionsFactory kubeActionsFactory, azureActionsFactory azureActionsFactory, + ocmActionsFactory ocmActionsFactory, enricher clusterdata.BestEffortEnricher, ) (*frontend, error) { f := &frontend{ @@ -160,6 +164,7 @@ func NewFrontend(ctx context.Context, hiveClusterManager: hiveClusterManager, kubeActionsFactory: kubeActionsFactory, azureActionsFactory: azureActionsFactory, + ocmActionsFactory: ocmActionsFactory, quotaValidator: quotaValidator{}, skuValidator: skuValidator{}, @@ -319,6 +324,10 @@ func (f *frontend) chiAuthenticatedRoutes(router chi.Router) { r.Get("/skus", f.getAdminOpenShiftClusterVMResizeOptions) + r.Get("/getocmclusterinfowithupgradepolicies", f.getOCMClusterInfo) + + r.With(f.maintenanceMiddleware.UnplannedMaintenanceSignal).Post("/cancelocmupgradepolicy", f.postAdminOCMCancelUpgradePolicy) + // We don't emit unplanned maintenance signal for resize since it is only used for planned maintenance r.Post("/resize", f.postAdminOpenShiftClusterVMResize) diff --git a/pkg/frontend/openshiftcluster_applensdetectors_test.go b/pkg/frontend/openshiftcluster_applensdetectors_test.go index fc3bc445548..f11e2e442f5 100644 --- a/pkg/frontend/openshiftcluster_applensdetectors_test.go +++ b/pkg/frontend/openshiftcluster_applensdetectors_test.go @@ -99,7 +99,7 @@ func TestAppLensDetectors(t *testing.T) { f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil - }, nil) + }, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/openshiftcluster_delete_test.go b/pkg/frontend/openshiftcluster_delete_test.go index e9774b58b76..f579a5b3eee 100644 --- a/pkg/frontend/openshiftcluster_delete_test.go +++ b/pkg/frontend/openshiftcluster_delete_test.go @@ -114,7 +114,7 @@ func TestDeleteOpenShiftCluster(t *testing.T) { ti.subscriptionsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_get_test.go b/pkg/frontend/openshiftcluster_get_test.go index 65fff16875c..68696c3dd62 100644 --- a/pkg/frontend/openshiftcluster_get_test.go +++ b/pkg/frontend/openshiftcluster_get_test.go @@ -95,7 +95,7 @@ func TestGetOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go index c22c16c99f7..f64a7b5a482 100644 --- a/pkg/frontend/openshiftcluster_list_test.go +++ b/pkg/frontend/openshiftcluster_list_test.go @@ -204,7 +204,7 @@ func TestListOpenShiftCluster(t *testing.T) { aead := testdatabase.NewFakeAEAD() - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_preflightvalidation_test.go b/pkg/frontend/openshiftcluster_preflightvalidation_test.go index fe70615c31c..e64994b3a6e 100644 --- a/pkg/frontend/openshiftcluster_preflightvalidation_test.go +++ b/pkg/frontend/openshiftcluster_preflightvalidation_test.go @@ -158,7 +158,7 @@ func TestPreflightValidation(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go index f7c10ade968..37cb8e5e2f4 100644 --- a/pkg/frontend/openshiftcluster_putorpatch_test.go +++ b/pkg/frontend/openshiftcluster_putorpatch_test.go @@ -1727,7 +1727,7 @@ func TestPutOrPatchOpenShiftClusterAdminAPI(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -2800,7 +2800,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -3133,7 +3133,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclustercredentials_post_test.go b/pkg/frontend/openshiftclustercredentials_post_test.go index 54f445928fd..545e48975ac 100644 --- a/pkg/frontend/openshiftclustercredentials_post_test.go +++ b/pkg/frontend/openshiftclustercredentials_post_test.go @@ -267,7 +267,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go index c233024ba3e..edccaf70487 100644 --- a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go +++ b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go @@ -242,7 +242,7 @@ func TestPostOpenShiftClusterKubeConfigCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftversions_list_test.go b/pkg/frontend/openshiftversions_list_test.go index 3f1413c9003..0988363f61e 100644 --- a/pkg/frontend/openshiftversions_list_test.go +++ b/pkg/frontend/openshiftversions_list_test.go @@ -78,7 +78,7 @@ func TestListInstallVersions(t *testing.T) { ti := newTestInfra(t).WithSubscriptions().WithOpenShiftVersions() defer ti.done() - frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/security_test.go b/pkg/frontend/security_test.go index ac47d43e28d..53ec0b207c1 100644 --- a/pkg/frontend/security_test.go +++ b/pkg/frontend/security_test.go @@ -77,7 +77,7 @@ func TestSecurity(t *testing.T) { log := logrus.NewEntry(logrus.StandardLogger()) auditHook, auditEntry := testlog.NewAudit() - f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/subscriptions_put_test.go b/pkg/frontend/subscriptions_put_test.go index cd236ba816a..3ea29afceb0 100644 --- a/pkg/frontend/subscriptions_put_test.go +++ b/pkg/frontend/subscriptions_put_test.go @@ -244,7 +244,7 @@ func TestPutSubscription(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/util/mocks/adminactions/adminactions.go b/pkg/util/mocks/adminactions/adminactions.go index e5ac3ab3a67..63316f41124 100644 --- a/pkg/util/mocks/adminactions/adminactions.go +++ b/pkg/util/mocks/adminactions/adminactions.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: KubeActions,AzureActions) +// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: KubeActions,AzureActions,OCMActions) // Package mock_adminactions is a generated GoMock package. package mock_adminactions @@ -18,6 +18,8 @@ import ( unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" + + api "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) // MockKubeActions is a mock of KubeActions interface. @@ -425,3 +427,56 @@ func (mr *MockAzureActionsMockRecorder) WriteToStream(arg0, arg1 interface{}) *g mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteToStream", reflect.TypeOf((*MockAzureActions)(nil).WriteToStream), arg0, arg1) } + +// MockOCMActions is a mock of OCMActions interface. +type MockOCMActions struct { + ctrl *gomock.Controller + recorder *MockOCMActionsMockRecorder +} + +// MockOCMActionsMockRecorder is the mock recorder for MockOCMActions. +type MockOCMActionsMockRecorder struct { + mock *MockOCMActions +} + +// NewMockOCMActions creates a new mock instance. +func NewMockOCMActions(ctrl *gomock.Controller) *MockOCMActions { + mock := &MockOCMActions{ctrl: ctrl} + mock.recorder = &MockOCMActionsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOCMActions) EXPECT() *MockOCMActionsMockRecorder { + return m.recorder +} + +// CancelClusterUpgradePolicy mocks base method. +func (m *MockOCMActions) CancelClusterUpgradePolicy(arg0 context.Context, arg1 string) (*api.CancelUpgradeResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelClusterUpgradePolicy", arg0, arg1) + ret0, _ := ret[0].(*api.CancelUpgradeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelClusterUpgradePolicy indicates an expected call of CancelClusterUpgradePolicy. +func (mr *MockOCMActionsMockRecorder) CancelClusterUpgradePolicy(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelClusterUpgradePolicy", reflect.TypeOf((*MockOCMActions)(nil).CancelClusterUpgradePolicy), arg0, arg1) +} + +// GetClusterInfoWithUpgradePolicies mocks base method. +func (m *MockOCMActions) GetClusterInfoWithUpgradePolicies(arg0 context.Context) (*api.ClusterInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterInfoWithUpgradePolicies", arg0) + ret0, _ := ret[0].(*api.ClusterInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterInfoWithUpgradePolicies indicates an expected call of GetClusterInfoWithUpgradePolicies. +func (mr *MockOCMActionsMockRecorder) GetClusterInfoWithUpgradePolicies(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterInfoWithUpgradePolicies", reflect.TypeOf((*MockOCMActions)(nil).GetClusterInfoWithUpgradePolicies), arg0) +} diff --git a/pkg/util/ocm/api/api.go b/pkg/util/ocm/api/api.go new file mode 100644 index 00000000000..ec887eb3cd9 --- /dev/null +++ b/pkg/util/ocm/api/api.go @@ -0,0 +1,198 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" +) + +type API interface { + GetClusterList(ctx context.Context, filter map[string]string) (*ClusterList, error) + GetClusterUpgradePolicies(ctx context.Context, ocmClusterID string) (*UpgradePolicyList, error) + CancelClusterUpgradePolicy(ctx context.Context, ocmClusterID, policyID string) (*CancelUpgradeResponse, error) + GetClusterUpgradePolicyState(ctx context.Context, ocmClusterID, policyID string) (*UpgradePolicyState, error) +} + +var _ API = (*Client)(nil) + +type Client struct { + httpClient *http.Client + baseURL string + clusterID string +} + +func NewClient(clusterID, baseURL, token string) *Client { + httpClient := &http.Client{ + Transport: NewAccessTokenTransport(NewAccessToken(clusterID, token)), + } + + return &Client{ + httpClient: httpClient, + baseURL: baseURL, + clusterID: clusterID, + } +} + +func (c *Client) Send(req *http.Request) ([]byte, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Check the status code and handle non-2xx responses as errors + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("unexpected response status code: %d, error reading response body: %w", resp.StatusCode, err) + } + return nil, fmt.Errorf("unexpected response status code: %d, body %s", resp.StatusCode, responseBody) + } + + return io.ReadAll(resp.Body) +} + +func (c *Client) GetClusterList(ctx context.Context, filter map[string]string) (*ClusterList, error) { + if len(filter) == 0 { + filter = map[string]string{ + "page": "1", + "size": "1", + "search": fmt.Sprintf("external_id='%s'", c.clusterID), + } + } + + rb := NewRequestBuilder(http.MethodGet, c.baseURL). + SetContext(ctx). + SetEndpoint(GetClusterListEndpointV1). + AddHeader("Content-Type", "application/json"). + AddHeader("Accept", "application/json") + + for k, v := range filter { + rb.AddParam(k, v) + } + + request, err := rb.Build() + if err != nil { + return nil, err + } + + clusterListBytes, err := c.Send(request) + if err != nil { + return nil, err + } + var clusterList *ClusterList + if err := json.Unmarshal(clusterListBytes, &clusterList); err != nil { + return nil, err + } + + return clusterList, nil +} + +func (c *Client) GetClusterUpgradePolicies(ctx context.Context, ocmClusterID string) (*UpgradePolicyList, error) { + endpoint, err := BuildEndpoint(GetClusterUpgradePoliciesEndpointV1, map[string]string{ + "ocmClusterID": ocmClusterID, + }) + if err != nil { + return nil, err + } + + rb := NewRequestBuilder(http.MethodGet, c.baseURL). + SetContext(ctx). + SetEndpoint(endpoint). + AddHeader("Content-Type", "application/json"). + AddHeader("Accept", "application/json") + + request, err := rb.Build() + if err != nil { + return nil, err + } + + upgradePolicyListBytes, err := c.Send(request) + if err != nil { + return nil, err + } + var upgradePolicyList *UpgradePolicyList + if err := json.Unmarshal(upgradePolicyListBytes, &upgradePolicyList); err != nil { + return nil, err + } + + return upgradePolicyList, nil +} + +func (c *Client) CancelClusterUpgradePolicy(ctx context.Context, ocmClusterID, policyID string) (*CancelUpgradeResponse, error) { + cancelDescription := map[string]interface{}{ + "Value": "cancelled", + "Description": "Manually cancelled by SRE", + } + cancelDescriptionBody, _ := json.Marshal(cancelDescription) + + endpoint, err := BuildEndpoint(CancelClusterUpgradePolicyEndpointV1, map[string]string{ + "ocmClusterID": ocmClusterID, + "policyID": policyID, + }) + if err != nil { + return nil, err + } + + rb := NewRequestBuilder(http.MethodPatch, c.baseURL). + SetContext(ctx). + SetEndpoint(endpoint). + AddHeader("Content-Type", "application/json"). + AddHeader("Accept", "application/json"). + SetBody(cancelDescriptionBody) + + request, err := rb.Build() + if err != nil { + return nil, err + } + + cancelUpgradeResponseBytes, err := c.Send(request) + if err != nil { + return nil, err + } + + var cancelUpgradeResponse *CancelUpgradeResponse + if err := json.Unmarshal(cancelUpgradeResponseBytes, &cancelUpgradeResponse); err != nil { + return nil, err + } + + return cancelUpgradeResponse, nil +} + +func (c *Client) GetClusterUpgradePolicyState(ctx context.Context, ocmClusterID, policyID string) (*UpgradePolicyState, error) { + endpoint, err := BuildEndpoint(GetClusterUpgradePolicyStateEndpointV1, map[string]string{ + "ocmClusterID": ocmClusterID, + "policyID": policyID, + }) + if err != nil { + return nil, err + } + + rb := NewRequestBuilder(http.MethodGet, c.baseURL). + SetContext(ctx). + SetEndpoint(endpoint). + AddHeader("Content-Type", "application/json"). + AddHeader("Accept", "application/json") + + request, err := rb.Build() + if err != nil { + return nil, err + } + + upgradePolicyStateBytes, err := c.Send(request) + if err != nil { + return nil, err + } + var upgradePolicyState *UpgradePolicyState + if err := json.Unmarshal(upgradePolicyStateBytes, &upgradePolicyState); err != nil { + return nil, err + } + + return upgradePolicyState, nil +} + +func (c *Client) GetBaseURL() string { + return c.baseURL +} diff --git a/pkg/util/ocm/api/api_test.go b/pkg/util/ocm/api/api_test.go new file mode 100644 index 00000000000..dcbe9639634 --- /dev/null +++ b/pkg/util/ocm/api/api_test.go @@ -0,0 +1,159 @@ +package api_test + +import ( + "context" + "encoding/json" + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestAPI(t *testing.T) { + clusterList := api.ClusterList{ + Items: []api.ClusterInfo{{ + Id: "testClusterID", + Name: "testClusterName", + }}, + } + clusterListBytes, _ := json.Marshal(clusterList) + + upgradePolicies := api.UpgradePolicyList{ + Items: []api.UpgradePolicy{{ + Id: "testPolicyID", + }}, + } + upgradePoliciesBytes, _ := json.Marshal(upgradePolicies) + + upgradePolicyState := api.UpgradePolicyState{ + Kind: "testKind", + UpgradePolicyStatus: api.UpgradePolicyStatus{ + State: "testState", + Description: "testDescription", + }, + } + upgradePolicyStateBytes, _ := json.Marshal(upgradePolicyState) + + cancelUpgradeResponse := api.CancelUpgradeResponse{ + Kind: "testKind", + Value: "cancelled", + Description: "Manually cancelled by SRE", + } + cancelUpgradeResponseBytes, _ := json.Marshal(cancelUpgradeResponse) + + getClusterInfoEndpoint, err := api.BuildEndpoint(api.GetClusterListEndpointV1, map[string]string{}) + if err != nil { + t.Fatalf("BuildEndpoint failed: %v", err) + } + getClusterUpgradePoliciesEndpoint, err := api.BuildEndpoint(api.GetClusterUpgradePoliciesEndpointV1, map[string]string{"ocmClusterID": "testClusterID"}) + if err != nil { + t.Fatalf("BuildEndpoint failed: %v", err) + } + getClusterUpgradePolicyStateEndpoint, err := api.BuildEndpoint(api.GetClusterUpgradePolicyStateEndpointV1, map[string]string{"ocmClusterID": "testClusterID", "policyID": "testPolicyID"}) + if err != nil { + t.Fatalf("BuildEndpoint failed: %v", err) + } + cancelClusterUpgradePolicyEndpoint, err := api.BuildEndpoint(api.CancelClusterUpgradePolicyEndpointV1, map[string]string{"ocmClusterID": "testClusterID", "policyID": "testPolicyID"}) + if err != nil { + t.Fatalf("BuildEndpoint failed: %v", err) + } + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + switch { + case req.URL.Path == getClusterInfoEndpoint && req.Method == http.MethodGet: + _, _ = rw.Write(clusterListBytes) + case req.URL.Path == getClusterUpgradePoliciesEndpoint && req.Method == http.MethodGet: + _, _ = rw.Write(upgradePoliciesBytes) + case req.URL.Path == cancelClusterUpgradePolicyEndpoint && req.Method == http.MethodPatch: + _, _ = rw.Write(cancelUpgradeResponseBytes) + case req.URL.Path == getClusterUpgradePolicyStateEndpoint && req.Method == http.MethodGet: + _, _ = rw.Write(upgradePolicyStateBytes) + case req.URL.Path == "/non-200": + http.Error(rw, "Internal Server Error", http.StatusInternalServerError) + default: + http.Error(rw, "Not Found", http.StatusNotFound) + } + })) + defer server.Close() + + testAPI := api.NewClient("testClusterID", server.URL, "testToken") + + testCases := []struct { + name string + runTest func() (interface{}, error) + expected interface{} + expectErr bool + }{ + { + name: "Test GetClusterList", + runTest: func() (interface{}, error) { + return testAPI.GetClusterList(context.Background(), map[string]string{"key": "value"}) + }, + expected: &clusterList, + expectErr: false, + }, + { + name: "Test GetClusterUpgradePolicies", + runTest: func() (interface{}, error) { + return testAPI.GetClusterUpgradePolicies(context.Background(), "testClusterID") + }, + expected: &upgradePolicies, + expectErr: false, + }, + { + name: "Test CancelClusterUpgradePolicy", + runTest: func() (interface{}, error) { + return testAPI.CancelClusterUpgradePolicy(context.Background(), "testClusterID", "testPolicyID") + }, + expected: &cancelUpgradeResponse, + expectErr: false, + }, + { + name: "Test GetClusterUpgradePolicyState", + runTest: func() (interface{}, error) { + return testAPI.GetClusterUpgradePolicyState(context.Background(), "testClusterID", "testPolicyID") + }, + expected: &upgradePolicyState, + expectErr: false, + }, + { + name: "Test Non-200 HTTP Status Code", + runTest: func() (interface{}, error) { + rb := api.NewRequestBuilder(http.MethodGet, server.URL). + SetEndpoint("/non-200"). + AddHeader("Content-Type", "application/json"). + AddHeader("Accept", "application/json") + + request, err := rb.Build() + if err != nil { + return nil, err + } + + _, err = testAPI.Send(request) + return nil, err + }, + expected: nil, + expectErr: true, + }, + { + name: "Test GetBaseURL", + runTest: func() (interface{}, error) { + return testAPI.GetBaseURL(), nil + }, + expected: server.URL, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual, err := tc.runTest() + if (err != nil) != tc.expectErr { + t.Errorf("Expected error: %v, got: %v", tc.expectErr, err) + } + if !reflect.DeepEqual(actual, tc.expected) { + t.Errorf("Got %v, expect %v", actual, tc.expected) + } + }) + } +} diff --git a/pkg/util/ocm/api/auth.go b/pkg/util/ocm/api/auth.go new file mode 100644 index 00000000000..8bc88a897ed --- /dev/null +++ b/pkg/util/ocm/api/auth.go @@ -0,0 +1,21 @@ +package api + +import "fmt" + +var _ fmt.Stringer = (*AccessToken)(nil) + +type AccessToken struct { + clusterID string + token string +} + +func NewAccessToken(clusterID, token string) *AccessToken { + return &AccessToken{ + clusterID: clusterID, + token: token, + } +} + +func (t *AccessToken) String() string { + return fmt.Sprintf("%s:%s", t.clusterID, t.token) +} diff --git a/pkg/util/ocm/api/auth_test.go b/pkg/util/ocm/api/auth_test.go new file mode 100644 index 00000000000..8ddfdd6295e --- /dev/null +++ b/pkg/util/ocm/api/auth_test.go @@ -0,0 +1,30 @@ +package api_test + +import ( + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "testing" +) + +func TestAccessToken(t *testing.T) { + testCases := []struct { + name string + clusterID string + token string + expAccessToken string + }{ + { + name: "Test AccessToken String", + clusterID: "123", + token: "abc", + expAccessToken: "123:abc", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + accessToken := api.NewAccessToken(tc.clusterID, tc.token) + if accessToken.String() != tc.expAccessToken { + t.Errorf("AccessToken got %v, expect %v", accessToken.String(), tc.expAccessToken) + } + }) + } +} diff --git a/pkg/util/ocm/api/endpoints.go b/pkg/util/ocm/api/endpoints.go new file mode 100644 index 00000000000..0e9c0e79c99 --- /dev/null +++ b/pkg/util/ocm/api/endpoints.go @@ -0,0 +1,36 @@ +package api + +import ( + "bytes" + "fmt" + "text/template" +) + +const ( + GetClusterListEndpointV1 = "/api/clusters_mgmt/v1/clusters" + GetClusterUpgradePoliciesEndpointV1 = GetClusterListEndpointV1 + "/{{required \"ocmClusterID\"}}/upgrade_policies" + CancelClusterUpgradePolicyEndpointV1 = GetClusterUpgradePoliciesEndpointV1 + "/{{required \"policyID\"}}/state" + GetClusterUpgradePolicyStateEndpointV1 = GetClusterUpgradePoliciesEndpointV1 + "/{{required \"policyID\"}}/state" +) + +func BuildEndpoint(templateStr string, params map[string]string) (string, error) { + tmplFuncs := template.FuncMap{ + "required": func(key string) (string, error) { + if value, ok := params[key]; ok { + return value, nil + } + return "", fmt.Errorf("missing required parameter: %s", key) + }, + } + tmpl, err := template.New("endpoint").Funcs(tmplFuncs).Parse(templateStr) + if err != nil { + return "", err + } + + var endpoint bytes.Buffer + if err := tmpl.Execute(&endpoint, params); err != nil { + return "", err + } + + return endpoint.String(), nil +} diff --git a/pkg/util/ocm/api/endpoints_test.go b/pkg/util/ocm/api/endpoints_test.go new file mode 100644 index 00000000000..5ea8b77e634 --- /dev/null +++ b/pkg/util/ocm/api/endpoints_test.go @@ -0,0 +1,50 @@ +package api_test + +import ( + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "testing" +) + +func TestBuildEndpoint(t *testing.T) { + testCases := []struct { + name string + template string + params map[string]string + exp string + expErr bool + }{ + { + name: "Test Case 1 - No Error", + template: api.GetClusterUpgradePolicyStateEndpointV1, + params: map[string]string{"ocmClusterID": "123", "policyID": "abc"}, + exp: "/api/clusters_mgmt/v1/clusters/123/upgrade_policies/abc/state", + expErr: false, + }, + { + name: "Test Case 2 - Template Parse Error", + template: "Invalid {{ .template", + params: map[string]string{"ocmClusterID": "123"}, + exp: "", + expErr: true, + }, + { + name: "Test Case 3 - Template Execute Error", + template: api.GetClusterUpgradePoliciesEndpointV1, + params: map[string]string{"nonexistentPlaceholder": "456"}, + exp: "", + expErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + endpoint, err := api.BuildEndpoint(tc.template, tc.params) + if (err != nil) != tc.expErr { + t.Fatalf("BuildEndpoint error: %v, expect error %v", err, tc.expErr) + } + if endpoint != tc.exp { + t.Errorf("BuildEndpoint got %v, expect %v", endpoint, tc.exp) + } + }) + } +} diff --git a/pkg/util/ocm/api/mock/api.go b/pkg/util/ocm/api/mock/api.go new file mode 100644 index 00000000000..ad177158bf5 --- /dev/null +++ b/pkg/util/ocm/api/mock/api.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/Azure/ARO-RP/pkg/util/ocm/api (interfaces: API) + +// Package mock_api is a generated GoMock package. +package mock_api + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + + api "github.com/Azure/ARO-RP/pkg/util/ocm/api" +) + +// MockAPI is a mock of API interface. +type MockAPI struct { + ctrl *gomock.Controller + recorder *MockAPIMockRecorder +} + +// MockAPIMockRecorder is the mock recorder for MockAPI. +type MockAPIMockRecorder struct { + mock *MockAPI +} + +// NewMockAPI creates a new mock instance. +func NewMockAPI(ctrl *gomock.Controller) *MockAPI { + mock := &MockAPI{ctrl: ctrl} + mock.recorder = &MockAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAPI) EXPECT() *MockAPIMockRecorder { + return m.recorder +} + +// CancelClusterUpgradePolicy mocks base method. +func (m *MockAPI) CancelClusterUpgradePolicy(arg0 context.Context, arg1, arg2 string) (*api.CancelUpgradeResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelClusterUpgradePolicy", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.CancelUpgradeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelClusterUpgradePolicy indicates an expected call of CancelClusterUpgradePolicy. +func (mr *MockAPIMockRecorder) CancelClusterUpgradePolicy(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelClusterUpgradePolicy", reflect.TypeOf((*MockAPI)(nil).CancelClusterUpgradePolicy), arg0, arg1, arg2) +} + +// GetClusterList mocks base method. +func (m *MockAPI) GetClusterList(arg0 context.Context, arg1 map[string]string) (*api.ClusterList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterList", arg0, arg1) + ret0, _ := ret[0].(*api.ClusterList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterList indicates an expected call of GetClusterList. +func (mr *MockAPIMockRecorder) GetClusterList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterList", reflect.TypeOf((*MockAPI)(nil).GetClusterList), arg0, arg1) +} + +// GetClusterUpgradePolicies mocks base method. +func (m *MockAPI) GetClusterUpgradePolicies(arg0 context.Context, arg1 string) (*api.UpgradePolicyList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterUpgradePolicies", arg0, arg1) + ret0, _ := ret[0].(*api.UpgradePolicyList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterUpgradePolicies indicates an expected call of GetClusterUpgradePolicies. +func (mr *MockAPIMockRecorder) GetClusterUpgradePolicies(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterUpgradePolicies", reflect.TypeOf((*MockAPI)(nil).GetClusterUpgradePolicies), arg0, arg1) +} + +// GetClusterUpgradePolicyState mocks base method. +func (m *MockAPI) GetClusterUpgradePolicyState(arg0 context.Context, arg1, arg2 string) (*api.UpgradePolicyState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClusterUpgradePolicyState", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.UpgradePolicyState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClusterUpgradePolicyState indicates an expected call of GetClusterUpgradePolicyState. +func (mr *MockAPIMockRecorder) GetClusterUpgradePolicyState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterUpgradePolicyState", reflect.TypeOf((*MockAPI)(nil).GetClusterUpgradePolicyState), arg0, arg1, arg2) +} diff --git a/pkg/util/ocm/api/models.go b/pkg/util/ocm/api/models.go new file mode 100644 index 00000000000..06d9445eabe --- /dev/null +++ b/pkg/util/ocm/api/models.go @@ -0,0 +1,113 @@ +package api + +import "time" + +// UpgradePolicyList represents an unmarshalled Upgrade Policy response from Cluster Services +type UpgradePolicyList struct { + Kind string `json:"kind"` + Page int64 `json:"page"` + Size int64 `json:"size"` + Total int64 `json:"total"` + Items []UpgradePolicy `json:"items"` +} + +// UpgradePolicyStatus represents an unmarshalled Upgrade Policy Status response from Cluster Services +type UpgradePolicyStatus struct { + State string `json:"value"` + Description string `json:"description"` +} + +// UpgradePolicy represents an unmarshalled individual Upgrade Policy response from Cluster Services +type UpgradePolicy struct { + Id string `json:"id"` + Kind string `json:"kind"` + Href string `json:"href"` + Schedule string `json:"schedule"` + ScheduleType string `json:"schedule_type"` + UpgradeType string `json:"upgrade_type"` + Version string `json:"version"` + NextRun string `json:"next_run"` + PrevRun string `json:"prev_run"` + ClusterId string `json:"cluster_id"` + CapacityReservation *bool `json:"capacity_reservation"` + UpgradePolicyStatus +} + +// ClusterList represents an unmarshalled Cluster List response from Cluster Services +type ClusterList struct { + Kind string `json:"kind"` + Page int64 `json:"page"` + Size int64 `json:"size"` + Total int64 `json:"total"` + Items []ClusterInfo `json:"items"` +} + +// ClusterInfo represents a partial unmarshalled Cluster response from Cluster Services +type ClusterInfo struct { + Id string `json:"id"` + Name string `json:"name"` + ExternalID string `json:"external_id"` + DisplayName string `json:"display_name"` + CreationTimestamp time.Time `json:"creation_timestamp"` + ActivityTimestamp time.Time `json:"activity_timestamp"` + OpenshiftVersion string `json:"openshift_version"` + Version ClusterVersion `json:"version"` + NodeDrainGracePeriod NodeDrainGracePeriod `json:"node_drain_grace_period"` + UpgradePolicies []UpgradePolicy `json:"upgrade_policies"` +} + +// NodeDrainGracePeriod represents a duration for node drain grace periods +type NodeDrainGracePeriod struct { + Value int64 `json:"value"` + Unit string `json:"unit"` +} + +// ClusterVersion represents a clusters version +type ClusterVersion struct { + Id string `json:"id"` + ChannelGroup string `json:"channel_group"` + AvailableUpgrades []string `json:"available_upgrades"` + EndOfLifeTimestamp time.Time `json:"end_of_life_timestamp"` +} + +// UpgradePolicyStateRequest represents an Upgrade Policy state for notifications +type UpgradePolicyStateRequest struct { + Value string `json:"value"` + Description string `json:"description"` +} + +// UpgradePolicyState represents an Upgrade Policy state for notifications +type UpgradePolicyState struct { + Kind string `json:"kind"` + Href string `json:"href"` + UpgradePolicyStatus +} + +// Config represents the configmap data for the managed-upgrade-operator +type Config struct { + ConfigManager ConfigManager `yaml:"configManager"` +} + +// ConfigManager represents the config manager data for the managed-upgrade-operator +type ConfigManager struct { + Source string `yaml:"source"` + OcmBaseURL string `yaml:"ocmBaseUrl"` +} + +// Error represents an error response from the API server +type Error struct { + Kind string `json:"kind"` + ID string `json:"id"` + Href string `json:"href"` + Code string `json:"code"` + Reason string `json:"reason"` + OperationID string `json:"operation_id"` +} + +// CancelUpgradeResponse represents a response from the API server +type CancelUpgradeResponse struct { + Kind string `json:"kind"` + Href string `json:"href"` + Value string `json:"value"` + Description string `json:"description"` +} diff --git a/pkg/util/ocm/api/requestbuilder.go b/pkg/util/ocm/api/requestbuilder.go new file mode 100644 index 00000000000..f2c899a38f1 --- /dev/null +++ b/pkg/util/ocm/api/requestbuilder.go @@ -0,0 +1,73 @@ +package api + +import ( + "bytes" + "context" + "net/http" + "net/url" +) + +type RequestBuilder struct { + method string + baseURL string + endpoint string + headers map[string]string + params url.Values + body []byte + ctx context.Context +} + +func NewRequestBuilder(method, baseURL string) *RequestBuilder { + return &RequestBuilder{ + method: method, + baseURL: baseURL, + headers: make(map[string]string), + params: url.Values{}, + ctx: context.Background(), + } +} + +func (rb *RequestBuilder) SetEndpoint(endpoint string) *RequestBuilder { + rb.endpoint = endpoint + return rb +} + +func (rb *RequestBuilder) AddHeader(key, value string) *RequestBuilder { + rb.headers[key] = value + return rb +} + +func (rb *RequestBuilder) AddParam(key, value string) *RequestBuilder { + rb.params.Add(key, value) + return rb +} + +func (rb *RequestBuilder) SetBody(body []byte) *RequestBuilder { + rb.body = body + return rb +} + +func (rb *RequestBuilder) SetContext(ctx context.Context) *RequestBuilder { + rb.ctx = ctx + return rb +} + +func (rb *RequestBuilder) Build() (*http.Request, error) { + parsedURL, err := url.Parse(rb.baseURL) + if err != nil { + return nil, err + } + parsedURL.Path = rb.endpoint + parsedURL.RawQuery = rb.params.Encode() + + req, err := http.NewRequestWithContext(rb.ctx, rb.method, parsedURL.String(), bytes.NewBuffer(rb.body)) + if err != nil { + return nil, err + } + + for key, value := range rb.headers { + req.Header.Set(key, value) + } + + return req, nil +} diff --git a/pkg/util/ocm/api/requestbuilder_test.go b/pkg/util/ocm/api/requestbuilder_test.go new file mode 100644 index 00000000000..43ddbcc3dd0 --- /dev/null +++ b/pkg/util/ocm/api/requestbuilder_test.go @@ -0,0 +1,81 @@ +package api_test + +import ( + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "net/http" + "testing" +) + +func TestRequestBuilder_Build(t *testing.T) { + testCases := []struct { + name string + method string + baseURL string + endpoint string + headers map[string]string + params map[string]string + body []byte + expURL string + expErr bool + }{ + { + name: "Test Request Builder", + method: http.MethodGet, + baseURL: "http://example.com", + endpoint: "/api/v1/test", + headers: map[string]string{"Content-Type": "application/json"}, + params: map[string]string{"key": "value"}, + body: []byte(`{"key":"value"}`), + expURL: "http://example.com/api/v1/test?key=value", + expErr: false, + }, + { + name: "Test Invalid URL", + method: http.MethodGet, + baseURL: "://invalid.url", + endpoint: "/api/v1/test", + headers: map[string]string{"Content-Type": "application/json"}, + params: map[string]string{"key": "value"}, + body: []byte(`{"key":"value"}`), + expErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rb := api.NewRequestBuilder(tc.method, tc.baseURL). + SetEndpoint(tc.endpoint). + SetBody(tc.body) + + for key, value := range tc.headers { + rb.AddHeader(key, value) + } + + for key, value := range tc.params { + rb.AddParam(key, value) + } + + req, err := rb.Build() + if (err != nil) != tc.expErr { + t.Fatalf("RequestBuilder Build error %v, expected error %v", err, tc.expErr) + return + } + + if tc.expErr { + return + } + + gotURL := req.URL.String() + if gotURL != tc.expURL { + t.Errorf("Got URL %v, expect %v", gotURL, tc.expURL) + } + + for key, value := range tc.headers { + gotValue := req.Header.Get(key) + if gotValue != value { + t.Errorf("Got Header %v = %v, expect %v", key, gotValue, value) + } + } + }) + } +} diff --git a/pkg/util/ocm/api/transport.go b/pkg/util/ocm/api/transport.go new file mode 100644 index 00000000000..efb26c4a5e5 --- /dev/null +++ b/pkg/util/ocm/api/transport.go @@ -0,0 +1,29 @@ +package api + +import ( + "fmt" + "net/http" + "time" +) + +var _ http.RoundTripper = (*AccessTokenTransport)(nil) + +type AccessTokenTransport struct { + AuthToken string + transport http.RoundTripper +} + +func NewAccessTokenTransport(authToken *AccessToken) *AccessTokenTransport { + return &AccessTokenTransport{ + AuthToken: fmt.Sprintf("AccessToken %s", authToken), + transport: &http.Transport{ + TLSHandshakeTimeout: time.Second * 5, + }, + } +} + +func (t *AccessTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", t.AuthToken) + + return t.transport.RoundTrip(req) +} diff --git a/pkg/util/ocm/api/transport_test.go b/pkg/util/ocm/api/transport_test.go new file mode 100644 index 00000000000..a183e062892 --- /dev/null +++ b/pkg/util/ocm/api/transport_test.go @@ -0,0 +1,43 @@ +package api_test + +import ( + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "net/http" + "net/http/httptest" + "testing" +) + +func TestAccessTokenTransport_RoundTrip(t *testing.T) { + testCases := []struct { + name string + authToken *api.AccessToken + expAuthHeader string + }{ + { + name: "Test Authorization header", + authToken: api.NewAccessToken("test", "123"), + expAuthHeader: "AccessToken test:123", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Test the Auth header + authHeader := r.Header.Get("Authorization") + if authHeader != tc.expAuthHeader { + t.Errorf("Authorization header = %v, expect %v", authHeader, tc.expAuthHeader) + } + })) + defer server.Close() + + accessTokenTransport := api.NewAccessTokenTransport(tc.authToken) + + req := httptest.NewRequest(http.MethodGet, server.URL, nil) + _, err := accessTokenTransport.RoundTrip(req) + if err != nil { + t.Fatalf("AccessTokenTransport RoundTrip error = %v", err) + } + }) + } +} diff --git a/pkg/util/ocm/generate.go b/pkg/util/ocm/generate.go new file mode 100644 index 00000000000..02b518cb97d --- /dev/null +++ b/pkg/util/ocm/generate.go @@ -0,0 +1,7 @@ +package ocm + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=./api/mock/api.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE/api API +//go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ./api/mock/api.go diff --git a/pkg/util/ocm/ocm.go b/pkg/util/ocm/ocm.go new file mode 100644 index 00000000000..6fb61445fb1 --- /dev/null +++ b/pkg/util/ocm/ocm.go @@ -0,0 +1,52 @@ +package ocm + +import ( + "context" + "fmt" + "github.com/Azure/ARO-RP/pkg/util/ocm/api" +) + +func GetClusterInfoWithUpgradePolices(ctx context.Context, apiInstance api.API) (*api.ClusterInfo, error) { + clusterList, err := apiInstance.GetClusterList(ctx, map[string]string{}) + if err != nil { + return nil, err + } + + if len(clusterList.Items) != 1 { + return nil, fmt.Errorf("expected 1 cluster, got %d", len(clusterList.Items)) + } + + clusterInfo := &clusterList.Items[0] + ocmClusterID := clusterInfo.Id + upgradePolicyList, err := apiInstance.GetClusterUpgradePolicies(ctx, ocmClusterID) + if err != nil { + return nil, err + } + + for i, policy := range upgradePolicyList.Items { + upgradePolicyState, err := apiInstance.GetClusterUpgradePolicyState(ctx, ocmClusterID, policy.Id) + if err != nil { + return nil, err + } + upgradePolicyList.Items[i].State = upgradePolicyState.State + upgradePolicyList.Items[i].Description = upgradePolicyState.Description + } + clusterInfo.UpgradePolicies = upgradePolicyList.Items + + return clusterInfo, nil +} + +func CancelClusterUpgradePolicy(ctx context.Context, apiInstance api.API, policyID string) (*api.CancelUpgradeResponse, error) { + clusterInfo, err := GetClusterInfoWithUpgradePolices(ctx, apiInstance) + if err != nil { + return nil, err + } + + ocmClusterID := clusterInfo.Id + cancelUpgradeResponse, err := apiInstance.CancelClusterUpgradePolicy(ctx, ocmClusterID, policyID) + if err != nil { + return nil, err + } + + return cancelUpgradeResponse, nil +} diff --git a/pkg/util/ocm/ocm_test.go b/pkg/util/ocm/ocm_test.go new file mode 100644 index 00000000000..b7c7b643afa --- /dev/null +++ b/pkg/util/ocm/ocm_test.go @@ -0,0 +1,117 @@ +package ocm_test + +import ( + "context" + "github.com/Azure/ARO-RP/pkg/util/ocm" + "github.com/Azure/ARO-RP/pkg/util/ocm/api" + mock_api "github.com/Azure/ARO-RP/pkg/util/ocm/api/mock" + "github.com/golang/mock/gomock" + "testing" +) + +func TestGetClusterInfoWithUpgradePolicies(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockAPI := mock_api.NewMockAPI(ctrl) + ctx := context.TODO() + + expectedClusterList := &api.ClusterList{ + Items: []api.ClusterInfo{ + {Id: "cluster-id"}, + }, + } + + expectedUpgradePolicies := &api.UpgradePolicyList{ + Items: []api.UpgradePolicy{ + {Id: "policy-id-1"}, + {Id: "policy-id-2"}, + }, + } + + expectedUpgradePolicyState := &api.UpgradePolicyState{ + Kind: "testKind", + UpgradePolicyStatus: api.UpgradePolicyStatus{ + State: "completed", + Description: "Upgrade completed successfully", + }, + } + + mockAPI.EXPECT().GetClusterList(ctx, map[string]string{}).Return(expectedClusterList, nil) + mockAPI.EXPECT().GetClusterUpgradePolicies(ctx, expectedClusterList.Items[0].Id).Return(expectedUpgradePolicies, nil) + mockAPI.EXPECT().GetClusterUpgradePolicyState(ctx, expectedClusterList.Items[0].Id, "policy-id-1").Return(expectedUpgradePolicyState, nil) + mockAPI.EXPECT().GetClusterUpgradePolicyState(ctx, expectedClusterList.Items[0].Id, "policy-id-2").Return(expectedUpgradePolicyState, nil) + + clusterInfo, err := ocm.GetClusterInfoWithUpgradePolices(ctx, mockAPI) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if clusterInfo.Id != expectedClusterList.Items[0].Id { + t.Errorf("expected cluster ID %s, got %s", expectedClusterList.Items[0].Id, clusterInfo.Id) + } + + for _, policy := range clusterInfo.UpgradePolicies { + if policy.State != "completed" { + t.Errorf("expected state 'completed', got %s", policy.State) + } + if policy.Description != "Upgrade completed successfully" { + t.Errorf("expected description 'Upgrade completed successfully', got %s", policy.Description) + } + } +} + +func TestCancelClusterUpgradePolicy(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockAPI := mock_api.NewMockAPI(ctrl) + ctx := context.TODO() + + expectedClusterInfo := &api.ClusterInfo{Id: "cluster-id"} + clusterList := &api.ClusterList{Items: []api.ClusterInfo{*expectedClusterInfo}} + + cancelUpgradeResponse := &api.CancelUpgradeResponse{ + Kind: "testKind", + Value: "cancelled", + Description: "Manually cancelled by SRE", + } + + expectedClusterList := &api.ClusterList{ + Items: []api.ClusterInfo{ + {Id: "cluster-id"}, + }, + } + + expectedUpgradePolicies := &api.UpgradePolicyList{ + Items: []api.UpgradePolicy{ + {Id: "policy-id-1"}, + {Id: "policy-id-2"}, + }, + } + + expectedUpgradePolicyState := &api.UpgradePolicyState{ + Kind: "testKind", + UpgradePolicyStatus: api.UpgradePolicyStatus{ + State: "completed", + Description: "Upgrade completed successfully", + }, + } + + mockAPI.EXPECT().GetClusterList(ctx, map[string]string{}).Return(clusterList, nil) + mockAPI.EXPECT().GetClusterUpgradePolicies(ctx, expectedClusterList.Items[0].Id).Return(expectedUpgradePolicies, nil) + mockAPI.EXPECT().GetClusterUpgradePolicyState(ctx, expectedClusterList.Items[0].Id, "policy-id-1").Return(expectedUpgradePolicyState, nil) + mockAPI.EXPECT().GetClusterUpgradePolicyState(ctx, expectedClusterList.Items[0].Id, "policy-id-2").Return(expectedUpgradePolicyState, nil) + mockAPI.EXPECT().CancelClusterUpgradePolicy(ctx, expectedClusterInfo.Id, "policy-id").Return(cancelUpgradeResponse, nil) + + cancelResponse, err := ocm.CancelClusterUpgradePolicy(ctx, mockAPI, "policy-id") + + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if cancelResponse.Value != "cancelled" { + t.Errorf("expected value 'cancelled', got %s", cancelResponse.Value) + } + if cancelResponse.Description != "Manually cancelled by SRE" { + t.Errorf("expected description 'Manually cancelled by SRE', got %s", cancelResponse.Description) + } +} From 9ca6b14059dff25e12fcec991e025dc8536e80a4 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Fri, 31 May 2024 11:32:38 +1000 Subject: [PATCH 2/5] added license in source code --- pkg/frontend/admin_openshiftcluster_ocm_operations.go | 3 +++ pkg/frontend/admin_openshiftcluster_ocm_operations_test.go | 3 +++ pkg/frontend/adminactions/ocmactions.go | 3 +++ pkg/util/ocm/api/api.go | 3 +++ pkg/util/ocm/api/api_test.go | 3 +++ pkg/util/ocm/api/auth.go | 3 +++ pkg/util/ocm/api/auth_test.go | 3 +++ pkg/util/ocm/api/endpoints.go | 3 +++ pkg/util/ocm/api/endpoints_test.go | 3 +++ pkg/util/ocm/api/models.go | 3 +++ pkg/util/ocm/api/requestbuilder.go | 3 +++ pkg/util/ocm/api/requestbuilder_test.go | 3 +++ pkg/util/ocm/api/transport.go | 3 +++ pkg/util/ocm/api/transport_test.go | 3 +++ pkg/util/ocm/ocm.go | 3 +++ pkg/util/ocm/ocm_test.go | 3 +++ 16 files changed, 48 insertions(+) diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations.go b/pkg/frontend/admin_openshiftcluster_ocm_operations.go index 08415d351df..316568bb568 100644 --- a/pkg/frontend/admin_openshiftcluster_ocm_operations.go +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations.go @@ -1,5 +1,8 @@ package frontend +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "encoding/json" diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go index 477022d7b85..6ddf2b6e56d 100644 --- a/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go @@ -1,5 +1,8 @@ package frontend +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "bytes" "context" diff --git a/pkg/frontend/adminactions/ocmactions.go b/pkg/frontend/adminactions/ocmactions.go index 1fb6a0066b8..ff23aea4d62 100644 --- a/pkg/frontend/adminactions/ocmactions.go +++ b/pkg/frontend/adminactions/ocmactions.go @@ -1,5 +1,8 @@ package adminactions +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "github.com/Azure/ARO-RP/pkg/util/ocm" diff --git a/pkg/util/ocm/api/api.go b/pkg/util/ocm/api/api.go index ec887eb3cd9..d062f431796 100644 --- a/pkg/util/ocm/api/api.go +++ b/pkg/util/ocm/api/api.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "encoding/json" diff --git a/pkg/util/ocm/api/api_test.go b/pkg/util/ocm/api/api_test.go index dcbe9639634..e1629f01c70 100644 --- a/pkg/util/ocm/api/api_test.go +++ b/pkg/util/ocm/api/api_test.go @@ -1,5 +1,8 @@ package api_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "encoding/json" diff --git a/pkg/util/ocm/api/auth.go b/pkg/util/ocm/api/auth.go index 8bc88a897ed..4599471c1d1 100644 --- a/pkg/util/ocm/api/auth.go +++ b/pkg/util/ocm/api/auth.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import "fmt" var _ fmt.Stringer = (*AccessToken)(nil) diff --git a/pkg/util/ocm/api/auth_test.go b/pkg/util/ocm/api/auth_test.go index 8ddfdd6295e..a748a75e448 100644 --- a/pkg/util/ocm/api/auth_test.go +++ b/pkg/util/ocm/api/auth_test.go @@ -1,5 +1,8 @@ package api_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "github.com/Azure/ARO-RP/pkg/util/ocm/api" "testing" diff --git a/pkg/util/ocm/api/endpoints.go b/pkg/util/ocm/api/endpoints.go index 0e9c0e79c99..21e1a73a6ea 100644 --- a/pkg/util/ocm/api/endpoints.go +++ b/pkg/util/ocm/api/endpoints.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "bytes" "fmt" diff --git a/pkg/util/ocm/api/endpoints_test.go b/pkg/util/ocm/api/endpoints_test.go index 5ea8b77e634..c25431e12c1 100644 --- a/pkg/util/ocm/api/endpoints_test.go +++ b/pkg/util/ocm/api/endpoints_test.go @@ -1,5 +1,8 @@ package api_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "github.com/Azure/ARO-RP/pkg/util/ocm/api" "testing" diff --git a/pkg/util/ocm/api/models.go b/pkg/util/ocm/api/models.go index 06d9445eabe..5224c9fa8d0 100644 --- a/pkg/util/ocm/api/models.go +++ b/pkg/util/ocm/api/models.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import "time" // UpgradePolicyList represents an unmarshalled Upgrade Policy response from Cluster Services diff --git a/pkg/util/ocm/api/requestbuilder.go b/pkg/util/ocm/api/requestbuilder.go index f2c899a38f1..f4c06950d05 100644 --- a/pkg/util/ocm/api/requestbuilder.go +++ b/pkg/util/ocm/api/requestbuilder.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "bytes" "context" diff --git a/pkg/util/ocm/api/requestbuilder_test.go b/pkg/util/ocm/api/requestbuilder_test.go index 43ddbcc3dd0..1eee34f2242 100644 --- a/pkg/util/ocm/api/requestbuilder_test.go +++ b/pkg/util/ocm/api/requestbuilder_test.go @@ -1,5 +1,8 @@ package api_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "github.com/Azure/ARO-RP/pkg/util/ocm/api" "net/http" diff --git a/pkg/util/ocm/api/transport.go b/pkg/util/ocm/api/transport.go index efb26c4a5e5..fe5750e7435 100644 --- a/pkg/util/ocm/api/transport.go +++ b/pkg/util/ocm/api/transport.go @@ -1,5 +1,8 @@ package api +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "fmt" "net/http" diff --git a/pkg/util/ocm/api/transport_test.go b/pkg/util/ocm/api/transport_test.go index a183e062892..93c88abf5c5 100644 --- a/pkg/util/ocm/api/transport_test.go +++ b/pkg/util/ocm/api/transport_test.go @@ -1,5 +1,8 @@ package api_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "github.com/Azure/ARO-RP/pkg/util/ocm/api" "net/http" diff --git a/pkg/util/ocm/ocm.go b/pkg/util/ocm/ocm.go index 6fb61445fb1..be47ff0225f 100644 --- a/pkg/util/ocm/ocm.go +++ b/pkg/util/ocm/ocm.go @@ -1,5 +1,8 @@ package ocm +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "fmt" diff --git a/pkg/util/ocm/ocm_test.go b/pkg/util/ocm/ocm_test.go index b7c7b643afa..2ec08f5f61e 100644 --- a/pkg/util/ocm/ocm_test.go +++ b/pkg/util/ocm/ocm_test.go @@ -1,5 +1,8 @@ package ocm_test +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + import ( "context" "github.com/Azure/ARO-RP/pkg/util/ocm" From 5b8621c624ce86546f367dcc6f7d63ee50b7897b Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Fri, 31 May 2024 11:52:03 +1000 Subject: [PATCH 3/5] changed import order and group --- .../admin_openshiftcluster_ocm_operations.go | 20 ++++++++++--------- ...in_openshiftcluster_ocm_operations_test.go | 19 +++++++++--------- pkg/frontend/adminactions/ocmactions.go | 1 + pkg/util/mocks/env/core.go | 5 +++-- pkg/util/ocm/api/api_test.go | 3 ++- pkg/util/ocm/api/auth_test.go | 3 ++- pkg/util/ocm/api/endpoints_test.go | 3 ++- pkg/util/ocm/api/mock/api.go | 4 ++-- pkg/util/ocm/api/requestbuilder_test.go | 3 ++- pkg/util/ocm/api/transport_test.go | 3 ++- pkg/util/ocm/ocm.go | 1 + pkg/util/ocm/ocm_test.go | 6 ++++-- 12 files changed, 42 insertions(+), 29 deletions(-) diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations.go b/pkg/frontend/admin_openshiftcluster_ocm_operations.go index 316568bb568..2e071503667 100644 --- a/pkg/frontend/admin_openshiftcluster_ocm_operations.go +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations.go @@ -7,21 +7,23 @@ import ( "context" "encoding/json" "fmt" - "github.com/Azure/ARO-RP/pkg/api" - "github.com/Azure/ARO-RP/pkg/database/cosmosdb" - "github.com/Azure/ARO-RP/pkg/frontend/adminactions" - "github.com/Azure/ARO-RP/pkg/frontend/middleware" - ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" - "github.com/Azure/ARO-RP/pkg/util/pullsecret" + "net/http" + "path/filepath" + "strings" + "github.com/ghodss/yaml" "github.com/go-chi/chi/v5" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" kruntime "k8s.io/apimachinery/pkg/runtime" - "net/http" - "path/filepath" - "strings" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" + ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" + "github.com/Azure/ARO-RP/pkg/util/pullsecret" ) func (f *frontend) getOCMClusterInfo(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go index 6ddf2b6e56d..2532b656d5e 100644 --- a/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go +++ b/pkg/frontend/admin_openshiftcluster_ocm_operations_test.go @@ -8,22 +8,23 @@ import ( "context" "encoding/json" "fmt" - "github.com/Azure/ARO-RP/pkg/api" - "github.com/Azure/ARO-RP/pkg/env" - "github.com/Azure/ARO-RP/pkg/frontend/adminactions" - "github.com/Azure/ARO-RP/pkg/metrics/noop" - ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" - testdatabase "github.com/Azure/ARO-RP/test/database" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" "strings" "testing" "time" - "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" "github.com/golang/mock/gomock" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/env" + "github.com/Azure/ARO-RP/pkg/frontend/adminactions" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + mock_adminactions "github.com/Azure/ARO-RP/pkg/util/mocks/adminactions" + ocmapi "github.com/Azure/ARO-RP/pkg/util/ocm/api" + testdatabase "github.com/Azure/ARO-RP/test/database" ) func createSecretBytesWithAuths(authsJson string) []byte { diff --git a/pkg/frontend/adminactions/ocmactions.go b/pkg/frontend/adminactions/ocmactions.go index ff23aea4d62..ee631b7ff4c 100644 --- a/pkg/frontend/adminactions/ocmactions.go +++ b/pkg/frontend/adminactions/ocmactions.go @@ -5,6 +5,7 @@ package adminactions import ( "context" + "github.com/Azure/ARO-RP/pkg/util/ocm" "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) diff --git a/pkg/util/mocks/env/core.go b/pkg/util/mocks/env/core.go index 8129b642cad..844bc378538 100644 --- a/pkg/util/mocks/env/core.go +++ b/pkg/util/mocks/env/core.go @@ -8,12 +8,13 @@ import ( context "context" reflect "reflect" - azureclient "github.com/Azure/ARO-RP/pkg/util/azureclient" - liveconfig "github.com/Azure/ARO-RP/pkg/util/liveconfig" azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore" autorest "github.com/Azure/go-autorest/autorest" gomock "github.com/golang/mock/gomock" logrus "github.com/sirupsen/logrus" + + azureclient "github.com/Azure/ARO-RP/pkg/util/azureclient" + liveconfig "github.com/Azure/ARO-RP/pkg/util/liveconfig" ) // MockCore is a mock of Core interface. diff --git a/pkg/util/ocm/api/api_test.go b/pkg/util/ocm/api/api_test.go index e1629f01c70..b6c33bbf4cc 100644 --- a/pkg/util/ocm/api/api_test.go +++ b/pkg/util/ocm/api/api_test.go @@ -6,11 +6,12 @@ package api_test import ( "context" "encoding/json" - "github.com/Azure/ARO-RP/pkg/util/ocm/api" "net/http" "net/http/httptest" "reflect" "testing" + + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) func TestAPI(t *testing.T) { diff --git a/pkg/util/ocm/api/auth_test.go b/pkg/util/ocm/api/auth_test.go index a748a75e448..b3e4e8e45fd 100644 --- a/pkg/util/ocm/api/auth_test.go +++ b/pkg/util/ocm/api/auth_test.go @@ -4,8 +4,9 @@ package api_test // Licensed under the Apache License 2.0. import ( - "github.com/Azure/ARO-RP/pkg/util/ocm/api" "testing" + + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) func TestAccessToken(t *testing.T) { diff --git a/pkg/util/ocm/api/endpoints_test.go b/pkg/util/ocm/api/endpoints_test.go index c25431e12c1..d10b35cfe62 100644 --- a/pkg/util/ocm/api/endpoints_test.go +++ b/pkg/util/ocm/api/endpoints_test.go @@ -4,8 +4,9 @@ package api_test // Licensed under the Apache License 2.0. import ( - "github.com/Azure/ARO-RP/pkg/util/ocm/api" "testing" + + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) func TestBuildEndpoint(t *testing.T) { diff --git a/pkg/util/ocm/api/mock/api.go b/pkg/util/ocm/api/mock/api.go index ad177158bf5..b06a6337801 100644 --- a/pkg/util/ocm/api/mock/api.go +++ b/pkg/util/ocm/api/mock/api.go @@ -5,8 +5,8 @@ package mock_api import ( - context "context" - reflect "reflect" + "context" + "reflect" gomock "github.com/golang/mock/gomock" diff --git a/pkg/util/ocm/api/requestbuilder_test.go b/pkg/util/ocm/api/requestbuilder_test.go index 1eee34f2242..8b5273f822d 100644 --- a/pkg/util/ocm/api/requestbuilder_test.go +++ b/pkg/util/ocm/api/requestbuilder_test.go @@ -4,9 +4,10 @@ package api_test // Licensed under the Apache License 2.0. import ( - "github.com/Azure/ARO-RP/pkg/util/ocm/api" "net/http" "testing" + + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) func TestRequestBuilder_Build(t *testing.T) { diff --git a/pkg/util/ocm/api/transport_test.go b/pkg/util/ocm/api/transport_test.go index 93c88abf5c5..64545a7a9c2 100644 --- a/pkg/util/ocm/api/transport_test.go +++ b/pkg/util/ocm/api/transport_test.go @@ -4,10 +4,11 @@ package api_test // Licensed under the Apache License 2.0. import ( - "github.com/Azure/ARO-RP/pkg/util/ocm/api" "net/http" "net/http/httptest" "testing" + + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) func TestAccessTokenTransport_RoundTrip(t *testing.T) { diff --git a/pkg/util/ocm/ocm.go b/pkg/util/ocm/ocm.go index be47ff0225f..26b85f84819 100644 --- a/pkg/util/ocm/ocm.go +++ b/pkg/util/ocm/ocm.go @@ -6,6 +6,7 @@ package ocm import ( "context" "fmt" + "github.com/Azure/ARO-RP/pkg/util/ocm/api" ) diff --git a/pkg/util/ocm/ocm_test.go b/pkg/util/ocm/ocm_test.go index 2ec08f5f61e..d4d35bb1e15 100644 --- a/pkg/util/ocm/ocm_test.go +++ b/pkg/util/ocm/ocm_test.go @@ -5,11 +5,13 @@ package ocm_test import ( "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/Azure/ARO-RP/pkg/util/ocm" "github.com/Azure/ARO-RP/pkg/util/ocm/api" mock_api "github.com/Azure/ARO-RP/pkg/util/ocm/api/mock" - "github.com/golang/mock/gomock" - "testing" ) func TestGetClusterInfoWithUpgradePolicies(t *testing.T) { From 097b4022e87f082c4324b0f3d623967ca0cc31c9 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Fri, 31 May 2024 12:09:36 +1000 Subject: [PATCH 4/5] revert changes suggested by validate --- pkg/util/mocks/env/core.go | 5 ++--- pkg/util/ocm/api/mock/api.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/util/mocks/env/core.go b/pkg/util/mocks/env/core.go index 844bc378538..8129b642cad 100644 --- a/pkg/util/mocks/env/core.go +++ b/pkg/util/mocks/env/core.go @@ -8,13 +8,12 @@ import ( context "context" reflect "reflect" + azureclient "github.com/Azure/ARO-RP/pkg/util/azureclient" + liveconfig "github.com/Azure/ARO-RP/pkg/util/liveconfig" azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore" autorest "github.com/Azure/go-autorest/autorest" gomock "github.com/golang/mock/gomock" logrus "github.com/sirupsen/logrus" - - azureclient "github.com/Azure/ARO-RP/pkg/util/azureclient" - liveconfig "github.com/Azure/ARO-RP/pkg/util/liveconfig" ) // MockCore is a mock of Core interface. diff --git a/pkg/util/ocm/api/mock/api.go b/pkg/util/ocm/api/mock/api.go index b06a6337801..ad177158bf5 100644 --- a/pkg/util/ocm/api/mock/api.go +++ b/pkg/util/ocm/api/mock/api.go @@ -5,8 +5,8 @@ package mock_api import ( - "context" - "reflect" + context "context" + reflect "reflect" gomock "github.com/golang/mock/gomock" From 232bf4e2bc1bb09e93ced8d7829d7c5f500ab689 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Fri, 31 May 2024 12:56:40 +1000 Subject: [PATCH 5/5] moved mock_api to util/mocks --- pkg/util/{ocm/api/mock => mocks/ocm/api}/api.go | 0 pkg/util/ocm/api/generate.go | 8 ++++++++ pkg/util/ocm/generate.go | 7 ------- pkg/util/ocm/ocm_test.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) rename pkg/util/{ocm/api/mock => mocks/ocm/api}/api.go (100%) create mode 100644 pkg/util/ocm/api/generate.go delete mode 100644 pkg/util/ocm/generate.go diff --git a/pkg/util/ocm/api/mock/api.go b/pkg/util/mocks/ocm/api/api.go similarity index 100% rename from pkg/util/ocm/api/mock/api.go rename to pkg/util/mocks/ocm/api/api.go diff --git a/pkg/util/ocm/api/generate.go b/pkg/util/ocm/api/generate.go new file mode 100644 index 00000000000..3aae7bd9203 --- /dev/null +++ b/pkg/util/ocm/api/generate.go @@ -0,0 +1,8 @@ +package api + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +//go:generate rm -rf ../../mocks/ocm/$GOPACKAGE +//go:generate go run ../../../../vendor/github.com/golang/mock/mockgen -destination=../../mocks/ocm/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/ocm/$GOPACKAGE API +//go:generate go run ../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../mocks/ocm/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/util/ocm/generate.go b/pkg/util/ocm/generate.go deleted file mode 100644 index 02b518cb97d..00000000000 --- a/pkg/util/ocm/generate.go +++ /dev/null @@ -1,7 +0,0 @@ -package ocm - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=./api/mock/api.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE/api API -//go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ./api/mock/api.go diff --git a/pkg/util/ocm/ocm_test.go b/pkg/util/ocm/ocm_test.go index d4d35bb1e15..5571907268d 100644 --- a/pkg/util/ocm/ocm_test.go +++ b/pkg/util/ocm/ocm_test.go @@ -9,9 +9,9 @@ import ( "github.com/golang/mock/gomock" + mock_api "github.com/Azure/ARO-RP/pkg/util/mocks/ocm/api" "github.com/Azure/ARO-RP/pkg/util/ocm" "github.com/Azure/ARO-RP/pkg/util/ocm/api" - mock_api "github.com/Azure/ARO-RP/pkg/util/ocm/api/mock" ) func TestGetClusterInfoWithUpgradePolicies(t *testing.T) {