-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #347 from bmc-toolbox/floppy-upload
Floppy upload and unmount
- Loading branch information
Showing
9 changed files
with
597 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Oops, something went wrong.