Skip to content

Latest commit

 

History

History
315 lines (247 loc) · 10.5 KB

CONTRIBUTING.md

File metadata and controls

315 lines (247 loc) · 10.5 KB

Contribution guide

Table of contents

Repository structure

.
├── apiv1
│  ├── client.go //  The main file of the package containing the exposed 'Client' interface
│  ├── internal
│  │  └── api
│  │      └── client
│  │          ├── harbor_client.go
│  │          └── products // Contains the 'Products' client which wraps all v1 client functions
│  │              └── products_client.go // contains the ClientService interface used to generate 'client_service.go'
│  ├── mocks
│  │  └── client_service.go
│  ├── model // v1 API definitions
│  ├── project // Project client (as an example)
│  │  ├── project_errors.go
│  │  ├── project.go
│  │  ├── project_integration_test.go
│  │  └── project_test.go
│  └── [...] // Other clients
│  ├── scripts -> ../scripts // Symbolic link to code generation scripts
│  ├── testdata -> ../testdata // Symbolic link to integration test configuration
│  └── testing // Testing definitions
│
├── apiv2
│  ├── client.go // The main file of the package containing the exposed 'Client' interface
│  ├── internal
│  │  └── api
│  │     └── client
│  │         ├── harbor_client.go // Contains the 'Harbor' struct which wraps all of the below clients
│  │         ├── auditlog // 'Auditlog' 'v2' API client wrapping all auditlog functions (as an example)
│  │         └── [...] // Other 'v2' API client functions
│  ├── mocks // Contains mock clients of the above clients in './apiv2/internal/api/client'
│  │  └── [...]
│  ├── model // v2 API definitions
│  │  └── [...]
│  ├── pkg
│  │  ├── clients // Contains the API clients implemented by the 'Client' interface in './apiv2/client.go'
│  │  │  ├── auditlog // 'Auditlog' client (as an example)
│  │  │  │  ├── auditlog_errors.go
│  │  │  │  ├── auditlog.go
│  │  │  │  ├── auditlog_integration_test.go
│  │  │  │  └── auditlog_test.go
│  │  │  └── [...] // Other clients
│  │  ├── common // Types used throughout this package
│  │  │  └── [...]
│  │  ├── config // goharbor-client configuration, e.g. for specifying the 'PageSize' when operating on API endpoints using pagination
│  │  │  └── options.go
│  │  ├── errors // Wrapped errors used throughout this package
│  │  │  └── [...]
│  │  ├── testing // Testing definitions
│  │  └── util
│  │      └── project.go
│  ├── scripts -> ../scripts // Symbolic link to code generation scripts
│  └── testdata -> ../testdata // Symbolic link to integration test configuration
│
├── go.mod
├── go.sum
├── Makefile
├── scripts
│  ├── gen-mock.sh // 'mockery' code generation (used by 'make generate')
│  ├── setup-harbor.sh // Local harbor bootstrapping (requires helm, used by 'make setup-harbor-v1', 'make setup-harbor-v2')
│  └── swagger-gen.sh // 'go-swagger' code generation (used by 'make generate')
└── testdata
    └── kind-config.yaml

Implementing sub-clients

This section helps you to get started when adding / updating a new sub-client.


A new sub-client can be added by creating the corresponding package in either the apiv1 or apiv2/pkg/clients directory.

For example, let's look at the existing user package of the v2 client:

.
└── apiv2
   └── pkg
      └── clients
         └── user
             ├── user_errors.go
             ├── user.go
             ├── user_integration_test.go
             └── user_test.go

To maintain integrity with the rest of the repository, new sub-client's .go-files should be prefixed with their package name.

user.go holds the methods that act operations on user objects on the goharbor API. The below examples are not guaranteed to be up-to-date.

It contains a RESTClient struct that groups together the v2 Goharbor client, client Options as well as a field AuthInfo for openAPI's runtime.ClientAuthInfoWriter. The latter is used to authenticate requests to the goharbor API:

package user

