diff --git a/check_command.go b/check_command.go index 8c35f44..9c4a86e 100644 --- a/check_command.go +++ b/check_command.go @@ -2,69 +2,65 @@ package resource import ( "sort" - "strconv" - - "github.com/google/go-github/github" "github.com/cppforlife/go-semi-semantic/version" + "github.com/xanzy/go-gitlab" ) type CheckCommand struct { - github GitHub + gitlab GitLab } -func NewCheckCommand(github GitHub) *CheckCommand { +func NewCheckCommand(gitlab GitLab) *CheckCommand { return &CheckCommand{ - github: github, + gitlab: gitlab, } } func (c *CheckCommand) Run(request CheckRequest) ([]Version, error) { - releases, err := c.github.ListReleases() + var tags []*gitlab.Tag + var err error + if (request.Version == Version{}) { + tags, err = c.gitlab.ListTags() + } else { + tags, err = c.gitlab.ListTagsUntil(request.Version.Tag) + } + if err != nil { return []Version{}, err } - if len(releases) == 0 { + if len(tags) == 0 { return []Version{}, nil } - var filteredReleases []*github.RepositoryRelease + var filteredTags []*gitlab.Tag + // TODO: make ListTagsUntil work better with this versionParser, err := newVersionParser(request.Source.TagFilter) if err != nil { return []Version{}, err } - for _, release := range releases { - if request.Source.Drafts != *release.Draft { + for _, tag := range tags { + if _, err := version.NewVersionFromString(versionParser.parse(tag.Name)); err != nil { continue } - // Should we skip this release - // a- prerelease condition dont match our source config - // b- release condition match prerealse in github since github has true/false to describe release/prerelase - if request.Source.PreRelease != *release.Prerelease && request.Source.Release == *release.Prerelease { + if tag.Release == nil { continue } - if release.TagName == nil { - continue - } - if _, err := version.NewVersionFromString(versionParser.parse(*release.TagName)); err != nil { - continue - } - - filteredReleases = append(filteredReleases, release) + filteredTags = append(filteredTags, tag) } - sort.Slice(filteredReleases, func(i, j int) bool { - first, err := version.NewVersionFromString(versionParser.parse(*filteredReleases[i].TagName)) + sort.Slice(filteredTags, func(i, j int) bool { + first, err := version.NewVersionFromString(versionParser.parse(filteredTags[i].Name)) if err != nil { return true } - second, err := version.NewVersionFromString(versionParser.parse(*filteredReleases[j].TagName)) + second, err := version.NewVersionFromString(versionParser.parse(filteredTags[j].Name)) if err != nil { return false } @@ -72,37 +68,32 @@ func (c *CheckCommand) Run(request CheckRequest) ([]Version, error) { return first.IsLt(second) }) - if len(filteredReleases) == 0 { + if len(filteredTags) == 0 { return []Version{}, nil } - latestRelease := filteredReleases[len(filteredReleases)-1] + latestTag := filteredTags[len(filteredTags)-1] if (request.Version == Version{}) { return []Version{ - versionFromRelease(latestRelease), + Version{Tag: latestTag.Name}, }, nil } - if *latestRelease.TagName == request.Version.Tag { + if latestTag.Name == request.Version.Tag { return []Version{}, nil } upToLatest := false reversedVersions := []Version{} - for _, release := range filteredReleases { + for _, release := range filteredTags { if !upToLatest { - if *release.Draft || *release.Prerelease { - id := *release.ID - upToLatest = request.Version.ID == strconv.Itoa(id) - } else { - version := *release.TagName - upToLatest = request.Version.Tag == version - } + version := release.Name + upToLatest = request.Version.Tag == version } if upToLatest { - reversedVersions = append(reversedVersions, versionFromRelease(release)) + reversedVersions = append(reversedVersions, Version{Tag: release.Name}) } } @@ -110,7 +101,7 @@ func (c *CheckCommand) Run(request CheckRequest) ([]Version, error) { // current version was removed; start over from latest reversedVersions = append( reversedVersions, - versionFromRelease(filteredReleases[len(filteredReleases)-1]), + Version{Tag: filteredTags[len(filteredTags)-1].Name}, ) } diff --git a/gitlab.go b/gitlab.go index c60553b..2f79406 100644 --- a/gitlab.go +++ b/gitlab.go @@ -3,10 +3,8 @@ package resource import ( "crypto/tls" "errors" - "io" "net/http" "net/url" - "os" "golang.org/x/oauth2" @@ -15,29 +13,21 @@ import ( "github.com/xanzy/go-gitlab" ) -//go:generate counterfeiter . GitHub +//go:generate counterfeiter . GitLab -type gitlab interface { - ListReleases() ([]*gitlab.RepositoryRelease, error) - GetReleaseByTag(tag string) (*gitlab.RepositoryRelease, error) - GetRelease(id int) (*gitlab.RepositoryRelease, error) - CreateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) - UpdateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) - - ListReleaseAssets(release gitlab.RepositoryRelease) ([]*gitlab.ReleaseAsset, error) - UploadReleaseAsset(release gitlab.RepositoryRelease, name string, file *os.File) error - DeleteReleaseAsset(asset gitlab.ReleaseAsset) error - DownloadReleaseAsset(asset gitlab.ReleaseAsset) (io.ReadCloser, error) - - GetTarballLink(tag string) (*url.URL, error) - GetZipballLink(tag string) (*url.URL, error) - GetRef(tag string) (*gitlab.Reference, error) +type GitLab interface { + ListTags() ([]*gitlab.Tag, error) + ListTagsUntil(tag_name string) ([]*gitlab.Tag, error) + GetTag(tag_name string) (*gitlab.Tag, error) + CreateTag(tag_name string, ref string) (*gitlab.Tag, error) + CreateRelease(tag_name string, description string) (*gitlab.Release, error) + UpdateRelease(description string) (*gitlab.Release, error) + UploadProjectFile(file string) (*gitlab.ProjectFile, error) } type gitlabClient struct { client *gitlab.Client - owner string repository string } @@ -52,221 +42,175 @@ func NewGitlabClient(source Source) (*gitlabClient, error) { ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) } - if source.AccessToken != "" { + client := gitlab.NewClient(httpClient, source.AccessToken) + + if source.GitlabAPIURL != "" { var err error - httpClient, err = oauthClient(ctx, source) + baseUrl, err := url.Parse(source.GitlabAPIURL) if err != nil { return nil, err } - } - - client := gitlab.NewClient(httpClient) - - if source.gitlabAPIURL != "" { - var err error - client.BaseURL, err = url.Parse(source.gitlabAPIURL) - if err != nil { - return nil, err - } - - client.UploadURL, err = url.Parse(source.gitlabAPIURL) - if err != nil { - return nil, err - } - } - - if source.gitlabUploadsURL != "" { - var err error - client.UploadURL, err = url.Parse(source.gitlabUploadsURL) - if err != nil { - return nil, err - } - } - - owner := source.Owner - if source.User != "" { - owner = source.User + client.SetBaseURL(baseUrl.String()) } return &gitlabClient{ client: client, - owner: owner, repository: source.Repository, }, nil } -func (g *gitlabClient) ListReleases() ([]*gitlab.RepositoryRelease, error) { - releases, res, err := g.client.Repositories.ListReleases(context.TODO(), g.owner, g.repository, nil) - if err != nil { - return []*gitlab.RepositoryRelease{}, err - } +func (g *gitlabClient) ListTags() ([]*gitlab.Tag, error) { + var allTags []*gitlab.Tag - err = res.Body.Close() - if err != nil { - return nil, err - } - - return releases, nil -} - -func (g *gitlabClient) GetReleaseByTag(tag string) (*gitlab.RepositoryRelease, error) { - release, res, err := g.client.Repositories.GetReleaseByTag(context.TODO(), g.owner, g.repository, tag) - if err != nil { - return &gitlab.RepositoryRelease{}, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - return release, nil -} - -func (g *gitlabClient) GetRelease(id int) (*gitlab.RepositoryRelease, error) { - release, res, err := g.client.Repositories.GetRelease(context.TODO(), g.owner, g.repository, id) - if err != nil { - return &gitlab.RepositoryRelease{}, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - return release, nil -} - -func (g *gitlabClient) CreateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) { - createdRelease, res, err := g.client.Repositories.CreateRelease(context.TODO(), g.owner, g.repository, &release) - if err != nil { - return &gitlab.RepositoryRelease{}, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - return createdRelease, nil -} - -func (g *gitlabClient) UpdateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) { - if release.ID == nil { - return nil, errors.New("release did not have an ID: has it been saved yet?") - } - - updatedRelease, res, err := g.client.Repositories.EditRelease(context.TODO(), g.owner, g.repository, *release.ID, &release) - if err != nil { - return &gitlab.RepositoryRelease{}, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - return updatedRelease, nil -} - -func (g *gitlabClient) ListReleaseAssets(release gitlab.RepositoryRelease) ([]*gitlab.ReleaseAsset, error) { - assets, res, err := g.client.Repositories.ListReleaseAssets(context.TODO(), g.owner, g.repository, *release.ID, nil) - if err != nil { - return nil, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - return assets, nil -} - -func (g *gitlabClient) UploadReleaseAsset(release gitlab.RepositoryRelease, name string, file *os.File) error { - _, res, err := g.client.Repositories.UploadReleaseAsset( - context.TODO(), - g.owner, - g.repository, - *release.ID, - &gitlab.UploadOptions{ - Name: name, + opt := &gitlab.ListTagsOptions{ + ListOptions: gitlab.ListOptions{ + PerPage: 100, + Page: 1, }, - file, - ) - if err != nil { - return err + OrderBy: gitlab.String("updated"), + Sort: gitlab.String("desc"), } - return res.Body.Close() -} - -func (g *gitlabClient) DeleteReleaseAsset(asset gitlab.ReleaseAsset) error { - res, err := g.client.Repositories.DeleteReleaseAsset(context.TODO(), g.owner, g.repository, *asset.ID) - if err != nil { - return err - } - - return res.Body.Close() -} - -func (g *gitlabClient) DownloadReleaseAsset(asset gitlab.ReleaseAsset) (io.ReadCloser, error) { - res, redir, err := g.client.Repositories.DownloadReleaseAsset(context.TODO(), g.owner, g.repository, *asset.ID) - if err != nil { - return nil, err - } - - if redir != "" { - resp, err := http.Get(redir) + for { + tags, res, err := g.client.Tags.ListTags(g.repository, opt) if err != nil { - return nil, err + return []*gitlab.Tag{}, err } - return resp.Body, nil + if opt.Page >= res.TotalPages { + break + } + + opt.Page = res.NextPage + + allTags = append(allTags, tags...) } - return res, err + return allTags, nil } -func (g *gitlabClient) GetTarballLink(tag string) (*url.URL, error) { - opt := &gitlab.RepositoryContentGetOptions{Ref: tag} - u, res, err := g.client.Repositories.GetArchiveLink(context.TODO(), g.owner, g.repository, gitlab.Tarball, opt) +func (g *gitlabClient) ListTagsUntil(tag_name string) ([]*gitlab.Tag, error) { + var allTags []*gitlab.Tag + + opt := &gitlab.ListTagsOptions{ + ListOptions: gitlab.ListOptions{ + PerPage: 100, + Page: 1, + }, + OrderBy: gitlab.String("updated"), + Sort: gitlab.String("desc"), + } + + for { + tags, res, err := g.client.Tags.ListTags(g.repository, opt) + if err != nil { + return []*gitlab.Tag{}, err + } + + if opt.Page >= res.TotalPages { + break + } + + for i, tag := range tags { + if tag.Name == tag_name { + allTags = append(allTags, tags[:i]...) + break + } + } + + opt.Page = res.NextPage + allTags = append(allTags, tags...) + } + + return allTags, nil +} + +func (g *gitlabClient) GetTag(tag_name string) (*gitlab.Tag, error) { + tag, res, err := g.client.Tags.GetTag(g.repository, tag_name) + if err != nil { + return &gitlab.Tag{}, err + } + + err = res.Body.Close() if err != nil { return nil, err } - res.Body.Close() - return u, nil + + return tag, nil } -func (g *gitlabClient) GetZipballLink(tag string) (*url.URL, error) { - opt := &gitlab.RepositoryContentGetOptions{Ref: tag} - u, res, err := g.client.Repositories.GetArchiveLink(context.TODO(), g.owner, g.repository, gitlab.Zipball, opt) +func (g *gitlabClient) CreateTag(ref string, tag_name string) (*gitlab.Tag, error) { + opt := &gitlab.CreateTagOptions{ + TagName: gitlab.String(tag_name), + Ref: gitlab.String(ref), + Message: gitlab.String(tag_name), + } + + tag, res, err := g.client.Tags.CreateTag(g.repository, opt) + if err != nil { + return &gitlab.Tag{}, err + } + + err = res.Body.Close() if err != nil { return nil, err } - res.Body.Close() - return u, nil + + return tag, nil } -func (g *gitlabClient) GetRef(tag string) (*gitlab.Reference, error) { - ref, res, err := g.client.Git.GetRef(context.TODO(), g.owner, g.repository, "tags/"+tag) +func (g *gitlabClient) CreateRelease(tag_name string, description string) (*gitlab.Release, error) { + opt := &gitlab.CreateReleaseOptions{ + Description: gitlab.String(description), + } + + release, res, err := g.client.Tags.CreateRelease(g.repository, tag_name, opt) + if err != nil { + return &gitlab.Release{}, err + } + + // https://docs.gitlab.com/ce/api/tags.html#create-a-new-release + // returns 409 if release already exists + if res.StatusCode == http.StatusConflict { + return nil, errors.New("release already exists") + } + + err = res.Body.Close() if err != nil { return nil, err } - res.Body.Close() - return ref, nil + + return release, nil } -func oauthClient(ctx context.Context, source Source) (*http.Client, error) { - ts := oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: source.AccessToken, - }) - - oauthClient := oauth2.NewClient(ctx, ts) - - gitlabHTTPClient := &http.Client{ - Transport: oauthClient.Transport, +func (g *gitlabClient) UpdateRelease(tag_name string, description string) (*gitlab.Release, error) { + opt := &gitlab.UpdateReleaseOptions{ + Description: gitlab.String(description), } - return gitlabHTTPClient, nil + release, res, err := g.client.Tags.UpdateRelease(g.repository, tag_name, opt) + if err != nil { + return &gitlab.Release{}, err + } + + err = res.Body.Close() + if err != nil { + return nil, err + } + + return release, nil +} + +func (g *gitlabClient) UploadProjectFile(file string) (*gitlab.ProjectFile, error) { + projectFile, res, err := g.client.Projects.UploadFile(g.repository, file) + if err != nil { + return &gitlab.ProjectFile{}, err + } + + err = res.Body.Close() + if err != nil { + return nil, err + } + + return projectFile, nil } diff --git a/go.mod b/go.mod index fb5b865..f9151b0 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,10 @@ module github.com/edtan/gitlab-release-resource + +require ( + github.com/concourse/github-release-resource v1.0.0 + github.com/cppforlife/go-semi-semantic v0.0.0-20160921010311-576b6af77ae4 + github.com/google/go-github v17.0.0+incompatible + github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286 + github.com/xanzy/go-gitlab v0.12.0 + golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 +) diff --git a/out_command.go b/out_command.go index c5fb5eb..ab56028 100644 --- a/out_command.go +++ b/out_command.go @@ -4,21 +4,20 @@ import ( "fmt" "io" "io/ioutil" - "os" "path/filepath" "strings" - "github.com/google/go-github/github" + "github.com/xanzy/go-gitlab" ) type OutCommand struct { - github GitHub + gitlab GitLab writer io.Writer } -func NewOutCommand(github GitHub, writer io.Writer) *OutCommand { +func NewOutCommand(gitlab GitLab, writer io.Writer) *OutCommand { return &OutCommand{ - github: github, + gitlab: gitlab, writer: writer, } } @@ -38,6 +37,11 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err tag = request.Params.TagPrefix + tag + targetCommitish, err = c.fileContents(filepath.Join(sourceDir, request.Params.CommitishPath)) + if err != nil { + return OutResponse{}, err + } + var body string bodySpecified := false if request.Params.BodyPath != "" { @@ -49,82 +53,39 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err } } - targetCommitish := "" - if request.Params.CommitishPath != "" { - targetCommitish, err = c.fileContents(filepath.Join(sourceDir, request.Params.CommitishPath)) - if err != nil { - return OutResponse{}, err - } + release := &gitlab.RepositoryRelease{ + Name: gitlab.String(name), + TagName: gitlab.String(tag), + Body: gitlab.String(body), + TargetCommitish: gitlab.String(targetCommitish), } - draft := request.Source.Drafts - prerelease := false - if request.Source.PreRelease == true && request.Source.Release == false { - prerelease = request.Source.PreRelease - } - - release := &github.RepositoryRelease{ - Name: github.String(name), - TagName: github.String(tag), - Body: github.String(body), - Draft: github.Bool(draft), - Prerelease: github.Bool(prerelease), - TargetCommitish: github.String(targetCommitish), - } - - existingReleases, err := c.github.ListReleases() + tagExists := false + existingTag, err := c.gitlab.GetTag(tag) if err != nil { - return OutResponse{}, err + //TODO: improve the check to be based on the specific error + tagExists = true } - var existingRelease *github.RepositoryRelease - for _, e := range existingReleases { - if e.TagName != nil && *e.TagName == tag { - existingRelease = e - break - } - } - - if existingRelease != nil { - releaseAssets, err := c.github.ListReleaseAssets(*existingRelease) - if err != nil { - return OutResponse{}, err - } - - existingRelease.Name = github.String(name) - existingRelease.TargetCommitish = github.String(targetCommitish) - existingRelease.Draft = github.Bool(draft) - existingRelease.Prerelease = github.Bool(prerelease) - - if bodySpecified { - existingRelease.Body = github.String(body) - } else { - existingRelease.Body = nil - } - - for _, asset := range releaseAssets { - fmt.Fprintf(c.writer, "clearing existing asset: %s\n", *asset.Name) - - err := c.github.DeleteReleaseAsset(*asset) - if err != nil { - return OutResponse{}, err - } - } - - fmt.Fprintf(c.writer, "updating release %s\n", name) - - release, err = c.github.UpdateRelease(*existingRelease) - if err != nil { - return OutResponse{}, err - } - } else { - fmt.Fprintf(c.writer, "creating release %s\n", name) - release, err = c.github.CreateRelease(*release) + // create the tag first, as next sections assume the tag exists + if !tagExists { + tag, err := c.gitlab.CreateTag(targetCommitish, tag) if err != nil { return OutResponse{}, err } } + // create a new release + _, err = c.gitlab.CreateRelease(tag, "Auto-generated from Concourse GitLab Release Resource") + if err != nil { + // if 409 error occurs, this means the release already existed, so just skip to the next section (update the release) + if err.Error() != "release already exists" { + return OutResponse{}, err + } + } + + // upload files + var fileLinks []string for _, fileGlob := range params.Globs { matches, err := filepath.Glob(filepath.Join(sourceDir, fileGlob)) if err != nil { @@ -136,13 +97,17 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err } for _, filePath := range matches { - err := c.upload(release, filePath) + projectFile, err := c.UploadProjectFile(filePath) if err != nil { return OutResponse{}, err } + fileLinks = append(fileLinks, projectFile.Markdown) } } + // update the release + release, err = c.gitlab.UpdateRelease(tag, fileLinks.Join("\n")) + return OutResponse{ Version: versionFromRelease(release), Metadata: metadataFromRelease(release, ""), @@ -157,45 +122,3 @@ func (c *OutCommand) fileContents(path string) (string, error) { return strings.TrimSpace(string(contents)), nil } - -func (c *OutCommand) upload(release *github.RepositoryRelease, filePath string) error { - fmt.Fprintf(c.writer, "uploading %s\n", filePath) - - name := filepath.Base(filePath) - - var retryErr error - for i := 0; i < 10; i++ { - file, err := os.Open(filePath) - if err != nil { - return err - } - - defer file.Close() - - retryErr = c.github.UploadReleaseAsset(*release, name, file) - if retryErr == nil { - break - } - - assets, err := c.github.ListReleaseAssets(*release) - if err != nil { - return err - } - - for _, asset := range assets { - if asset.Name != nil && *asset.Name == name { - err = c.github.DeleteReleaseAsset(*asset) - if err != nil { - return err - } - break - } - } - } - - if retryErr != nil { - return retryErr - } - - return nil -} diff --git a/resources.go b/resources.go index b55f9eb..1d494fa 100644 --- a/resources.go +++ b/resources.go @@ -7,8 +7,8 @@ type Source struct { // Deprecated; use Owner instead User string `json:"user"` - GitHubAPIURL string `json:"github_api_url"` - GitHubUploadsURL string `json:"github_uploads_url"` + GitlabAPIURL string `json:"gitlab_api_url"` + GitlabUploadsURL string `json:"gitlab_uploads_url"` AccessToken string `json:"access_token"` Drafts bool `json:"drafts"` PreRelease bool `json:"pre_release"` diff --git a/versions.go b/versions.go index c148122..e4fa1b9 100644 --- a/versions.go +++ b/versions.go @@ -2,9 +2,6 @@ package resource import ( "regexp" - "strconv" - - "github.com/google/go-github/github" ) var defaultTagFilter = "^v?([^v].*)" @@ -31,11 +28,3 @@ func (vp *versionParser) parse(tag string) string { } return "" } - -func versionFromRelease(release *github.RepositoryRelease) Version { - if *release.Draft { - return Version{ID: strconv.Itoa(*release.ID)} - } else { - return Version{Tag: *release.TagName} - } -}