diff --git a/internal/build/build.go b/internal/build/build.go index 5c4351c..95d8ea3 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -114,7 +114,7 @@ func OS() OSInfo { //nolint:cyclop func PlatformAPIHost() string { if IsDev { - return "http://localhost:8080" + return "http://localhost:8081" } return PlatformBaseURI 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/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..60c255f --- /dev/null +++ b/pkg/commands/pullsecret/create.go @@ -0,0 +1,91 @@ +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/client" + "github.com/intility/indev/pkg/clientset" +) + +var ( + errEmptyPullSecretName = redact.Errorf("pull secret name cannot be empty") + errEmptyAddress = redact.Errorf("registry address cannot be empty") + errEmptyUsername = redact.Errorf("registry username cannot be empty") + errEmptyPassword = redact.Errorf("registry password cannot be empty") +) + +// CreateOptions holds the options for the pull secret create command. +type CreateOptions struct { + Name string + Address string + Username string + Password string //nolint:gosec // G117: this is a credential payload, not a hardcoded secret +} + +func NewCreateCommand(set clientset.ClientSet) *cobra.Command { + var options CreateOptions + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new image pull secret", + Long: `Create a new image pull secret with the specified name and an initial registry.`, + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.create") + defer span.End() + + if err := validateCreateOptions(options); err != nil { + return err + } + + cmd.SilenceUsage = true + + ps, err := set.PlatformClient.CreatePullSecret(ctx, client.NewPullSecretRequest{ + Name: options.Name, + Registries: map[string]client.PullSecretCredential{ + options.Address: { + Username: options.Username, + Password: options.Password, + }, + }, + }) + 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(&options.Name, "name", "n", "", "Name of the pull secret to create") + cmd.Flags().StringVarP(&options.Address, "address", "a", "", "Registry address (e.g. ghcr.io)") + cmd.Flags().StringVarP(&options.Username, "username", "u", "", "Registry username") + cmd.Flags().StringVar(&options.Password, "password", "", "Registry password") + + return cmd +} + +func validateCreateOptions(options CreateOptions) error { + if options.Name == "" { + return errEmptyPullSecretName + } + + if options.Address == "" { + return errEmptyAddress + } + + if options.Username == "" { + return errEmptyUsername + } + + if options.Password == "" { + return errEmptyPassword + } + + return 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/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/registry/add.go b/pkg/commands/pullsecret/registry/add.go new file mode 100644 index 0000000..6bdcd4a --- /dev/null +++ b/pkg/commands/pullsecret/registry/add.go @@ -0,0 +1,96 @@ +package registry + +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/client" + "github.com/intility/indev/pkg/clientset" + pullsecretcmd "github.com/intility/indev/pkg/commands/pullsecret" +) + +var ( + errEmptyPullSecretName = redact.Errorf("pull secret name cannot be empty") + errEmptyAddress = redact.Errorf("registry address cannot be empty") + errEmptyUsername = redact.Errorf("registry username cannot be empty") + errEmptyPassword = redact.Errorf("registry password cannot be empty") +) + +// AddOptions holds the options for the registry add command. +type AddOptions struct { + PullSecretName string + Address string + Username string + Password string //nolint:gosec // G117: this is a credential payload, not a hardcoded secret +} + +func NewAddCommand(set clientset.ClientSet) *cobra.Command { + var options AddOptions + + cmd := &cobra.Command{ + Use: "add", + Short: "Add a registry to a pull secret", + Long: `Add a registry with credentials to an existing image pull secret.`, + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.registry.add") + defer span.End() + + if err := validateAddOptions(options); err != nil { + return err + } + + cmd.SilenceUsage = true + + ps, err := pullsecretcmd.FindPullSecretByName(ctx, set.PlatformClient, options.PullSecretName) + if err != nil { + return redact.Errorf("could not find pull secret: %w", redact.Safe(err)) + } + + _, err = set.PlatformClient.EditPullSecret(ctx, ps.ID, client.EditPullSecretRequest{ + Registries: map[string]*client.PullSecretCredential{ + options.Address: { + Username: options.Username, + Password: options.Password, + }, + }, + }) + if err != nil { + return redact.Errorf("could not add registry: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "added registry %s to pull secret: %s\n", options.Address, options.PullSecretName) + + return nil + }, + } + + cmd.Flags().StringVarP(&options.PullSecretName, "pull-secret", "p", "", "Name of the pull secret") + cmd.Flags().StringVarP(&options.Address, "address", "a", "", "Registry address (e.g. ghcr.io)") + cmd.Flags().StringVarP(&options.Username, "username", "u", "", "Registry username") + cmd.Flags().StringVar(&options.Password, "password", "", "Registry password") + + return cmd +} + +func validateAddOptions(options AddOptions) error { + if options.PullSecretName == "" { + return errEmptyPullSecretName + } + + if options.Address == "" { + return errEmptyAddress + } + + if options.Username == "" { + return errEmptyUsername + } + + if options.Password == "" { + return errEmptyPassword + } + + return nil +} diff --git a/pkg/commands/pullsecret/registry/remove.go b/pkg/commands/pullsecret/registry/remove.go new file mode 100644 index 0000000..6c623fc --- /dev/null +++ b/pkg/commands/pullsecret/registry/remove.go @@ -0,0 +1,75 @@ +package registry + +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/client" + "github.com/intility/indev/pkg/clientset" + pullsecretcmd "github.com/intility/indev/pkg/commands/pullsecret" +) + +// RemoveOptions holds the options for the registry remove command. +type RemoveOptions struct { + PullSecretName string + Address string +} + +func NewRemoveCommand(set clientset.ClientSet) *cobra.Command { + var options RemoveOptions + + cmd := &cobra.Command{ + Use: "remove", + Short: "Remove a registry from a pull secret", + Long: `Remove a registry from an existing image pull secret.`, + PreRunE: set.EnsureSignedInPreHook, + RunE: func(cmd *cobra.Command, args []string) error { + ctx, span := telemetry.StartSpan(cmd.Context(), "pullsecret.registry.remove") + defer span.End() + + if err := validateRemoveOptions(options); err != nil { + return err + } + + cmd.SilenceUsage = true + + ps, err := pullsecretcmd.FindPullSecretByName(ctx, set.PlatformClient, options.PullSecretName) + if err != nil { + return redact.Errorf("could not find pull secret: %w", redact.Safe(err)) + } + + _, err = set.PlatformClient.EditPullSecret(ctx, ps.ID, client.EditPullSecretRequest{ + Registries: map[string]*client.PullSecretCredential{ + options.Address: nil, + }, + }) + if err != nil { + return redact.Errorf("could not remove registry: %w", redact.Safe(err)) + } + + ux.Fsuccessf(cmd.OutOrStdout(), "removed registry %s from pull secret: %s\n", + options.Address, options.PullSecretName) + + return nil + }, + } + + cmd.Flags().StringVarP(&options.PullSecretName, "pull-secret", "p", "", "Name of the pull secret") + cmd.Flags().StringVarP(&options.Address, "address", "a", "", "Registry address to remove") + + return cmd +} + +func validateRemoveOptions(options RemoveOptions) error { + if options.PullSecretName == "" { + return errEmptyPullSecretName + } + + if options.Address == "" { + return errEmptyAddress + } + + return nil +} diff --git a/pkg/rootcommand/rootcommand.go b/pkg/rootcommand/rootcommand.go index 3d30cb9..92791cf 100644 --- a/pkg/rootcommand/rootcommand.go +++ b/pkg/rootcommand/rootcommand.go @@ -15,6 +15,9 @@ 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" + pullsecretregistry "github.com/intility/indev/pkg/commands/pullsecret/registry" "github.com/intility/indev/pkg/commands/teams" "github.com/intility/indev/pkg/commands/teams/member" "github.com/intility/indev/pkg/commands/user" @@ -42,6 +45,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 +80,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 +230,36 @@ 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.NewDeleteCommand(set)) + cmd.AddCommand(getRegistryCommand(set)) + + return cmd +} + +func getRegistryCommand(set clientset.ClientSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "registry", + Short: "Manage registries in a pull secret", + Long: "Manage registries in a pull secret", + Run: showHelp, + } + + cmd.AddCommand(pullsecretregistry.NewAddCommand(set)) + cmd.AddCommand(pullsecretregistry.NewRemoveCommand(set)) + + return cmd +} + func showHelp(cmd *cobra.Command, args []string) { _, span := telemetry.StartSpan(cmd.Context(), cmd.Use) defer span.End()