Skip to content

Commit

Permalink
Merge pull request #347 from bmc-toolbox/floppy-upload
Browse files Browse the repository at this point in the history
Floppy upload and unmount
  • Loading branch information
joelrebel authored Oct 31, 2023
2 parents 25d7063 + 7932b1c commit 81949eb
Show file tree
Hide file tree
Showing 9 changed files with 597 additions and 1 deletion.
150 changes: 150 additions & 0 deletions bmc/floppy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package bmc

import (
"context"
"fmt"
"io"

bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)

// FloppyImageMounter defines methods to upload a floppy image
type FloppyImageMounter interface {
MountFloppyImage(ctx context.Context, image io.Reader) (err error)
}

// floppyImageUploaderProvider is an internal struct to correlate an implementation/provider and its name
type floppyImageUploaderProvider struct {
name string
impl FloppyImageMounter
}

// mountFloppyImage is a wrapper method to invoke methods for the FloppyImageMounter interface
func mountFloppyImage(ctx context.Context, image io.Reader, p []floppyImageUploaderProvider) (metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range p {
if elem.impl == nil {
continue
}

select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
uploadErr := elem.impl.MountFloppyImage(ctx, image)
if uploadErr != nil {
err = multierror.Append(err, errors.WithMessagef(uploadErr, "provider: %v", elem.name))
continue
}

metadataLocal.SuccessfulProvider = elem.name
return metadataLocal, nil
}
}

return metadataLocal, multierror.Append(err, errors.New("failed to mount floppy image"))
}

// MountFloppyImageFromInterfaces identifies implementations of the FloppyImageMounter interface and passes the found implementations to the mountFloppyImage() wrapper
func MountFloppyImageFromInterfaces(ctx context.Context, image io.Reader, p []interface{}) (metadata Metadata, err error) {
providers := make([]floppyImageUploaderProvider, 0)
for _, elem := range p {
temp := floppyImageUploaderProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FloppyImageMounter:
temp.impl = p
providers = append(providers, temp)
default:
e := fmt.Sprintf("not a FloppyImageMounter implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}

if len(providers) == 0 {
return metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
"no FloppyImageMounter implementations found",
),
)

}

return mountFloppyImage(ctx, image, providers)
}

// FloppyImageMounter defines methods to unmount a floppy image
type FloppyImageUnmounter interface {
UnmountFloppyImage(ctx context.Context) (err error)
}

// floppyImageUnmounterProvider is an internal struct to correlate an implementation/provider and its name
type floppyImageUnmounterProvider struct {
name string
impl FloppyImageUnmounter
}

// unmountFloppyImage is a wrapper method to invoke methods for the FloppyImageUnmounter interface
func unmountFloppyImage(ctx context.Context, p []floppyImageUnmounterProvider) (metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range p {
if elem.impl == nil {
continue
}

select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
uploadErr := elem.impl.UnmountFloppyImage(ctx)
if uploadErr != nil {
err = multierror.Append(err, errors.WithMessagef(uploadErr, "provider: %v", elem.name))
continue
}

metadataLocal.SuccessfulProvider = elem.name
return metadataLocal, nil
}
}

return metadataLocal, multierror.Append(err, errors.New("failed to unmount floppy image"))
}

// MountFloppyImageFromInterfaces identifies implementations of the FloppyImageUnmounter interface and passes the found implementations to the unmountFloppyImage() wrapper
func UnmountFloppyImageFromInterfaces(ctx context.Context, p []interface{}) (metadata Metadata, err error) {
providers := make([]floppyImageUnmounterProvider, 0)
for _, elem := range p {
temp := floppyImageUnmounterProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FloppyImageUnmounter:
temp.impl = p
providers = append(providers, temp)
default:
e := fmt.Sprintf("not a FloppyImageUnmounter implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}

if len(providers) == 0 {
return metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
"no FloppyImageUnmounter implementations found",
),
)
}

return unmountFloppyImage(ctx, providers)
}
114 changes: 114 additions & 0 deletions bmc/floppy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package bmc

import (
"context"
"io"
"testing"
"time"

bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
"github.com/stretchr/testify/assert"
)

type mountFloppyImageTester struct {
returnError error
}

func (p *mountFloppyImageTester) MountFloppyImage(ctx context.Context, reader io.Reader) (err error) {
return p.returnError
}

func (p *mountFloppyImageTester) Name() string {
return "foo"
}

func TestMountFloppyFromInterfaces(t *testing.T) {
testCases := []struct {
testName string
image io.Reader
returnError error
ctxTimeout time.Duration
providerName string
providersAttempted int
badImplementation bool
}{
{"success with metadata", nil, nil, 5 * time.Second, "foo", 1, false},
{"failure with bad implementation", nil, bmclibErrs.ErrProviderImplementation, 1 * time.Nanosecond, "foo", 1, true},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
var generic []interface{}
if tc.badImplementation {
badImplementation := struct{}{}
generic = []interface{}{&badImplementation}
} else {
testImplementation := &mountFloppyImageTester{returnError: tc.returnError}
generic = []interface{}{testImplementation}
}
metadata, err := MountFloppyImageFromInterfaces(context.Background(), tc.image, generic)
if tc.returnError != nil {
assert.ErrorContains(t, err, tc.returnError.Error())
return
}

if err != nil {
t.Fatal(err)
}

assert.Equal(t, tc.returnError, err)
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
})
}
}

