Add Reconcile methods and TeamAccess struct. Mention ErrNotFound and ErrAlreadyExists. Add List() to TeamAccess. Rename GetTitle() to GetName()

pull/88/head
Lucas Käldström 5 years ago
parent e8096dec88
commit b1993d2fb7
No known key found for this signature in database
GPG Key ID: 3FA3783D77751514

@ -19,9 +19,10 @@ client for multiple providers and domains.
- For a given **Organization**: - For a given **Organization**:
- **Teams**: `GET` and `LIST` - **Teams**: `GET` and `LIST`
- **Repositories**: `GET`, `LIST` and `POST` - **Repositories**: `GET`, `LIST` and `POST`
- **Teams**: `ADD` and `REMOVE` - **Team Access**: `LIST`, `POST` and `DELETE`
- **Credentials**: `GET`, `LIST` and `POST` - **Credentials**: `LIST`, `POST` and `DELETE`
- Support sub-organizations (or "sub-groups" in GitLab) if possible - Support sub-organizations (or "sub-groups" in GitLab) if possible
- Support reconciling an object for idempotent operations
- Pagination is automatically handled for `LIST` requests - Pagination is automatically handled for `LIST` requests
- Transparently can manage teams (collections of users, sub-groups in Gitlab) with varying access to repos - Transparently can manage teams (collections of users, sub-groups in Gitlab) with varying access to repos
- Follow library best practices in order to be easy to vendor (e.g. use major `vX` versioning & go.mod) - Follow library best practices in order to be easy to vendor (e.g. use major `vX` versioning & go.mod)
@ -43,7 +44,6 @@ client for multiple providers and domains.
- It should be possible to create a fake API client for testing, implementing the same interfaces - It should be possible to create a fake API client for testing, implementing the same interfaces
- All type structs shall have a `Validate()` method, and optionally a `Default()` one - All type structs shall have a `Validate()` method, and optionally a `Default()` one
- All type structs shall expose their internal representation (from the underlying library) through the `InternalGetter` interface with a method `GetInternal() interface{}` - All type structs shall expose their internal representation (from the underlying library) through the `InternalGetter` interface with a method `GetInternal() interface{}`
- By default, mutating methods (e.g. `Create()`) shall be idempotent, unless opted out in `*CreateOptions` field.
- Typed errors shall be returned, wrapped using Go 1.14's new features - Typed errors shall be returned, wrapped using Go 1.14's new features
- Go-style enums are used when there are only a few supported values for a field - Go-style enums are used when there are only a few supported values for a field
- Every field is documented using Godoc comment, including `+required` or `+optional` to clearly signify its importance - Every field is documented using Godoc comment, including `+required` or `+optional` to clearly signify its importance
@ -194,6 +194,7 @@ The `OrganizationsClient` provides access to a set of organizations, as follows:
type OrganizationsClient interface { type OrganizationsClient interface {
// Get a specific organization the user has access to // Get a specific organization the user has access to
// This might also refer to a sub-organization // This might also refer to a sub-organization
// ErrNotFound is returned if the resource does not exist
Get(ctx context.Context, o OrganizationRef) (*Organization, error) Get(ctx context.Context, o OrganizationRef) (*Organization, error)
// List all top-level organizations the specific user has access to // List all top-level organizations the specific user has access to
@ -219,6 +220,7 @@ type Organization struct {
// (Domain, Organization and SubOrganizations) required for being an OrganizationRef // (Domain, Organization and SubOrganizations) required for being an OrganizationRef
OrganizationInfo `json:",inline"` OrganizationInfo `json:",inline"`
// InternalHolder implements the InternalGetter interface // InternalHolder implements the InternalGetter interface
// +optional
InternalHolder `json:",inline"` InternalHolder `json:",inline"`
// Name is the human-friendly name of this organization, e.g. "Weaveworks" or "Kubernetes SIGs" // Name is the human-friendly name of this organization, e.g. "Weaveworks" or "Kubernetes SIGs"
@ -273,6 +275,7 @@ type OrganizationTeamsClient interface {
// Get a team within the specific organization // Get a team within the specific organization
// teamName may include slashes, to point to e.g. "sub-teams" i.e. subgroups in Gitlab // teamName may include slashes, to point to e.g. "sub-teams" i.e. subgroups in Gitlab
// teamName must not be an empty string // teamName must not be an empty string
// ErrNotFound is returned if the resource does not exist
Get(ctx context.Context, teamName string) (*Team, error) Get(ctx context.Context, teamName string) (*Team, error)
// List all teams (recursively, in terms of subgroups) within the specific organization // List all teams (recursively, in terms of subgroups) within the specific organization
@ -288,9 +291,10 @@ The `Team` struct is defined as follows:
```go ```go
// Team is a representation for a team of users inside of an organization // Team is a representation for a team of users inside of an organization
type Team struct { type Team struct {
// Team embeds OrganizationInfo which makes it automatically // Team embeds OrganizationInfo which makes it automatically comply with OrganizationRef
OrganizationRef `json:",inline"` OrganizationInfo `json:",inline"`
// Team embeds InternalHolder for accessing the underlying object // Team embeds InternalHolder for accessing the underlying object
// +optional
InternalHolder `json:",inline"` InternalHolder `json:",inline"`
// Name describes the name of the team. The team name may contain slashes // Name describes the name of the team. The team name may contain slashes
@ -314,6 +318,7 @@ to access a given repository.
// RepositoriesClient operates on repositories the user has access to // RepositoriesClient operates on repositories the user has access to
type RepositoriesClient interface { type RepositoriesClient interface {
// Get returns the repository at the given path // Get returns the repository at the given path
// ErrNotFound is returned if the resource does not exist
Get(ctx context.Context, r RepositoryRef) (*Repository, error) Get(ctx context.Context, r RepositoryRef) (*Repository, error)
// List all repositories in the given organization // List all repositories in the given organization
@ -321,7 +326,13 @@ type RepositoriesClient interface {
List(ctx context.Context, o OrganizationRef) ([]Repository, error) List(ctx context.Context, o OrganizationRef) ([]Repository, error)
// Create creates a repository at the given organization path, with the given URL-encoded name and options // Create creates a repository at the given organization path, with the given URL-encoded name and options
// ErrAlreadyExists will be returned if the resource already exists
Create(ctx context.Context, r *Repository, opts RepositoryCreateOptions) (*Repository, error) Create(ctx context.Context, r *Repository, opts RepositoryCreateOptions) (*Repository, error)
// Reconcile makes sure r is the actual state in the backing Git provider. If r doesn't exist
// under the hood, it is created. If r is already the actual state, this is a no-op. If r isn't
// the actual state, the resource will either be updated or deleted/recreated.
Reconcile(ctx context.Context, r *Repository) error
} }
``` ```
@ -334,10 +345,11 @@ The `Repository` struct is defined as follows:
// Repository represents a Git repository provided by a Git provider // Repository represents a Git repository provided by a Git provider
type Repository struct { type Repository struct {
// RepositoryInfo provides the required fields // RepositoryInfo provides the required fields
// (Domain, Organization, SubOrganizations and (Repository)Name) // (Domain, Organization, SubOrganizations and RepositoryName)
// required for being an RepositoryRef // required for being an RepositoryRef
RepositoryInfo `json:",inline"` RepositoryInfo `json:",inline"`
// InternalHolder implements the InternalGetter interface // InternalHolder implements the InternalGetter interface
// +optional
InternalHolder `json:",inline"` InternalHolder `json:",inline"`
// Description returns a description for the repository // Description returns a description for the repository
@ -383,15 +395,51 @@ type RepositoryClient interface {
```go ```go
// RepositoryTeamAccessClient operates on the teams list for a specific repository // RepositoryTeamAccessClient operates on the teams list for a specific repository
type RepositoryTeamAccessClient interface { type RepositoryTeamAccessClient interface {
// Add adds a given team in the repo's (top-level) organization to the repository // Create adds a given team to the repo's team access control list
Add(ctx context.Context, teamName string, opts RepositoryAddTeamOptions) error // ErrAlreadyExists will be returned if the resource already exists
// The embedded RepositoryInfo of ta does not need to be populated, but if it is,
// Remove removes the given team in the repo's (top-level) organization from the repository // it must equal to the RepositoryRef given to the RepositoryClient.
Remove(ctx context.Context, teamName string) error Create(ctx context.Context, ta *TeamAccess, opts RepositoryAddTeamOptions) error
// Lists the team access control list for this repo
List(ctx context.Context) ([]TeamAccess, error)
// Reconcile makes sure ta is the actual state in the backing Git provider. If ta doesn't exist
// under the hood, it is created. If ta is already the actual state, this is a no-op. If ta isn't
// the actual state, the resource will either be updated or deleted/recreated.
// The embedded RepositoryInfo of ta does not need to be populated, but if it is,
// it must equal to the RepositoryRef given to the RepositoryClient.
Reconcile(ctx context.Context, ta *TeamAccess) error
// Delete removes the given team from the repo's team access control list
// ErrNotFound is returned if the resource does not exist
Delete(ctx context.Context, teamName string) error
} }
``` ```
`RepositoryAddTeamOptions` has a `Permission *TeamRepositoryPermission` which allows specifying what access level the team should have. The `TeamAccess` struct looks as follows:
```go
// TeamAccess describes a binding between a repository and a team
type TeamAccess struct {
// TeamAccess embeds RepositoryInfo which makes it automatically comply with RepositoryRef
// +optional
RepositoryInfo `json:",inline"`
// TeamAccess embeds InternalHolder for accessing the underlying object
// +optional
InternalHolder `json:",inline"`
// Name describes the name of the team. The team name may contain slashes
// +required
Name string `json:"name"`
// Permission describes the permission level for which the team is allowed to operate
// Default: read
// Available options: See the TeamRepositoryPermission enum
// +optional
Permission *TeamRepositoryPermission
}
```
#### Repository Credentials #### Repository Credentials
@ -401,26 +449,33 @@ type RepositoryTeamAccessClient interface {
// RepositoryCredentialsClient operates on the access credential list for a specific repository // RepositoryCredentialsClient operates on the access credential list for a specific repository
type RepositoryCredentialsClient interface { type RepositoryCredentialsClient interface {
// Create a credential with the given human-readable name, the given bytes and optional options // Create a credential with the given human-readable name, the given bytes and optional options
// ErrAlreadyExists will be returned if the resource already exists
Create(ctx context.Context, c RepositoryCredential, opts CredentialCreateOptions) error Create(ctx context.Context, c RepositoryCredential, opts CredentialCreateOptions) error
// Lists all credentials // Lists all credentials for the given credential type
List(ctx context.Context) ([]RepositoryCredential, error) List(ctx context.Context, t RepositoryCredentialType) ([]RepositoryCredential, error)
// Deletes a credential from the repo. keyName is the human-friendly title of the credential // Reconcile makes sure c is the actual state in the backing Git provider. If c doesn't exist
Delete(ctx context.Context, keyName string) error // under the hood, it is created. If c is already the actual state, this is a no-op. If c isn't
// the actual state, the resource will either be updated or deleted/recreated.
Reconcile(ctx context.Context, c RepositoryCredential) error
// Deletes a credential from the repo. name corresponds to GetName() of the credential
// ErrNotFound is returned if the resource does not exist
Delete(ctx context.Context, t RepositoryCredentialType, name string) error
} }
``` ```
In order to support multiple different types of credentials, `RepositoryCredential` is an interface: In order to support multiple different types of credentials, `RepositoryCredential` is an interface:
```go ```go
// RepositoryCredential is a credential that allows // RepositoryCredential is a credential that allows access (either read-only or read-write) to the repo
type RepositoryCredential interface { type RepositoryCredential interface {
// GetType returns the type of the credential // GetType returns the type of the credential
GetType() RepositoryCredentialType GetType() RepositoryCredentialType
// Title returns a description of the credential // GetName returns a name (or title/description) of the credential
GetTitle() string GetName() string
// GetData returns the key that will be authorized to access the repo, this can e.g. be a SSH public key // GetData returns the key that will be authorized to access the repo, this can e.g. be a SSH public key
GetData() []byte GetData() []byte
@ -436,6 +491,7 @@ The default implementation of `RepositoryCredential` is `DeployKey`:
// DeployKey represents a short-lived credential (e.g. an SSH public key) used for accessing a repository // DeployKey represents a short-lived credential (e.g. an SSH public key) used for accessing a repository
type DeployKey struct { type DeployKey struct {
// DeployKey embeds InternalHolder for accessing the underlying object // DeployKey embeds InternalHolder for accessing the underlying object
// +optional
InternalHolder `json:",inline"` InternalHolder `json:",inline"`
// Title is the human-friendly interpretation of what the key is for (and does) // Title is the human-friendly interpretation of what the key is for (and does)

Loading…
Cancel
Save