This commit is contained in:
Chris Gavin 2020-08-20 21:24:45 +01:00
Родитель 20fe60d834
Коммит 556ea9e527
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 07F950B80C27E4DA
40 изменённых файлов: 647 добавлений и 8 удалений

32
cmd/push.go Normal file
Просмотреть файл

@ -0,0 +1,32 @@
package cmd
import (
"github.com/github/codeql-action-sync/internal/cachedirectory"
"github.com/github/codeql-action-sync/internal/push"
"github.com/spf13/cobra"
)
var pushCmd = &cobra.Command{
Use: "push",
Short: "Push the CodeQL Action from the local cache to a GitHub Enterprise Server installation.",
RunE: func(cmd *cobra.Command, args []string) error {
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository)
},
}
type pushFlagFields struct {
destinationURL string
destinationToken string
destinationRepository string
}
var pushFlags = pushFlagFields{}
func (f *pushFlagFields) Init(cmd *cobra.Command) {
cmd.Flags().StringVar(&f.destinationURL, "destination-url", "", "The URL of the GitHub Enterprise instance to push to.")
cmd.MarkFlagRequired("destination-url")
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.")
}

Просмотреть файл

@ -55,5 +55,8 @@ func Execute(ctx context.Context) error {
rootCmd.AddCommand(pullCmd)
pullFlags.Init(pullCmd)
rootCmd.AddCommand(pushCmd)
pushFlags.Init(pushCmd)
return rootCmd.ExecuteContext(ctx)
}

1
go.mod
Просмотреть файл

@ -10,4 +10,5 @@ require (
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.6.1
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
)

4
go.sum сгенерированный
Просмотреть файл

@ -52,7 +52,9 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -166,6 +168,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -187,6 +190,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

Просмотреть файл

@ -8,6 +8,7 @@ import (
"path"
"path/filepath"
"github.com/go-git/go-git/v5"
"github.com/pkg/errors"
)
@ -16,6 +17,8 @@ const errorNotACacheOrEmpty = "The cache directory you have selected is not empt
const errorCacheParentDoesNotExist = "Cannot create cache directory because its parent, does not exist."
const errorPushNonCache = "The directory you have provided does not appear to be valid. Please check it exists and that you have run the `pull` command to populate it."
const CacheReferencePrefix = "refs/remotes/" + git.DefaultRemoteName + "/"
type CacheDirectory struct {
path string
}

Просмотреть файл

