Skip to content

Commit

Permalink
add quota client (#81)
Browse files Browse the repository at this point in the history
* add quota client

* add error handling
  • Loading branch information
elenz97 authored Feb 10, 2021
1 parent 7b692f5 commit f38d9fa
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 8 deletions.
14 changes: 14 additions & 0 deletions apiv2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

modelv2 "github.com/mittwald/goharbor-client/v3/apiv2/model"
"github.com/mittwald/goharbor-client/v3/apiv2/quota"
"github.com/mittwald/goharbor-client/v3/apiv2/retention"

"github.com/go-openapi/runtime"
Expand All @@ -30,6 +31,7 @@ type Client interface {
replication.Client
system.Client
retention.Client
quota.Client
}

// RESTClient implements the Client interface as a REST client
Expand All @@ -40,6 +42,7 @@ type RESTClient struct {
replication *replication.RESTClient
system *system.RESTClient
retention *retention.RESTClient
quota *quota.RESTClient
}

// NewRESTClient constructs a new REST client containing each sub client.
Expand All @@ -51,6 +54,7 @@ func NewRESTClient(legacyClient *client.Harbor, v2Client *v2client.Harbor, authI
replication: replication.NewClient(legacyClient, v2Client, authInfo),
system: system.NewClient(legacyClient, v2Client, authInfo),
retention: retention.NewClient(legacyClient, v2Client, authInfo),
quota: quota.NewClient(legacyClient, v2Client, authInfo),
}
}

