Skip to content

Commit

Permalink
Several improvements re GHA integrations (#4182)
Browse files Browse the repository at this point in the history
- Adds new subcommands: `ls` and `remove`
- Renames subcommand category from `github` to `gha`, since it is GitHub
Actions related
- Removes the hidden attribute
- Updates the documentation accordingly

---------

Co-authored-by: Alex Couture-Beil <[email protected]>
  • Loading branch information
idelvall and alexcb authored May 31, 2024
1 parent 1e0e0ef commit 60b336d
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 25 deletions.
36 changes: 33 additions & 3 deletions cloud/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,49 @@ import (
pb "github.com/earthly/cloud-api/compute"
)

func (c *Client) SetGithubToken(ctx context.Context, orgName string, ghOrg string, ghRepo string, token string) error {
func (c *Client) CreateGHAIntegration(ctx context.Context, orgName string, ghOrg string, ghRepo string, token string) error {
orgID, err := c.GetOrgID(ctx, orgName)
if err != nil {
return fmt.Errorf("failed getting org ID: %w", err)
}
_, err = c.compute.SetGithubToken(c.withAuth(ctx), &pb.SetGithubTokenRequest{
_, err = c.compute.CreateGHAIntegration(c.withAuth(ctx), &pb.CreateGHAIntegrationRequest{
OrgId: orgID,
GithubOrgName: ghOrg,
GithubRepoName: ghRepo,
GithubToken: token,
})
if err != nil {
return fmt.Errorf("failed setting GitHub token: %w", err)
return fmt.Errorf("failed to create GitHub integration: %w", err)
}
return nil
}

func (c *Client) RemoveGHAIntegration(ctx context.Context, orgName string, ghOrg string, ghRepo string) error {
orgID, err := c.GetOrgID(ctx, orgName)
if err != nil {
return fmt.Errorf("failed getting org ID: %w", err)
}
_, err = c.compute.RemoveGHAIntegration(c.withAuth(ctx), &pb.RemoveGHAIntegrationRequest{
OrgId: orgID,
GithubOrgName: ghOrg,
GithubRepoName: ghRepo,
})
if err != nil {
return fmt.Errorf("failed remove GitHub integration: %w", err)
}
return nil
}

func (c *Client) ListGHAIntegrations(ctx context.Context, orgName string) (*pb.ListGHAIntegrationsResponse, error) {
orgID, err := c.GetOrgID(ctx, orgName)
if err != nil {
return nil, fmt.Errorf("failed getting org ID: %w", err)
}
integrations, err := c.compute.ListGHAIntegrations(c.withAuth(ctx), &pb.ListGHAIntegrationsRequest{
OrgId: orgID,
})
if err != nil {
return nil, fmt.Errorf("failed remove GitHub integration: %w", err)
}
return integrations, nil
}
172 changes: 158 additions & 14 deletions cmd/earthly/subcmd/github_cmds.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package subcmd

import (
"encoding/json"
"fmt"
"os"
"os/signal"
"syscall"
"text/tabwriter"
"time"

pb "github.com/earthly/cloud-api/compute"
"github.com/fatih/color"
"github.com/urfave/cli/v2"

"github.com/earthly/earthly/cmd/earthly/helper"
Expand All @@ -17,6 +23,15 @@ type Github struct {
GHOrg string
GHRepo string
GHToken string

printJSON bool
}

type integrationJSON struct {
GithubOrgName string `json:"githubOrg"`
GitHubRepoName string `json:"githubRepo"`
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
}

func NewGithub(cli CLI) *Github {
Expand All @@ -28,18 +43,16 @@ func NewGithub(cli CLI) *Github {
func (a *Github) Cmds() []*cli.Command {
return []*cli.Command{
{
Name: "github",
Usage: "*experimental* Manage GitHub integration",
Description: "*experimental* Manage GitHub integration.",
Hidden: true,
Name: "gha",
Usage: "*experimental* Manage GitHub Actions integrations",
Description: "*experimental* Manage GitHub Actions integrations. See https://docs.earthly.dev/earthly-cloud/satellites/gha-runners for detailed information.",
Subcommands: []*cli.Command{
{
Name: "add",
Usage: "Add GHA integration",
Description: `This command sets the configuration to create a new Github-Earthly integration, to trigger satellite builds from GHA (GitHub Actions).
From the Github side, integration can be done at two levels: organization-wide and per repository.
The provided token must have enough permissions to register webhooks and to create Github self hosted runners in those two scenarios.`,
UsageText: "earthly github add --org <earthly_org> --gh-org <github_org> [--gh-repo <github_repo>] --gh-token <github_token>",
Name: "add",
Usage: "Add a GHA integration",
Description: `Creates a new integration to trigger satellite builds directly from GitHub Actions without the need of an intermediate runner. See https://docs.earthly.dev/earthly-cloud/satellites/gha-runners for detailed information.`,

UsageText: "earthly gha add --org <earthly-org> --gh-org <github-org> [--gh-repo <github-repo>] --gh-token <github-token>",
Action: a.actionAdd,
Flags: []cli.Flag{
&cli.StringFlag{
Expand All @@ -55,7 +68,7 @@ The provided token must have enough permissions to register webhooks and to crea
},
&cli.StringFlag{
Name: "gh-repo",
Usage: "The name of the GitHub repository to set an integration with",
Usage: "The name of the GitHub repository to set an integration with (blank for an organization-wide integration)",
Destination: &a.GHRepo,
},
&cli.StringFlag{
Expand All @@ -65,19 +78,73 @@ The provided token must have enough permissions to register webhooks and to crea
},
},
},
{
Name: "remove",
Usage: "Remove a GHA integration",
Description: `Removes a GitHub-Earthly integration, to trigger satellite builds from GHA (GitHub Actions).
From the GitHub side, integration can be done at two levels: organization-wide and per repository.
The provided token must have enough permissions to register webhooks and to create GitHub self hosted runners in those two scenarios.`,
UsageText: "earthly gha remove --org <earthly_org> --gh-org <github_org> [--gh-repo <github_repo>]",
Action: a.actionRemove,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "org",
Usage: "The name of the Earthly organization to remove an integration from. Defaults to selected organization",
Destination: &a.Org,
},
&cli.StringFlag{
Name: "gh-org",
Usage: "The name of the GitHub organization of the integration",
Required: true,
Destination: &a.GHOrg,
},
&cli.StringFlag{
Name: "gh-repo",
Usage: "The name of the GitHub repository of the integration (blank if the integration is organization-wide)",
Destination: &a.GHRepo,
},
},
},
{
Name: "ls",
Usage: "List GHA integrations",
Description: `List the GitHub-Earthly integrations of a given organization.`,
UsageText: "earthly gha ls --org <earthly_org>",
Action: a.actionList,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "org",
Usage: "The name of the Earthly organization to set an integration with. Defaults to selected organization",
Destination: &a.Org,
},
&cli.BoolFlag{
Name: "json",
Usage: "Prints the output in JSON format",
Required: false,
Destination: &a.printJSON,
},
},
},
},
},
}
}

func (a *Github) actionAdd(cliCtx *cli.Context) error {
a.cli.SetCommandName("githubAdd")
func (a *Github) resolveOrg() error {
if a.Org == "" {
if a.cli.OrgName() == "" {
return fmt.Errorf("coudn't determine Earthly organization")
}
a.Org = a.cli.OrgName()
}
return nil
}

func (a *Github) actionAdd(cliCtx *cli.Context) error {
a.cli.SetCommandName("githubAdd")
if err := a.resolveOrg(); err != nil {
return err
}
if a.GHToken == "" {
// Our signal handling under main() doesn't cause reading from stdin to cancel
// as there's no way to pass app.ctx to stdin read calls.
Expand All @@ -92,14 +159,91 @@ func (a *Github) actionAdd(cliCtx *cli.Context) error {
if err != nil {
return err
}
err = cloudClient.SetGithubToken(cliCtx.Context, a.Org, a.GHOrg, a.GHRepo, a.GHToken)
err = cloudClient.CreateGHAIntegration(cliCtx.Context, a.Org, a.GHOrg, a.GHRepo, a.GHToken)
if err != nil {
return fmt.Errorf("error found running github add: %w", err)
}
a.cli.Console().Printf("GitHub integration successfully created")
return nil
}

func (a *Github) actionRemove(cliCtx *cli.Context) error {
a.cli.SetCommandName("githubRemove")
if err := a.resolveOrg(); err != nil {
return err
}
cloudClient, err := helper.NewCloudClient(a.cli)
if err != nil {
return err
}
err = cloudClient.RemoveGHAIntegration(cliCtx.Context, a.Org, a.GHOrg, a.GHRepo)
if err != nil {
return fmt.Errorf("github remove failed: %w", err)
}
a.cli.Console().Printf("GitHub integration successfully removed")
return nil
}

func (a *Github) actionList(cliCtx *cli.Context) error {
a.cli.SetCommandName("githubList")
if err := a.resolveOrg(); err != nil {
return err
}
cloudClient, err := helper.NewCloudClient(a.cli)
if err != nil {
return err
}
integrations, err := cloudClient.ListGHAIntegrations(cliCtx.Context, a.Org)
if err != nil {
return fmt.Errorf("github list failed: %w", err)
}
if a.printJSON {
if err := a.printIntegrationsJSON(integrations); err != nil {
return err
}
} else {
if err := a.printIntegrationsTable(integrations); err != nil {
return err
}
}
return nil
}

func (a *Github) printIntegrationsJSON(integrations *pb.ListGHAIntegrationsResponse) error {
jsonInts := make([]integrationJSON, len(integrations.Integrations))
for i, integration := range integrations.Integrations {
jsonInts[i] = integrationJSON{
GithubOrgName: integration.GithubOrgName,
GitHubRepoName: integration.GithubRepoName,
CreatedBy: integration.CreatedBy,
CreatedAt: integration.CreatedAt.AsTime().Format(time.DateTime),
}
}
b, err := json.MarshalIndent(jsonInts, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal json: %w", err)
}
a.cli.Console().Printf("%s\n", string(b))
return nil
}

func (a *Github) printIntegrationsTable(integrations *pb.ListGHAIntegrationsResponse) error {
t := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
headerRow := []string{"GH_ORG", "GH_REPO", "CREATED_BY", "CREATED_AT"}
printRow(t, []color.Attribute{color.Reset}, headerRow)

for _, integration := range integrations.Integrations {
row := []string{integration.GithubOrgName, integration.GithubRepoName, integration.CreatedBy, integration.CreatedAt.AsTime().Format(time.DateTime)}
c := []color.Attribute{color.Reset}
printRow(t, c, row)
}
err := t.Flush()
if err != nil {
return fmt.Errorf("failed to print table: %w", err)
}
return nil
}

func promptToken() (string, error) {
return promptHiddenText("Enter GH token")
}
34 changes: 30 additions & 4 deletions docs/cloud/satellites/gha-runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Earthly satellites are now bundled with a GitHub Actions runner, so they can dir

These runners come with the Earthly CLI preinstalled and configured to use the satellite BuildKit instance, so GitHub Actions jobs will share the same satellite cache than the traditional satellite builds.

## Getting started
## Creating a GitHub Actions integration

Satellite-based GitHub Actions runners can be enabled for a particular repository or for all repositories of a GitHub organization at once.

Expand All @@ -36,19 +36,19 @@ Follow the [official docs](https://docs.github.com/en/authentication/keeping-you
{% endhint %}

### 2. Register the integration via CLI
Create the integration using the `earthly github add` CLI command, passing the token created in the previous step.
Create the integration using the `earthly gha add` CLI command, passing the token created in the previous step.

#### Organization integration
```
earthly github add \
earthly gha add \
--org <earthly_organization> \
--gh-org <github_organization> \
--gh-token <github_token>
```

#### Single repository integration
```
earthly github add \
earthly gha add \
--org <earthly_organization> \
--gh-org <github_organization> \
--gh-repo <github_repo> \
Expand Down Expand Up @@ -123,3 +123,29 @@ earthly-job:
For Earthly-Cloud satellites make sure you have an [EARTHLY_TOKEN](https://docs.earthly.dev/docs/earthly-command#earthly-account-create-token) available in your [GitHub Actions secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions) store, and add it to the job environment, as shown in the previous example. Future versions will remove this requirement.
{% endhint %}
## Listing registered integrations
List the integrations of your Earthly organization with the `earthly gha ls` CLI command.

```
earthly gha ls \
--org <earthly_organization>
```
## Removing an integration
Remove an integration using the `earthly gha remove` CLI command.
### Organization integration
```
earthly gha remove \
--org <earthly_organization> \
--gh-org <github_organization>
```
### Single repository integration
```
earthly gha remove \
--org <earthly_organization> \
--gh-org <github_organization> \
--gh-repo <github_repo>
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.0
github.com/dustin/go-humanize v1.0.1
github.com/earthly/cloud-api v1.0.1-0.20240516231256-26ec57717150
github.com/earthly/cloud-api v1.0.1-0.20240530080539-c5171a73ad6f
github.com/earthly/earthly/ast v0.0.0-00010101000000-000000000000
github.com/earthly/earthly/util/deltautil v0.0.0-20240507235053-335389ed3e2a
github.com/elastic/go-sysinfo v1.9.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/earthly/buildkit v0.0.0-20240515200521-531b303aa8ec h1:vf6x0fPOWKakjH3n2N1O9Tg5j1HDIJsC3Kkgmuko2U0=
github.com/earthly/buildkit v0.0.0-20240515200521-531b303aa8ec/go.mod h1:1/yAC8A0Tu94Bdmv07gaG1pFBp+CetVwO7oB3qvZXUc=
github.com/earthly/cloud-api v1.0.1-0.20240516231256-26ec57717150 h1:hHdgFB5BE+OAFee+CpassZP2GOkdRt5YyhsFmJJVtc8=
github.com/earthly/cloud-api v1.0.1-0.20240516231256-26ec57717150/go.mod h1:rU/tYJ7GFBjdKAITV2heDbez++glpGSbtJaZcp73rNI=
github.com/earthly/cloud-api v1.0.1-0.20240530080539-c5171a73ad6f h1:QjB03JW1E4dS4nvkqE1JKrVxGs95jBBqMYhM6LOEd34=
github.com/earthly/cloud-api v1.0.1-0.20240530080539-c5171a73ad6f/go.mod h1:rU/tYJ7GFBjdKAITV2heDbez++glpGSbtJaZcp73rNI=
github.com/earthly/fsutil v0.0.0-20231030221755-644b08355b65 h1:6oyWHoxHXwcTt4EqmMw6361scIV87uEAB1N42+VpIwk=
github.com/earthly/fsutil v0.0.0-20231030221755-644b08355b65/go.mod h1:9kMVqMyQ/Sx2df5LtnGG+nbrmiZzCS7V6gjW3oGHsvI=
github.com/elastic/go-sysinfo v1.9.0 h1:usICqY/Nw4Mpn9f4LdtpFrKxXroJDe81GaxxUlCckIo=
Expand Down
3 changes: 2 additions & 1 deletion tests/autocompletion/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cloud
config
doc
docker-build
gha
init
ls
org
Expand All @@ -36,7 +37,7 @@ debug
doc
docker-build
docker2earthly
github
gha
init
ls
org
Expand Down

0 comments on commit 60b336d

Please sign in to comment.