Skip to content

Commit 9436204

Browse files
authored
Merge pull request #48 from github/update-bundled-action
Make various improvements to the sync tool to handle updating a bundled CodeQL Action.
2 parents 0375789 + ef02b83 commit 9436204

File tree

7 files changed

+49
-23
lines changed

7 files changed

+49
-23
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ From a machine with access to both GitHub.com and GitHub Enterprise Server use t
2020

2121
**Required Arguments:**
2222
* `--destination-url` - The URL of the GitHub Enterprise Server instance to push the Action to.
23-
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. The token should be granted at least the `public_repo` scope. If the destination repository is in an organization that does not yet exist, your token will need to have the `site_admin` scope in order to create the organization. The organization can also be created manually or an existing organization used.
23+
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. The token should be granted at least the `public_repo` scope. If the destination repository is in an organization that does not yet exist or that you are not an owner of, your token will need to have the `site_admin` scope in order to create the organization. The organization can also be created manually or an existing organization used.
2424

2525
**Optional Arguments:**
2626
* `--cache-dir` - A temporary directory in which to store data downloaded from GitHub.com before it is uploaded to GitHub Enterprise Server. If not specified a directory next to the sync tool will be used.
2727
* `--source-token` - A token to access the API of GitHub.com. This is normally not required, but can be provided if you have issues with API rate limiting. The token does not need to have any scopes.
2828
* `--destination-repository` - The name of the repository in which to create or update the CodeQL Action. If not specified `github/codeql-action` will be used.
29+
* `--actions-admin-user` - The name of the Actions admin user, which will be used if you are updating the bundled CodeQL Action. If not specified `actions-admin` will be used.
2930
* `--force` - By default the tool will not overwrite existing repositories. Providing this flag will allow it to.
3031
* `--push-ssh` - Push Git contents over SSH rather than HTTPS. To use this option you must have SSH access to your GitHub Enterprise instance configured.
3132

@@ -42,11 +43,12 @@ Now use the `./codeql-action-sync push` command to upload the CodeQL Action and
4243

4344
**Required Arguments:**
4445
* `--destination-url` - The URL of the GitHub Enterprise Server instance to push the Action to.
45-
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. The token should be granted at least the `public_repo` scope. If the destination repository is in an organization that does not yet exist, your token will need to have the `site_admin` scope in order to create the organization. The organization can also be created manually or an existing organization used.
46+
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. The token should be granted at least the `public_repo` scope. If the destination repository is in an organization that does not yet exist or that you are not an owner of, your token will need to have the `site_admin` scope in order to create the organization. The organization can also be created manually or an existing organization used.
4647

4748
**Optional Arguments:**
4849
* `--cache-dir` - The directory to which the Action was previously downloaded.
4950
* `--destination-repository` - The name of the repository in which to create or update the CodeQL Action. If not specified `github/codeql-action` will be used.
51+
* `--actions-admin-user` - The name of the Actions admin user, which will be used if you are updating the bundled CodeQL Action. If not specified `actions-admin` will be used.
5052
* `--force` - By default the tool will not overwrite existing repositories. Providing this flag will allow it to.
5153
* `--push-ssh` - Push Git contents over SSH rather than HTTPS. To use this option you must have SSH access to your GitHub Enterprise instance configured.
5254

cmd/push.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ var pushCmd = &cobra.Command{
1313
RunE: func(cmd *cobra.Command, args []string) error {
1414
version.LogVersion()
1515
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
16-
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.force, pushFlags.pushSSH)
16+
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.actionsAdminUser, pushFlags.force, pushFlags.pushSSH)
1717
},
1818
}
1919

2020
type pushFlagFields struct {
2121
destinationURL string
2222
destinationToken string
2323
destinationRepository string
24+
actionsAdminUser string
2425
force bool
2526
pushSSH bool
2627
}
@@ -33,6 +34,7 @@ func (f *pushFlagFields) Init(cmd *cobra.Command) {
3334
cmd.Flags().StringVar(&f.destinationToken, "destination-token", "", "A token to access the API on the GitHub Enterprise instance.")
3435
cmd.MarkFlagRequired("destination-token")
3536
cmd.Flags().StringVar(&f.destinationRepository, "destination-repository", "github/codeql-action", "The name of the repository to create on GitHub Enterprise.")
37+
cmd.Flags().StringVar(&f.actionsAdminUser, "actions-admin-user", "actions-admin", "The name of the Actions admin user.")
3638
cmd.Flags().BoolVar(&f.force, "force", false, "Replace the existing repository even if it was not created by the sync tool.")
3739
cmd.Flags().BoolVar(&f.pushSSH, "push-ssh", false, "Push Git contents over SSH rather than HTTPS. To use this option you must have SSH access to your GitHub Enterprise instance configured.")
3840
}

cmd/sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var syncCmd = &cobra.Command{
1818
if err != nil {
1919
return err
2020
}
21-
err = push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.force, pushFlags.pushSSH)
21+
err = push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.actionsAdminUser, pushFlags.force, pushFlags.pushSSH)
2222
if err != nil {
2323
return err
2424
}

internal/githubapiutil/githubapiutil.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
const xOAuthScopesHeader = "X-OAuth-Scopes"
1010

11-
func MissingAllScopes(response *github.Response, requiredAnyScopes ...string) bool {
11+
func HasAnyScope(response *github.Response, scopes ...string) bool {
1212
if response == nil {
1313
return false
1414
}
@@ -18,11 +18,11 @@ func MissingAllScopes(response *github.Response, requiredAnyScopes ...string) bo
1818
actualScopes := strings.Split(response.Header.Get(xOAuthScopesHeader), ",")
1919
for _, actualScope := range actualScopes {
2020
actualScope = strings.Trim(actualScope, " ")
21-
for _, requiredAnyScope := range requiredAnyScopes {
22-
if actualScope == requiredAnyScope {
23-
return false
21+
for _, requiredScope := range scopes {
22+
if actualScope == requiredScope {
23+
return true
2424
}
2525
}
2626
}
27-
return true
27+
return false
2828
}

internal/githubapiutil/githubapiutil_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import (
99
"github.com/google/go-github/v32/github"
1010
)
1111

12-
func TestHasAnyScopes(t *testing.T) {
12+
func TestHasAnyScope(t *testing.T) {
1313
response := github.Response{
1414
Response: &http.Response{Header: http.Header{}},
1515
}
1616

1717
response.Header.Set(xOAuthScopesHeader, "gist, notifications, admin:org, repo")
18-
require.False(t, MissingAllScopes(&response, "public_repo", "repo"))
18+
require.True(t, HasAnyScope(&response, "public_repo", "repo"))
1919

2020
response.Header.Set(xOAuthScopesHeader, "gist, notifications, public_repo, admin:org")
21-
require.False(t, MissingAllScopes(&response, "public_repo", "repo"))
21+
require.True(t, HasAnyScope(&response, "public_repo", "repo"))
2222

2323
response.Header.Set(xOAuthScopesHeader, "gist, notifications, admin:org")
24-
require.True(t, MissingAllScopes(&response, "public_repo", "repo"))
24+
require.False(t, HasAnyScope(&response, "public_repo", "repo"))
2525
}

internal/push/push.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ type pushService struct {
4343
githubEnterpriseClient *github.Client
4444
destinationRepositoryName string
4545
destinationRepositoryOwner string
46-
destinationToken string
46+
destinationToken *oauth2.Token
47+
actionsAdminUser string
4748
force bool
4849
pushSSH bool
4950
}
@@ -76,12 +77,25 @@ func (pushService *pushService) createRepository() (*github.Repository, error) {
7677
Name: github.String(pushService.destinationRepositoryOwner),
7778
}, user.GetLogin())
7879
if err != nil {
79-
if response != nil && response.StatusCode == http.StatusNotFound && githubapiutil.MissingAllScopes(response, "site_admin") {
80+
if response != nil && response.StatusCode == http.StatusNotFound && !githubapiutil.HasAnyScope(response, "site_admin") {
8081
return nil, usererrors.New("The destination token you have provided does not have the `site_admin` scope, so the destination organization cannot be created.")
8182
}
8283
return nil, errors.Wrap(err, "Error creating organization.")
8384
}
8485
}
86+
87+
_, response, err = pushService.githubEnterpriseClient.Organizations.GetOrgMembership(pushService.ctx, user.GetLogin(), pushService.destinationRepositoryOwner)
88+
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
89+
return nil, errors.Wrap(err, "Failed to check membership of destination organization.")
90+
}
91+
if err != nil && githubapiutil.HasAnyScope(response, "site_admin") {
92+
log.Debugf("No access to destination organization. Switching to impersonation token for %s...", pushService.actionsAdminUser)
93+
impersonationToken, _, err := pushService.githubEnterpriseClient.Admin.CreateUserImpersonation(pushService.ctx, pushService.actionsAdminUser, &github.ImpersonateUserOptions{Scopes: []string{"public_repo", "workflow"}})
94+
if err != nil {
95+
return nil, errors.Wrap(err, "Failed to impersonate Actions admin user.")
96+
}
97+
pushService.destinationToken.AccessToken = impersonationToken.GetToken()
98+
}
8599
}
86100

87101
repository, response, err := pushService.githubEnterpriseClient.Repositories.Get(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName)
@@ -105,16 +119,20 @@ func (pushService *pushService) createRepository() (*github.Repository, error) {
105119
if response.StatusCode == http.StatusNotFound {
106120
repository, response, err = pushService.githubEnterpriseClient.Repositories.Create(pushService.ctx, destinationOrganization, &desiredRepositoryProperties)
107121
if err != nil {
108-
if response.StatusCode == http.StatusNotFound && githubapiutil.MissingAllScopes(response, "public_repo", "repo") {
122+
if response.StatusCode == http.StatusNotFound && !githubapiutil.HasAnyScope(response, "public_repo", "repo") {
109123
return nil, usererrors.New("The destination token you have provided does not have the `public_repo` scope.")
110124
}
111125
return nil, errors.Wrap(err, "Error creating destination repository.")
112126
}
113127
} else {
114128
repository, response, err = pushService.githubEnterpriseClient.Repositories.Edit(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, &desiredRepositoryProperties)
115129
if err != nil {
116-
if response.StatusCode == http.StatusNotFound && githubapiutil.MissingAllScopes(response, "public_repo", "repo") {
117-
return nil, usererrors.New("The destination token you have provided does not have the `public_repo` scope.")
130+
if response.StatusCode == http.StatusNotFound {
131+
if !githubapiutil.HasAnyScope(response, "public_repo", "repo") {
132+
return nil, usererrors.New("The destination token you have provided does not have the `public_repo` scope.")
133+
} else {
134+
return nil, fmt.Errorf("You don't have permission to update the repository at %s/%s. If you wish to update the bundled CodeQL Action please provide a token with the `site_admin` scope.", pushService.destinationRepositoryOwner, pushService.destinationRepositoryName)
135+
}
118136
}
119137
return nil, errors.Wrap(err, "Error updating destination repository.")
120138
}
@@ -145,7 +163,7 @@ func (pushService *pushService) pushGit(repository *github.Repository, initialPu
145163

146164
credentials := &githttp.BasicAuth{
147165
Username: "x-access-token",
148-
Password: pushService.destinationToken,
166+
Password: pushService.destinationToken.AccessToken,
149167
}
150168
if pushService.pushSSH {
151169
// Use the SSH key from the environment.
@@ -327,7 +345,7 @@ func (pushService *pushService) pushReleases() error {
327345
return nil
328346
}
329347

330-
func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string, force bool, pushSSH bool) error {
348+
func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string, actionsAdminUser string, force bool, pushSSH bool) error {
331349
err := cacheDirectory.CheckOrCreateVersionFile(false, version.Version())
332350
if err != nil {
333351
return err
@@ -338,8 +356,9 @@ func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, des
338356
}
339357

340358
destinationURL = strings.TrimRight(destinationURL, "/")
359+
token := oauth2.Token{AccessToken: destinationToken}
341360
tokenSource := oauth2.StaticTokenSource(
342-
&oauth2.Token{AccessToken: destinationToken},
361+
&token,
343362
)
344363
tokenClient := oauth2.NewClient(ctx, tokenSource)
345364
client, err := github.NewEnterpriseClient(destinationURL+"/api/v3", destinationURL+"/api/uploads", tokenClient)
@@ -357,7 +376,8 @@ func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, des
357376
githubEnterpriseClient: client,
358377
destinationRepositoryOwner: destinationRepositoryOwner,
359378
destinationRepositoryName: destinationRepositoryName,
360-
destinationToken: destinationToken,
379+
destinationToken: &token,
380+
actionsAdminUser: actionsAdminUser,
361381
force: force,
362382
pushSSH: pushSSH,
363383
}

internal/push/push_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/go-git/go-git/v5"
1616
"github.com/gorilla/mux"
1717
"github.com/stretchr/testify/require"
18+
"golang.org/x/oauth2"
1819

1920
"github.com/google/go-github/v32/github"
2021
)
@@ -29,13 +30,14 @@ func getTestPushService(t *testing.T, cacheDirectoryString string, githubEnterpr
2930
} else {
3031
githubEnterpriseClient = nil
3132
}
33+
token := oauth2.Token{AccessToken: "token"}
3234
return pushService{
3335
ctx: context.Background(),
3436
cacheDirectory: cacheDirectory,
3537
githubEnterpriseClient: githubEnterpriseClient,
3638
destinationRepositoryOwner: "destination-repository-owner",
3739
destinationRepositoryName: "destination-repository-name",
38-
destinationToken: "token",
40+
destinationToken: &token,
3941
}
4042
}
4143

0 commit comments

Comments
 (0)