From eb84215bf4e83e91b18a470b698bc2d605f518a1 Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Mon, 7 Aug 2023 14:33:31 +0200 Subject: [PATCH 1/9] GH-169: Implement Configure Public Client --- .../configure/get_internalconfig_responses.go | 4 +- apiv2/pkg/clients/configure/configure.go | 65 +++++++++++++++++ .../pkg/clients/configure/configure_errors.go | 27 ++++++++ .../configure/configure_integration_test.go | 29 ++++++++ apiv2/pkg/clients/configure/configure_test.go | 69 +++++++++++++++++++ apiv2/pkg/errors/configure_errors.go | 38 ++++++++++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 apiv2/pkg/clients/configure/configure.go create mode 100644 apiv2/pkg/clients/configure/configure_errors.go create mode 100644 apiv2/pkg/clients/configure/configure_integration_test.go create mode 100644 apiv2/pkg/clients/configure/configure_test.go create mode 100644 apiv2/pkg/errors/configure_errors.go diff --git a/apiv2/internal/api/client/configure/get_internalconfig_responses.go b/apiv2/internal/api/client/configure/get_internalconfig_responses.go index 1729cd96..70f603be 100644 --- a/apiv2/internal/api/client/configure/get_internalconfig_responses.go +++ b/apiv2/internal/api/client/configure/get_internalconfig_responses.go @@ -63,14 +63,14 @@ func NewGetInternalconfigOK() *GetInternalconfigOK { Get system configurations successfully. The response body is a map. */ type GetInternalconfigOK struct { - Payload model.InternalConfigurationsResponse + Payload *model.InternalConfigurationsResponse } func (o *GetInternalconfigOK) Error() string { return fmt.Sprintf("[GET /internalconfig][%d] getInternalconfigOK %+v", 200, o.Payload) } -func (o *GetInternalconfigOK) GetPayload() model.InternalConfigurationsResponse { +func (o *GetInternalconfigOK) GetPayload() *model.InternalConfigurationsResponse { return o.Payload } diff --git a/apiv2/pkg/clients/configure/configure.go b/apiv2/pkg/clients/configure/configure.go new file mode 100644 index 00000000..009edf50 --- /dev/null +++ b/apiv2/pkg/clients/configure/configure.go @@ -0,0 +1,65 @@ +package configure + +import ( + "context" + + "github.com/go-openapi/runtime" + + v2client "github.com/mittwald/goharbor-client/v5/apiv2/internal/api/client" + "github.com/mittwald/goharbor-client/v5/apiv2/internal/api/client/configure" + "github.com/mittwald/goharbor-client/v5/apiv2/model" + "github.com/mittwald/goharbor-client/v5/apiv2/pkg/config" +) + +// RESTClient is a subclient for handling project related actions. +type RESTClient struct { + // Options contains optional configuration when making API calls. + Options *config.Options + + // The new client of the harbor v2 API + V2Client *v2client.Harbor + + // AuthInfo contains the auth information that is provided on API calls. + AuthInfo runtime.ClientAuthInfoWriter +} + +func NewClient(v2Client *v2client.Harbor, opts *config.Options, authInfo runtime.ClientAuthInfoWriter) *RESTClient { + return &RESTClient{ + Options: opts, + V2Client: v2Client, + AuthInfo: authInfo, + } +} + +type Client interface { + GetConfigs(ctx context.Context) (*model.ConfigurationsResponse, error) + GetInternalConfigs(ctx context.Context) (*model.InternalConfigurationsResponse, error) + UpdateConfigs(ctx context.Context, newConfiguration *model.Configurations) error +} + +// GetConfigs returns a system configurations object. +func (c *RESTClient) GetConfigs(ctx context.Context) (*model.ConfigurationsResponse, error) { + params := &configure.GetConfigurationsParams{ + Context: ctx, + } + + params.WithTimeout(c.Options.Timeout) + + resp, err := c.V2Client.Configure.GetConfigurations(params, c.AuthInfo) + if err != nil { + return nil, handleSwaggerConfigurationsErrors(err) + } + + return resp.Payload, nil +} + +// UpdateConfigs modifying system configurations that only provides for admin users. +func (c *RESTClient) UpdateConfigs(ctx context.Context, cfg *model.Configurations) error { + params := &configure.UpdateConfigurationsParams{ + Configurations: cfg, + Context: ctx, + } + params.WithTimeout(c.Options.Timeout) + _, err := c.V2Client.Configure.UpdateConfigurations(params, c.AuthInfo) + return handleSwaggerConfigurationsErrors(err) +} diff --git a/apiv2/pkg/clients/configure/configure_errors.go b/apiv2/pkg/clients/configure/configure_errors.go new file mode 100644 index 00000000..62177d37 --- /dev/null +++ b/apiv2/pkg/clients/configure/configure_errors.go @@ -0,0 +1,27 @@ +package configure + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/mittwald/goharbor-client/v5/apiv2/pkg/errors" +) + +// handleSwaggerConfigurationsErrors takes a swagger generated error as input, +// which usually does not contain any form of error message, +// and outputs a new error with a proper message. +func handleSwaggerConfigurationsErrors(in error) error { + t, ok := in.(*runtime.APIError) + if ok { + switch t.Code { + case http.StatusUnauthorized: + return &errors.ErrConfigureUnauthorized{} + case http.StatusForbidden: + return &errors.ErrConfigureNoPermission{} + case http.StatusInternalServerError: + return &errors.ErrConfigureInternalServerError{} + } + } + return nil +} diff --git a/apiv2/pkg/clients/configure/configure_integration_test.go b/apiv2/pkg/clients/configure/configure_integration_test.go new file mode 100644 index 00000000..612919b2 --- /dev/null +++ b/apiv2/pkg/clients/configure/configure_integration_test.go @@ -0,0 +1,29 @@ +//go:build integration + +package configure + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAPIGetConfig(t *testing.T) { + ctx := context.Background() + c := NewClient(clienttesting.V2SwaggerClient, clienttesting.DefaultOpts, clienttesting.AuthInfo) + + resp, err := c.GetConfigs(ctx) + require.NoError(t, err) + + require.Equal(t, "db_auth", *resp.AuthMode) +} + +func TestAPIUpdateConfigs(t *testing.T) { + authMode := "oidc" + ctx := context.Background() + c := NewClient(clienttesting.V2SwaggerClient, clienttesting.DefaultOpts, clienttesting.AuthInfo) + + resp, err := c.UpdateConfigs(ctx, &modelv2.Configurations{AuthMode: authMode}) + require.NoError(t, err) +} diff --git a/apiv2/pkg/clients/configure/configure_test.go b/apiv2/pkg/clients/configure/configure_test.go new file mode 100644 index 00000000..5e663189 --- /dev/null +++ b/apiv2/pkg/clients/configure/configure_test.go @@ -0,0 +1,69 @@ +//go:build !integration + +package configure + +import ( + "context" + "testing" + + "github.com/mittwald/goharbor-client/v5/apiv2/internal/api/client/configure" + "github.com/mittwald/goharbor-client/v5/apiv2/mocks" + "github.com/mittwald/goharbor-client/v5/apiv2/model" + clienttesting "github.com/mittwald/goharbor-client/v5/apiv2/pkg/testing" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var ( + authMode = "oidc" + OIDCName = "example" + exampleConfig = &model.Configurations{ + AuthMode: &authMode, + OIDCName: &OIDCName, + } + ctx = context.Background() +) + +func APIandMockClientsForTests() (*RESTClient, *clienttesting.MockClients) { + desiredMockClients := &clienttesting.MockClients{ + Configure: mocks.MockConfigureClientService{}, + } + + v2Client := clienttesting.BuildV2ClientWithMocks(desiredMockClients) + + cl := NewClient(v2Client, clienttesting.DefaultOpts, clienttesting.AuthInfo) + + return cl, desiredMockClients +} + +func TestRESTClient_GetConfigurations(t *testing.T) { + apiClient, mockClient := APIandMockClientsForTests() + + params := &configure.GetConfigurationsParams{ + Context: ctx, + } + params.WithTimeout(apiClient.Options.Timeout) + + mockClient.Configure.On("GetConfigurations", params, mock.AnythingOfType("runtime.ClientAuthInfoWriterFunc")).Return(&configure.GetConfigurationsOK{}, nil) + + _, err := apiClient.GetConfigs(ctx) + + require.NoError(t, err) + mockClient.Configure.AssertExpectations(t) +} + +func TestRESTClient_UpdateConfigs(t *testing.T) { + apiClient, mockClient := APIandMockClientsForTests() + + params := &configure.UpdateConfigurationsParams{ + Configurations: exampleConfig, + Context: ctx, + } + params.WithTimeout(apiClient.Options.Timeout) + + mockClient.Configure.On("UpdateConfigurations", params, mock.AnythingOfType("runtime.ClientAuthInfoWriterFunc")).Return(&configure.UpdateConfigurationsOK{}, nil) + + err := apiClient.UpdateConfigs(ctx, exampleConfig) + require.NoError(t, err) + mockClient.Configure.AssertExpectations(t) +} diff --git a/apiv2/pkg/errors/configure_errors.go b/apiv2/pkg/errors/configure_errors.go new file mode 100644 index 00000000..517b0fa1 --- /dev/null +++ b/apiv2/pkg/errors/configure_errors.go @@ -0,0 +1,38 @@ +package errors + +const ( + // ErrConfigureUnauthorizedMsg is the error message for ErrConfigureUnauthorized error. + ErrConfigureUnauthorizedMsg = "unauthorized" + + // ErrConfigureNoPermissionMsg is the error message for ErrConfigureNoPermission error. + ErrConfigureNoPermissionMsg = "user does not have permission of admin role" + + // ErrConfigureInternalServerErrorMsg is the error message for ErrConfigureInternalServerError error. + ErrConfigureInternalServerErrorMsg = "unexpected internal errors" +) + +type ( + // ErrConfigureInternalServerError describes server-side internal errors. + ErrConfigureInternalServerError struct{} + + // ErrConfigureNoPermission describes a request error without permission. + ErrConfigureNoPermission struct{} + + // ErrConfigureUnauthorized describes an unauthorized request. + ErrConfigureUnauthorized struct{} +) + +// Error returns the error message. +func (e *ErrConfigureUnauthorized) Error() string { + return ErrConfigureUnauthorizedMsg +} + +// Error returns the error message. +func (e *ErrConfigureNoPermission) Error() string { + return ErrConfigureNoPermissionMsg +} + +// Error returns the error message. +func (e *ErrConfigureInternalServerError) Error() string { + return ErrConfigureInternalServerErrorMsg +} From 1ed5440e9d09eb0ca6b6628935167978f92a11af Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Mon, 7 Aug 2023 14:45:00 +0200 Subject: [PATCH 2/9] GH-169: Fix integration tests --- apiv2/pkg/clients/configure/configure_integration_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apiv2/pkg/clients/configure/configure_integration_test.go b/apiv2/pkg/clients/configure/configure_integration_test.go index 612919b2..123eea58 100644 --- a/apiv2/pkg/clients/configure/configure_integration_test.go +++ b/apiv2/pkg/clients/configure/configure_integration_test.go @@ -6,6 +6,9 @@ import ( "context" "testing" + modelv2 "github.com/mittwald/goharbor-client/v5/apiv2/model" + clienttesting "github.com/mittwald/goharbor-client/v5/apiv2/pkg/testing" + "github.com/stretchr/testify/require" ) From 49dabd86c280f46409e529b64d87e9da231428de Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Mon, 7 Aug 2023 14:46:44 +0200 Subject: [PATCH 3/9] GH-169: Fix UpdateConfigs() in integration tests --- apiv2/pkg/clients/configure/configure_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiv2/pkg/clients/configure/configure_integration_test.go b/apiv2/pkg/clients/configure/configure_integration_test.go index 123eea58..148e1393 100644 --- a/apiv2/pkg/clients/configure/configure_integration_test.go +++ b/apiv2/pkg/clients/configure/configure_integration_test.go @@ -27,6 +27,6 @@ func TestAPIUpdateConfigs(t *testing.T) { ctx := context.Background() c := NewClient(clienttesting.V2SwaggerClient, clienttesting.DefaultOpts, clienttesting.AuthInfo) - resp, err := c.UpdateConfigs(ctx, &modelv2.Configurations{AuthMode: authMode}) + err := c.UpdateConfigs(ctx, &modelv2.Configurations{AuthMode: authMode}) require.NoError(t, err) } From 30e6b99c7a33134ff7ccaf835bd2f26114ebfafa Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 11:01:54 +0200 Subject: [PATCH 4/9] Remove GetInternalConfigs() signature as it is not used ref: GH-169 --- apiv2/pkg/clients/configure/configure.go | 1 - 1 file changed, 1 deletion(-) diff --git a/apiv2/pkg/clients/configure/configure.go b/apiv2/pkg/clients/configure/configure.go index 009edf50..6ea10801 100644 --- a/apiv2/pkg/clients/configure/configure.go +++ b/apiv2/pkg/clients/configure/configure.go @@ -33,7 +33,6 @@ func NewClient(v2Client *v2client.Harbor, opts *config.Options, authInfo runtime type Client interface { GetConfigs(ctx context.Context) (*model.ConfigurationsResponse, error) - GetInternalConfigs(ctx context.Context) (*model.InternalConfigurationsResponse, error) UpdateConfigs(ctx context.Context, newConfiguration *model.Configurations) error } From 72b79a95add53ac3ab525e1570f0551238daacce Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 11:04:50 +0200 Subject: [PATCH 5/9] Revert changes in internal configure api ref: GH-169 --- .../api/client/configure/get_internalconfig_responses.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiv2/internal/api/client/configure/get_internalconfig_responses.go b/apiv2/internal/api/client/configure/get_internalconfig_responses.go index 70f603be..1729cd96 100644 --- a/apiv2/internal/api/client/configure/get_internalconfig_responses.go +++ b/apiv2/internal/api/client/configure/get_internalconfig_responses.go @@ -63,14 +63,14 @@ func NewGetInternalconfigOK() *GetInternalconfigOK { Get system configurations successfully. The response body is a map. */ type GetInternalconfigOK struct { - Payload *model.InternalConfigurationsResponse + Payload model.InternalConfigurationsResponse } func (o *GetInternalconfigOK) Error() string { return fmt.Sprintf("[GET /internalconfig][%d] getInternalconfigOK %+v", 200, o.Payload) } -func (o *GetInternalconfigOK) GetPayload() *model.InternalConfigurationsResponse { +func (o *GetInternalconfigOK) GetPayload() model.InternalConfigurationsResponse { return o.Payload } From c47ef5ebc6eec25484d3dcba2b09a52478d91184 Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 11:57:10 +0200 Subject: [PATCH 6/9] Remove integration test for GetInternalconfig() ref: GH-169 --- apiv2/mocks/configure_client_service.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/apiv2/mocks/configure_client_service.go b/apiv2/mocks/configure_client_service.go index 2316ef44..6629bdd8 100644 --- a/apiv2/mocks/configure_client_service.go +++ b/apiv2/mocks/configure_client_service.go @@ -37,29 +37,6 @@ func (_m *MockConfigureClientService) GetConfigurations(params *configure.GetCon return r0, r1 } -// GetInternalconfig provides a mock function with given fields: params, authInfo -func (_m *MockConfigureClientService) GetInternalconfig(params *configure.GetInternalconfigParams, authInfo runtime.ClientAuthInfoWriter) (*configure.GetInternalconfigOK, error) { - ret := _m.Called(params, authInfo) - - var r0 *configure.GetInternalconfigOK - if rf, ok := ret.Get(0).(func(*configure.GetInternalconfigParams, runtime.ClientAuthInfoWriter) *configure.GetInternalconfigOK); ok { - r0 = rf(params, authInfo) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*configure.GetInternalconfigOK) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(*configure.GetInternalconfigParams, runtime.ClientAuthInfoWriter) error); ok { - r1 = rf(params, authInfo) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // SetTransport provides a mock function with given fields: transport func (_m *MockConfigureClientService) SetTransport(transport runtime.ClientTransport) { _m.Called(transport) From bdc97ad4033e80162a6bd3064208646cedfacccd Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 12:45:30 +0200 Subject: [PATCH 7/9] Revert mock funtion ref: GH-169 --- apiv2/mocks/configure_client_service.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apiv2/mocks/configure_client_service.go b/apiv2/mocks/configure_client_service.go index 6629bdd8..2316ef44 100644 --- a/apiv2/mocks/configure_client_service.go +++ b/apiv2/mocks/configure_client_service.go @@ -37,6 +37,29 @@ func (_m *MockConfigureClientService) GetConfigurations(params *configure.GetCon return r0, r1 } +// GetInternalconfig provides a mock function with given fields: params, authInfo +func (_m *MockConfigureClientService) GetInternalconfig(params *configure.GetInternalconfigParams, authInfo runtime.ClientAuthInfoWriter) (*configure.GetInternalconfigOK, error) { + ret := _m.Called(params, authInfo) + + var r0 *configure.GetInternalconfigOK + if rf, ok := ret.Get(0).(func(*configure.GetInternalconfigParams, runtime.ClientAuthInfoWriter) *configure.GetInternalconfigOK); ok { + r0 = rf(params, authInfo) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*configure.GetInternalconfigOK) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*configure.GetInternalconfigParams, runtime.ClientAuthInfoWriter) error); ok { + r1 = rf(params, authInfo) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // SetTransport provides a mock function with given fields: transport func (_m *MockConfigureClientService) SetTransport(transport runtime.ClientTransport) { _m.Called(transport) From 12a4ea71e0faab20e4784bf669b3ae58300405de Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 13:45:26 +0200 Subject: [PATCH 8/9] Implement configure client interface ref: GH-169 --- apiv2/client.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apiv2/client.go b/apiv2/client.go index ac47987f..e41fc73e 100644 --- a/apiv2/client.go +++ b/apiv2/client.go @@ -5,6 +5,7 @@ import ( "net/url" "strings" + "github.com/mittwald/goharbor-client/v5/apiv2/pkg/clients/configure" "github.com/mittwald/goharbor-client/v5/apiv2/pkg/clients/ping" "github.com/mittwald/goharbor-client/v5/apiv2/pkg/clients/statistic" @@ -49,6 +50,7 @@ const v2URLSuffix string = "/v2.0" type Client interface { auditlog.Client artifact.Client + configure.Client gc.Client health.Client label.Client @@ -73,6 +75,7 @@ type Client interface { type RESTClient struct { auditlog *auditlog.RESTClient artifact *artifact.RESTClient + configure *configure.RESTClient gc *gc.RESTClient health *health.RESTClient label *label.RESTClient @@ -103,6 +106,7 @@ func NewRESTClient(v2Client *v2client.Harbor, opts *config.Options, authInfo run return &RESTClient{ auditlog: auditlog.NewClient(v2Client, opts, authInfo), artifact: artifact.NewClient(v2Client, opts, authInfo), + configure: configure.NewClient(v2Client, opts, authInfo), gc: gc.NewClient(v2Client, opts, authInfo), health: health.NewClient(v2Client, opts, authInfo), label: label.NewClient(v2Client, opts, authInfo), @@ -213,6 +217,16 @@ func (c *RESTClient) RemoveLabel(ctx context.Context, projectName, repositoryNam return c.artifact.RemoveLabel(ctx, projectName, repositoryName, reference, id) } +// Configure Client + +func (c *RESTClient) GetConfig(ctx context.Context) (*modelv2.ConfigurationsResponse, error) { + return c.configure.GetConfigs(ctx) +} + +func (c *RESTClient) UpdateConfigs(ctx context.Context, cfg *modelv2.Configurations) error { + return c.configure.UpdateConfigs(ctx, cfg) +} + // GC Client func (c *RESTClient) NewGarbageCollection(ctx context.Context, gcSchedule *modelv2.Schedule) error { From 7ef91d4325bcc0f574874ec4c462d693f69e8da6 Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 10 Aug 2023 13:53:26 +0200 Subject: [PATCH 9/9] Fix integration tests ref: GH-169 --- apiv2/pkg/clients/configure/configure_integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiv2/pkg/clients/configure/configure_integration_test.go b/apiv2/pkg/clients/configure/configure_integration_test.go index 148e1393..de44266a 100644 --- a/apiv2/pkg/clients/configure/configure_integration_test.go +++ b/apiv2/pkg/clients/configure/configure_integration_test.go @@ -19,14 +19,14 @@ func TestAPIGetConfig(t *testing.T) { resp, err := c.GetConfigs(ctx) require.NoError(t, err) - require.Equal(t, "db_auth", *resp.AuthMode) + require.Equal(t, "db_auth", *&resp.AuthMode.Value) } func TestAPIUpdateConfigs(t *testing.T) { - authMode := "oidc" + authMode := "db_auth" ctx := context.Background() c := NewClient(clienttesting.V2SwaggerClient, clienttesting.DefaultOpts, clienttesting.AuthInfo) - err := c.UpdateConfigs(ctx, &modelv2.Configurations{AuthMode: authMode}) + err := c.UpdateConfigs(ctx, &modelv2.Configurations{AuthMode: &authMode}) require.NoError(t, err) }