internal/ocm/internalid.go (127 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 ( "fmt" "net/http" "path" "strings" arohcpv1alpha1 "github.com/openshift-online/ocm-sdk-go/arohcp/v1alpha1" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) const ( v1Pattern = "/api/clusters_mgmt/v1" v1ClusterPattern = v1Pattern + "/clusters/*" v1NodePoolPattern = v1ClusterPattern + "/node_pools/*" v1BreakGlassCredentialPattern = v1ClusterPattern + "/break_glass_credentials/*" aroHcpV1Alpha1Pattern = "/api/aro_hcp/v1alpha1" aroHcpV1Alpha1ClusterPattern = aroHcpV1Alpha1Pattern + "/clusters/*" aroHcpV1Alpha1NodePoolPattern = aroHcpV1Alpha1ClusterPattern + "/node_pools/*" ) func GenerateClusterHREF(clusterName string) string { return path.Join(v1Pattern, "clusters", clusterName) } func GenerateNodePoolHREF(clusterPath string, nodePoolName string) string { return path.Join(clusterPath, "node_pools", nodePoolName) } func GenerateBreakGlassCredentialHREF(clusterPath string, credentialName string) string { return path.Join(clusterPath, "break_glass_credentials", credentialName) } // InternalID represents a Cluster Service resource. type InternalID struct { path string kind string } func (id *InternalID) validate() error { var match bool // This is where we will catch and convert any legacy API versions // to the version the RP is actively using. // // For example, once the RP is using "v2" we will convert "v1" // and any other legacy transitional versions we see to "v2". if match, _ = path.Match(v1ClusterPattern, id.path); match { id.kind = cmv1.ClusterKind return nil } if match, _ = path.Match(v1NodePoolPattern, id.path); match { id.kind = cmv1.NodePoolKind return nil } if match, _ = path.Match(v1BreakGlassCredentialPattern, id.path); match { id.kind = cmv1.BreakGlassCredentialKind return nil } if match, _ = path.Match(aroHcpV1Alpha1ClusterPattern, id.path); match { id.kind = arohcpv1alpha1.ClusterKind return nil } if match, _ = path.Match(aroHcpV1Alpha1NodePoolPattern, id.path); match { id.kind = arohcpv1alpha1.NodePoolKind return nil } return fmt.Errorf("invalid InternalID: %s", id.path) } // NewInternalID attempts to create a new InternalID from a Cluster Service // API path, returning an error if the API path is invalid or unsupported. func NewInternalID(path string) (InternalID, error) { internalID := InternalID{path: strings.ToLower(path)} if err := internalID.validate(); err != nil { return InternalID{}, err } return internalID, nil } // String allows an InternalID to be used as a fmt.Stringer. func (id *InternalID) String() string { return id.path } // MarshalText allows an InternalID to be used as an encoding.TextMarshaler. func (id InternalID) MarshalText() ([]byte, error) { return []byte(id.path), nil } // UnmarshalText allows an InternalID to be used as an encoding.TextUnmarshaler. func (id *InternalID) UnmarshalText(text []byte) error { id.path = strings.ToLower(string(text)) return id.validate() } // ID returns the last path element of the resource described by InternalID. func (id *InternalID) ID() string { return path.Base(id.path) } // Kind returns the kind of resource described by InternalID, currently // limited to "Cluster" and "NodePool". func (id *InternalID) Kind() string { return id.kind } // GetClusterClient returns a v1 ClusterClient from the InternalID. // This works for both cluster and node pool resources. The transport // is most likely to be a Connection object from the SDK. func (id *InternalID) GetClusterClient(transport http.RoundTripper) (*cmv1.ClusterClient, bool) { switch matchClusterPath(id.path) { case v1ClusterPattern: return cmv1.NewClusterClient(transport, id.path), true case aroHcpV1Alpha1ClusterPattern: // support clusters received via ARO HCP APIs // without duplicating the whole codebase calling this method newPath := strings.ReplaceAll(id.path, aroHcpV1Alpha1Pattern, v1Pattern) return cmv1.NewClusterClient(transport, newPath), true default: return nil, false } } // GetAroHCPClusterClient returns a arohcpv1alpha1 ClusterClient from the InternalID. func (id *InternalID) GetAroHCPClusterClient(transport http.RoundTripper) (*arohcpv1alpha1.ClusterClient, bool) { switch matchClusterPath(id.path) { case v1ClusterPattern: // support clusters received via cluster APIs // without duplicating the whole codebase calling this method newPath := strings.ReplaceAll(id.path, v1Pattern, aroHcpV1Alpha1Pattern) return arohcpv1alpha1.NewClusterClient(transport, newPath), true case aroHcpV1Alpha1ClusterPattern: return arohcpv1alpha1.NewClusterClient(transport, id.path), true default: return nil, false } } func matchClusterPath(clusterPath string) string { var thisPath = clusterPath var lastPath string for thisPath != lastPath { if match, _ := path.Match(v1ClusterPattern, thisPath); match { return v1ClusterPattern } else if match, _ := path.Match(aroHcpV1Alpha1ClusterPattern, thisPath); match { return aroHcpV1Alpha1ClusterPattern } else { lastPath = thisPath thisPath = path.Dir(thisPath) } } return "" } // GetNodePoolClient returns a arohcpv1alpha1 NodePoolClient from the InternalID. // The transport is most likely to be a Connection object from the SDK. func (id *InternalID) GetNodePoolClient(transport http.RoundTripper) (*arohcpv1alpha1.NodePoolClient, bool) { if id.Kind() != arohcpv1alpha1.NodePoolKind { return nil, false } return arohcpv1alpha1.NewNodePoolClient(transport, id.path), true } // GetBreakGlassCredentialClient returns a v1 BreakGlassCredentialClient // from the InternalID. The transport is most likely to be a Connection // object from the SDK. func (id *InternalID) GetBreakGlassCredentialClient(transport http.RoundTripper) (*cmv1.BreakGlassCredentialClient, bool) { if id.Kind() != cmv1.BreakGlassCredentialKind { return nil, false } return cmv1.NewBreakGlassCredentialClient(transport, id.path), true }