A few fixes

* CLI properly formats the IP addresses in runner show
  * LXD provider now waits for an IP address before returning on Create
  * Added a few mocks for testing

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2022-07-10 14:52:15 +00:00
parent afb1d31394
commit 5566cde77f
46 changed files with 8829 additions and 6 deletions

View file

@ -0,0 +1,268 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
context "context"
github "github.com/google/go-github/v43/github"
mock "github.com/stretchr/testify/mock"
)
// GithubClient is an autogenerated mock type for the GithubClient type
type GithubClient struct {
mock.Mock
}
// CreateOrganizationRegistrationToken provides a mock function with given fields: ctx, owner
func (_m *GithubClient) CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx, owner)
var r0 *github.RegistrationToken
if rf, ok := ret.Get(0).(func(context.Context, string) *github.RegistrationToken); ok {
r0 = rf(ctx, owner)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, owner)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, owner)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// CreateRegistrationToken provides a mock function with given fields: ctx, owner, repo
func (_m *GithubClient) CreateRegistrationToken(ctx context.Context, owner string, repo string) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx, owner, repo)
var r0 *github.RegistrationToken
if rf, ok := ret.Get(0).(func(context.Context, string, string) *github.RegistrationToken); ok {
r0 = rf(ctx, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, string) *github.Response); ok {
r1 = rf(ctx, owner, repo)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok {
r2 = rf(ctx, owner, repo)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListOrganizationRunnerApplicationDownloads provides a mock function with given fields: ctx, owner
func (_m *GithubClient) ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, owner)
var r0 []*github.RunnerApplicationDownload
if rf, ok := ret.Get(0).(func(context.Context, string) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx, owner)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, owner)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, owner)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListOrganizationRunners provides a mock function with given fields: ctx, owner, opts
func (_m *GithubClient) ListOrganizationRunners(ctx context.Context, owner string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, owner, opts)
var r0 *github.Runners
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, owner, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, owner, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, *github.ListOptions) error); ok {
r2 = rf(ctx, owner, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunnerApplicationDownloads provides a mock function with given fields: ctx, owner, repo
func (_m *GithubClient) ListRunnerApplicationDownloads(ctx context.Context, owner string, repo string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, owner, repo)
var r0 []*github.RunnerApplicationDownload
if rf, ok := ret.Get(0).(func(context.Context, string, string) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, string) *github.Response); ok {
r1 = rf(ctx, owner, repo)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok {
r2 = rf(ctx, owner, repo)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunners provides a mock function with given fields: ctx, owner, repo, opts
func (_m *GithubClient) ListRunners(ctx context.Context, owner string, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, opts)
var r0 *github.Runners
if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, owner, repo, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.ListOptions) error); ok {
r2 = rf(ctx, owner, repo, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// RemoveOrganizationRunner provides a mock function with given fields: ctx, owner, runnerID
func (_m *GithubClient) RemoveOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, runnerID)
var r0 *github.Response
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
r1 = rf(ctx, owner, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveRunner provides a mock function with given fields: ctx, owner, repo, runnerID
func (_m *GithubClient) RemoveRunner(ctx context.Context, owner string, repo string, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, owner, repo, runnerID)
var r0 *github.Response
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Response); ok {
r0 = rf(ctx, owner, repo, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
r1 = rf(ctx, owner, repo, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewGithubClient interface {
mock.TestingT
Cleanup(func())
}
// NewGithubClient creates a new instance of GithubClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewGithubClient(t mockConstructorTestingTNewGithubClient) *GithubClient {
mock := &GithubClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,141 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
params "garm/params"
mock "github.com/stretchr/testify/mock"
)
// PoolManager is an autogenerated mock type for the PoolManager type
type PoolManager struct {
mock.Mock
}
// ForceDeleteRunner provides a mock function with given fields: runner
func (_m *PoolManager) ForceDeleteRunner(runner params.Instance) error {
ret := _m.Called(runner)
var r0 error
if rf, ok := ret.Get(0).(func(params.Instance) error); ok {
r0 = rf(runner)
} else {
r0 = ret.Error(0)
}
return r0
}
// HandleWorkflowJob provides a mock function with given fields: job
func (_m *PoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
ret := _m.Called(job)
var r0 error
if rf, ok := ret.Get(0).(func(params.WorkflowJob) error); ok {
r0 = rf(job)
} else {
r0 = ret.Error(0)
}
return r0
}
// ID provides a mock function with given fields:
func (_m *PoolManager) ID() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// RefreshState provides a mock function with given fields: param
func (_m *PoolManager) RefreshState(param params.UpdatePoolStateParams) error {
ret := _m.Called(param)
var r0 error
if rf, ok := ret.Get(0).(func(params.UpdatePoolStateParams) error); ok {
r0 = rf(param)
} else {
r0 = ret.Error(0)
}
return r0
}
// Start provides a mock function with given fields:
func (_m *PoolManager) Start() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Stop provides a mock function with given fields:
func (_m *PoolManager) Stop() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Wait provides a mock function with given fields:
func (_m *PoolManager) Wait() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// WebhookSecret provides a mock function with given fields:
func (_m *PoolManager) WebhookSecret() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
type mockConstructorTestingTNewPoolManager interface {
mock.TestingT
Cleanup(func())
}
// NewPoolManager creates a new instance of PoolManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewPoolManager(t mockConstructorTestingTNewPoolManager) *PoolManager {
mock := &PoolManager{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,165 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
context "context"
params "garm/params"
mock "github.com/stretchr/testify/mock"
)
// Provider is an autogenerated mock type for the Provider type
type Provider struct {
mock.Mock
}
// AsParams provides a mock function with given fields:
func (_m *Provider) AsParams() params.Provider {
ret := _m.Called()
var r0 params.Provider
if rf, ok := ret.Get(0).(func() params.Provider); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(params.Provider)
}
return r0
}
// CreateInstance provides a mock function with given fields: ctx, bootstrapParams
func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.Instance, error) {
ret := _m.Called(ctx, bootstrapParams)
var r0 params.Instance
if rf, ok := ret.Get(0).(func(context.Context, params.BootstrapInstance) params.Instance); ok {
r0 = rf(ctx, bootstrapParams)
} else {
r0 = ret.Get(0).(params.Instance)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, params.BootstrapInstance) error); ok {
r1 = rf(ctx, bootstrapParams)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteInstance provides a mock function with given fields: ctx, instance
func (_m *Provider) DeleteInstance(ctx context.Context, instance string) error {
ret := _m.Called(ctx, instance)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instance)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetInstance provides a mock function with given fields: ctx, instance
func (_m *Provider) GetInstance(ctx context.Context, instance string) (params.Instance, error) {
ret := _m.Called(ctx, instance)
var r0 params.Instance
if rf, ok := ret.Get(0).(func(context.Context, string) params.Instance); ok {
r0 = rf(ctx, instance)
} else {
r0 = ret.Get(0).(params.Instance)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, instance)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListInstances provides a mock function with given fields: ctx, poolID
func (_m *Provider) ListInstances(ctx context.Context, poolID string) ([]params.Instance, error) {
ret := _m.Called(ctx, poolID)
var r0 []params.Instance
if rf, ok := ret.Get(0).(func(context.Context, string) []params.Instance); ok {
r0 = rf(ctx, poolID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Instance)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, poolID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveAllInstances provides a mock function with given fields: ctx
func (_m *Provider) RemoveAllInstances(ctx context.Context) error {
ret := _m.Called(ctx)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// Start provides a mock function with given fields: ctx, instance
func (_m *Provider) Start(ctx context.Context, instance string) error {
ret := _m.Called(ctx, instance)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, instance)
} else {
r0 = ret.Error(0)
}
return r0
}
// Stop provides a mock function with given fields: ctx, instance, force
func (_m *Provider) Stop(ctx context.Context, instance string, force bool) error {
ret := _m.Called(ctx, instance, force)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, bool) error); ok {
r0 = rf(ctx, instance, force)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewProvider interface {
mock.TestingT
Cleanup(func())
}
// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewProvider(t mockConstructorTestingTNewProvider) *Provider {
mock := &Provider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -298,7 +298,7 @@ func (l *LXD) CreateInstance(ctx context.Context, bootstrapParams params.Bootstr
return params.Instance{}, errors.Wrap(err, "creating instance")
}
ret, err := l.GetInstance(ctx, args.Name)
ret, err := l.waitInstanceHasIP(ctx, args.Name)
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
}

View file

@ -20,15 +20,19 @@ import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strings"
"time"
"garm/config"
"garm/params"
"garm/runner/providers/common"
"garm/util"
"github.com/juju/clock"
"github.com/juju/retry"
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/shared/api"
"github.com/pkg/errors"
@ -177,3 +181,39 @@ func resolveArchitecture(osArch config.OSArch) (string, error) {
}
return arch, nil
}
// waitDeviceActive is a function capable of figuring out when a Equinix Metal
// device is active
func (l *LXD) waitInstanceHasIP(ctx context.Context, instanceName string) (params.Instance, error) {
var p params.Instance
var errIPNotFound error = fmt.Errorf("ip not found")
err := retry.Call(retry.CallArgs{
Func: func() error {
var err error
p, err = l.GetInstance(ctx, instanceName)
if err != nil {
return errors.Wrap(err, "fetching instance")
}
for _, addr := range p.Addresses {
ip := net.ParseIP(addr.Address)
if ip == nil {
continue
}
if ip.To4() == nil {
continue
}
return nil
}
return errIPNotFound
},
Attempts: 20,
Delay: 5 * time.Second,
Clock: clock.WallClock,
})
if err != nil && err != errIPNotFound {
return params.Instance{}, err
}
return p, nil
}