import (
	"context"
	"errors"

	"k8s.io/apimachinery/pkg/util/intstr"

	v2client "github.com/mittwald/goharbor-client/v5/apiv2/internal/api/client"
	"github.com/mittwald/goharbor-client/v5/apiv2/internal/api/client/user"
	modelv2 "github.com/mittwald/goharbor-client/v5/apiv2/model"
	"github.com/mittwald/goharbor-client/v5/apiv2/pkg/config"
	clienterrors "github.com/mittwald/goharbor-client/v5/apiv2/pkg/errors"

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

// RESTClient is a subclient for handling user related actions.
type RESTClient struct {
	// The new client of the harbor v2 API
	V2Client *v2client.Harbor

	// Options contains optional configuration when making API calls.
	Options *config.Options

	// AuthInfo contains the auth information that is provided on API calls.
	AuthInfo runtime.ClientAuthInfoWriter
}

A NewClient function constructs and returns the user.RESTClient:

func NewClient(v2Client *v2client.Harbor, opts *config.Options, authInfo runtime.ClientAuthInfoWriter) *RESTClient {
	return &RESTClient{
		Options:  opts,
		V2Client: v2Client,
		AuthInfo: authInfo,
	}
}

The package also contains a Client interface that holds all method signatures:

type Client interface {
	[...]
}

Since there are no methods in this example setup yet, let's implement a method called GetUserByName that returns a *modelv2.UserResp object:

// GetUserByName returns an existing user identified by name.
func (c *RESTClient) GetUserByName(ctx context.Context, username string) (*modelv2.UserResp, error) {
	if username == "" {
		return nil, errors.New("no username provided")
	}

	c.Options.PageSize = 100

	resp, err := c.ListUsers(ctx)
	if err != nil {
		return nil, handleSwaggerUserErrors(err)
	}

	for _, u := range resp {
		if u.Username == username {
			return u, nil
		}
	}

	return nil, &clienterrors.ErrUserNotFound{}
}

The Client interface should now change to include the method signature of GetUser:

type Client interface {
	GetUserByName(ctx context.Context, username string) (*modelv2.UserResp, error)
	[...]
}

To make the sub-client's methods accessible via the github.com/mittwald/goharbor-client/apiv2 package, the above implementation has to be wrapped together in apiv2/client.go:

// RESTClient implements the Client interface as a REST client
type RESTClient struct {
	user        *user.RESTClient
    [...]
}

// NewRESTClient constructs a new REST client containing each sub client.
func NewRESTClient(legacyClient *client.Harbor, v2Client *v2client.Harbor, authInfo runtime.ClientAuthInfoWriter) *RESTClient {
	return &RESTClient{
		user:        user.NewClient(legacyClient, v2Client, authInfo),
        [...]
	}
}

type Client interface {
    user.Client
    [...]
}

// User Client

// GetUserByName wraps the GetUserByName method of the user sub-package.
func (c *RESTClient) GetUserByName(ctx context.Context, username string) (*modelv2.UserResp, error) {
    return c.user.GetUserByName(ctx, username string)
}

[...]

Testing

Unit Tests

Unit tests are marked via the build tag !integration.

For running unit tests against the goharbor-client package, the following command can be used:

make test

Integration Tests

Integration tests are marked via the build tag integration.

The scripts contained in this repository can be used to set up an environment using kind.

For the Harbor registry component to work properly, an entry in the /etc/hosts file is required:

127.0.0.1 core.harbor.domain

For running integration tests against the goharbor-client package, the following commands can be used:

# Set up the Harbor helm chart locally (using kind).
# Note: The setup/test run will only be executed once.
make integration-test-v[1|2]-ci

The above is intended to be used in a CI environment (such as Github Actions).

Executing the above is equivalent to running the following:

make setup-harbor-v[1|2]
make integration-test-v[1|2]

The remaining cluster can be deleted using the following command:

make uninstall-harbor-v[1|2]

Code generation

Client APIs

goharbor-client's API types are generated using the goharbor swagger API specifications.

The API versions used for generation are specified in the repository's Makefile:

[...]
V1_VERSION = v1.x.x
V2_VERSION = v2.x.x

Please note, that versions have to match a valid goharbor release version. For reference, see: harbor/releases.

When changing the above API versions, make sure to always run code generation:

make generate

The above versions are then passed on to generator scripts that will fetch the corresponding .yaml API specification.

go-swagger is then utilized to generate the client API types found under ./apiv*/internal.

API mocks

goharbor-client's unit tests rely on a mocked goharbor API to interact with. The stretchr/testify assert & mock packages are used throughout all unit tests.

API Mocks for the above types are automatically generated by make generate.

After generating new API types, vektra/mockery is used to generate mocks based on the former.

The resulting mock files can be found under ./apiv*/mocks.


See also: