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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions cmd/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"

ocmsdk "github.com/openshift-online/ocm-sdk-go"
bplogin "github.com/openshift/backplane-cli/cmd/ocm-backplane/login"
bpconfig "github.com/openshift/backplane-cli/pkg/cli/config"
"github.com/openshift/osdctl/pkg/utils"
Expand Down Expand Up @@ -76,3 +77,31 @@ func GetKubeConfigAndClient(clusterID string, elevationReasons ...string) (clien
}
return kubeCli, kubeconfig, clientset, err
}

// If some elevationReasons are provided, then the config will be elevated with user backplane-cluster-admin
// Using provided OCM sdk connection for config values.
func GetKubeConfigAndClientWithConn(clusterID string, ocm *ocmsdk.Connection, elevationReasons ...string) (client.Client, *rest.Config, *kubernetes.Clientset, error) {
bp, err := bpconfig.GetBackplaneConfigurationWithConn(ocm)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to load backplane-cli config: %v", err)
}
var kubeconfig *rest.Config
if len(elevationReasons) == 0 {
kubeconfig, err = bplogin.GetRestConfigWithConn(bp, ocm, clusterID)
} else {
kubeconfig, err = bplogin.GetRestConfigAsUserWithConn(bp, ocm, clusterID, "backplane-cluster-admin", elevationReasons...)
}
if err != nil {
return nil, nil, nil, err
}
// create the clientset
clientset, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
return nil, nil, nil, err
}
kubeCli, err := client.New(kubeconfig, client.Options{})
if err != nil {
return nil, nil, nil, err
}
return kubeCli, kubeconfig, clientset, nil
}
70 changes: 70 additions & 0 deletions cmd/common/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package common

import (
"testing"

sdk "github.com/openshift-online/ocm-sdk-go"
)

// TestGetKubeConfigAndClientWithConn tests the GetKubeConfigAndClientWithConn function
// which creates a Kubernetes client, REST config, and clientset using a provided OCM SDK
// connection. This function supports both regular and elevated (backplane-cluster-admin)
// access based on the presence of elevation reasons.
func TestGetKubeConfigAndClientWithConn(t *testing.T) {
tests := []struct {
name string
clusterID string
ocmConn *sdk.Connection
elevationReasons []string
wantErr bool
}{
{
// Test that passing a nil OCM connection without elevation returns an error
name: "nil OCM connection",
clusterID: "test-cluster-id",
ocmConn: nil,
elevationReasons: nil,
wantErr: true,
},
{
// Test that passing a nil OCM connection with elevation reasons also returns an error
name: "nil OCM connection with elevation reasons",
clusterID: "test-cluster-id",
ocmConn: nil,
elevationReasons: []string{"testing"},
wantErr: true,
},
{
// Test that passing an empty cluster ID with nil connection returns an error
name: "empty cluster ID with nil connection",
clusterID: "",
ocmConn: nil,
elevationReasons: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kubeCli, kubeconfig, clientset, err := GetKubeConfigAndClientWithConn(tt.clusterID, tt.ocmConn, tt.elevationReasons...)
if tt.wantErr {
if err == nil {
t.Errorf("GetKubeConfigAndClientWithConn() expected error but got none")
}
} else {
if err != nil {
t.Errorf("GetKubeConfigAndClientWithConn() unexpected error = %v", err)
}
if kubeCli == nil {
t.Errorf("GetKubeConfigAndClientWithConn() returned nil kubeCli")
}
if kubeconfig == nil {
t.Errorf("GetKubeConfigAndClientWithConn() returned nil kubeconfig")
}
if clientset == nil {
t.Errorf("GetKubeConfigAndClientWithConn() returned nil clientset")
}
}
})
}
}
1 change: 1 addition & 0 deletions cmd/hive/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hive

