Skip to content

Commit

Permalink
Merge pull request #27 from fluxcd/docs
Browse files Browse the repository at this point in the history
Add user documentation
  • Loading branch information
luxas authored Aug 18, 2020
2 parents e566b19 + 9658a1c commit 971412c
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 23 deletions.
186 changes: 185 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,188 @@
[![Release](https://img.shields.io/github/v/release/fluxcd/go-git-providers?include_prereleases)](https://github.com/fluxcd/go-git-providers/releases/latest)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/fluxcd/go-git-providers/blob/master/CONTRIBUTING.md)

The go-git-providers is a general-purpose Go client for interacting with Git providers APIs (e.g. GitHub, GitLab, Bitbucket).
[go-git-providers](https://pkg.go.dev/github.com/fluxcd/go-git-providers) is a general-purpose Go client for interacting with Git providers' APIs (e.g. GitHub, GitLab, Bitbucket).

## Features

- **Consistency:** Using the same Client interface and high-level structs for multiple backends.
- **Authentication:** Personal Access Tokens, OAuth2 Tokens, and unauthenticated.
- **Pagination:** List calls automatically return all available pages.
- **Conditional Requests:** Asks the Git provider if cached data is up-to-date before requesting, to avoid being rate limited.
- **Reconciling:** Support reconciling desired state towards actual state and drift detection.
- **Low-level access:** Access the underlying, provider-specific data easily, if needed, and support applying it to the server.
- **Wrapped errors:** Data-rich, Go 1.14-errors are consistent across provider, including cases like rate limit, validation, not found, etc.
- **Go modules:** The major version is bumped if breaking changes, or major library upgrades are made.
- **Validation-first:** Both server and user data is validated prior to manipulation.
- **URL Parsing:** HTTPS user, organization and repository URLs can be parsed into machine-readable structs.
- **Enums:** Consistent enums are used across providers for similar lists of values.
- **Domain customization:** The user can specify their desired domain for the Git provider backend.
- **Context-first:** `context.Context` is the first parameter for every API call.

## Operations and Design

The top-level `gitprovider.Client` has the following sub-clients with their described capabilities:

- `OrganizationsClient` operates on organizations the user has access to.
- `Get` a specific organization the user has access to.
- `List` all top-level organizations the specific user has access to.
- `Children` returns the immediate child-organizations for the specific OrganizationRef.

- `{Org,User}RepositoriesClient` operates on repositories for organizations and users, respectively.
- `Get` returns the repository for the given reference.
- `List` all repositories in the given organization or user account.
- `Create` creates a repository, with the specified data and options.
- `Reconcile` makes sure the given desired state becomes the actual state in the backing Git provider.

The sub-clients above return `gitprovider.Organization` or `gitprovider.{Org,User}Repository` interfaces.
These object interfaces lets you access their data (through their `.Get()` function), internal,
provider-specific representation (through their `.APIObject()` function), or sub-resources like deploy keys
and teams.

The following object-scoped clients are available:

- `Organization` represents an organization in a Git provider.
- `Teams` gives access to the `TeamsClient` for this specific organization.
- `Get` a team within the specific organization.
- `List` all teams within the specific organization.

- `UserRepository` describes a repository owned by an user.
- `DeployKeys` gives access to manipulating deploy keys, using this `DeployKeyClient`.
- `Get` a DeployKey by its name.
- `List` all deploy keys for the given repository.
- `Create` a deploy key with the given specifications.
- `Reconcile` makes sure the given desired state becomes the actual state in the backing Git provider.

- `OrgRepository` is a superset of `UserRepository`, and describes a repository owned by an organization.
- `DeployKeys` as in `UserRepository`.
- `TeamAccess` returns a `TeamsAccessClient` for operating on teams' access to this specific repository.
- `Get` a team's permission level of this given repository.
- `List` the team access control list for this repository.
- `Create` adds a given team to the repository's team access control list.
- `Reconcile` makes sure the given desired state (req) becomes the actual state in the backing Git provider.

Wait, how do I `Delete` or `Update` an object?

That's done on the returned objects themselves, using the following `Updatable`, `Reconcilable` and `Deletable`
interfaces implemented by `{Org,User}Repository`, `DeployKey` and `TeamAccess`:

```go
// Updatable is an interface which all objects that can be updated
// using the Client implement.
type Updatable interface {
// Update will apply the desired state in this object to the server.
// Only set fields will be respected (i.e. PATCH behaviour).
// In order to apply changes to this object, use the .Set({Resource}Info) error
// function, or cast .APIObject() to a pointer to the provider-specific type
// and set custom fields there.
//
// ErrNotFound is returned if the resource does not exist.
//
// The internal API object will be overridden with the received server data.
Update(ctx context.Context) error
}

// Deletable is an interface which all objects that can be deleted
// using the Client implement.
type Deletable interface {
// Delete deletes the current resource irreversibly.
//
// ErrNotFound is returned if the resource doesn't exist anymore.
Delete(ctx context.Context) error
}

// Reconcilable is an interface which all objects that can be reconciled
// using the Client implement.
type Reconcilable interface {
// Reconcile makes sure the desired state in this object (called "req" here) becomes
// the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be updated (actionTaken == true).
// If req is already the actual state, this is a no-op (actionTaken == false).
//
// The internal API object will be overridden with the received server data if actionTaken == true.
Reconcile(ctx context.Context) (actionTaken bool, err error)
}
```

In order to access the provider-specific, internal object, all resources implement the `gitprovider.Object` interface:

```go
// Object is the interface all types should implement.
type Object interface {
// APIObject returns the underlying value that was returned from the server.
// This is always a pointer to a struct.
APIObject() interface{}
}
```

So, how do I set the desired state for an object before running `Update` or `Reconcile`?

Using the `Get() {Resource}Info` or `Set({Resource}Info) error` methods. An example as follows, for `TeamAccess`:

```go
// TeamAccess describes a binding between a repository and a team.
type TeamAccess interface {
// TeamAccess implements the Object interface,
// allowing access to the underlying object returned from the API.
Object
// The deploy key can be updated.
Updatable
// The deploy key can be reconciled.
Reconcilable
// The deploy key can be deleted.
Deletable
// RepositoryBound returns repository reference details.
RepositoryBound

// Get returns high-level information about this team access for the repository.
Get() TeamAccessInfo
// Set sets high-level desired state for this team access object. In order to apply these changes in
// the Git provider, run .Update() or .Reconcile().
Set(TeamAccessInfo) error
}

// TeamAccessInfo contains high-level information about a team's access to a repository.
type TeamAccessInfo struct {
// 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: pull.
// Available options: See the RepositoryPermission enum.
// +optional
Permission *RepositoryPermission `json:"permission,omitempty"`
}
```

## Examples

See the following (automatically tested) examples:

- [github/example_organization_test.go](github/example_organization_test.go)
- [github/example_repository_test.go](github/example_repository_test.go)

## Maintainers

- Stefan Prodan, [@stefanprodan](https://github.com/stefanprodan)
- Lucas Käldström, [@luxas](https://github.com/luxas)

## Getting Help

If you have any questions about this library:

- Read [the pkg.go.dev reference](https://pkg.go.dev/github.com/fluxcd/go-git-providers).
- Invite yourself to the <a href="https://slack.cncf.io" target="_blank">CNCF community</a>
slack and ask a question on the [#flux](https://cloud-native.slack.com/messages/flux/)
channel.
- To be part of the conversation about Flux's development, join the
[flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev).
- [File an issue.](https://github.com/fluxcd/go-git-providers/issues/new)

Your feedback is always welcome!

## License

[Apache 2.0](LICENSE)
8 changes: 4 additions & 4 deletions github/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import (
)

const (
// defaultDomain specifies the default domain used as the backend.
defaultDomain = "github.com"
// DefaultDomain specifies the default domain used as the backend.
DefaultDomain = "github.com"
// patUsername is the "username" for the basic auth authentication flow
// when using a personal access token as the "password". This string could
// be arbitrary, even unset, as it is not respected server-side. For conventions'
Expand Down Expand Up @@ -258,9 +258,9 @@ func NewClient(ctx context.Context, optFns ...ClientOption) (gitprovider.Client,
var gh *github.Client
var domain string

if opts.Domain == nil || *opts.Domain == defaultDomain {
if opts.Domain == nil || *opts.Domain == DefaultDomain {
// No domain or the default github.com used
domain = defaultDomain
domain = DefaultDomain
gh = github.NewClient(httpClient)
} else {
// GitHub Enterprise is used
Expand Down
2 changes: 1 addition & 1 deletion github/client_repositories_org.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (c *OrgRepositoriesClient) Create(ctx context.Context, ref gitprovider.OrgR
return newOrgRepository(c.clientContext, apiObj, ref), nil
}

// Reconcile makes sure req is the actual state in the backing Git provider.
// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be updated (actionTaken == true).
Expand Down
2 changes: 1 addition & 1 deletion github/client_repositories_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *UserRepositoriesClient) Create(ctx context.Context,
return newUserRepository(c.clientContext, apiObj, ref), nil
}

// Reconcile makes sure req is the actual state in the backing Git provider.
// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be updated (actionTaken == true).
Expand Down
2 changes: 1 addition & 1 deletion github/client_repository_deploykey.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (c *DeployKeyClient) Create(ctx context.Context, req gitprovider.DeployKeyI
return newDeployKey(c, apiObj)
}

// Reconcile makes sure req is the actual state in the backing Git provider.
// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be deleted and recreated (actionTaken == true).
Expand Down
2 changes: 1 addition & 1 deletion github/client_repository_teamaccess.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (c *TeamAccessClient) Create(ctx context.Context, req gitprovider.TeamAcces
return newTeamAccess(c, req), nil
}

// Reconcile makes sure req is the actual state in the backing Git provider.
// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be deleted and recreated (actionTaken == true).
Expand Down
40 changes: 40 additions & 0 deletions github/example_organization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package github_test

import (
"context"
"fmt"
"log"

"github.com/fluxcd/go-git-providers/github"
"github.com/fluxcd/go-git-providers/gitprovider"
gogithub "github.com/google/go-github/v32/github"
)

// checkErr is used for examples in this repository.
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}

func ExampleOrganizationsClient_Get() {
// Create a new client
ctx := context.Background()
c, err := github.NewClient(ctx)
checkErr(err)

// Get public information about the fluxcd organization
org, err := c.Organizations().Get(ctx, gitprovider.OrganizationRef{
Domain: github.DefaultDomain,
Organization: "fluxcd",
})
checkErr(err)

// Use .Get() to aquire a high-level gitprovider.OrganizationInfo struct
orgInfo := org.Get()
// Cast the internal object to a *gogithub.Organization to access custom data
internalOrg := org.APIObject().(*gogithub.Organization)

fmt.Printf("Name: %s. Location: %s.", *orgInfo.Name, internalOrg.GetLocation())
// Output: Name: Flux project. Location: CNCF sandbox.
}
33 changes: 33 additions & 0 deletions github/example_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package github_test

import (
"context"
"fmt"

"github.com/fluxcd/go-git-providers/github"
"github.com/fluxcd/go-git-providers/gitprovider"
gogithub "github.com/google/go-github/v32/github"
)

func ExampleOrgRepositoriesClient_Get() {
// Create a new client
ctx := context.Background()
c, err := github.NewClient(ctx)
checkErr(err)

// Parse the URL into an OrgRepositoryRef
ref, err := gitprovider.ParseOrgRepositoryURL("https://github.com/fluxcd/flux")
checkErr(err)

// Get public information about the flux repository.
repo, err := c.OrgRepositories().Get(ctx, *ref)
checkErr(err)

// Use .Get() to aquire a high-level gitprovider.OrganizationInfo struct
repoInfo := repo.Get()
// Cast the internal object to a *gogithub.Repository to access custom data
internalRepo := repo.APIObject().(*gogithub.Repository)

fmt.Printf("Description: %s. Homepage: %s", *repoInfo.Description, internalRepo.GetHomepage())
// Output: Description: The GitOps Kubernetes operator. Homepage: https://docs.fluxcd.io
}
2 changes: 1 addition & 1 deletion github/resource_teamaccess.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (ta *teamAccess) Update(ctx context.Context) error {
return ta.Set(resp.Get())
}

// Reconcile makes sure req is the actual state in the backing Git provider.
// Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.
//
// If req doesn't exist under the hood, it is created (actionTaken == true).
// If req doesn't equal the actual state, the resource will be deleted and recreated (actionTaken == true).
Expand Down
Loading

0 comments on commit 971412c

Please sign in to comment.