From 28b2855f16c326642b4846c4c1951f14e68a5cfc Mon Sep 17 00:00:00 2001 From: Chris Gavin Date: Tue, 25 Aug 2020 13:27:14 +0100 Subject: [PATCH] Implement a safety check to ensure we're not force-pushing over a non-Action repository. --- README.md | 2 ++ cmd/push.go | 4 ++- cmd/sync.go | 2 +- internal/push/push.go | 9 ++++++- internal/push/push_test.go | 25 ++++++++++++++++--- .../push_test/action-cache-initial/git/config | 3 --- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e1d8e6a..a5a7d96 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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). diff --git a/cmd/push.go b/cmd/push.go index c40048a..3f0447c 100644 --- a/cmd/push.go +++ b/cmd/push.go @@ -13,7 +13,7 @@ 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) }, } @@ -21,6 +21,7 @@ type pushFlagFields struct { destinationURL string destinationToken string destinationRepository string + force bool } var pushFlags = pushFlagFields{} @@ -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.") } diff --git a/cmd/sync.go b/cmd/sync.go index 9027adc..170d05e 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -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 } diff --git a/internal/push/push.go b/internal/push/push.go index 555f4cb..208f67c 100644 --- a/internal/push/push.go +++ b/internal/push/push.go @@ -28,6 +28,8 @@ 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 @@ -35,6 +37,7 @@ type pushService struct { destinationRepositoryName string destinationRepositoryOwner string destinationToken string + force bool } func (pushService *pushService) createRepository() (*github.Repository, error) { @@ -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), @@ -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 @@ -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() diff --git a/internal/push/push_test.go b/internal/push/push_test.go index 29c0f4c..c14505e 100644 --- a/internal/push/push_test.go +++ b/internal/push/push_test.go @@ -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) diff --git a/internal/push/push_test/action-cache-initial/git/config b/internal/push/push_test/action-cache-initial/git/config index afde36a..07d359d 100644 --- a/internal/push/push_test/action-cache-initial/git/config +++ b/internal/push/push_test/action-cache-initial/git/config @@ -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/*