type unmountFloppyImageTester struct {
returnError error
}

func (p *unmountFloppyImageTester) UnmountFloppyImage(ctx context.Context) (err error) {
return p.returnError
}

func (p *unmountFloppyImageTester) Name() string {
return "foo"
}

func TestUnmountFloppyFromInterfaces(t *testing.T) {
testCases := []struct {
testName string
returnError error
ctxTimeout time.Duration
providerName string
providersAttempted int
badImplementation bool
}{
{"success with metadata", nil, 5 * time.Second, "foo", 1, false},
{"failure with bad implementation", bmclibErrs.ErrProviderImplementation, 1 * time.Nanosecond, "foo", 1, true},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
var generic []interface{}
if tc.badImplementation {
badImplementation := struct{}{}
generic = []interface{}{&badImplementation}
} else {
testImplementation := &unmountFloppyImageTester{returnError: tc.returnError}
generic = []interface{}{testImplementation}
}
metadata, err := UnmountFloppyImageFromInterfaces(context.Background(), generic)
if tc.returnError != nil {
assert.ErrorContains(t, err, tc.returnError.Error())
return
}

if err != nil {
t.Fatal(err)
}

assert.Equal(t, tc.returnError, err)
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
})
}
}
15 changes: 15 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,5 +450,20 @@ func (c *Client) Screenshot(ctx context.Context) (image []byte, fileType string,
func (c *Client) ClearSystemEventLog(ctx context.Context) (err error) {
metadata, err := bmc.ClearSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)

return err
}

func (c *Client) MountFloppyImage(ctx context.Context, image io.Reader) (err error) {
metadata, err := bmc.MountFloppyImageFromInterfaces(ctx, image, c.registry().GetDriverInterfaces())
c.setMetadata(metadata)

return err
}

func (c *Client) UnmountFloppyImage(ctx context.Context) (err error) {
metadata, err := bmc.UnmountFloppyImageFromInterfaces(ctx, c.registry().GetDriverInterfaces())
c.setMetadata(metadata)

return err
}
19 changes: 19 additions & 0 deletions examples/floppy-image/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
inventory is an example commmand that utilizes the 'v1' bmclib interface
methods to upload and mount, unmount a floppy image.
# mount image
$ go run examples/floppy-image/main.go \
-host 10.1.2.3 \
-user ADMIN \
-password hunter2 \
-image /tmp/floppy.img
# un-mount image
$ go run examples/floppy-image/main.go \
-host 10.1.2.3 \
-user ADMIN \
-password hunter2 \
-unmount
*/
package main
79 changes: 79 additions & 0 deletions examples/floppy-image/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"context"
"crypto/x509"
"flag"
"log"
"os"
"time"

bmclib "github.com/bmc-toolbox/bmclib/v2"
"github.com/bombsimon/logrusr/v2"
"github.com/sirupsen/logrus"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()

user := flag.String("user", "", "Username to login with")
pass := flag.String("password", "", "Username to login with")
host := flag.String("host", "", "BMC hostname to connect to")
imagePath := flag.String("image", "", "The .img file to be uploaded")
unmountImage := flag.Bool("unmount", false, "Unmount floppy image")

withSecureTLS := flag.Bool("secure-tls", false, "Enable secure TLS")
certPoolFile := flag.String("cert-pool", "", "Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true")
flag.Parse()

l := logrus.New()
l.Level = logrus.DebugLevel
logger := logrusr.New(l)

clientOpts := []bmclib.Option{bmclib.WithLogger(logger)}

if *withSecureTLS {
var pool *x509.CertPool
if *certPoolFile != "" {
pool = x509.NewCertPool()
data, err := os.ReadFile(*certPoolFile)
if err != nil {
l.Fatal(err)
}
pool.AppendCertsFromPEM(data)
}
// a nil pool uses the system certs
clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))
}

cl := bmclib.NewClient(*host, *user, *pass, clientOpts...)
err := cl.Open(ctx)
if err != nil {
log.Fatal(err, "bmc login failed")
}

defer cl.Close(ctx)

if *unmountImage {
if err := cl.UnmountFloppyImage(ctx); err != nil {
log.Fatal(err)
}

return
}

// open file handle
fh, err := os.Open(*imagePath)
if err != nil {
l.Fatal(err)
}
defer fh.Close()

err = cl.MountFloppyImage(ctx, fh)
if err != nil {
l.Fatal(err)
}

l.WithField("img", *imagePath).Info("image mounted successfully")
}
Loading

0 comments on commit 81949eb

Please sign in to comment.