Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bmclib version 2 #280

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
126 changes: 126 additions & 0 deletions v2/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
govet:
linters-settings:
enable:
- fieldalignment
check-shadowing: true
settings:
printf:
funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
golint:
min-confidence: 0
gocyclo:
min-complexity: 10
maligned:
suggest-new: true
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 2
depguard:
list-type: blacklist
packages:
# logging is allowed only by logutils.Log, logrus
# is allowed to use only in logutils package
- github.com/sirupsen/logrus
misspell:
locale: US
auto-fix: true
lll:
line-length: 140
goimports:
local-prefixes: github.com/golangci/golangci-lint
gocritic:
auto-fix: true
enabled-tags:
- performance
- style
- experimental
disabled-checks:
- wrapperFunc
gofumpt:
extra-rules: true
auto-fix: true
wsl:
auto-fix: true
govet:
auto-fix: true
stylecheck:
auto-fix: true

linters:
enable:
- errcheck
- gosimple
- govet
- gofmt
- gocyclo
- ineffassign
- stylecheck
- deadcode
- staticcheck
- structcheck
- unused
- prealloc
- typecheck
- varcheck
# additional linters
- bodyclose
- gocritic
- whitespace
- wsl
- goimports
- golint
- misspell
- goerr113
- noctx
enable-all: false
disable-all: true

run:
skip-dirs:


issues:
exclude-rules:
- linters:
- gosec
text: "weak cryptographic primitive"

- linters:
- stylecheck
text: "ST1016"
exclude:
# Default excludes from `golangci-lint run --help` with EXC0002 removed
# EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked
# EXC0002 golint: Annoying issue about not having a comment. The rare codebase has such comments
# - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
# EXC0003 golint: False positive when tests are defined in package 'test'
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
# EXC0004 govet: Common false positives
- (possible misuse of unsafe.Pointer|should have signature)
# EXC0005 staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore
- ineffective break statement. Did you mean to break out of the outer loop
# EXC0006 gosec: Too many false-positives on 'unsafe' usage
- Use of unsafe calls should be audited
# EXC0007 gosec: Too many false-positives for parametrized shell calls
- Subprocess launch(ed with variable|ing should be audited)
# EXC0008 gosec: Duplicated errcheck checks
- (G104|G307)
# EXC0009 gosec: Too many issues in popular repos
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
# EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'
- Potential file inclusion via variable
exclude-use-default: false

# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
#service:
# golangci-lint-version: 1.15.x # use the fixed version to not introduce new linters unexpectedly
# prepare:
# - echo "here I can run custom commands, but no preparation needed for this repo"
34 changes: 34 additions & 0 deletions v2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: test
test: ## Run unit tests
go test -gcflags=-l -v -covermode=count ./...

.PHONY: cover
cover: ## Run unit tests with coverage report
go test -gcflags=-l -coverprofile=coverage.txt ./...
go tool cover -func=coverage.txt

.PHONY: all-tests
all-tests: test cover ## run all tests

.PHONY: lint
lint: ## Run linting
@echo be sure golangci-lint is installed: https://golangci-lint.run/usage/install/
golangci-lint run --config .golangci.yml

.PHONY: goimports
goimports: ## run goimports updating files in place
@echo be sure goimports is installed
goimports -w .

.PHONY: goimports-check
goimports-check: ## run goimports displaying diffs
@echo be sure goimports is installed
goimports -d . | (! grep .)

.PHONY: all-checks
all-checks: lint goimports ## run all formatters
go mod tidy
go vet ./...
13 changes: 13 additions & 0 deletions v2/bmc/bmc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bmc

// Metadata represents details about a bmc method
type Metadata struct {
// SuccessfulProvider is the name of the provider that successfully executed
SuccessfulProvider string
// ProvidersAttempted is a slice of all providers that were attempt to execute
ProvidersAttempted []string
// SuccessfulOpenConns is a slice of provider names that were opened successfully
SuccessfulOpenConns []string
// SuccessfulCloseConns is a slice of provider names that were closed successfully
SuccessfulCloseConns []string
}
72 changes: 72 additions & 0 deletions v2/bmc/boot_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package bmc

import (
"context"
"fmt"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)

// BootDeviceSetter sets the next boot device for a machine
type BootDeviceSetter interface {
BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error)
}

// powerProviders is an internal struct to correlate an implementation/provider and its name
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
type bootDeviceProviders struct {
name string
bootDeviceSetter BootDeviceSetter
}