import (
"fmt"

cd "github.com/openshift/osdctl/cmd/hive/clusterdeployment"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand Down
59 changes: 59 additions & 0 deletions docs/osdctl_hive_login-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## osdctl hive login-tests

Test utility to exercise OSDCTL client connections for both Target Cluster and it's Hive Cluster.

### Synopsis


This test utility attempts to exercise and validate OSDCTL's functions related to
OCM and backplane client connections.

This test utility can be run against an OSD/Rosa Classic target cluster. This utility
will attempt to discover the Hive cluster, and create both
OCM and kube client connections, and perform basic requests for each to connection in
order to validate functionality of the related OSDCTL utility functions.

This test utility allows for the target cluster to exist in a separate OCM
environment (ie integration, staging) from the hive cluster (ie production).

The default OCM environment vars should be set for the target cluster.
If the target cluster exists outside of the OCM 'production' environment, the user
has the option to provide the production OCM config (with valid token set),
or provide the production OCM API url as a command argument, or set the value in the osdctl
config yaml file (ie: "hive_ocm_url: https://api.openshift.com" or "hive_ocm_url: production" ).
For testing purposes comment out 'hive_ocm_url' from osdctl's config if testing an empty value.


```
osdctl hive login-tests [flags]
```

### Options

```
-C, --cluster-id string Cluster ID
-h, --help help for login-tests
--hive-ocm-config string OCM config for hive if different than Cluster
--hive-ocm-url string OCM URL for hive, this will fallback to reading from the osdctl config value: 'hive_ocm_url' if left empty
--verbose Verbose output
```

### Options inherited from parent commands

```
--as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
-o, --output string Valid formats are ['', 'json', 'yaml', 'env']
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-s, --server string The address and port of the Kubernetes API server
--skip-aws-proxy-check aws_proxy Don't use the configured aws_proxy value
-S, --skip-version-check skip checking to see if this is the most recent release
```

### SEE ALSO

* [osdctl hive](osdctl_hive.md) - hive related utilities

41 changes: 41 additions & 0 deletions pkg/k8s/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"

sdk "github.com/openshift-online/ocm-sdk-go"
bplogin "github.com/openshift/backplane-cli/cmd/ocm-backplane/login"
bpconfig "github.com/openshift/backplane-cli/pkg/cli/config"
bputils "github.com/openshift/backplane-cli/pkg/utils"
Expand Down Expand Up @@ -162,6 +163,26 @@ func NewRestConfig(clusterID string) (*rest.Config, error) {
return cfg, nil
}

// Create Backplane connection to a provided cluster, using a provided ocm sdk connection
// This is intended to allow backplane connections to multiple clusters which exist in different
// ocm environments by allowing the caller to provide an ocm connection to the function.
func NewWithConn(clusterID string, options client.Options, ocmConn *sdk.Connection) (client.Client, error) {
if ocmConn == nil {
return nil, fmt.Errorf("nil OCM sdk connection provided to NewWithConn()")
}
bp, err := bpconfig.GetBackplaneConfigurationWithConn(ocmConn)
if err != nil {
return nil, fmt.Errorf("failed to load backplane-cli config: %v", err)
}

cfg, err := bplogin.GetRestConfigWithConn(bp, ocmConn, clusterID)
if err != nil {
return nil, err
}
setRuntimeLoggerDiscard()
return client.New(cfg, options)
}

func NewAsBackplaneClusterAdmin(clusterID string, options client.Options, elevationReasons ...string) (client.Client, error) {
bp, err := bpconfig.GetBackplaneConfiguration()
if err != nil {
Expand All @@ -176,6 +197,26 @@ func NewAsBackplaneClusterAdmin(clusterID string, options client.Options, elevat
return client.New(cfg, options)
}

// Create Backplane connection as cluster admin to a provided cluster, using a provided ocm sdk connection
// This is intended to allow backplane connections to multiple clusters which exist in different
// ocm environments by allowing the caller to provide an ocm connection to the function.
func NewAsBackplaneClusterAdminWithConn(clusterID string, options client.Options, ocmConn *sdk.Connection, elevationReasons ...string) (client.Client, error) {
if ocmConn == nil {
return nil, fmt.Errorf("nil OCM sdk connection provided to NewAsBackplaneClusterAdminWithConn()")
}
bp, err := bpconfig.GetBackplaneConfigurationWithConn(ocmConn)
if err != nil {
return nil, fmt.Errorf("failed to load backplane-cli config: %v", err)
}

cfg, err := bplogin.GetRestConfigAsUserWithConn(bp, ocmConn, clusterID, "backplane-cluster-admin", elevationReasons...)
if err != nil {
return nil, err
}
setRuntimeLoggerDiscard()
return client.New(cfg, options)
}

func setRuntimeLoggerDiscard() {
// To avoid warnings/backtrace, if k8s controller-runtime logger has not already been set, do it now...
if !log.Log.Enabled() {
Expand Down
117 changes: 117 additions & 0 deletions pkg/k8s/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package k8s

import (
"testing"

sdk "github.com/openshift-online/ocm-sdk-go"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// TestNewWithConn tests the NewWithConn function which creates a Kubernetes client
// using backplane with a provided OCM SDK connection. This allows connecting to clusters
// in different OCM environments by providing a custom OCM connection.
func TestNewWithConn(t *testing.T) {
tests := []struct {
name string
clusterID string
options client.Options
ocmConn *sdk.Connection
wantErr bool
}{
{
// Test that passing a nil OCM connection returns an error
name: "nil OCM connection",
clusterID: "test-cluster-id",
options: client.Options{},
ocmConn: nil,
wantErr: true,
},
{
// Test that passing an empty cluster ID with nil connection returns an error
name: "empty cluster ID with nil connection",
clusterID: "",
options: client.Options{},
ocmConn: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewWithConn(tt.clusterID, tt.options, tt.ocmConn)
if tt.wantErr {
if err == nil {
t.Errorf("NewWithConn() expected error but got none")
}
} else {
if err != nil {
t.Errorf("NewWithConn() unexpected error = %v", err)
}
if client == nil {
t.Errorf("NewWithConn() returned nil client")
}
}
})
}
}

// TestNewAsBackplaneClusterAdminWithConn tests the NewAsBackplaneClusterAdminWithConn function
// which creates an elevated Kubernetes client (backplane-cluster-admin) using a provided OCM
// SDK connection. This function allows connecting to clusters in different OCM environments
// with elevated permissions.
func TestNewAsBackplaneClusterAdminWithConn(t *testing.T) {
tests := []struct {
name string
clusterID string
options client.Options
ocmConn *sdk.Connection
elevationReasons []string
wantErr bool
}{
{
// Test that passing a nil OCM connection with elevation reasons returns an error
name: "nil OCM connection",
clusterID: "test-cluster-id",
options: client.Options{},
ocmConn: nil,
elevationReasons: []string{"testing"},
wantErr: true,
},
{
// Test that passing a nil OCM connection without elevation reasons also returns an error
name: "nil OCM connection with no elevation reasons",
clusterID: "test-cluster-id",
options: client.Options{},
ocmConn: nil,
elevationReasons: nil,
wantErr: true,
},
{
// Test that passing an empty cluster ID with nil connection returns an error
name: "empty cluster ID with nil connection",
clusterID: "",
options: client.Options{},
ocmConn: nil,
elevationReasons: []string{"testing"},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewAsBackplaneClusterAdminWithConn(tt.clusterID, tt.options, tt.ocmConn, tt.elevationReasons...)
if tt.wantErr {
if err == nil {
t.Errorf("NewAsBackplaneClusterAdminWithConn() expected error but got none")
}
} else {
if err != nil {
t.Errorf("NewAsBackplaneClusterAdminWithConn() unexpected error = %v", err)
}
if client == nil {
t.Errorf("NewAsBackplaneClusterAdminWithConn() returned nil client")
}
}
})
}
}
Loading