internal/ocm/ocm.go (265 lines of code) (raw):
// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ocm
import (
"context"
"fmt"
sdk "github.com/openshift-online/ocm-sdk-go"
arohcpv1alpha1 "github.com/openshift-online/ocm-sdk-go/arohcp/v1alpha1"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
)
type ClusterServiceClientSpec interface {
// AddProperties injects the some additional properties into the ClusterBuilder.
AddProperties(builder *arohcpv1alpha1.ClusterBuilder) *arohcpv1alpha1.ClusterBuilder
// GetCluster sends a GET request to fetch a cluster from Cluster Service.
GetCluster(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.Cluster, error)
// GetClusterStatus sends a GET request to fetch a cluster's status from Cluster Service.
GetClusterStatus(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.ClusterStatus, error)
// GetClusterInflightChecks sends a GET request to fetch a cluster's inflight checks from Cluster Service.
GetClusterInflightChecks(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.InflightCheckList, error)
// PostCluster sends a POST request to create a cluster in Cluster Service.
PostCluster(ctx context.Context, cluster *arohcpv1alpha1.Cluster) (*arohcpv1alpha1.Cluster, error)
// UpdateCluster sends a PATCH request to update a cluster in Cluster Service.
UpdateCluster(ctx context.Context, internalID InternalID, cluster *arohcpv1alpha1.Cluster) (*arohcpv1alpha1.Cluster, error)
// DeleteCluster sends a DELETE request to delete a cluster from Cluster Service.
DeleteCluster(ctx context.Context, internalID InternalID) error
// ListClusters prepares a GET request with the given search expression. Call Items() on
// the returned iterator in a for/range loop to execute the request and paginate over results,
// then call GetError() to check for an iteration error.
ListClusters(searchExpression string) ClusterListIterator
// GetNodePool sends a GET request to fetch a node pool from Cluster Service.
GetNodePool(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.NodePool, error)
// GetNodePoolStatus sends a GET request to fetch a node pool's status from Cluster Service.
GetNodePoolStatus(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.NodePoolStatus, error)
// PostNodePool sends a POST request to create a node pool in Cluster Service.
PostNodePool(ctx context.Context, clusterInternalID InternalID, nodePool *arohcpv1alpha1.NodePool) (*arohcpv1alpha1.NodePool, error)
// UpdateNodePool sends a PATCH request to update a node pool in Cluster Service.
UpdateNodePool(ctx context.Context, internalID InternalID, nodePool *arohcpv1alpha1.NodePool) (*arohcpv1alpha1.NodePool, error)
// DeleteNodePool sends a DELETE request to delete a node pool from Cluster Service.
DeleteNodePool(ctx context.Context, internalID InternalID) error
// ListNodePools prepares a GET request with the given search expression. Call Items() on
// the returned iterator in a for/range loop to execute the request and paginate over results,
// then call GetError() to check for an iteration error.
ListNodePools(clusterInternalID InternalID, searchExpression string) NodePoolListIterator
// GetBreakGlassCredential sends a GET request to fetch a break-glass cluster credential from Cluster Service.
GetBreakGlassCredential(ctx context.Context, internalID InternalID) (*cmv1.BreakGlassCredential, error)
// PostBreakGlassCredential sends a POST request to create a break-glass cluster credential in Cluster Service.
PostBreakGlassCredential(ctx context.Context, clusterInternalID InternalID) (*cmv1.BreakGlassCredential, error)
// DeleteBreakGlassCredentials sends a DELETE request to revoke all break-glass credentials for a cluster in Cluster Service.
DeleteBreakGlassCredentials(ctx context.Context, clusterInternalID InternalID) error
// ListBreakGlassCredentials prepares a GET request with the given search expression. Call
// Items() on the returned iterator in a for/range loop to execute the request and paginate
// over results, then call GetError() to check for an iteration error.
ListBreakGlassCredentials(clusterInternalID InternalID, searchExpression string) BreakGlassCredentialListIterator
}
type ClusterServiceClient struct {
// Conn is an ocm-sdk-go connection to Cluster Service
Conn *sdk.Connection
// ProvisionShardID sets the provision_shard_id property for all cluster requests to Cluster Service, which pins all
// cluster requests to Cluster Service to a specific shard during testing
ProvisionShardID *string
// ProvisionerNoOpProvision sets the provisioner_noop_provision property for all cluster requests to Cluster
// Service, which short-circuits the full provision flow during testing
ProvisionerNoOpProvision bool
// ProvisionerNoOpDeprovision sets the provisioner_noop_deprovision property for all cluster requests to Cluster
// Service, which short-circuits the full deprovision flow during testing
ProvisionerNoOpDeprovision bool
}
func (csc *ClusterServiceClient) AddProperties(builder *arohcpv1alpha1.ClusterBuilder) *arohcpv1alpha1.ClusterBuilder {
additionalProperties := map[string]string{}
if csc.ProvisionShardID != nil {
additionalProperties["provision_shard_id"] = *csc.ProvisionShardID
}
if csc.ProvisionerNoOpProvision {
additionalProperties["provisioner_noop_provision"] = "true"
}
if csc.ProvisionerNoOpDeprovision {
additionalProperties["provisioner_noop_deprovision"] = "true"
}
return builder.Properties(additionalProperties)
}
func (csc *ClusterServiceClient) GetCluster(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.Cluster, error) {
client, ok := internalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", internalID)
}
clusterGetResponse, err := client.Get().SendContext(ctx)
if err != nil {
return nil, err
}
cluster, ok := clusterGetResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return cluster, nil
}
func (csc *ClusterServiceClient) GetClusterStatus(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.ClusterStatus, error) {
client, ok := internalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", internalID)
}
clusterStatusGetResponse, err := client.Status().Get().SendContext(ctx)
if err != nil {
return nil, err
}
status, ok := clusterStatusGetResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return status, nil
}
func (csc *ClusterServiceClient) GetClusterInflightChecks(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.InflightCheckList, error) {
client, ok := internalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", internalID)
}
clusterInflightChecksResponse, err := client.InflightChecks().List().SendContext(ctx)
if err != nil {
return nil, err
}
inflightChecks, ok := clusterInflightChecksResponse.GetItems()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return inflightChecks, nil
}
func (csc *ClusterServiceClient) PostCluster(ctx context.Context, cluster *arohcpv1alpha1.Cluster) (*arohcpv1alpha1.Cluster, error) {
clustersAddResponse, err := csc.Conn.AroHCP().V1alpha1().Clusters().Add().Body(cluster).SendContext(ctx)
if err != nil {
return nil, err
}
cluster, ok := clustersAddResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return cluster, nil
}
func (csc *ClusterServiceClient) UpdateCluster(ctx context.Context, internalID InternalID, cluster *arohcpv1alpha1.Cluster) (*arohcpv1alpha1.Cluster, error) {
client, ok := internalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", internalID)
}
clusterUpdateResponse, err := client.Update().Body(cluster).SendContext(ctx)
if err != nil {
return nil, err
}
cluster, ok = clusterUpdateResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return cluster, nil
}
func (csc *ClusterServiceClient) DeleteCluster(ctx context.Context, internalID InternalID) error {
client, ok := internalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return fmt.Errorf("OCM path is not a cluster: %s", internalID)
}
_, err := client.Delete().SendContext(ctx)
return err
}
func (csc *ClusterServiceClient) ListClusters(searchExpression string) ClusterListIterator {
clustersListRequest := csc.Conn.AroHCP().V1alpha1().Clusters().List()
if searchExpression != "" {
clustersListRequest.Search(searchExpression)
}
return ClusterListIterator{request: clustersListRequest}
}
func (csc *ClusterServiceClient) GetNodePool(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.NodePool, error) {
client, ok := internalID.GetNodePoolClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a node pool: %s", internalID)
}
nodePoolGetResponse, err := client.Get().SendContext(ctx)
if err != nil {
return nil, err
}
nodePool, ok := nodePoolGetResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return nodePool, nil
}
func (csc *ClusterServiceClient) GetNodePoolStatus(ctx context.Context, internalID InternalID) (*arohcpv1alpha1.NodePoolStatus, error) {
client, ok := internalID.GetNodePoolClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a node pool: %s", internalID)
}
nodePoolStatusGetResponse, err := client.Status().Get().SendContext(ctx)
if err != nil {
return nil, err
}
status, ok := nodePoolStatusGetResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return status, nil
}
func (csc *ClusterServiceClient) PostNodePool(ctx context.Context, clusterInternalID InternalID, nodePool *arohcpv1alpha1.NodePool) (*arohcpv1alpha1.NodePool, error) {
client, ok := clusterInternalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", clusterInternalID)
}
nodePoolsAddResponse, err := client.NodePools().Add().Body(nodePool).SendContext(ctx)
if err != nil {
return nil, err
}
nodePool, ok = nodePoolsAddResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return nodePool, nil
}
func (csc *ClusterServiceClient) UpdateNodePool(ctx context.Context, internalID InternalID, nodePool *arohcpv1alpha1.NodePool) (*arohcpv1alpha1.NodePool, error) {
client, ok := internalID.GetNodePoolClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a node pool: %s", internalID)
}
nodePoolUpdateResponse, err := client.Update().Body(nodePool).SendContext(ctx)
if err != nil {
return nil, err
}
nodePool, ok = nodePoolUpdateResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return nodePool, nil
}
func (csc *ClusterServiceClient) DeleteNodePool(ctx context.Context, internalID InternalID) error {
client, ok := internalID.GetNodePoolClient(csc.Conn)
if !ok {
return fmt.Errorf("OCM path is not a node pool: %s", internalID)
}
_, err := client.Delete().SendContext(ctx)
return err
}
func (csc *ClusterServiceClient) ListNodePools(clusterInternalID InternalID, searchExpression string) NodePoolListIterator {
client, ok := clusterInternalID.GetAroHCPClusterClient(csc.Conn)
if !ok {
return NodePoolListIterator{err: fmt.Errorf("OCM path is not a cluster: %s", clusterInternalID)}
}
nodePoolsListRequest := client.NodePools().List()
if searchExpression != "" {
nodePoolsListRequest.Search(searchExpression)
}
return NodePoolListIterator{request: nodePoolsListRequest}
}
func (csc *ClusterServiceClient) GetBreakGlassCredential(ctx context.Context, internalID InternalID) (*cmv1.BreakGlassCredential, error) {
client, ok := internalID.GetBreakGlassCredentialClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a break-glass credential: %s", internalID)
}
breakGlassCredentialGetResponse, err := client.Get().SendContext(ctx)
if err != nil {
return nil, err
}
breakGlassCredential, ok := breakGlassCredentialGetResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return breakGlassCredential, nil
}
func (csc *ClusterServiceClient) PostBreakGlassCredential(ctx context.Context, clusterInternalID InternalID) (*cmv1.BreakGlassCredential, error) {
client, ok := clusterInternalID.GetClusterClient(csc.Conn)
if !ok {
return nil, fmt.Errorf("OCM path is not a cluster: %s", clusterInternalID)
}
breakGlassCredential, err := cmv1.NewBreakGlassCredential().Build()
if err != nil {
return nil, err
}
breakGlassCredentialsAddResponse, err := client.BreakGlassCredentials().Add().Body(breakGlassCredential).SendContext(ctx)
if err != nil {
return nil, err
}
breakGlassCredential, ok = breakGlassCredentialsAddResponse.GetBody()
if !ok {
return nil, fmt.Errorf("empty response body")
}
return breakGlassCredential, nil
}
func (csc *ClusterServiceClient) DeleteBreakGlassCredentials(ctx context.Context, clusterInternalID InternalID) error {
client, ok := clusterInternalID.GetClusterClient(csc.Conn)
if !ok {
return fmt.Errorf("OCM path is not a cluster: %s", clusterInternalID)
}
_, err := client.BreakGlassCredentials().Delete().SendContext(ctx)
return err
}
func (csc *ClusterServiceClient) ListBreakGlassCredentials(clusterInternalID InternalID, searchExpression string) BreakGlassCredentialListIterator {
client, ok := clusterInternalID.GetClusterClient(csc.Conn)
if !ok {
return BreakGlassCredentialListIterator{err: fmt.Errorf("OCM path is not a cluster: %s", clusterInternalID)}
}
breakGlassCredentialsListRequest := client.BreakGlassCredentials().List()
if searchExpression != "" {
breakGlassCredentialsListRequest.Search(searchExpression)
}
return BreakGlassCredentialListIterator{request: breakGlassCredentialsListRequest}
}