@ -81,8 +81,8 @@ func (pullService *pullService) pullGit(fresh bool) error {
err = localRepository.FetchContext(pullService.ctx, &git.FetchOptions{
RemoteName: git.DefaultRemoteName,
RefSpecs: []config.RefSpec{
config.RefSpec("+refs/heads/*:refs/remotes/" + git.DefaultRemoteName + "/heads/*"),
config.RefSpec("+refs/tags/*:refs/remotes/" + git.DefaultRemoteName + "/tags/*"),
config.RefSpec("+refs/heads/*:" + cachedirectory.CacheReferencePrefix + "heads/*"),
config.RefSpec("+refs/tags/*:" + cachedirectory.CacheReferencePrefix + "tags/*"),
},
Progress: os.Stderr,
Tags: git.NoTags,

Просмотреть файл

@ -58,7 +58,7 @@ func TestPullGitFresh(t *testing.T) {
pullService := getTestPullService(t, temporaryDirectory, initialActionRepository, "")
err := pullService.pullGit(true)
require.NoError(t, err)
checkExpectedReferencesInCache(t, pullService.cacheDirectory, []string{
test.CheckExpectedReferencesInRepository(t, pullService.cacheDirectory.GitPath(), []string{
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main",
"26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1",
"e529a54fad10a936308b2220e05f7f00757f8e7c refs/remotes/origin/heads/v3",
@ -93,7 +93,7 @@ func TestPullGitNotFreshWithChanges(t *testing.T) {
pullService = getTestPullService(t, temporaryDirectory, modifiedActionRepository, "")
err = pullService.pullGit(false)
require.NoError(t, err)
checkExpectedReferencesInCache(t, pullService.cacheDirectory, []string{
test.CheckExpectedReferencesInRepository(t, pullService.cacheDirectory.GitPath(), []string{
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main",
"26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1",
"33d42021633d74bcd0bf9c95e3d3159131a5faa7 refs/remotes/origin/heads/v3", // v3 was force-pushed, and should have been force-pulled too.

Просмотреть файл

@ -2,5 +2,3 @@
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = /tmp/codeql-sim

Просмотреть файл

@ -2,5 +2,3 @@
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = /tmp/codeql-sim

342
internal/push/push.go Normal file
Просмотреть файл

@ -0,0 +1,342 @@
package push
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/github/codeql-action-sync/internal/cachedirectory"
"github.com/github/codeql-action-sync/internal/version"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/google/go-github/v32/github"
"github.com/mitchellh/ioprogress"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
const remoteName = "enterprise"
const repositoryHomepage = "https://github.com/github/codeql-action-sync-tool/"
type pushService struct {
ctx context.Context
cacheDirectory cachedirectory.CacheDirectory
githubEnterpriseClient *github.Client
destinationRepositoryName string
destinationRepositoryOwner string
destinationToken string
}
func (pushService *pushService) createRepository() (*github.Repository, error) {
log.Printf("Ensuring repository exists...")
user, _, err := pushService.githubEnterpriseClient.Users.Get(pushService.ctx, "")
if err != nil {
return nil, errors.Wrap(err, "Error getting current user.")
}
// When creating a repository we can either create it in a named organization or under the current user (represented in go-github by an empty string).
destinationOrganization := ""
if pushService.destinationRepositoryOwner != user.GetLogin() {
destinationOrganization = pushService.destinationRepositoryOwner
}
if destinationOrganization != "" {
_, response, err := pushService.githubEnterpriseClient.Organizations.Get(pushService.ctx, pushService.destinationRepositoryOwner)
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
return nil, errors.Wrap(err, "Error checking if destination organization exists.")
}
if response.StatusCode == http.StatusNotFound {
log.Printf("The organization %s does not exist. Creating it...", pushService.destinationRepositoryOwner)
createOrganizationRequest, err := pushService.githubEnterpriseClient.NewRequest("POST", "admin/organizations", map[string]interface{}{
"login": pushService.destinationRepositoryOwner,
"profile_name": pushService.destinationRepositoryOwner,
"admin": user.GetLogin(),
})
if err != nil {
return nil, errors.Wrap(err, "Error checking if destination organization exists.")
}
response, err = pushService.githubEnterpriseClient.Do(pushService.ctx, createOrganizationRequest, nil)
if err != nil {
return nil, errors.Wrap(err, "Error creating organization.")
}
}
}
repository, response, err := pushService.githubEnterpriseClient.Repositories.Get(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName)
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
return nil, errors.Wrap(err, "Error checking if destination repository exists.")
}
desiredRepositoryProperties := github.Repository{
Name: github.String(pushService.destinationRepositoryName),
Homepage: github.String(repositoryHomepage),
HasIssues: github.Bool(false),
HasProjects: github.Bool(false),
HasPages: github.Bool(false),
HasWiki: github.Bool(false),
HasDownloads: github.Bool(false),
Archived: github.Bool(false),
Private: github.Bool(false),
}
if response.StatusCode == http.StatusNotFound {
repository, _, err = pushService.githubEnterpriseClient.Repositories.Create(pushService.ctx, destinationOrganization, &desiredRepositoryProperties)
if err != nil {
return nil, errors.Wrap(err, "Error creating destination repository.")
}
} else {
repository, _, err = pushService.githubEnterpriseClient.Repositories.Edit(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, &desiredRepositoryProperties)
if err != nil {
return nil, errors.Wrap(err, "Error updating destination repository.")
}
}
return repository, nil
}
func (pushService *pushService) pushGit(repository *github.Repository, initialPush bool) error {
remoteURL := repository.GetCloneURL()
if initialPush {
log.Printf("Pushing Git releases to %s...", remoteURL)
} else {
log.Printf("Pushing Git references to %s...", remoteURL)
}
gitRepository, err := git.PlainOpen(pushService.cacheDirectory.GitPath())
if err != nil {
return errors.Wrap(err, "Error reading Git repository from cache.")
}
_ = gitRepository.DeleteRemote(remoteName)
gitRemote, err := gitRepository.CreateRemote(&config.RemoteConfig{
Name: remoteName,
URLs: []string{remoteURL},
})
if err != nil {
return errors.Wrap(err, "Error adding repository remote.")
}
credentials := &githttp.BasicAuth{
Username: "x-access-token",
Password: pushService.destinationToken,
}
refSpecBatches := [][]config.RefSpec{}
if initialPush {
releasePathStats, err := ioutil.ReadDir(pushService.cacheDirectory.ReleasesPath())
if err != nil {
return errors.Wrap(err, "Error reading releases.")
}
refSpecBatches = append(refSpecBatches, []config.RefSpec{})
for _, releasePathStat := range releasePathStats {
refSpecBatches[0] = append(refSpecBatches[0], config.RefSpec("+"+cachedirectory.CacheReferencePrefix+"tags/"+releasePathStat.Name()+":refs/tags/"+releasePathStat.Name()))
}
} else {
// We've got to push `main` on its own, so that it will be made the default branch if the repository has just been created. We then push everything else afterwards.
refSpecBatches = [][]config.RefSpec{
[]config.RefSpec{
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "heads/main:refs/heads/main"),
},
[]config.RefSpec{
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "heads/*:refs/heads/*"),
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "tags/*:refs/tags/*"),
},
}
}
for _, refSpecs := range refSpecBatches {
err = gitRemote.PushContext(pushService.ctx, &git.PushOptions{
RemoteName: remoteName,
RefSpecs: refSpecs,
Auth: credentials,
Progress: os.Stderr,
Force: true,
})
if err != nil && errors.Cause(err) != git.NoErrAlreadyUpToDate {
return errors.Wrap(err, "Error pushing Action to GitHub Enterprise Server.")
}
}
return nil
}
func (pushService *pushService) createOrUpdateRelease(releaseName string) (*github.RepositoryRelease, error) {
releaseMetadata := github.RepositoryRelease{}
releaseMetadataPath := pushService.cacheDirectory.MetadataPath(releaseName)
releaseMetadataFile, err := ioutil.ReadFile(releaseMetadataPath)
if err != nil {
return nil, errors.Wrap(err, "Error reading release metadata.")
}
err = json.Unmarshal([]byte(releaseMetadataFile), &releaseMetadata)
if err != nil {
return nil, errors.Wrap(err, "Error converting release from JSON.")
}
// Some of our target commitishes are invalid as they point to `main` which we've not pushed yet.
releaseMetadata.TargetCommitish = nil
release, response, err := pushService.githubEnterpriseClient.Repositories.GetReleaseByTag(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, releaseMetadata.GetTagName())
if err != nil && response.StatusCode != http.StatusNotFound {
return nil, errors.Wrap(err, "Error checking for existing CodeQL release.")
}
if release == nil {
log.Printf("Creating release %s...", releaseMetadata.GetTagName())
release, _, err := pushService.githubEnterpriseClient.Repositories.CreateRelease(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, &releaseMetadata)
if err != nil {
return nil, errors.Wrap(err, "Error creating release.")
}
return release, nil
}
release, _, err = pushService.githubEnterpriseClient.Repositories.EditRelease(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), &releaseMetadata)
if err != nil {
log.Printf("Updating release %s...", releaseMetadata.GetTagName())
return nil, errors.Wrap(err, "Error updating release.")
}
return release, nil
}
func (pushService *pushService) uploadReleaseAsset(release *github.RepositoryRelease, assetPathStat os.FileInfo, reader io.Reader) (*github.ReleaseAsset, *github.Response, error) {
// This is technically already part of the go-github library, but we re-implement it here since otherwise we can't get a progress bar.
url := fmt.Sprintf("repos/%s/%s/releases/%d/assets?name=%s", pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), url.QueryEscape(assetPathStat.Name()))
mediaType := mime.TypeByExtension(filepath.Ext(assetPathStat.Name()))
request, err := pushService.githubEnterpriseClient.NewUploadRequest(url, reader, assetPathStat.Size(), mediaType)
if err != nil {
return nil, nil, errors.Wrap(err, "Error constructing upload request.")
}
asset := &github.ReleaseAsset{}
response, err := pushService.githubEnterpriseClient.Do(pushService.ctx, request, asset)
if err != nil {
return nil, response, errors.Wrap(err, "Error uploading release asset.")
}
return asset, response, nil
}
func (pushService *pushService) createOrUpdateReleaseAsset(release *github.RepositoryRelease, existingAssets []*github.ReleaseAsset, assetPathStat os.FileInfo) error {
for _, existingAsset := range existingAssets {
if existingAsset.GetName() == assetPathStat.Name() {
if int64(existingAsset.GetSize()) == assetPathStat.Size() {
return nil
}
}
}
log.Printf("Uploading release asset %s...", assetPathStat.Name())
assetFile, err := os.Open(pushService.cacheDirectory.AssetPath(release.GetTagName(), assetPathStat.Name()))
defer assetFile.Close()
progressReader := &ioprogress.Reader{
Reader: assetFile,
Size: assetPathStat.Size(),
DrawFunc: ioprogress.DrawTerminalf(os.Stderr, ioprogress.DrawTextFormatBytes),
}
if err != nil {
return errors.Wrap(err, "Error opening release asset.")
}
_, _, err = pushService.uploadReleaseAsset(release, assetPathStat, progressReader)
if err != nil {
return errors.Wrap(err, "Error uploading release asset.")
}
return nil
}
func (pushService *pushService) pushReleases() error {
log.Print("Pushing CodeQL bundles...")
releasesPath := pushService.cacheDirectory.ReleasesPath()
releasePathStats, err := ioutil.ReadDir(releasesPath)
if err != nil {
return errors.Wrap(err, "Error reading releases.")
}
for _, releasePathStat := range releasePathStats {
releaseName := releasePathStat.Name()
release, err := pushService.createOrUpdateRelease(releaseName)
if err != nil {
return err
}
existingAssets := []*github.ReleaseAsset{}
for page := 1; ; page++ {
assets, _, err := pushService.githubEnterpriseClient.Repositories.ListReleaseAssets(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), &github.ListOptions{Page: page})
if err != nil {
return errors.Wrap(err, "Error fetching existing release assets.")
}
if len(assets) == 0 {
break
}
existingAssets = append(existingAssets, assets...)
}
assetsPath := pushService.cacheDirectory.AssetsPath(releaseName)
assetPathStats, err := ioutil.ReadDir(assetsPath)
if err != nil {
return errors.Wrap(err, "Error reading release assets.")
}
for _, assetPathStat := range assetPathStats {
err := pushService.createOrUpdateReleaseAsset(release, existingAssets, assetPathStat)
if err != nil {
return errors.Wrap(err, "Error uploading release assets.")
}
}
}
return nil
}
func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string) error {
err := cacheDirectory.CheckOrCreateVersionFile(false, version.Version())
if err != nil {
return err
}
destinationURL = strings.TrimRight(destinationURL, "/")
tokenSource := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: destinationToken},
)
tokenClient := oauth2.NewClient(ctx, tokenSource)
client, err := github.NewEnterpriseClient(destinationURL+"/api/v3", destinationURL+"/api/uploads", tokenClient)
if err != nil {
return errors.Wrap(err, "Error creating GitHub Enterprise client.")
}
destinationRepositorySplit := strings.Split(destinationRepository, "/")
destinationRepositoryOwner := destinationRepositorySplit[0]
destinationRepositoryName := destinationRepositorySplit[1]
pushService := pushService{
ctx: ctx,
cacheDirectory: cacheDirectory,
githubEnterpriseClient: client,
destinationRepositoryOwner: destinationRepositoryOwner,
destinationRepositoryName: destinationRepositoryName,
destinationToken: destinationToken,
}
repository, err := pushService.createRepository()
if err != nil {
return err
}
// "He was going to live forever, or die in the attempt." - Catch-22, Joseph Heller
// We can't push the releases first because you can't create tags in an empty Git repository.
// We can't push the Git content first because then we'd have Git content that references releases that don't exist yet.
// In this compromise solution we push only the tags that are referenced by releases, we then push the releases, and then finally we push the rest of the Git content.
// This should work so long as no one uses a tag both to reference a specific version of the CodeQL Action and as a storage mechanism for a CodeQL bundle.
err = pushService.pushGit(repository, true)
if err != nil {
return err
}
err = pushService.pushReleases()
if err != nil {
return err
}
err = pushService.pushGit(repository, false)
if err != nil {
return err
}
log.Printf("Finished pushing CodeQL Action to %s!", destinationRepository)
return nil
}

187
internal/push/push_test.go Normal file
Просмотреть файл

@ -0,0 +1,187 @@
package push
import (
"context"
"encoding/json"
"io/ioutil"
"math/rand"
"net/http"
"path"
"strconv"
"testing"
"github.com/github/codeql-action-sync/internal/cachedirectory"
"github.com/github/codeql-action-sync/test"
"github.com/go-git/go-git/v5"
"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
"github.com/google/go-github/v32/github"
)
func getTestPushService(t *testing.T, cacheDirectoryString string, githubEnterpriseURL string) pushService {
cacheDirectory := cachedirectory.NewCacheDirectory(cacheDirectoryString)
var githubEnterpriseClient *github.Client
if githubEnterpriseURL != "" {
client, err := github.NewEnterpriseClient(githubEnterpriseURL+"/api/v3", githubEnterpriseURL+"/api/uploads", &http.Client{})
githubEnterpriseClient = client
require.NoError(t, err)
} else {
githubEnterpriseClient = nil
}
return pushService{
ctx: context.Background(),
cacheDirectory: cacheDirectory,
githubEnterpriseClient: githubEnterpriseClient,
destinationRepositoryOwner: "destination-repository-owner",
destinationRepositoryName: "destination-repository-name",
destinationToken: "token",
}
}
func TestCreateRepositoryWhenUserIsOwner(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) {
response.WriteHeader(http.StatusNotFound)
}).Methods("GET")
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 TestUpdateRepositoryWhenUserIsOwner(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) {
response.Write([]byte("{}"))
}).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 TestCreateOrganizationAndRepositoryWhenOrganizationIsOwner(t *testing.T) {
temporaryDirectory := test.CreateTemporaryDirectory(t)
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
pushService := getTestPushService(t, temporaryDirectory, githubEnterpriseURL)
organizationCreated := false
githubTestServer.HandleFunc("/api/v3/user", func(response http.ResponseWriter, request *http.Request) {
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-not-owner.json", response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/orgs/destination-repository-owner", func(response http.ResponseWriter, request *http.Request) {
if organizationCreated {
response.Write([]byte("{}"))
} else {
response.WriteHeader(http.StatusNotFound)
}
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/admin/organizations", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("{}"))
organizationCreated = true
}).Methods("POST")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusNotFound)
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/orgs/destination-repository-owner/repos", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("{}"))
}).Methods("POST")
_, err := pushService.createRepository()
require.NoError(t, err)
}
func TestPushGit(t *testing.T) {
temporaryDirectory := test.CreateTemporaryDirectory(t)
destinationPath := path.Join(temporaryDirectory, "target")
_, err := git.PlainInit(destinationPath, true)
require.NoError(t, err)
pushService := getTestPushService(t, "./push_test/action-cache-initial/", "")
repository := github.Repository{
CloneURL: github.String(destinationPath),
}
err = pushService.pushGit(&repository, true)
require.NoError(t, err)
test.CheckExpectedReferencesInRepository(t, destinationPath, []string{
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200101",
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200630",
})
err = pushService.pushGit(&repository, false)
require.NoError(t, err)
test.CheckExpectedReferencesInRepository(t, destinationPath, []string{
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200101",
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200630",
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/heads/main",
"26936381e619a01122ea33993e3cebc474496805 refs/heads/v1",
"e529a54fad10a936308b2220e05f7f00757f8e7c refs/heads/v3",
"bd82b85707bc13904e3526517677039d4da4a9bb refs/heads/very-ignored-branch",
"bd82b85707bc13904e3526517677039d4da4a9bb refs/tags/an-ignored-tag-too",
"26936381e619a01122ea33993e3cebc474496805 refs/tags/v2",
})
}
func TestPushReleases(t *testing.T) {
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
pushService := getTestPushService(t, "./push_test/action-cache-initial/", githubEnterpriseURL)
existingReleases := map[string]github.RepositoryRelease{}
existingAssets := map[int][]github.ReleaseAsset{}
existingAssetBodys := map[int]map[string][]byte{}
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases/tags/{tag}", func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
if value, ok := existingReleases[vars["tag"]]; ok {
test.ServeHTTPResponseFromObject(t, value, response)
} else {
response.WriteHeader(http.StatusNotFound)
}
}).Methods("GET")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases", func(response http.ResponseWriter, request *http.Request) {
body, err := ioutil.ReadAll(request.Body)
require.NoError(t, err)
var release *github.RepositoryRelease
err = json.Unmarshal(body, &release)
require.NoError(t, err)
release.ID = github.Int64(rand.Int63())
existingReleases[release.GetTagName()] = *release
test.ServeHTTPResponseFromObject(t, release, response)
}).Methods("POST")
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases/{id:[0-9]+}/assets", func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
releaseID, err := strconv.Atoi(vars["id"])
require.NoError(t, err)
test.ServeHTTPResponseFromObject(t, existingAssets[releaseID], response)
}).Methods("GET")
githubTestServer.HandleFunc("/api/uploads/repos/destination-repository-owner/destination-repository-name/releases/{id:[0-9]+}/assets", func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
releaseID, err := strconv.Atoi(vars["id"])
require.NoError(t, err)
assetName := request.URL.Query().Get("name")
asset := github.ReleaseAsset{
Name: github.String(assetName),
}
existingAssets[releaseID] = append(existingAssets[releaseID], asset)
if existingAssetBodys[releaseID] == nil {
existingAssetBodys[releaseID] = map[string][]byte{}
}
existingAssetBodys[releaseID][assetName], err = ioutil.ReadAll(request.Body)
require.NoError(t, err)
test.ServeHTTPResponseFromObject(t, asset, response)
}).Methods("POST")
err := pushService.pushReleases()
require.NoError(t, err)
}

