Skip to content

Implement a safety check to ensure we're not force-pushing over a non-Action repository. #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ From a machine with access to both GitHub.com and GitHub Enterprise Server use t
* `--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.
* `--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. If provided, it should have the `public_repo` scope.
* `--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.
* `--force` - By default the tool will not overwrite existing repositories. Providing this flag will allow it to.

### I don't have a machine that can access both GitHub.com and GitHub Enterprise Server.
From a machine with access to GitHub.com use the `./codeql-action-sync pull` command to download a copy of the CodeQL Action and bundles to a local folder.
Expand All @@ -45,6 +46,7 @@ Now use the `./codeql-action-sync push` command to upload the CodeQL Action and
**Optional Arguments:**
* `--cache-dir` - The directory to which the Action was previously downloaded.
* `--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.
* `--force` - By default the tool will not overwrite existing repositories. Providing this flag will allow it to.

## Contributing
For more details on contributing improvements to this tool, see our [contributor guide](CONTRIBUTING.md).
4 changes: 3 additions & 1 deletion cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ var pushCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
version.LogVersion()
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository)
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.force)
},
}

type pushFlagFields struct {
destinationURL string
destinationToken string
destinationRepository string
force bool
}

var pushFlags = pushFlagFields{}
Expand All @@ -31,4 +32,5 @@ func (f *pushFlagFields) Init(cmd *cobra.Command) {
cmd.Flags().StringVar(&f.destinationToken, "destination-token", "", "A token to access the API on the GitHub Enterprise instance.")
cmd.MarkFlagRequired("destination-token")
cmd.Flags().StringVar(&f.destinationRepository, "destination-repository", "github/codeql-action", "The name of the repository to create on GitHub Enterprise.")
cmd.Flags().BoolVar(&f.force, "force", false, "Replace the existing repository even if it was not created by the sync tool.")
}
2 changes: 1 addition & 1 deletion cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var syncCmd = &cobra.Command{
if err != nil {
return err
}
err = push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository)
err = push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository, pushFlags.force)
if err != nil {
return err
}
Expand Down
9 changes: 8 additions & 1 deletion internal/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ import (
const remoteName = "enterprise"
const repositoryHomepage = "https://github.com/github/codeql-action-sync-tool/"

const errorAlreadyExists = "The destination repository already exists, but it was not created with the CodeQL Action sync tool. If you are sure you want to push the CodeQL Action to it, re-run this command with the `--force` flag."

type pushService struct {
ctx context.Context
cacheDirectory cachedirectory.CacheDirectory
githubEnterpriseClient *github.Client
destinationRepositoryName string
destinationRepositoryOwner string
destinationToken string
force bool
}

func (pushService *pushService) createRepository() (*github.Repository, error) {
Expand Down Expand Up @@ -76,6 +79,9 @@ func (pushService *pushService) createRepository() (*github.Repository, error) {
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
return nil, errors.Wrap(err, "Error checking if destination repository exists.")
}
if response.StatusCode != http.StatusNotFound && repositoryHomepage != repository.GetHomepage() && !pushService.force {
return nil, errors.Errorf(errorAlreadyExists)
}
desiredRepositoryProperties := github.Repository{
Name: github.String(pushService.destinationRepositoryName),
Homepage: github.String(repositoryHomepage),
Expand Down Expand Up @@ -286,7 +292,7 @@ func (pushService *pushService) pushReleases() error {
return nil
}

func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string) error {
func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string, force bool) error {
err := cacheDirectory.CheckOrCreateVersionFile(false, version.Version())
if err != nil {
return err
Expand Down Expand Up @@ -317,6 +323,7 @@ func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, des
destinationRepositoryOwner: destinationRepositoryOwner,
destinationRepositoryName: destinationRepositoryName,
destinationToken: destinationToken,
force: force,
}

repository, err := pushService.createRepository()
Expand Down
25 changes: 21 additions & 4 deletions internal/push/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,35 @@ func TestUpdateRepositoryWhenUserIsOwner(t *testing.T) {
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-owner.json", response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("{}"))
test.ServeHTTPResponseFromObject(t, github.Repository{Homepage: github.String(repositoryHomepage)}, response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("{}"))
}).Methods("PATCH")
githubTestServer.HandleFunc("/api/v3/user/repos", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("{}"))
}).Methods("POST")
_, err := pushService.createRepository()
require.NoError(t, err)
}

func TestUpdateRepositoryWhenUserIsOwnerForced(t *testing.T) {
temporaryDirectory := test.CreateTemporaryDirectory(t)
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
pushService := getTestPushService(t, temporaryDirectory, githubEnterpriseURL)
githubTestServer.HandleFunc("/api/v3/user", func(response http.ResponseWriter, request *http.Request) {
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-owner.json", response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
test.ServeHTTPResponseFromObject(t, github.Repository{}, response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
test.ServeHTTPResponseFromObject(t, github.Repository{}, response)
}).Methods("PATCH")
_, err := pushService.createRepository()
require.EqualError(t, err, errorAlreadyExists)
pushService.force = true
_, err = pushService.createRepository()
require.NoError(t, err)
}

func TestCreateOrganizationAndRepositoryWhenOrganizationIsOwner(t *testing.T) {
temporaryDirectory := test.CreateTemporaryDirectory(t)
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
Expand Down
3 changes: 0 additions & 3 deletions internal/push/push_test/action-cache-initial/git/config
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@
repositoryformatversion = 0
filemode = true
bare = true
[remote "enterprise"]
url = /tmp/codeql-action-sync-tests128816160/target
fetch = +refs/heads/*:refs/remotes/enterprise/*