Expand Down Expand Up @@ -317,3 +321,13 @@ func (c *RESTClient) UpdateRetentionPolicy(ctx context.Context, ret *model.Reten
func (c *RESTClient) DisableRetentionPolicy(ctx context.Context, ret *model.RetentionPolicy) error {
return c.retention.DisableRetentionPolicy(ctx, ret)
}

// GetQuotaByProjectID wraps the GetQuotaByProjectID method of the retention sub-package.
func (c *RESTClient) GetQuotaByProjectID(ctx context.Context, projectID int64) (*model.Quota, error) {
return c.quota.GetQuotaByProjectID(ctx, projectID)
}

// UpdateStorageQuotaByProjectID wraps the UpdateStorageQuotaByProjectID method of the retention sub-package.
func (c *RESTClient) UpdateStorageQuotaByProjectID(ctx context.Context, projectID int64, storageLimit int64) error {
return c.quota.UpdateStorageQuotaByProjectID(ctx, projectID, storageLimit)
}
4 changes: 3 additions & 1 deletion apiv2/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ type MetadataKey string
// CountLimit limits the number of repositories for this project.
// StorageLimit limits the allocatable space for this project.
func (c *RESTClient) NewProject(ctx context.Context, name string, storageLimit *int64) (*modelv2.Project, error) {

pReq := &modelv2.ProjectReq{
ProjectName: name,
StorageLimit: storageLimit,
Expand Down Expand Up @@ -201,6 +200,9 @@ func (c *RESTClient) ListProjects(ctx context.Context, nameFilter string) ([]*mo

// UpdateProject updates a project with the specified data.
// Returns an error if name/ID pair of p does not match a stored project.
// Note: Only positive values of storageLimit are supported through this method.
// If you want to set an infinite storageLimit (-1),
// please refer to the quota client's 'UpdateStorageQuotaByProjectID' method.
func (c *RESTClient) UpdateProject(ctx context.Context, p *modelv2.Project, storageLimit *int64) error {
project, err := c.GetProjectByName(ctx, p.Name)
if err != nil {
Expand Down
14 changes: 7 additions & 7 deletions apiv2/project/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ var (
exampleUser = "example-user"
exampleUserRoleID = int64(1)
exampleProject = &modelv2.Project{Name: "example-project", ProjectID: int32(exampleProjectID)}
exampleProject2 = &modelv2.Project{Name: "example-project-2", ProjectID: int32(exampleProjectID + 1)}
exampleProject3 = &modelv2.Project{Name: "example-project-3", ProjectID: int32(exampleProjectID)}
exampleProject2 = &modelv2.Project{Name: "example-project", ProjectID: int32(exampleProjectID + 1)}
exampleProject3 = &modelv2.Project{Name: "example-project", ProjectID: int32(exampleProjectID)}
usr = &model.User{Username: exampleUser}
sPtr = exampleStorageLimitPositive * 1024 * 1024
sPtr = exampleStorageLimitPositive
pReq = &modelv2.ProjectReq{
ProjectName: "example-project",
StorageLimit: &exampleStorageLimitPositive,
}
pReq2 = &modelv2.ProjectReq{
ProjectName: "example-project-2",
ProjectName: "example-project",
Metadata: &modelv2.ProjectMetadata{},
}
pReq3 = &modelv2.ProjectReq{
ProjectName: "example-project-3",
ProjectName: "example-project",
StorageLimit: &exampleStorageLimitNegative,
}
exampleMetadataKey = ProjectMetadataKeyEnableContentTrust
Expand Down Expand Up @@ -828,7 +828,7 @@ func TestRESTClient_UpdateProject(t *testing.T) {
}

updateProjectParams := &projectapi.UpdateProjectParams{
Project: pReq,
Project: pReq3,
ProjectID: exampleProjectID,
Context: ctx,
}
Expand All @@ -849,7 +849,7 @@ func TestRESTClient_UpdateProject(t *testing.T) {

assert.NoError(t, err)

err = cl.UpdateProject(ctx, project, &exampleStorageLimitPositive)
err = cl.UpdateProject(ctx, project, &exampleStorageLimitNegative)

assert.NoError(t, err)

Expand Down
69 changes: 69 additions & 0 deletions apiv2/quota/quota.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package quota

import (
"context"

"github.com/go-openapi/runtime"
v2client "github.com/mittwald/goharbor-client/v3/apiv2/internal/api/client"
"github.com/mittwald/goharbor-client/v3/apiv2/internal/legacyapi/client"
"github.com/mittwald/goharbor-client/v3/apiv2/internal/legacyapi/client/products"
legacymodel "github.com/mittwald/goharbor-client/v3/apiv2/model/legacy"
)

// RESTClient is a subclient for handling project related actions.
type RESTClient struct {
// The legacy swagger client
LegacyClient *client.Harbor

// 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(legacyClient *client.Harbor, v2Client *v2client.Harbor, authInfo runtime.ClientAuthInfoWriter) *RESTClient {
return &RESTClient{
LegacyClient: legacyClient,
V2Client: v2Client,
AuthInfo: authInfo,
}
}

type Client interface {
GetQuotaByProjectID(ctx context.Context, projectID int64) (*legacymodel.Quota, error)
UpdateStorageQuotaByProjectID(ctx context.Context, projectID int64, storageLimit int64) error
}

// GetQuotaByProjectID returns a quota object containing all configured quotas for a project.
func (c *RESTClient) GetQuotaByProjectID(ctx context.Context, projectID int64) (*legacymodel.Quota, error) {
quota, err := c.LegacyClient.Products.GetQuotasID(&products.GetQuotasIDParams{
ID: projectID,
Context: ctx,
}, c.AuthInfo)
if err != nil {
return nil, handleSwaggerQuotaErrors(err)
}

return quota.Payload, nil
}

// UpdateStorageQuotaByProjectID updates the storageLimit quota of a project.
func (c *RESTClient) UpdateStorageQuotaByProjectID(ctx context.Context, projectID int64, storageLimit int64) error {
params := &products.PutQuotasIDParams{
Hard: &legacymodel.QuotaUpdateReq{
Hard: map[string]int64{
"storage": storageLimit,
},
},
ID: projectID,
Context: ctx,
}

_, err := c.LegacyClient.Products.PutQuotasID(params, c.AuthInfo)
if err != nil {
return handleSwaggerQuotaErrors(err)
}

return nil
}
86 changes: 86 additions & 0 deletions apiv2/quota/quota_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package quota

import (
"net/http"

"github.com/go-openapi/runtime"
)

const (
// ErrQuotaIllegalIDFormatMsg is the error for message for ErrQuotaIllegalIDFormat errors.
ErrQuotaIllegalIDFormatMsg = "illegal format in quota update request"

// ErrQuotaUnauthorizedMsg is the error for message for ErrQuotaUnauthorized errors.
ErrQuotaUnauthorizedMsg = "unauthorized"

// ErrQuotaInternalServerErrorsMsg is the error message for ErrQuotaInternalServerErrors errors.
ErrQuotaInternalServerErrorsMsg = "unexpected internal errors"

// ErrQuotaNoPermissionMsg is the error message for ErrQuotaNoPermission errors.
ErrQuotaNoPermissionMsg = "user does not have permission to the quota"

// ErrQuotaUnknownResourceMsg is the errors message for ErrQuotaUnknownResource errors.
ErrQuotaUnknownResourceMsg = "quota does not exist"
)

// ErrQuotaIllegalIDFormat describes an error due to an illegal request format.
type ErrQuotaIllegalIDFormat struct{}

// Error returns the error message.
func (e *ErrQuotaIllegalIDFormat) Error() string {
return ErrQuotaIllegalIDFormatMsg
}

// ErrQuotaUnauthorized describes an unauthorized request.
type ErrQuotaUnauthorized struct{}

// Error returns the error message.
func (e *ErrQuotaUnauthorized) Error() string {
return ErrQuotaUnauthorizedMsg
}

// ErrQuotaNoPermission describes an error in the request due to the lack of permissions.
type ErrQuotaNoPermission struct{}

// Error returns the error message.
func (e *ErrQuotaNoPermission) Error() string {
return ErrQuotaNoPermissionMsg
}

// ErrQuotaUnknownResource describes an error when the specified quota could not be found.
type ErrQuotaUnknownResource struct{}

// Error returns the error message.
func (e *ErrQuotaUnknownResource) Error() string {
return ErrQuotaUnknownResourceMsg
}

// ErrQuotaInternalServerErrors describes miscellaneous internal server errors.
type ErrQuotaInternalServerErrors struct{}

// Error returns the error message.
func (e *ErrQuotaInternalServerErrors) Error() string {
return ErrQuotaInternalServerErrorsMsg
}

// handleSwaggerQuotaErrors takes a swagger generated error as input,
// which usually does not contain any form of error message,
// and outputs a new error with proper message.
func handleSwaggerQuotaErrors(in error) error {
t, ok := in.(*runtime.APIError)
if ok {
switch t.Code {
case http.StatusBadRequest:
return &ErrQuotaIllegalIDFormat{}
case http.StatusUnauthorized:
return &ErrQuotaUnauthorized{}
case http.StatusForbidden:
return &ErrQuotaNoPermission{}
case http.StatusNotFound:
return &ErrQuotaUnknownResource{}
case http.StatusInternalServerError:
return &ErrQuotaInternalServerErrors{}
}
}
return nil
}
62 changes: 62 additions & 0 deletions apiv2/quota/quota_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// +build integration

package quota

import (
"context"
"net/url"
"testing"

runtimeclient "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
v2client "github.com/mittwald/goharbor-client/v3/apiv2/internal/api/client"
"github.com/mittwald/goharbor-client/v3/apiv2/internal/legacyapi/client"
"github.com/mittwald/goharbor-client/v3/apiv2/project"
integrationtest "github.com/mittwald/goharbor-client/v3/apiv2/testing"
"github.com/stretchr/testify/require"
)

var (
u, _ = url.Parse(integrationtest.Host)
legacySwaggerClient = client.New(runtimeclient.New(u.Host, u.Path, []string{u.Scheme}), strfmt.Default)
v2SwaggerClient = v2client.New(runtimeclient.New(u.Host, u.Path, []string{u.Scheme}), strfmt.Default)
authInfo = runtimeclient.BasicAuth(integrationtest.User, integrationtest.Password)
storageLimitPositive int64 = 1
storageLimitNegative int64 = -1
testProjectName = "test-project"
)

func TestAPIGetQuotaByProjectID_PositiveQuota(t *testing.T) {
ctx := context.Background()
c := NewClient(legacySwaggerClient, v2SwaggerClient, authInfo)

pc := project.NewClient(legacySwaggerClient, v2SwaggerClient, authInfo)
p, err := pc.NewProject(ctx, testProjectName, &storageLimitPositive)
defer pc.DeleteProject(ctx, p)

project, err := pc.GetProjectByName(ctx, testProjectName)
require.NoError(t, err)

q, err := c.GetQuotaByProjectID(ctx, int64(project.ProjectID))
require.NoError(t, err)
require.NotNil(t, q)

require.Equal(t, q.Hard["storage"], storageLimitPositive)
}

func TestAPIGetQuotaByProjectID_NegativeQuota(t *testing.T) {
ctx := context.Background()
c := NewClient(legacySwaggerClient, v2SwaggerClient, authInfo)

pc := project.NewClient(legacySwaggerClient, v2SwaggerClient, authInfo)
p, err := pc.NewProject(ctx, testProjectName, &storageLimitNegative)
defer pc.DeleteProject(ctx, p)

project, err := pc.GetProjectByName(ctx, testProjectName)
require.NoError(t, err)

q, err := c.GetQuotaByProjectID(ctx, int64(project.ProjectID))
require.NoError(t, err)
require.NotNil(t, q)
require.Equal(t, q.Hard["storage"], storageLimitNegative)
}
Loading

0 comments on commit f38d9fa

Please sign in to comment.