diff --git a/mocks/Client.go b/mocks/Client.go index 7a3effd..ebc3338 100644 --- a/mocks/Client.go +++ b/mocks/Client.go @@ -297,6 +297,65 @@ func (_c *Client_CreateCluster_Call) RunAndReturn(run func(context.Context, clie return _c } +// CreatePullSecret provides a mock function with given fields: ctx, request +func (_m *Client) CreatePullSecret(ctx context.Context, request client.NewPullSecretRequest) (*client.PullSecret, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for CreatePullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, client.NewPullSecretRequest) (*client.PullSecret, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, client.NewPullSecretRequest) *client.PullSecret); ok { + r0 = rf(ctx, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, client.NewPullSecretRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_CreatePullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePullSecret' +type Client_CreatePullSecret_Call struct { + *mock.Call +} + +// CreatePullSecret is a helper method to define mock.On call +// - ctx context.Context +// - request client.NewPullSecretRequest +func (_e *Client_Expecter) CreatePullSecret(ctx interface{}, request interface{}) *Client_CreatePullSecret_Call { + return &Client_CreatePullSecret_Call{Call: _e.mock.On("CreatePullSecret", ctx, request)} +} + +func (_c *Client_CreatePullSecret_Call) Run(run func(ctx context.Context, request client.NewPullSecretRequest)) *Client_CreatePullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(client.NewPullSecretRequest)) + }) + return _c +} + +func (_c *Client_CreatePullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *Client_CreatePullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_CreatePullSecret_Call) RunAndReturn(run func(context.Context, client.NewPullSecretRequest) (*client.PullSecret, error)) *Client_CreatePullSecret_Call { + _c.Call.Return(run) + return _c +} + // CreateTeam provides a mock function with given fields: ctx, request func (_m *Client) CreateTeam(ctx context.Context, request client.NewTeamRequest) (*client.Team, error) { ret := _m.Called(ctx, request) @@ -498,6 +557,53 @@ func (_c *Client_DeleteCluster_Call) RunAndReturn(run func(context.Context, stri return _c } +// DeletePullSecret provides a mock function with given fields: ctx, id +func (_m *Client) DeletePullSecret(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DeletePullSecret") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Client_DeletePullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePullSecret' +type Client_DeletePullSecret_Call struct { + *mock.Call +} + +// DeletePullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *Client_Expecter) DeletePullSecret(ctx interface{}, id interface{}) *Client_DeletePullSecret_Call { + return &Client_DeletePullSecret_Call{Call: _e.mock.On("DeletePullSecret", ctx, id)} +} + +func (_c *Client_DeletePullSecret_Call) Run(run func(ctx context.Context, id string)) *Client_DeletePullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Client_DeletePullSecret_Call) Return(_a0 error) *Client_DeletePullSecret_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Client_DeletePullSecret_Call) RunAndReturn(run func(context.Context, string) error) *Client_DeletePullSecret_Call { + _c.Call.Return(run) + return _c +} + // DeleteTeam provides a mock function with given fields: ctx, request func (_m *Client) DeleteTeam(ctx context.Context, request client.DeleteTeamRequest) error { ret := _m.Called(ctx, request) @@ -545,6 +651,66 @@ func (_c *Client_DeleteTeam_Call) RunAndReturn(run func(context.Context, client. return _c } +// EditPullSecret provides a mock function with given fields: ctx, id, request +func (_m *Client) EditPullSecret(ctx context.Context, id string, request client.EditPullSecretRequest) (*client.PullSecret, error) { + ret := _m.Called(ctx, id, request) + + if len(ret) == 0 { + panic("no return value specified for EditPullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, client.EditPullSecretRequest) (*client.PullSecret, error)); ok { + return rf(ctx, id, request) + } + if rf, ok := ret.Get(0).(func(context.Context, string, client.EditPullSecretRequest) *client.PullSecret); ok { + r0 = rf(ctx, id, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, client.EditPullSecretRequest) error); ok { + r1 = rf(ctx, id, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_EditPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EditPullSecret' +type Client_EditPullSecret_Call struct { + *mock.Call +} + +// EditPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - request client.EditPullSecretRequest +func (_e *Client_Expecter) EditPullSecret(ctx interface{}, id interface{}, request interface{}) *Client_EditPullSecret_Call { + return &Client_EditPullSecret_Call{Call: _e.mock.On("EditPullSecret", ctx, id, request)} +} + +func (_c *Client_EditPullSecret_Call) Run(run func(ctx context.Context, id string, request client.EditPullSecretRequest)) *Client_EditPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(client.EditPullSecretRequest)) + }) + return _c +} + +func (_c *Client_EditPullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *Client_EditPullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_EditPullSecret_Call) RunAndReturn(run func(context.Context, string, client.EditPullSecretRequest) (*client.PullSecret, error)) *Client_EditPullSecret_Call { + _c.Call.Return(run) + return _c +} + // GetAIAPIKey provides a mock function with given fields: ctx, deploymentID, name func (_m *Client) GetAIAPIKey(ctx context.Context, deploymentID string, name string) (*client.AIAPIKey, error) { ret := _m.Called(ctx, deploymentID, name) @@ -897,6 +1063,65 @@ func (_c *Client_GetMe_Call) RunAndReturn(run func(context.Context) (client.Me, return _c } +// GetPullSecret provides a mock function with given fields: ctx, id +func (_m *Client) GetPullSecret(ctx context.Context, id string) (*client.PullSecret, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetPullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*client.PullSecret, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *client.PullSecret); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_GetPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPullSecret' +type Client_GetPullSecret_Call struct { + *mock.Call +} + +// GetPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *Client_Expecter) GetPullSecret(ctx interface{}, id interface{}) *Client_GetPullSecret_Call { + return &Client_GetPullSecret_Call{Call: _e.mock.On("GetPullSecret", ctx, id)} +} + +func (_c *Client_GetPullSecret_Call) Run(run func(ctx context.Context, id string)) *Client_GetPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Client_GetPullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *Client_GetPullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_GetPullSecret_Call) RunAndReturn(run func(context.Context, string) (*client.PullSecret, error)) *Client_GetPullSecret_Call { + _c.Call.Return(run) + return _c +} + // GetTeam provides a mock function with given fields: ctx, name func (_m *Client) GetTeam(ctx context.Context, name string) (*client.Team, error) { ret := _m.Called(ctx, name) @@ -1365,6 +1590,64 @@ func (_c *Client_ListIntegrationInstances_Call) RunAndReturn(run func(context.Co return _c } +// ListPullSecrets provides a mock function with given fields: ctx +func (_m *Client) ListPullSecrets(ctx context.Context) ([]client.PullSecret, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListPullSecrets") + } + + var r0 []client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]client.PullSecret, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []client.PullSecret); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_ListPullSecrets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPullSecrets' +type Client_ListPullSecrets_Call struct { + *mock.Call +} + +// ListPullSecrets is a helper method to define mock.On call +// - ctx context.Context +func (_e *Client_Expecter) ListPullSecrets(ctx interface{}) *Client_ListPullSecrets_Call { + return &Client_ListPullSecrets_Call{Call: _e.mock.On("ListPullSecrets", ctx)} +} + +func (_c *Client_ListPullSecrets_Call) Run(run func(ctx context.Context)) *Client_ListPullSecrets_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Client_ListPullSecrets_Call) Return(_a0 []client.PullSecret, _a1 error) *Client_ListPullSecrets_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_ListPullSecrets_Call) RunAndReturn(run func(context.Context) ([]client.PullSecret, error)) *Client_ListPullSecrets_Call { + _c.Call.Return(run) + return _c +} + // ListTeams provides a mock function with given fields: ctx func (_m *Client) ListTeams(ctx context.Context) ([]client.Team, error) { ret := _m.Called(ctx) @@ -1577,6 +1860,54 @@ func (_c *Client_RemoveTeamMember_Call) RunAndReturn(run func(context.Context, s return _c } +// SetClusterPullSecret provides a mock function with given fields: ctx, clusterID, pullSecretID +func (_m *Client) SetClusterPullSecret(ctx context.Context, clusterID string, pullSecretID string) error { + ret := _m.Called(ctx, clusterID, pullSecretID) + + if len(ret) == 0 { + panic("no return value specified for SetClusterPullSecret") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, clusterID, pullSecretID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Client_SetClusterPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetClusterPullSecret' +type Client_SetClusterPullSecret_Call struct { + *mock.Call +} + +// SetClusterPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - clusterID string +// - pullSecretID string +func (_e *Client_Expecter) SetClusterPullSecret(ctx interface{}, clusterID interface{}, pullSecretID interface{}) *Client_SetClusterPullSecret_Call { + return &Client_SetClusterPullSecret_Call{Call: _e.mock.On("SetClusterPullSecret", ctx, clusterID, pullSecretID)} +} + +func (_c *Client_SetClusterPullSecret_Call) Run(run func(ctx context.Context, clusterID string, pullSecretID string)) *Client_SetClusterPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *Client_SetClusterPullSecret_Call) Return(_a0 error) *Client_SetClusterPullSecret_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Client_SetClusterPullSecret_Call) RunAndReturn(run func(context.Context, string, string) error) *Client_SetClusterPullSecret_Call { + _c.Call.Return(run) + return _c +} + // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewClient(t interface { diff --git a/mocks/PullSecretClient.go b/mocks/PullSecretClient.go new file mode 100644 index 0000000..37c2b4b --- /dev/null +++ b/mocks/PullSecretClient.go @@ -0,0 +1,369 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + client "github.com/intility/indev/pkg/client" + + mock "github.com/stretchr/testify/mock" +) + +// PullSecretClient is an autogenerated mock type for the PullSecretClient type +type PullSecretClient struct { + mock.Mock +} + +type PullSecretClient_Expecter struct { + mock *mock.Mock +} + +func (_m *PullSecretClient) EXPECT() *PullSecretClient_Expecter { + return &PullSecretClient_Expecter{mock: &_m.Mock} +} + +// CreatePullSecret provides a mock function with given fields: ctx, request +func (_m *PullSecretClient) CreatePullSecret(ctx context.Context, request client.NewPullSecretRequest) (*client.PullSecret, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for CreatePullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, client.NewPullSecretRequest) (*client.PullSecret, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, client.NewPullSecretRequest) *client.PullSecret); ok { + r0 = rf(ctx, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, client.NewPullSecretRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PullSecretClient_CreatePullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePullSecret' +type PullSecretClient_CreatePullSecret_Call struct { + *mock.Call +} + +// CreatePullSecret is a helper method to define mock.On call +// - ctx context.Context +// - request client.NewPullSecretRequest +func (_e *PullSecretClient_Expecter) CreatePullSecret(ctx interface{}, request interface{}) *PullSecretClient_CreatePullSecret_Call { + return &PullSecretClient_CreatePullSecret_Call{Call: _e.mock.On("CreatePullSecret", ctx, request)} +} + +func (_c *PullSecretClient_CreatePullSecret_Call) Run(run func(ctx context.Context, request client.NewPullSecretRequest)) *PullSecretClient_CreatePullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(client.NewPullSecretRequest)) + }) + return _c +} + +func (_c *PullSecretClient_CreatePullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *PullSecretClient_CreatePullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PullSecretClient_CreatePullSecret_Call) RunAndReturn(run func(context.Context, client.NewPullSecretRequest) (*client.PullSecret, error)) *PullSecretClient_CreatePullSecret_Call { + _c.Call.Return(run) + return _c +} + +// DeletePullSecret provides a mock function with given fields: ctx, id +func (_m *PullSecretClient) DeletePullSecret(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DeletePullSecret") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// PullSecretClient_DeletePullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePullSecret' +type PullSecretClient_DeletePullSecret_Call struct { + *mock.Call +} + +// DeletePullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *PullSecretClient_Expecter) DeletePullSecret(ctx interface{}, id interface{}) *PullSecretClient_DeletePullSecret_Call { + return &PullSecretClient_DeletePullSecret_Call{Call: _e.mock.On("DeletePullSecret", ctx, id)} +} + +func (_c *PullSecretClient_DeletePullSecret_Call) Run(run func(ctx context.Context, id string)) *PullSecretClient_DeletePullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *PullSecretClient_DeletePullSecret_Call) Return(_a0 error) *PullSecretClient_DeletePullSecret_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *PullSecretClient_DeletePullSecret_Call) RunAndReturn(run func(context.Context, string) error) *PullSecretClient_DeletePullSecret_Call { + _c.Call.Return(run) + return _c +} + +// EditPullSecret provides a mock function with given fields: ctx, id, request +func (_m *PullSecretClient) EditPullSecret(ctx context.Context, id string, request client.EditPullSecretRequest) (*client.PullSecret, error) { + ret := _m.Called(ctx, id, request) + + if len(ret) == 0 { + panic("no return value specified for EditPullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, client.EditPullSecretRequest) (*client.PullSecret, error)); ok { + return rf(ctx, id, request) + } + if rf, ok := ret.Get(0).(func(context.Context, string, client.EditPullSecretRequest) *client.PullSecret); ok { + r0 = rf(ctx, id, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, client.EditPullSecretRequest) error); ok { + r1 = rf(ctx, id, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PullSecretClient_EditPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EditPullSecret' +type PullSecretClient_EditPullSecret_Call struct { + *mock.Call +} + +// EditPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - request client.EditPullSecretRequest +func (_e *PullSecretClient_Expecter) EditPullSecret(ctx interface{}, id interface{}, request interface{}) *PullSecretClient_EditPullSecret_Call { + return &PullSecretClient_EditPullSecret_Call{Call: _e.mock.On("EditPullSecret", ctx, id, request)} +} + +func (_c *PullSecretClient_EditPullSecret_Call) Run(run func(ctx context.Context, id string, request client.EditPullSecretRequest)) *PullSecretClient_EditPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(client.EditPullSecretRequest)) + }) + return _c +} + +func (_c *PullSecretClient_EditPullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *PullSecretClient_EditPullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PullSecretClient_EditPullSecret_Call) RunAndReturn(run func(context.Context, string, client.EditPullSecretRequest) (*client.PullSecret, error)) *PullSecretClient_EditPullSecret_Call { + _c.Call.Return(run) + return _c +} + +// GetPullSecret provides a mock function with given fields: ctx, id +func (_m *PullSecretClient) GetPullSecret(ctx context.Context, id string) (*client.PullSecret, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetPullSecret") + } + + var r0 *client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*client.PullSecret, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *client.PullSecret); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PullSecretClient_GetPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPullSecret' +type PullSecretClient_GetPullSecret_Call struct { + *mock.Call +} + +// GetPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *PullSecretClient_Expecter) GetPullSecret(ctx interface{}, id interface{}) *PullSecretClient_GetPullSecret_Call { + return &PullSecretClient_GetPullSecret_Call{Call: _e.mock.On("GetPullSecret", ctx, id)} +} + +func (_c *PullSecretClient_GetPullSecret_Call) Run(run func(ctx context.Context, id string)) *PullSecretClient_GetPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *PullSecretClient_GetPullSecret_Call) Return(_a0 *client.PullSecret, _a1 error) *PullSecretClient_GetPullSecret_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PullSecretClient_GetPullSecret_Call) RunAndReturn(run func(context.Context, string) (*client.PullSecret, error)) *PullSecretClient_GetPullSecret_Call { + _c.Call.Return(run) + return _c +} + +// ListPullSecrets provides a mock function with given fields: ctx +func (_m *PullSecretClient) ListPullSecrets(ctx context.Context) ([]client.PullSecret, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListPullSecrets") + } + + var r0 []client.PullSecret + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]client.PullSecret, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []client.PullSecret); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]client.PullSecret) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PullSecretClient_ListPullSecrets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPullSecrets' +type PullSecretClient_ListPullSecrets_Call struct { + *mock.Call +} + +// ListPullSecrets is a helper method to define mock.On call +// - ctx context.Context +func (_e *PullSecretClient_Expecter) ListPullSecrets(ctx interface{}) *PullSecretClient_ListPullSecrets_Call { + return &PullSecretClient_ListPullSecrets_Call{Call: _e.mock.On("ListPullSecrets", ctx)} +} + +func (_c *PullSecretClient_ListPullSecrets_Call) Run(run func(ctx context.Context)) *PullSecretClient_ListPullSecrets_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *PullSecretClient_ListPullSecrets_Call) Return(_a0 []client.PullSecret, _a1 error) *PullSecretClient_ListPullSecrets_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *PullSecretClient_ListPullSecrets_Call) RunAndReturn(run func(context.Context) ([]client.PullSecret, error)) *PullSecretClient_ListPullSecrets_Call { + _c.Call.Return(run) + return _c +} + +// SetClusterPullSecret provides a mock function with given fields: ctx, clusterID, pullSecretID +func (_m *PullSecretClient) SetClusterPullSecret(ctx context.Context, clusterID string, pullSecretID string) error { + ret := _m.Called(ctx, clusterID, pullSecretID) + + if len(ret) == 0 { + panic("no return value specified for SetClusterPullSecret") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, clusterID, pullSecretID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// PullSecretClient_SetClusterPullSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetClusterPullSecret' +type PullSecretClient_SetClusterPullSecret_Call struct { + *mock.Call +} + +// SetClusterPullSecret is a helper method to define mock.On call +// - ctx context.Context +// - clusterID string +// - pullSecretID string +func (_e *PullSecretClient_Expecter) SetClusterPullSecret(ctx interface{}, clusterID interface{}, pullSecretID interface{}) *PullSecretClient_SetClusterPullSecret_Call { + return &PullSecretClient_SetClusterPullSecret_Call{Call: _e.mock.On("SetClusterPullSecret", ctx, clusterID, pullSecretID)} +} + +func (_c *PullSecretClient_SetClusterPullSecret_Call) Run(run func(ctx context.Context, clusterID string, pullSecretID string)) *PullSecretClient_SetClusterPullSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *PullSecretClient_SetClusterPullSecret_Call) Return(_a0 error) *PullSecretClient_SetClusterPullSecret_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *PullSecretClient_SetClusterPullSecret_Call) RunAndReturn(run func(context.Context, string, string) error) *PullSecretClient_SetClusterPullSecret_Call { + _c.Call.Return(run) + return _c +} + +// NewPullSecretClient creates a new instance of PullSecretClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPullSecretClient(t interface { + mock.TestingT + Cleanup(func()) +}) *PullSecretClient { + mock := &PullSecretClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 5267f16..3ea5786 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -75,6 +75,15 @@ type AIAPIKeyClient interface { DeleteAIAPIKey(ctx context.Context, deploymentID string, keyID string) error } +type PullSecretClient interface { + ListPullSecrets(ctx context.Context) ([]PullSecret, error) + GetPullSecret(ctx context.Context, id string) (*PullSecret, error) + CreatePullSecret(ctx context.Context, request NewPullSecretRequest) (*PullSecret, error) + EditPullSecret(ctx context.Context, id string, request EditPullSecretRequest) (*PullSecret, error) + DeletePullSecret(ctx context.Context, id string) error + SetClusterPullSecret(ctx context.Context, clusterID string, pullSecretID string) error +} + type Client interface { ClusterClient IntegrationClient @@ -84,6 +93,7 @@ type Client interface { MemberClient AIClient AIAPIKeyClient + PullSecretClient } type RestClientOption func(*RestClient) @@ -284,3 +294,106 @@ func (c *RestClient) RemoveClusterMember(ctx context.Context, clusterID string, return nil } + +func (c *RestClient) ListPullSecrets(ctx context.Context) ([]PullSecret, error) { + var pullSecrets []PullSecret + + req, err := c.createAuthenticatedRequest(ctx, "GET", c.baseURI+"/api/v1/settings/image-pull-secrets", nil) + if err != nil { + return pullSecrets, err + } + + if err = doRequest(c.httpClient, req, &pullSecrets); err != nil { + return pullSecrets, fmt.Errorf("request failed: %w", err) + } + + return pullSecrets, nil +} + +func (c *RestClient) GetPullSecret(ctx context.Context, id string) (*PullSecret, error) { + req, err := c.createAuthenticatedRequest(ctx, "GET", c.baseURI+"/api/v1/settings/image-pull-secrets/"+id, nil) + if err != nil { + return nil, err + } + + var pullSecret PullSecret + if err = doRequest(c.httpClient, req, &pullSecret); err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + return &pullSecret, nil +} + +func (c *RestClient) CreatePullSecret(ctx context.Context, request NewPullSecretRequest) (*PullSecret, error) { + body, err := json.Marshal(request) + if err != nil { + return nil, fmt.Errorf("could not marshal request: %w", err) + } + + endpoint := c.baseURI + "/api/v1/settings/image-pull-secrets" + + req, err := c.createAuthenticatedRequest(ctx, "POST", endpoint, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + var pullSecret PullSecret + if err = doRequest(c.httpClient, req, &pullSecret); err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + return &pullSecret, nil +} + +func (c *RestClient) EditPullSecret( + ctx context.Context, + id string, + request EditPullSecretRequest, +) (*PullSecret, error) { + body, err := json.Marshal(request) + if err != nil { + return nil, fmt.Errorf("could not marshal request: %w", err) + } + + endpoint := c.baseURI + "/api/v1/settings/image-pull-secrets/" + id + + req, err := c.createAuthenticatedRequest(ctx, "PATCH", endpoint, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + var pullSecret PullSecret + if err = doRequest(c.httpClient, req, &pullSecret); err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + return &pullSecret, nil +} + +func (c *RestClient) DeletePullSecret(ctx context.Context, id string) error { + req, err := c.createAuthenticatedRequest(ctx, "DELETE", c.baseURI+"/api/v1/settings/image-pull-secrets/"+id, nil) + if err != nil { + return err + } + + if err = doRequest[any](c.httpClient, req, nil); err != nil { + return fmt.Errorf("request failed: %w", err) + } + + return nil +} + +func (c *RestClient) SetClusterPullSecret(ctx context.Context, clusterID string, pullSecretID string) error { + endpoint := c.baseURI + "/api/v1/clusters/" + clusterID + "/image-pull-secrets/" + pullSecretID + + req, err := c.createAuthenticatedRequest(ctx, "PUT", endpoint, nil) + if err != nil { + return err + } + + if err = doRequest[any](c.httpClient, req, nil); err != nil { + return fmt.Errorf("request failed: %w", err) + } + + return nil +} diff --git a/pkg/client/cluster.go b/pkg/client/cluster.go index 4a5d115..bbb4b21 100644 --- a/pkg/client/cluster.go +++ b/pkg/client/cluster.go @@ -3,15 +3,16 @@ package client import "github.com/google/uuid" type Cluster struct { - ID string `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - ConsoleURL string `json:"consoleUrl"` - EPG string `json:"epg"` - IngressIP string `json:"ingressIp"` - NodePools NodePools `json:"nodePools"` - Status ClusterStatus `json:"status"` - Roles []string `json:"roles"` + ID string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + ConsoleURL string `json:"consoleUrl"` + EPG string `json:"epg"` + IngressIP string `json:"ingressIp"` + NodePools NodePools `json:"nodePools"` + Status ClusterStatus `json:"status"` + Roles []string `json:"roles"` + ImagePullSecret *ImagePullSecret `json:"imagePullSecret"` } type ClusterStatus struct { diff --git a/pkg/client/pullsecret.go b/pkg/client/pullsecret.go new file mode 100644 index 0000000..0ddd006 --- /dev/null +++ b/pkg/client/pullsecret.go @@ -0,0 +1,28 @@ +package client + +type PullSecret struct { + ID string `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + CreatedAt string `json:"createdAt" yaml:"createdAt"` + Registries []string `json:"registries" yaml:"registries"` +} + +type PullSecretCredential struct { + Username string `json:"username"` + Password string `json:"password"` //nolint:gosec // G117: this is a credential payload, not a hardcoded secret +} + +type NewPullSecretRequest struct { + Name string `json:"name"` + Registries map[string]PullSecretCredential `json:"registries"` +} + +type EditPullSecretRequest struct { + Registries map[string]*PullSecretCredential `json:"registries"` +} + +type ImagePullSecret struct { + ID string `json:"id"` + Name string `json:"name"` + Registries []string `json:"registries"` +} diff --git a/pkg/commands/cluster/create.go b/pkg/commands/cluster/create.go index 82bfec1..195152d 100644 --- a/pkg/commands/cluster/create.go +++ b/pkg/commands/cluster/create.go @@ -18,10 +18,11 @@ import ( ) const ( - maxCount = 8 - minCount = 2 - answerYes = "yes" - answerNo = "no" + maxCount = 8 + minCount = 2 + answerYes = "yes" + answerNo = "no" + noPullSecret = "(none)" ) var ( @@ -41,6 +42,7 @@ type CreateOptions struct { EnableAutoscaling bool MinNodes int // Used when autoscaling is enabled MaxNodes int // Used when autoscaling is enabled + PullSecret string } func NewCreateCommand(set clientset.ClientSet) *cobra.Command { @@ -77,14 +79,23 @@ func NewCreateCommand(set clientset.ClientSet) *cobra.Command { cmd.Flags().IntVar(&options.MaxNodes, "max-nodes", maxCount, fmt.Sprintf("Maximum number of nodes when autoscaling is enabled (%d-%d)", minCount, maxCount)) + cmd.Flags().StringVar(&options.PullSecret, + "pull-secret", "", "Name of the image pull secret to use") + return cmd } func runCreateCommand(ctx context.Context, cmd *cobra.Command, set clientset.ClientSet, options CreateOptions) error { var err error + // Fetch pull secrets for wizard + pullSecrets, psErr := set.PlatformClient.ListPullSecrets(ctx) + if psErr != nil { + pullSecrets = nil + } + if options.Name == "" { - options, err = optionsFromWizard() + options, err = optionsFromWizard(pullSecrets) if err != nil { if errors.Is(err, errCancelledByUser) { return nil @@ -110,15 +121,18 @@ func runCreateCommand(ctx context.Context, cmd *cobra.Command, set clientset.Cli nodePool := buildNodePool(options) - var cluster *client.Cluster + pullSecretRef, err := resolvePullSecretRef(options.PullSecret, pullSecrets) + if err != nil { + return err + } - cluster, err = set.PlatformClient.CreateCluster(ctx, client.NewClusterRequest{ + cluster, err := set.PlatformClient.CreateCluster(ctx, client.NewClusterRequest{ Name: options.Name, SSOProvisioner: ssoProvisioner, NodePools: []client.NodePool{nodePool}, Version: "", Environment: "", - PullSecretRef: nil, + PullSecretRef: pullSecretRef, }) if err != nil { return redact.Errorf("could not create cluster: %w", redact.Safe(err)) @@ -157,10 +171,26 @@ func buildNodePool(options CreateOptions) client.NodePool { } } -func optionsFromWizard() (CreateOptions, error) { +var errPullSecretNotFound = redact.Errorf("pull secret not found") + +func resolvePullSecretRef(name string, pullSecrets []client.PullSecret) (*string, error) { + if name == "" { + return nil, nil //nolint:nilnil // nil,nil is intentional: no name means no pull secret ref + } + + for i := range pullSecrets { + if pullSecrets[i].Name == name { + return &pullSecrets[i].ID, nil + } + } + + return nil, errPullSecretNotFound +} + +func optionsFromWizard(pullSecrets []client.PullSecret) (CreateOptions, error) { var options CreateOptions - wz := wizard.NewWizard(getClusterWizardInputs()) + wz := wizard.NewWizard(getClusterWizardInputs(pullSecrets)) result, err := wz.Run() if err != nil { @@ -198,12 +228,19 @@ func optionsFromWizard() (CreateOptions, error) { } } + if len(pullSecrets) > 0 { + selected := result.MustGetValue("pullSecret") + if selected != noPullSecret { + options.PullSecret = selected + } + } + return options, nil } //nolint:funlen // wizard input definitions are declarative -func getClusterWizardInputs() []wizard.Input { - return []wizard.Input{ +func getClusterWizardInputs(pullSecrets []client.PullSecret) []wizard.Input { + inputs := []wizard.Input{ { ID: "name", Placeholder: "Cluster Name", @@ -274,6 +311,28 @@ func getClusterWizardInputs() []wizard.Input { }, }, } + + if len(pullSecrets) > 0 { + psOptions := make([]string, 0, 1+len(pullSecrets)) + psOptions = append(psOptions, noPullSecret) + + for _, ps := range pullSecrets { + psOptions = append(psOptions, ps.Name) + } + + inputs = append(inputs, wizard.Input{ + ID: "pullSecret", + Placeholder: "Image Pull Secret", + Type: wizard.InputTypeSelect, + Limit: 0, + Validator: nil, + Options: psOptions, + DependsOn: "", + ShowWhen: nil, + }) + } + + return inputs } //nolint:cyclop // validation logic is inherently sequential diff --git a/pkg/commands/cluster/create_test.go b/pkg/commands/cluster/create_test.go index 175fd9c..a65400e 100644 --- a/pkg/commands/cluster/create_test.go +++ b/pkg/commands/cluster/create_test.go @@ -235,6 +235,8 @@ func TestValidateOptions(t *testing.T) { } } + + func TestSelectSSOProvisioner(t *testing.T) { tests := []struct { name string diff --git a/pkg/commands/cluster/get.go b/pkg/commands/cluster/get.go index e107388..9def4e1 100644 --- a/pkg/commands/cluster/get.go +++ b/pkg/commands/cluster/get.go @@ -54,6 +54,10 @@ func printClusterDetails(writer io.Writer, cluster *client.Cluster) { ux.Fprintf(writer, " Roles: %s\n", strings.Join(cluster.Roles, ", ")) } + if cluster.ImagePullSecret != nil { + ux.Fprintf(writer, " Pull Secret: %s\n", cluster.ImagePullSecret.Name) + } + printStatusInfo(writer, cluster) // Node pools diff --git a/pkg/commands/cluster/pullsecret/set.go b/pkg/commands/cluster/pullsecret/set.go new file mode 100644 index 0000000..18432ec --- /dev/null +++ b/pkg/commands/cluster/pullsecret/set.go @@ -0,0 +1,77 @@ +package pullsecret + +import ( + "github.com/spf13/cobra" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/pkg/clientset" + pullsecretcmd "github.com/intility/indev/pkg/commands/pullsecret" +) + +var ( + errClusterNameRequired = redact.Errorf("cluster name is required") + errPullSecretNameRequired = redact.Errorf("pull secret name is required") +) + +func NewSetCommand(set clientset.ClientSet) *cobra.Command { + var ( + clusterName string + pullSecretName string + ) + + cmd := &cobra.Command{ + Use: "set [cluster-name] [pull-secret-name]", + Short: "Set the image pull secret for a cluster", + Long: `Assign an image pull secret to a cluster.`, + Args: cobra.MaximumNArgs(2), //nolint:mnd + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "cluster.pullsecret.set") + defer span.End() + + if len(args) > 0 { + clusterName = args[0] + } + + if len(args) > 1 { + pullSecretName = args[1] + } + + if clusterName == "" { + return errClusterNameRequired + } + + if pullSecretName == "" { + return errPullSecretNameRequired + } + + cmd.SilenceUsage = true + + cluster, err := set.PlatformClient.GetCluster(ctx, clusterName) + if err != nil { + return redact.Errorf("could not get cluster: %w", redact.Safe(err)) + } + + ps, err := pullsecretcmd.FindPullSecretByName(ctx, set.PlatformClient, pullSecretName) + if err != nil { + return redact.Errorf("could not find pull secret: %w", redact.Safe(err)) + } + + err = set.PlatformClient.SetClusterPullSecret(ctx, cluster.ID, ps.ID) + if err != nil { + return redact.Errorf("could not set cluster pull secret: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "set pull secret %s on cluster %s\n", pullSecretName, clusterName) + + return nil + }, + } + + cmd.Flags().StringVarP(&clusterName, "cluster", "c", "", "Name of the cluster") + cmd.Flags().StringVarP(&pullSecretName, "pull-secret", "p", "", "Name of the pull secret") + + return cmd +} diff --git a/pkg/commands/pullsecret/create.go b/pkg/commands/pullsecret/create.go new file mode 100644 index 0000000..5ff3078 --- /dev/null +++ b/pkg/commands/pullsecret/create.go @@ -0,0 +1,132 @@ +package pullsecret + +import ( + "github.com/spf13/cobra" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/internal/wizard" + "github.com/intility/indev/pkg/client" + "github.com/intility/indev/pkg/clientset" +) + +var ( + errEmptyPullSecretName = redact.Errorf("pull secret name cannot be empty") + errNoRegistries = redact.Errorf("at least one registry is required") + errCancelledByUser = redact.Errorf("cancelled by user") +) + +func NewCreateCommand(set clientset.ClientSet) *cobra.Command { + var name string + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new image pull secret", + Long: `Create a new image pull secret with registry credentials.`, + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.create") + defer span.End() + + registries := make(map[string]client.PullSecretCredential) + + if name == "" { + var err error + + name, registries, err = createFromWizard() + if err != nil { + return err + } + } + + if name == "" { + return errEmptyPullSecretName + } + + if len(registries) == 0 { + return errNoRegistries + } + + cmd.SilenceUsage = true + + ps, err := set.PlatformClient.CreatePullSecret(ctx, client.NewPullSecretRequest{ + Name: name, + Registries: registries, + }) + if err != nil { + return redact.Errorf("could not create pull secret: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "created pull secret: %s\n", ps.Name) + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "Name of the pull secret to create") + + return cmd +} + +const answerYes = "yes" + +func createFromWizard() (string, map[string]client.PullSecretCredential, error) { + nameWz := wizard.NewWizard([]wizard.Input{ + { + ID: "name", + Placeholder: "Pull Secret Name", + Type: wizard.InputTypeText, + Limit: 0, + Validator: nil, + Options: nil, + DependsOn: "", + ShowWhen: nil, + }, + }) + + nameResult, err := nameWz.Run() + if err != nil { + return "", nil, redact.Errorf("could not gather information: %w", redact.Safe(err)) + } + + if nameResult.Cancelled() { + return "", nil, errCancelledByUser + } + + name := nameResult.MustGetValue("name") + + registries, err := collectRegistries() + if err != nil { + return "", nil, err + } + + return name, registries, nil +} + +func collectRegistries() (map[string]client.PullSecretCredential, error) { + registries := make(map[string]client.PullSecretCredential) + + for { + cred, err := promptRegistryCredential() + if err != nil { + return nil, err + } + + registries[cred.address] = client.PullSecretCredential{ + Username: cred.username, + Password: cred.password, + } + + more, err := promptAddMore("Add another registry?") + if err != nil { + return nil, err + } + + if !more { + break + } + } + + return registries, nil +} diff --git a/pkg/commands/pullsecret/delete.go b/pkg/commands/pullsecret/delete.go new file mode 100644 index 0000000..51daa13 --- /dev/null +++ b/pkg/commands/pullsecret/delete.go @@ -0,0 +1,57 @@ +package pullsecret + +import ( + "github.com/spf13/cobra" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/pkg/clientset" +) + +func NewDeleteCommand(set clientset.ClientSet) *cobra.Command { + var ( + name string + errEmptyName = redact.Errorf("pull secret name cannot be empty") + ) + + cmd := &cobra.Command{ + Use: "delete [name]", + Short: "Delete an existing image pull secret", + Long: `Delete an existing image pull secret by name.`, + Args: cobra.MaximumNArgs(1), + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.delete") + defer span.End() + + if len(args) > 0 { + name = args[0] + } + + if name == "" { + return errEmptyName + } + + cmd.SilenceUsage = true + + ps, err := FindPullSecretByName(ctx, set.PlatformClient, name) + if err != nil { + return err + } + + err = set.PlatformClient.DeletePullSecret(ctx, ps.ID) + if err != nil { + return redact.Errorf("could not delete pull secret: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "deleted pull secret: %s\n", name) + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "Name of the pull secret to delete") + + return cmd +} diff --git a/pkg/commands/pullsecret/edit.go b/pkg/commands/pullsecret/edit.go new file mode 100644 index 0000000..ff7ba77 --- /dev/null +++ b/pkg/commands/pullsecret/edit.go @@ -0,0 +1,203 @@ +package pullsecret + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/internal/wizard" + "github.com/intility/indev/pkg/client" + "github.com/intility/indev/pkg/clientset" +) + +var ( + errInvalidAddRegistryFormat = redact.Errorf("--add-registry format must be address,username,password") + errNoChanges = redact.Errorf("no changes specified") +) + +const addRegistryParts = 3 + +func NewEditCommand(set clientset.ClientSet) *cobra.Command { + var ( + name string + addRegistries []string + removeRegistries []string + ) + + cmd := &cobra.Command{ + Use: "edit [name]", + Short: "Edit an existing image pull secret", + Long: `Edit an existing image pull secret by adding or removing registries.`, + Args: cobra.MaximumNArgs(1), + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + _, span := telemetry.StartSpan(cmd.Context(), "pullsecret.edit") + defer span.End() + + if len(args) > 0 { + name = args[0] + } + + if name == "" { + return redact.Errorf("pull secret name cannot be empty") + } + + cmd.SilenceUsage = true + + return runEdit(cmd, set, editOptions{ + name: name, + addRegistries: addRegistries, + removeRegistries: removeRegistries, + }) + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "Name of the pull secret") + cmd.Flags().StringArrayVar(&addRegistries, "add-registry", nil, "Add registry (format: address,username,password)") + cmd.Flags().StringArrayVar(&removeRegistries, "remove-registry", nil, "Remove registry by address") + + return cmd +} + +type editOptions struct { + name string + addRegistries []string + removeRegistries []string +} + +func runEdit(cmd *cobra.Command, set clientset.ClientSet, opts editOptions) error { + ctx := cmd.Context() + + ps, err := FindPullSecretByName(ctx, set.PlatformClient, opts.name) + if err != nil { + return err + } + + registries := make(map[string]*client.PullSecretCredential) + + hasFlags := len(opts.addRegistries) > 0 || len(opts.removeRegistries) > 0 + + if hasFlags { + if err = applyFlagEdits(registries, opts.addRegistries, opts.removeRegistries); err != nil { + return err + } + } else { + registries, err = editFromWizard(ps) + if err != nil { + return err + } + } + + if len(registries) == 0 { + return errNoChanges + } + + result, err := set.PlatformClient.EditPullSecret(ctx, ps.ID, client.EditPullSecretRequest{ + Registries: registries, + }) + if err != nil { + return redact.Errorf("could not edit pull secret: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "updated pull secret: %s\n", result.Name) + + return nil +} + +func applyFlagEdits( + registries map[string]*client.PullSecretCredential, + addRegistries []string, + removeRegistries []string, +) error { + for _, add := range addRegistries { + parts := strings.SplitN(add, ",", addRegistryParts) + if len(parts) != addRegistryParts { + return errInvalidAddRegistryFormat + } + + registries[parts[0]] = &client.PullSecretCredential{ + Username: parts[1], + Password: parts[2], + } + } + + for _, remove := range removeRegistries { + registries[remove] = nil + } + + return nil +} + +func editFromWizard(ps *client.PullSecret) (map[string]*client.PullSecretCredential, error) { + registries := make(map[string]*client.PullSecretCredential) + + if err := promptRemoveRegistries(registries, ps.Registries); err != nil { + return nil, err + } + + if err := promptAddRegistries(registries); err != nil { + return nil, err + } + + return registries, nil +} + +func promptRemoveRegistries(registries map[string]*client.PullSecretCredential, current []string) error { + for _, registry := range current { + wz := wizard.NewWizard([]wizard.Input{ + { + ID: "remove", + Placeholder: "Remove " + registry + "?", + Type: wizard.InputTypeToggle, + Limit: 0, + Validator: nil, + Options: []string{"no", answerYes}, + DependsOn: "", + ShowWhen: nil, + }, + }) + + result, err := wz.Run() + if err != nil { + return redact.Errorf("could not gather information: %w", redact.Safe(err)) + } + + if result.Cancelled() { + return errCancelledByUser + } + + if result.MustGetValue("remove") == answerYes { + registries[registry] = nil + } + } + + return nil +} + +func promptAddRegistries(registries map[string]*client.PullSecretCredential) error { + for { + more, err := promptAddMore("Add a new registry?") + if err != nil { + return err + } + + if !more { + break + } + + cred, err := promptRegistryCredential() + if err != nil { + return err + } + + registries[cred.address] = &client.PullSecretCredential{ + Username: cred.username, + Password: cred.password, + } + } + + return nil +} diff --git a/pkg/commands/pullsecret/get.go b/pkg/commands/pullsecret/get.go new file mode 100644 index 0000000..79872e0 --- /dev/null +++ b/pkg/commands/pullsecret/get.go @@ -0,0 +1,95 @@ +package pullsecret + +import ( + "context" + "io" + "strings" + + "github.com/spf13/cobra" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/pkg/client" + "github.com/intility/indev/pkg/clientset" +) + +var errPullSecretNotFound = redact.Errorf("pull secret not found") + +func NewGetCommand(set clientset.ClientSet) *cobra.Command { + var ( + name string + errEmptyName = redact.Errorf("pull secret name cannot be empty") + ) + + cmd := &cobra.Command{ + Use: "get [name]", + Short: "Get detailed information about a pull secret", + Long: `Display comprehensive information about a specific image pull secret`, + Args: cobra.MaximumNArgs(1), + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.get") + defer span.End() + + cmd.SilenceUsage = true + + if len(args) > 0 { + name = args[0] + } + + if name == "" { + return errEmptyName + } + + ps, err := FindPullSecretByName(ctx, set.PlatformClient, name) + if err != nil { + return err + } + + printPullSecretDetails(cmd.OutOrStdout(), ps) + + return nil + }, + } + + cmd.Flags().StringVarP(&name, "name", "n", "", "Name of the pull secret") + + return cmd +} + +// FindPullSecretByName lists all pull secrets and returns the one matching the given name. +func FindPullSecretByName(ctx context.Context, c client.Client, name string) (*client.PullSecret, error) { + pullSecrets, err := c.ListPullSecrets(ctx) + if err != nil { + return nil, redact.Errorf("could not list pull secrets: %w", redact.Safe(err)) + } + + for i := range pullSecrets { + if pullSecrets[i].Name == name { + return &pullSecrets[i], nil + } + } + + return nil, errPullSecretNotFound +} + +func printPullSecretDetails(writer io.Writer, ps *client.PullSecret) { + ux.Fprintf(writer, "Pull Secret Information:\n") + ux.Fprintf(writer, " Name: %s\n", ps.Name) + ux.Fprintf(writer, " ID: %s\n", ps.ID) + ux.Fprintf(writer, " Created At: %s\n", ps.CreatedAt) + + if len(ps.Registries) > 0 { + ux.Fprintf(writer, "Registries:\n") + + for _, registry := range ps.Registries { + ux.Fprintf(writer, " - %s\n", registry) + } + } +} + +// FormatRegistries joins registry addresses with commas for display. +func FormatRegistries(registries []string) string { + return strings.Join(registries, ", ") +} diff --git a/pkg/commands/pullsecret/list.go b/pkg/commands/pullsecret/list.go new file mode 100644 index 0000000..03b7eda --- /dev/null +++ b/pkg/commands/pullsecret/list.go @@ -0,0 +1,96 @@ +package pullsecret + +import ( + "encoding/json" + "io" + "strconv" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/telemetry" + "github.com/intility/indev/internal/ux" + "github.com/intility/indev/pkg/client" + "github.com/intility/indev/pkg/clientset" + "github.com/intility/indev/pkg/outputformat" +) + +func NewListCommand(set clientset.ClientSet) *cobra.Command { + output := outputformat.Format("") + cmd := &cobra.Command{ + Use: "list", + Short: "List all image pull secrets", + Long: `List all image pull secrets in the Intility Developer Platform`, + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.list") + defer span.End() + + cmd.SilenceUsage = true + + pullSecrets, err := set.PlatformClient.ListPullSecrets(ctx) + if err != nil { + return redact.Errorf("could not list pull secrets: %w", redact.Safe(err)) + } + + if len(pullSecrets) == 0 { + ux.Fprintf(cmd.OutOrStdout(), "No pull secrets found\n") + return nil + } + + if err = printPullSecretsList(cmd.OutOrStdout(), output, pullSecrets); err != nil { + return redact.Errorf("could not print pull secrets list: %w", redact.Safe(err)) + } + + return nil + }, + } + + cmd.Flags().VarP(&output, "output", "o", "Output format (wide, json, yaml)") + + return cmd +} + +func printPullSecretsList(writer io.Writer, format outputformat.Format, pullSecrets []client.PullSecret) error { + var err error + + switch format { + case "wide": + table := ux.TableFromObjects(pullSecrets, func(ps client.PullSecret) []ux.Row { + return []ux.Row{ + ux.NewRow("Id", ps.ID), + ux.NewRow("Name", ps.Name), + ux.NewRow("Registries", strconv.Itoa(len(ps.Registries))), + ux.NewRow("Created At", ps.CreatedAt), + } + }) + + ux.Fprintf(writer, "%s", table.String()) + case "json": + enc := json.NewEncoder(writer) + enc.SetIndent("", " ") + err = enc.Encode(pullSecrets) + case "yaml": + indent := 2 + enc := yaml.NewEncoder(writer) + enc.SetIndent(indent) + err = enc.Encode(pullSecrets) + default: + table := ux.TableFromObjects(pullSecrets, func(ps client.PullSecret) []ux.Row { + return []ux.Row{ + ux.NewRow("Name", ps.Name), + ux.NewRow("Registries", strconv.Itoa(len(ps.Registries))), + ux.NewRow("Created At", ps.CreatedAt), + } + }) + + ux.Fprintf(writer, "%s", table.String()) + } + + if err != nil { + return redact.Errorf("output encoder failed: %w", redact.Safe(err)) + } + + return nil +} diff --git a/pkg/commands/pullsecret/wizard.go b/pkg/commands/pullsecret/wizard.go new file mode 100644 index 0000000..4d85cf0 --- /dev/null +++ b/pkg/commands/pullsecret/wizard.go @@ -0,0 +1,92 @@ +package pullsecret + +import ( + "github.com/intility/indev/internal/redact" + "github.com/intility/indev/internal/wizard" +) + +type registryInput struct { + address string + username string + password string +} + +func registryWizardInputs() []wizard.Input { + return []wizard.Input{ + { + ID: "address", + Placeholder: "Registry Address (e.g. ghcr.io)", + Type: wizard.InputTypeText, + Limit: 0, + Validator: nil, + Options: nil, + DependsOn: "", + ShowWhen: nil, + }, + { + ID: "username", + Placeholder: "Username", + Type: wizard.InputTypeText, + Limit: 0, + Validator: nil, + Options: nil, + DependsOn: "", + ShowWhen: nil, + }, + { + ID: "password", + Placeholder: "Password", + Type: wizard.InputTypePassword, + Limit: 0, + Validator: nil, + Options: nil, + DependsOn: "", + ShowWhen: nil, + }, + } +} + +func promptRegistryCredential() (*registryInput, error) { + wz := wizard.NewWizard(registryWizardInputs()) + + result, err := wz.Run() + if err != nil { + return nil, redact.Errorf("could not gather registry information: %w", redact.Safe(err)) + } + + if result.Cancelled() { + return nil, errCancelledByUser + } + + return ®istryInput{ + address: result.MustGetValue("address"), + username: result.MustGetValue("username"), + password: result.MustGetValue("password"), + }, nil +} + +func promptAddMore(placeholder string) (bool, error) { + wz := wizard.NewWizard([]wizard.Input{ + { + ID: "addMore", + Placeholder: placeholder, + Type: wizard.InputTypeToggle, + Limit: 0, + Validator: nil, + Options: []string{"no", answerYes}, + DependsOn: "", + ShowWhen: nil, + }, + }) + + result, err := wz.Run() + if err != nil { + return false, redact.Errorf("could not gather information: %w", redact.Safe(err)) + } + + if result.Cancelled() { + return false, errCancelledByUser + } + + return result.MustGetValue("addMore") == answerYes, nil +} diff --git a/pkg/rootcommand/rootcommand.go b/pkg/rootcommand/rootcommand.go index 3d30cb9..7eebd40 100644 --- a/pkg/rootcommand/rootcommand.go +++ b/pkg/rootcommand/rootcommand.go @@ -15,6 +15,8 @@ import ( "github.com/intility/indev/pkg/commands/ai/deployment" "github.com/intility/indev/pkg/commands/cluster" "github.com/intility/indev/pkg/commands/cluster/access" + clusterpullsecret "github.com/intility/indev/pkg/commands/cluster/pullsecret" + "github.com/intility/indev/pkg/commands/pullsecret" "github.com/intility/indev/pkg/commands/teams" "github.com/intility/indev/pkg/commands/teams/member" "github.com/intility/indev/pkg/commands/user" @@ -42,6 +44,7 @@ func GetRootCommand() *cobra.Command { rootCmd.AddCommand(getTeamsCommand(clients)) rootCmd.AddCommand(getUserCommand(clients)) rootCmd.AddCommand(getAICommand(clients)) + rootCmd.AddCommand(getPullSecretCommand(clients)) return rootCmd } @@ -76,6 +79,19 @@ func getClusterCommand(set clientset.ClientSet) *cobra.Command { cmd.AddCommand(cluster.NewOpenCommand(set)) cmd.AddCommand(cluster.NewStatusCommand(set)) cmd.AddCommand(getAccessCommand(set)) + cmd.AddCommand(getClusterPullSecretCommand(set)) + + return cmd +} + +func getClusterPullSecretCommand(set clientset.ClientSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "pull-secret", + Short: "Manage cluster pull secrets", + Run: showHelp, + } + + cmd.AddCommand(clusterpullsecret.NewSetCommand(set)) return cmd } @@ -213,6 +229,22 @@ func getAIAPIKeyCommand(set clientset.ClientSet) *cobra.Command { return cmd } +func getPullSecretCommand(set clientset.ClientSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "pull-secret", + Short: "Manage image pull secrets", + Run: showHelp, + } + + cmd.AddCommand(pullsecret.NewCreateCommand(set)) + cmd.AddCommand(pullsecret.NewListCommand(set)) + cmd.AddCommand(pullsecret.NewGetCommand(set)) + cmd.AddCommand(pullsecret.NewEditCommand(set)) + cmd.AddCommand(pullsecret.NewDeleteCommand(set)) + + return cmd +} + func showHelp(cmd *cobra.Command, args []string) { _, span := telemetry.StartSpan(cmd.Context(), cmd.Use) defer span.End()