Просмотреть файл

@ -0,0 +1 @@
ref: refs/heads/main

Просмотреть файл

@ -0,0 +1,7 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
[remote "enterprise"]
url = /tmp/codeql-action-sync-tests128816160/target
fetch = +refs/heads/*:refs/remotes/enterprise/*

Просмотреть файл

@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,9 @@
# pack-refs with: peeled fully-peeled sorted
b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1
e529a54fad10a936308b2220e05f7f00757f8e7c refs/remotes/origin/heads/v3
bd82b85707bc13904e3526517677039d4da4a9bb refs/remotes/origin/heads/very-ignored-branch
bd82b85707bc13904e3526517677039d4da4a9bb refs/remotes/origin/tags/an-ignored-tag-too
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/codeql-bundle-20200101
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/codeql-bundle-20200630
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/v2

Просмотреть файл

@ -0,0 +1 @@
b9f01aa2c50f49898d4c7845a66be8824499fe9d

Просмотреть файл

@ -0,0 +1 @@
26936381e619a01122ea33993e3cebc474496805

Просмотреть файл

@ -0,0 +1 @@
e529a54fad10a936308b2220e05f7f00757f8e7c

Просмотреть файл

@ -0,0 +1 @@
bd82b85707bc13904e3526517677039d4da4a9bb

Просмотреть файл

@ -0,0 +1 @@
This isn't really a CodeQL bundle either!

Просмотреть файл

@ -0,0 +1,4 @@
{
"tag_name": "codeql-bundle-20200101",
"target_commitish": "main"
}

Просмотреть файл

@ -0,0 +1 @@
This isn't really a CodeQL bundle!

Просмотреть файл

@ -0,0 +1,4 @@
{
"tag_name": "codeql-bundle-20200630",
"target_commitish": "main"
}

Просмотреть файл

@ -0,0 +1,3 @@
{
"login": "user"
}

Просмотреть файл

@ -0,0 +1,3 @@
{
"login": "destination-repository-owner"
}

Просмотреть файл

@ -1,6 +1,7 @@
package test
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -8,6 +9,9 @@ import (
"testing"
"github.com/gorilla/mux"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/stretchr/testify/require"
)
@ -40,6 +44,13 @@ func ServeHTTPResponseFromFile(t *testing.T, statusCode int, path string, respon
require.NoError(t, err)
}
func ServeHTTPResponseFromObject(t *testing.T, object interface{}, response http.ResponseWriter) {
bytes, err := json.Marshal(object)
require.NoError(t, err)
_, err = response.Write(bytes)
require.NoError(t, err)
}
func RequireFilesAreEqual(t *testing.T, expectedPath string, actualPath string) {
expectedData, err := ioutil.ReadFile(expectedPath)
require.NoError(t, err)
@ -47,3 +58,20 @@ func RequireFilesAreEqual(t *testing.T, expectedPath string, actualPath string)
require.NoError(t, err)
require.Equal(t, expectedData, actualData)
}
func CheckExpectedReferencesInRepository(t *testing.T, repositoryPath string, expectedReferences []string) {
localRepository, err := git.PlainOpen(repositoryPath)
require.NoError(t, err)
referenceIterator, err := localRepository.References()
require.NoError(t, err)
actualReferences := []string{}
err = referenceIterator.ForEach(func(reference *plumbing.Reference) error {
referenceString := reference.String()
if referenceString != "ref: refs/heads/master HEAD" {
actualReferences = append(actualReferences, referenceString)
}
return nil
})
require.NoError(t, err)
require.ElementsMatch(t, expectedReferences, actualReferences)
}