// SetBootDevice sets the boot device. Next boot only unless setPersistent=true
// if a successfulProviderName is passed in, it will be updated to be the name of the provider that successfully executed
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
func SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool, b []bootDeviceProviders) (ok bool, metadata Metadata, err error) {
var metadataLocal Metadata
Loop:
for _, elem := range b {
if elem.bootDeviceSetter == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())
break Loop
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
ok, setErr := elem.bootDeviceSetter.BootDeviceSet(ctx, bootDevice, setPersistent, efiBoot)
if setErr != nil {
err = multierror.Append(err, errors.WithMessagef(setErr, "provider: %v", elem.name))
continue
}
if !ok {
err = multierror.Append(err, fmt.Errorf("provider: %v, failed to set boot device", elem.name))
continue
}
metadataLocal.SuccessfulProvider = elem.name
return ok, metadataLocal, nil
}
}
return ok, metadataLocal, multierror.Append(err, errors.New("failed to set boot device"))
}

// SetBootDeviceFromInterfaces pass through to library function
// if a successfulProviderName is passed in, it will be updated to be the name of the provider that successfully executed
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
func SetBootDeviceFromInterfaces(ctx context.Context, bootDevice string, setPersistent, efiBoot bool, generic []interface{}) (ok bool, metadata Metadata, err error) {
bdSetters := make([]bootDeviceProviders, 0)
for _, elem := range generic {
temp := bootDeviceProviders{name: getProviderName(elem)}
switch p := elem.(type) {
case BootDeviceSetter:
temp.bootDeviceSetter = p
bdSetters = append(bdSetters, temp)
default:
e := fmt.Sprintf("not a BootDeviceSetter implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(bdSetters) == 0 {
return ok, metadata, multierror.Append(err, errors.New("no BootDeviceSetter implementations found"))
}
return SetBootDevice(ctx, bootDevice, setPersistent, efiBoot, bdSetters)
}
119 changes: 119 additions & 0 deletions v2/bmc/boot_device_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package bmc

import (
"context"
"errors"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
)

type bootDeviceTester struct {
MakeNotOK bool
MakeErrorOut bool
}

func (b *bootDeviceTester) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
if b.MakeErrorOut {
return ok, errors.New("boot device set failed")
}
if b.MakeNotOK {
return false, nil
}
return true, nil
}

func (b *bootDeviceTester) Name() string {
return "test provider"
}

func TestSetBootDevice(t *testing.T) {
testCases := map[string]struct {
bootDevice string
makeErrorOut bool
makeNotOk bool
want bool
err error
ctxTimeout time.Duration
}{
"success": {bootDevice: "pxe", want: true},
"not ok return": {bootDevice: "pxe", want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New("provider: test provider, failed to set boot device"), errors.New("failed to set boot device")}}},
"error": {bootDevice: "pxe", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New("provider: test provider: boot device set failed"), errors.New("failed to set boot device")}}},
"error context timeout": {bootDevice: "pxe", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New("context deadline exceeded"), errors.New("failed to set boot device")}}, ctxTimeout: time.Nanosecond * 1},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
testImplementation := bootDeviceTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}
expectedResult := tc.want
if tc.ctxTimeout == 0 {
tc.ctxTimeout = time.Second * 3
}
ctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)
defer cancel()
result, _, err := SetBootDevice(ctx, tc.bootDevice, false, false, []bootDeviceProviders{{"test provider", &testImplementation}})
if err != nil {
if tc.err != nil {
diff := cmp.Diff(err.Error(), tc.err.Error())
if diff != "" {
t.Fatal(diff)
}
} else {
t.Fatal(err)
}
} else {
diff := cmp.Diff(result, expectedResult)
if diff != "" {
t.Fatal(diff)
}
}
})
}
}

func TestSetBootDeviceFromInterfaces(t *testing.T) {
testCases := map[string]struct {
bootDevice string
err error
badImplementation bool
want bool
withName bool
}{
"success": {bootDevice: "pxe", want: true},
"success with metadata": {bootDevice: "pxe", want: true, withName: true},
"no implementations found": {bootDevice: "pxe", want: false, badImplementation: true, err: &multierror.Error{Errors: []error{errors.New("not a BootDeviceSetter implementation: *struct {}"), errors.New("no BootDeviceSetter implementations found")}}},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
var generic []interface{}
if tc.badImplementation {
badImplementation := struct{}{}
generic = []interface{}{&badImplementation}
} else {
testImplementation := bootDeviceTester{}
generic = []interface{}{&testImplementation}
}
expectedResult := tc.want
result, metadata, err := SetBootDeviceFromInterfaces(context.Background(), tc.bootDevice, false, false, generic)
if err != nil {
diff := cmp.Diff(tc.err.Error(), err.Error())
if diff != "" {
t.Fatal(diff)
}
} else {
diff := cmp.Diff(result, expectedResult)
if diff != "" {
t.Fatal(diff)
}
}
if tc.withName {
if diff := cmp.Diff(metadata.SuccessfulProvider, "test provider"); diff != "" {
t.Fatal(diff)
}
}
})
}
}
Loading