Merge "Add Redfish Authentication Support"

This commit is contained in:
Zuul 2020-03-27 23:30:59 +00:00 committed by Gerrit Code Review
commit 6ea552bbbc
15 changed files with 393 additions and 65 deletions

View File

@ -74,7 +74,7 @@ func getNetworkData(docBundle document.Bundle) ([]byte, error) {
}
// try and find these documents in our bundle
selector, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
selector, err = document.NewNetworkDataSelector(bmhDoc)
if err != nil {
return nil, err
}

View File

@ -47,6 +47,7 @@ type Bundle interface {
SetFileSystem(FileSystem) error
GetFileSystem() FileSystem
Select(selector Selector) ([]Document, error)
SelectOne(selector Selector) (Document, error)
SelectBundle(selector Selector) (Bundle, error)
SelectByFieldValue(string, func(interface{}) bool) (Bundle, error)
GetByGvk(string, string, string) ([]Document, error)
@ -220,6 +221,29 @@ func (b *BundleFactory) Select(selector Selector) ([]Document, error) {
return docSet, err
}
// SelectOne serves the common use case where you expect one match
// and only one match to your selector -- in other words, you want to
// error if you didn't find any documents, and error if you found
// more than one. This reduces code repetition that would otherwise
// be scattered around that evaluates the length of the doc set returned
// for this common case
func (b *BundleFactory) SelectOne(selector Selector) (Document, error) {
docSet, err := b.Select(selector)
if err != nil {
return nil, err
}
// evaluate docSet for at least one document, and no more than
// one document and raise errors as appropriate
switch numDocsFound := len(docSet); {
case numDocsFound == 0:
return nil, ErrDocNotFound{Selector: selector}
case numDocsFound > 1:
return nil, ErrMultipleDocsFound{Selector: selector}
}
return docSet[0], nil
}
// SelectBundle offers an interface to pass a Selector, built on top of kustomize Selector
// to the bundle returning a new Bundle that matches the criteria. This is useful
// where you want to actually prune the underlying bundle you are working with

View File

@ -0,0 +1,60 @@
package document
// GetBMHNetworkData retrieves the associated network data string
// for the bmh document supplied from the bundle supplied
func GetBMHNetworkData(bmh Document, bundle Bundle) (string, error) {
// try and find these documents in our bundle
selector, err := NewNetworkDataSelector(bmh)
if err != nil {
return "", err
}
doc, err := bundle.SelectOne(selector)
if err != nil {
return "", err
}
networkData, err := GetSecretDataKey(doc, "networkData")
if err != nil {
return "", err
}
return networkData, nil
}
// GetBMHBMCAddress returns the bmc address for a particular the document supplied
func GetBMHBMCAddress(bmh Document) (string, error) {
bmcAddress, err := bmh.GetString("spec.bmc.address")
if err != nil {
return "", err
}
return bmcAddress, nil
}
// GetBMHBMCCredentials returns the BMC credentials for the bmh document supplied from
// the supplied bundle
func GetBMHBMCCredentials(bmh Document, bundle Bundle) (username string, password string, err error) {
// extract the secret document name
bmcCredentialsName, err := bmh.GetString("spec.bmc.credentialsName")
if err != nil {
return "", "", err
}
// find the secret within the bundle supplied
selector := NewBMCCredentialsSelector(bmcCredentialsName)
doc, err := bundle.SelectOne(selector)
if err != nil {
return "", "", err
}
username, err = GetSecretDataKey(doc, "username")
if err != nil {
return "", "", err
}
password, err = GetSecretDataKey(doc, "password")
if err != nil {
return "", "", err
}
// extract the username and password from them
return username, password, nil
}

View File

@ -0,0 +1,55 @@
package document_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/testutil"
)
func TestDocHelpers(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
fSys := testutil.SetupTestFs(t, "testdata/dochelper")
bundle, err := document.NewBundle(fSys, "/", "/")
require.NoError(err, "Building Bundle Failed")
require.NotNil(bundle)
t.Run("GetBMHNetworkData", func(t *testing.T) {
// retrieve our single bmh in the dataset
selector := document.NewSelector().ByKind("BareMetalHost")
doc, err := bundle.SelectOne(selector)
require.NoError(err)
networkData, err := document.GetBMHNetworkData(doc, bundle)
require.NoError(err, "Unexpected error trying to GetBMHNetworkData")
assert.Equal(networkData, "some network data")
})
t.Run("GetBMHBMCAddress", func(t *testing.T) {
// retrieve our single bmh in the dataset
selector := document.NewSelector().ByKind("BareMetalHost")
doc, err := bundle.SelectOne(selector)
require.NoError(err)
bmcAddress, err := document.GetBMHBMCAddress(doc)
require.NoError(err, "Unexpected error trying to GetBMHBMCAddress")
assert.Equal(bmcAddress, "redfish+https://192.168.111.1/v1/Redfish/Foo/Bar")
})
t.Run("GetBMHBMCCredentials", func(t *testing.T) {
// retrieve our single bmh in the dataset
selector := document.NewSelector().ByKind("BareMetalHost")
doc, err := bundle.SelectOne(selector)
require.NoError(err)
bmcUsername, bmcPassword, err := document.GetBMHBMCCredentials(doc, bundle)
require.NoError(err, "Unexpected error trying to GetBMHBMCCredentials")
assert.Equal(bmcUsername, "username")
assert.Equal(bmcPassword, "password")
})
}

