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 { diff --git a/apiv2/pkg/clients/configure/configure.go b/apiv2/pkg/clients/configure/configure.go new file mode 100644 index 00000000..6ea10801 --- /dev/null +++ b/apiv2/pkg/clients/configure/configure.go @@ -0,0 +1,64 @@ +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) + 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..de44266a --- /dev/null +++ b/apiv2/pkg/clients/configure/configure_integration_test.go @@ -0,0 +1,32 @@ +//go:build integration + +package configure + +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" +) + +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.Value) +} + +func TestAPIUpdateConfigs(t *testing.T) { + authMode := "db_auth" + ctx := context.Background() + c := NewClient(clienttesting.V2SwaggerClient, clienttesting.DefaultOpts, clienttesting.AuthInfo) + + 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 +}