View File

@ -0,0 +1,44 @@
package document
import (
b64 "encoding/base64"
)
// GetSecretDataKey understands how to retrieve a specific top level key from a secret
// that may have the data stored under a data or stringData field in which
// case the key may be base64 encoded or it may be plain text
//
// it is meant to be used by other high level dochelpers
func GetSecretDataKey(cfg Document, key string) (string, error) {
var needsBase64Decode = true
var docName = cfg.GetName()
// this purposely doesn't handle binaryData as that isn't
// something we could support anyways
data, err := cfg.GetStringMap("stringData")
if err == nil {
needsBase64Decode = false
} else {
data, err = cfg.GetStringMap("data")
if err != nil {
return "", ErrDocumentMalformed{
DocName: docName,
Message: "The secret document lacks a data or stringData top level field",
}
}
}
res, ok := data[key]
if !ok {
return "", ErrDocumentDataKeyNotFound{DocName: docName, Key: key}
}
if needsBase64Decode {
byteSlice, err := b64.StdEncoding.DecodeString(res)
if err != nil {
return "", err
}
return string(byteSlice), nil
}
return res, nil
}

View File

@ -9,6 +9,19 @@ type ErrDocNotFound struct {
Selector Selector
}
// ErrDocumentDataKeyNotFound returned if desired key within a document not found
type ErrDocumentDataKeyNotFound struct {
DocName string
Key string
}
// ErrDocumentMalformed returned if the document is structurally malformed
// (e.g. missing required low level keys)
type ErrDocumentMalformed struct {
DocName string
Message string
}
// ErrMultipleDocsFound returned if desired document not found
type ErrMultipleDocsFound struct {
Selector Selector
@ -18,6 +31,14 @@ func (e ErrDocNotFound) Error() string {
return fmt.Sprintf("Document filtered by selector %q found no documents", e.Selector)
}
func (e ErrDocumentDataKeyNotFound) Error() string {
return fmt.Sprintf("Document %q cannot retrieve data key %q", e.DocName, e.Key)
}
func (e ErrDocumentMalformed) Error() string {
return fmt.Sprintf("Document %q is malformed: %q", e.DocName, e.Message)
}
func (e ErrMultipleDocsFound) Error() string {
return fmt.Sprintf("Document filtered by selector %q found more than one document", e.Selector)
}

View File

@ -64,7 +64,7 @@ func (s Selector) ByAnnotation(annotationSelector string) Selector {
return s
}
// EphemeralCloudDataSelector returns selector to get BaremetalHost for ephemeral node
// NewEphemeralCloudDataSelector returns selector to get BaremetalHost for ephemeral node
func NewEphemeralCloudDataSelector() Selector {
return NewSelector().ByKind(SecretKind).ByLabel(EphemeralUserDataSelector)
}
@ -74,11 +74,16 @@ func NewEphemeralBMHSelector() Selector {
return NewSelector().ByKind(BareMetalHostKind).ByLabel(EphemeralHostSelector)
}
// NewEphemeralNetworkDataSelector returns selector that can be used to get secret with
// NewBMCCredentialsSelector returns selector to get BaremetalHost BMC credentials
func NewBMCCredentialsSelector(name string) Selector {
return NewSelector().ByKind(SecretKind).ByName(name)
}
// NewNetworkDataSelector returns selector that can be used to get secret with
// network data bmhDoc argument is a document interface, that should hold fields
// spec.networkData.name and spec.networkData.namespace where to find the secret,
// if either of these fields are not defined in Document error will be returned
func NewEphemeralNetworkDataSelector(bmhDoc Document) (Selector, error) {
func NewNetworkDataSelector(bmhDoc Document) (Selector, error) {
selector := NewSelector()
// extract the network data document pointer from the bmh document
netConfDocName, err := bmhDoc.GetString("spec.networkData.name")

View File

@ -24,7 +24,7 @@ func TestSelectorsPositive(t *testing.T) {
require.NoError(t, err)
assert.Len(t, docs, 1)
bmhDoc := docs[0]
selector, err := document.NewEphemeralNetworkDataSelector(bmhDoc)
selector, err := document.NewNetworkDataSelector(bmhDoc)
require.NoError(t, err)
assert.Equal(t, "validName", selector.Name)
})
@ -42,12 +42,12 @@ func TestSelectorsNegative(t *testing.T) {
// test coverage
bundle := testutil.NewTestBundle(t, "testdata/selectors/invalid")
t.Run("TestNewEphemeralNetworkDataSelectorErr", func(t *testing.T) {
t.Run("TestNewNetworkDataSelectorErr", func(t *testing.T) {
docs, err := bundle.Select(document.NewEphemeralBMHSelector())
require.NoError(t, err)
assert.Len(t, docs, 2)
bmhDoc := docs[0]
_, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
_, err = document.NewNetworkDataSelector(bmhDoc)
assert.Error(t, err)
})
@ -56,7 +56,7 @@ func TestSelectorsNegative(t *testing.T) {
require.NoError(t, err)
assert.Len(t, docs, 2)
bmhDoc := docs[1]
_, err = document.NewEphemeralNetworkDataSelector(bmhDoc)
_, err = document.NewNetworkDataSelector(bmhDoc)
assert.Error(t, err)
})
}
@ -67,7 +67,7 @@ func TestSelectorsSkip(t *testing.T) {
// test coverage
bundle := testutil.NewTestBundle(t, "testdata/selectors/exclude-from-k8s")
t.Run("TestNewEphemeralNetworkDataSelectorErr", func(t *testing.T) {
t.Run("TestNewNetworkDataSelectorErr", func(t *testing.T) {
selector := document.NewDeployToK8sSelector()
docs, err := bundle.Select(selector)
require.NoError(t, err)

View File

@ -0,0 +1,16 @@
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-0
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+https://192.168.111.1/v1/Redfish/Foo/Bar
credentialsName: master-0-bmc
networkData:
name: master-0-networkdata
namespace: metal3

View File

@ -0,0 +1,3 @@
resources:
- baremetalhost.yaml
- secret.yaml

View File

@ -0,0 +1,28 @@
apiVersion: v1
kind: Secret
metadata:
labels:
airshipit.org/ephemeral-user-data: "true"
name: ephemeral-user-data
type: Opaque
stringData:
userData: cloud-init
---
apiVersion: v1
kind: Secret
metadata:
name: master-0-bmc
namespace: metal3
type: Opaque
stringData:
username: username
password: password
---
apiVersion: v1
kind: Secret
metadata:
name: master-0-networkdata
namespace: metal3
type: Opaque
stringData:
networkData: some network data

View File

@ -17,5 +17,4 @@ metadata:
namespace: validNamespace
type: Opaque
stringData:
userData: cloud-init
userData: cloud-init

View File

@ -29,14 +29,18 @@ type RemoteDirect struct {
// Redfish Client implementation
RedfishAPI redfishApi.RedfishAPI
// optional Username Authentication
Username string
// optional Password
Password string
}
// Top level function to handle Redfish remote direct
func (cfg RemoteDirect) DoRemoteDirect() error {
alog.Debugf("Using Redfish Endpoint: '%s'", cfg.RemoteURL.String())
/* TODO: Add Authentication when redfish library supports it. */
/* Get system details */
systemID := cfg.EphemeralNodeID
system, _, err := cfg.RedfishAPI.GetSystem(cfg.Context, systemID)
@ -79,10 +83,12 @@ func (cfg RemoteDirect) DoRemoteDirect() error {
return nil
}
// Creates a new Redfish remote direct client.
func NewRedfishRemoteDirectClient(ctx context.Context,
// NewRedfishRemoteDirectClient creates a new Redfish remote direct client.
func NewRedfishRemoteDirectClient(
remoteURL string,
ephNodeID string,
username string,
password string,
isoPath string,
insecure bool,
useproxy bool,
@ -101,6 +107,17 @@ func NewRedfishRemoteDirectClient(ctx context.Context,
}
}
var ctx context.Context
if username != "" && password != "" {
ctx = context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: username, Password: password},
)
} else {
ctx = context.Background()
}
if isoPath == "" {
return RemoteDirect{},
ErrRedfishMissingConfig{

View File

@ -28,37 +28,44 @@ func TestRedfishRemoteDirectNormal(t *testing.T) {
systemID := computerSystemID
httpResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).Times(1).
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
m.On("GetSystem", ctx, systemID).Times(1).
Return(getTestSystem(), httpResp, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, httpResp, nil)
m.On("GetSystem", context.Background(), systemID).Times(1).
m.On("GetSystem", ctx, systemID).Times(1).
Return(getTestSystem(), httpResp, nil)
systemReq := redfishClient.ComputerSystem{
Boot: redfishClient.Boot{
BootSourceOverrideTarget: redfishClient.BOOTSOURCE_CD,
},
}
m.On("SetSystem", context.Background(), systemID, systemReq).
m.On("SetSystem", ctx, systemID, systemReq).
Times(1).
Return(redfishClient.ComputerSystem{}, httpResp, nil)
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", context.Background(), systemID, resetReq).
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, nil)
m.On("GetSystem", context.Background(), systemID).Times(1).
m.On("GetSystem", ctx, systemID).Times(1).
Return(redfishClient.ComputerSystem{PowerState: redfishClient.POWERSTATE_OFF}, httpResp, nil)
resetReq.ResetType = redfishClient.RESETTYPE_ON
m.On("ResetSystem", context.Background(), systemID, resetReq).
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, nil)
m.On("GetSystem", context.Background(), systemID).Times(1).
m.On("GetSystem", ctx, systemID).Times(1).
Return(redfishClient.ComputerSystem{PowerState: redfishClient.POWERSTATE_ON}, httpResp, nil)
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
@ -72,13 +79,19 @@ func TestRedfishRemoteDirectInvalidSystemId(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := "invalid-server"
localRDCfg := getDefaultRedfishRemoteDirectObj(t, m)
localRDCfg.EphemeralNodeID = systemID
realErr := fmt.Errorf("%s system do not exist", systemID)
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Times(1).
Return(redfishClient.ComputerSystem{}, nil, realErr)
@ -92,10 +105,16 @@ func TestRedfishRemoteDirectGetSystemNetworkError(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := computerSystemID
realErr := fmt.Errorf("server request timeout")
httpResp := &http.Response{StatusCode: 408}
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Times(1).
Return(redfishClient.ComputerSystem{}, httpResp, realErr)
@ -111,6 +130,12 @@ func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := computerSystemID
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
localRDCfg := rDCfg
@ -118,11 +143,11 @@ func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) {
realErr := redfishClient.GenericOpenAPIError{}
httpResp := &http.Response{StatusCode: 500}
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Times(1).
Return(getTestSystem(), nil, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, httpResp, realErr)
err := localRDCfg.DoRemoteDirect()
@ -135,6 +160,12 @@ func TestRedfishRemoteDirectCdDvdNotAvailableInBootSources(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := computerSystemID
invalidSystem := getTestSystem()
invalidSystem.Boot.BootSourceOverrideTargetRedfishAllowableValues = []redfishClient.BootSource{
@ -142,10 +173,10 @@ func TestRedfishRemoteDirectCdDvdNotAvailableInBootSources(t *testing.T) {
redfishClient.BOOTSOURCE_PXE,
}
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Return(invalidSystem, nil, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, nil, nil)
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
@ -160,15 +191,21 @@ func TestRedfishRemoteDirectSetSystemBootSourceFailed(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := computerSystemID
httpSuccResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Return(getTestSystem(), httpSuccResp, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
m.On("InsertVirtualMedia", ctx, "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
m.On("SetSystem", context.Background(), systemID, mock.Anything).
m.On("SetSystem", ctx, systemID, mock.Anything).
Times(1).
Return(redfishClient.ComputerSystem{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
@ -185,21 +222,27 @@ func TestRedfishRemoteDirectSystemRebootFailed(t *testing.T) {
m := &redfishMocks.RedfishAPI{}
defer m.AssertExpectations(t)
ctx := context.WithValue(
context.Background(),
redfishClient.ContextBasicAuth,
redfishClient.BasicAuth{UserName: "username", Password: "password"},
)
systemID := computerSystemID
httpSuccResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).
m.On("GetSystem", ctx, systemID).
Return(getTestSystem(), httpSuccResp, nil)
m.On("InsertVirtualMedia", context.Background(), mock.Anything, mock.Anything, mock.Anything).
m.On("InsertVirtualMedia", ctx, mock.Anything, mock.Anything, mock.Anything).
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
m.On("SetSystem", context.Background(), systemID, mock.Anything).
m.On("SetSystem", ctx, systemID, mock.Anything).
Times(1).
Return(redfishClient.ComputerSystem{}, httpSuccResp, nil)
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", context.Background(), systemID, resetReq).
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
@ -244,9 +287,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
defer m.AssertExpectations(t)
_, err := NewRedfishRemoteDirectClient(
context.Background(),
defaultURL,
computerSystemID,
"username",
"password",
"/tmp/test.iso",
true,
false,
@ -255,9 +299,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
// Test with empty remote URL
_, err = NewRedfishRemoteDirectClient(
context.Background(),
"",
computerSystemID,
"username",
"password",
"/tmp/test.iso",
false,
false,
@ -267,9 +312,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
// Test with empty ephemeral NodeID
_, err = NewRedfishRemoteDirectClient(
context.Background(),
defaultURL,
"",
"username",
"password",
"/tmp/test.iso",
false,
false,
@ -279,9 +325,10 @@ func TestNewRedfishRemoteDirectClient(t *testing.T) {
// Test with empty Iso Path
_, err = NewRedfishRemoteDirectClient(
context.Background(),
defaultURL,
computerSystemID,
"username",
"password",
"",
false,
false,
@ -294,9 +341,10 @@ func getDefaultRedfishRemoteDirectObj(t *testing.T, api redfishAPI.RedfishAPI) R
t.Helper()
rDCfg, err := NewRedfishRemoteDirectClient(
context.Background(),
defaultURL,
computerSystemID,
"username",
"password",
"/tmp/test.iso",
false,
false,

View File

@ -1,7 +1,6 @@
package remote
import (
"context"
"fmt"
"net/url"
"strings"
@ -23,7 +22,11 @@ type Client interface {
}
// Get remotedirect client based on config
func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string) (Client, error) {
func getRemoteDirectClient(
remoteConfig *config.RemoteDirect,
remoteURL string,
username string,
password string) (Client, error) {
var client Client
switch remoteConfig.RemoteType {
case AirshipRemoteTypeRedfish:
@ -44,9 +47,10 @@ func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string)
nodeID := urlPath[len(urlPath)-1]
client, err = redfish.NewRedfishRemoteDirectClient(
context.Background(),
baseURL,
nodeID,
username,
password,
remoteConfig.IsoURL,
remoteConfig.Insecure,
remoteConfig.UseProxy,
@ -63,56 +67,60 @@ func getRemoteDirectClient(remoteConfig *config.RemoteDirect, remoteURL string)
return client, nil
}
func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (*config.RemoteDirect, string, error) {
func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (
remoteConfig *config.RemoteDirect,
remoteURL string,
username string,
password string,
err error) {
cfg := settings.Config()
bootstrapSettings, err := cfg.CurrentContextBootstrapInfo()
if err != nil {
return nil, "", err
return nil, "", "", "", err
}
remoteConfig := bootstrapSettings.RemoteDirect
remoteConfig = bootstrapSettings.RemoteDirect
if remoteConfig == nil {
return nil, "", config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"}
return nil, "", "", "", config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"}
}
root, err := cfg.CurrentContextEntryPoint(config.Ephemeral, "")
bundlePath, err := cfg.CurrentContextEntryPoint(config.Ephemeral, "")
if err != nil {
return nil, "", err
return nil, "", "", "", err
}
docBundle, err := document.NewBundleByPath(root)
docBundle, err := document.NewBundleByPath(bundlePath)
if err != nil {
return nil, "", err
return nil, "", "", "", err
}
selector := document.NewEphemeralBMHSelector()
docs, err := docBundle.Select(selector)
doc, err := docBundle.SelectOne(selector)
if err != nil {
return nil, "", err
}
if len(docs) == 0 {
return nil, "", document.ErrDocNotFound{
Selector: selector,
}
return nil, "", "", "", err
}
// NOTE If filter returned more than one document chose first
remoteURL, err := docs[0].GetString("spec.bmc.address")
remoteURL, err = document.GetBMHBMCAddress(doc)
if err != nil {
return nil, "", err
return nil, "", "", "", err
}
return remoteConfig, remoteURL, nil
username, password, err = document.GetBMHBMCCredentials(doc, docBundle)
if err != nil {
return nil, "", "", "", err
}
return remoteConfig, remoteURL, username, password, nil
}
// Top level function to execute remote direct based on remote type
func DoRemoteDirect(settings *environment.AirshipCTLSettings) error {
remoteConfig, remoteURL, err := getRemoteDirectConfig(settings)
remoteConfig, remoteURL, username, password, err := getRemoteDirectConfig(settings)
if err != nil {
return err
}
client, err := getRemoteDirectClient(remoteConfig, remoteURL)
client, err := getRemoteDirectClient(remoteConfig, remoteURL, username, password)
if err != nil {
return err
}