First draft of check and out

This commit is contained in:
Ed
2018-12-16 19:17:03 -05:00
parent bf7e6d5e02
commit bc89091d9f
6 changed files with 219 additions and 363 deletions

View File

@@ -2,69 +2,65 @@ package resource
import ( import (
"sort" "sort"
"strconv"
"github.com/google/go-github/github"
"github.com/cppforlife/go-semi-semantic/version" "github.com/cppforlife/go-semi-semantic/version"
"github.com/xanzy/go-gitlab"
) )
type CheckCommand struct { type CheckCommand struct {
github GitHub gitlab GitLab
} }
func NewCheckCommand(github GitHub) *CheckCommand { func NewCheckCommand(gitlab GitLab) *CheckCommand {
return &CheckCommand{ return &CheckCommand{
github: github, gitlab: gitlab,
} }
} }
func (c *CheckCommand) Run(request CheckRequest) ([]Version, error) { 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 { if err != nil {
return []Version{}, err return []Version{}, err
} }
if len(releases) == 0 { if len(tags) == 0 {
return []Version{}, nil return []Version{}, nil
} }
var filteredReleases []*github.RepositoryRelease var filteredTags []*gitlab.Tag
// TODO: make ListTagsUntil work better with this
versionParser, err := newVersionParser(request.Source.TagFilter) versionParser, err := newVersionParser(request.Source.TagFilter)
if err != nil { if err != nil {
return []Version{}, err return []Version{}, err
} }
for _, release := range releases { for _, tag := range tags {
if request.Source.Drafts != *release.Draft { if _, err := version.NewVersionFromString(versionParser.parse(tag.Name)); err != nil {
continue continue
} }
// Should we skip this release if tag.Release == nil {
// 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 {
continue continue
} }
if release.TagName == nil { filteredTags = append(filteredTags, tag)
continue
}
if _, err := version.NewVersionFromString(versionParser.parse(*release.TagName)); err != nil {
continue
}
filteredReleases = append(filteredReleases, release)
} }
sort.Slice(filteredReleases, func(i, j int) bool { sort.Slice(filteredTags, func(i, j int) bool {
first, err := version.NewVersionFromString(versionParser.parse(*filteredReleases[i].TagName)) first, err := version.NewVersionFromString(versionParser.parse(filteredTags[i].Name))
if err != nil { if err != nil {
return true return true
} }
second, err := version.NewVersionFromString(versionParser.parse(*filteredReleases[j].TagName)) second, err := version.NewVersionFromString(versionParser.parse(filteredTags[j].Name))
if err != nil { if err != nil {
return false return false
} }
@@ -72,37 +68,32 @@ func (c *CheckCommand) Run(request CheckRequest) ([]Version, error) {
return first.IsLt(second) return first.IsLt(second)
}) })
if len(filteredReleases) == 0 { if len(filteredTags) == 0 {
return []Version{}, nil return []Version{}, nil
} }
latestRelease := filteredReleases[len(filteredReleases)-1] latestTag := filteredTags[len(filteredTags)-1]
if (request.Version == Version{}) { if (request.Version == Version{}) {
return []Version{ return []Version{
versionFromRelease(latestRelease), Version{Tag: latestTag.Name},
}, nil }, nil
} }
if *latestRelease.TagName == request.Version.Tag { if latestTag.Name == request.Version.Tag {
return []Version{}, nil return []Version{}, nil
} }
upToLatest := false upToLatest := false
reversedVersions := []Version{} reversedVersions := []Version{}
for _, release := range filteredReleases { for _, release := range filteredTags {
if !upToLatest { if !upToLatest {
if *release.Draft || *release.Prerelease { version := release.Name
id := *release.ID upToLatest = request.Version.Tag == version
upToLatest = request.Version.ID == strconv.Itoa(id)
} else {
version := *release.TagName
upToLatest = request.Version.Tag == version
}
} }
if upToLatest { 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 // current version was removed; start over from latest
reversedVersions = append( reversedVersions = append(
reversedVersions, reversedVersions,
versionFromRelease(filteredReleases[len(filteredReleases)-1]), Version{Tag: filteredTags[len(filteredTags)-1].Name},
) )
} }

336
gitlab.go
View File

@@ -3,10 +3,8 @@ package resource
import ( import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"io"
"net/http" "net/http"
"net/url" "net/url"
"os"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@@ -15,29 +13,21 @@ import (
"github.com/xanzy/go-gitlab" "github.com/xanzy/go-gitlab"
) )
//go:generate counterfeiter . GitHub //go:generate counterfeiter . GitLab
type gitlab interface { type GitLab interface {
ListReleases() ([]*gitlab.RepositoryRelease, error) ListTags() ([]*gitlab.Tag, error)
GetReleaseByTag(tag string) (*gitlab.RepositoryRelease, error) ListTagsUntil(tag_name string) ([]*gitlab.Tag, error)
GetRelease(id int) (*gitlab.RepositoryRelease, error) GetTag(tag_name string) (*gitlab.Tag, error)
CreateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) CreateTag(tag_name string, ref string) (*gitlab.Tag, error)
UpdateRelease(release gitlab.RepositoryRelease) (*gitlab.RepositoryRelease, error) CreateRelease(tag_name string, description string) (*gitlab.Release, error)
UpdateRelease(description string) (*gitlab.Release, error)
ListReleaseAssets(release gitlab.RepositoryRelease) ([]*gitlab.ReleaseAsset, error) UploadProjectFile(file string) (*gitlab.ProjectFile, 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 gitlabClient struct { type gitlabClient struct {
client *gitlab.Client client *gitlab.Client
owner string
repository string repository string
} }
@@ -52,221 +42,175 @@ func NewGitlabClient(source Source) (*gitlabClient, error) {
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
} }
if source.AccessToken != "" { client := gitlab.NewClient(httpClient, source.AccessToken)
if source.GitlabAPIURL != "" {
var err error var err error
httpClient, err = oauthClient(ctx, source) baseUrl, err := url.Parse(source.GitlabAPIURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} client.SetBaseURL(baseUrl.String())
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
} }
return &gitlabClient{ return &gitlabClient{
client: client, client: client,
owner: owner,
repository: source.Repository, repository: source.Repository,
}, nil }, nil
} }
func (g *gitlabClient) ListReleases() ([]*gitlab.RepositoryRelease, error) { func (g *gitlabClient) ListTags() ([]*gitlab.Tag, error) {
releases, res, err := g.client.Repositories.ListReleases(context.TODO(), g.owner, g.repository, nil) var allTags []*gitlab.Tag
if err != nil {
return []*gitlab.RepositoryRelease{}, err
}
err = res.Body.Close() opt := &gitlab.ListTagsOptions{
if err != nil { ListOptions: gitlab.ListOptions{
return nil, err PerPage: 100,
} Page: 1,
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,
}, },
file, OrderBy: gitlab.String("updated"),
) Sort: gitlab.String("desc"),
if err != nil {
return err
} }
return res.Body.Close() for {
} tags, res, err := g.client.Tags.ListTags(g.repository, opt)
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)
if err != nil { 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) { func (g *gitlabClient) ListTagsUntil(tag_name string) ([]*gitlab.Tag, error) {
opt := &gitlab.RepositoryContentGetOptions{Ref: tag} var allTags []*gitlab.Tag
u, res, err := g.client.Repositories.GetArchiveLink(context.TODO(), g.owner, g.repository, gitlab.Tarball, opt)
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 { if err != nil {
return nil, err return nil, err
} }
res.Body.Close()
return u, nil return tag, nil
} }
func (g *gitlabClient) GetZipballLink(tag string) (*url.URL, error) { func (g *gitlabClient) CreateTag(ref string, tag_name string) (*gitlab.Tag, error) {
opt := &gitlab.RepositoryContentGetOptions{Ref: tag} opt := &gitlab.CreateTagOptions{
u, res, err := g.client.Repositories.GetArchiveLink(context.TODO(), g.owner, g.repository, gitlab.Zipball, opt) 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 { if err != nil {
return nil, err return nil, err
} }
res.Body.Close()
return u, nil return tag, nil
} }
func (g *gitlabClient) GetRef(tag string) (*gitlab.Reference, error) { func (g *gitlabClient) CreateRelease(tag_name string, description string) (*gitlab.Release, error) {
ref, res, err := g.client.Git.GetRef(context.TODO(), g.owner, g.repository, "tags/"+tag) 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 { if err != nil {
return nil, err return nil, err
} }
res.Body.Close()
return ref, nil return release, nil
} }
func oauthClient(ctx context.Context, source Source) (*http.Client, error) { func (g *gitlabClient) UpdateRelease(tag_name string, description string) (*gitlab.Release, error) {
ts := oauth2.StaticTokenSource(&oauth2.Token{ opt := &gitlab.UpdateReleaseOptions{
AccessToken: source.AccessToken, Description: gitlab.String(description),
})
oauthClient := oauth2.NewClient(ctx, ts)
gitlabHTTPClient := &http.Client{
Transport: oauthClient.Transport,
} }
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
} }

9
go.mod
View File

@@ -1 +1,10 @@
module github.com/edtan/gitlab-release-resource 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
)

View File

@@ -4,21 +4,20 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/google/go-github/github" "github.com/xanzy/go-gitlab"
) )
type OutCommand struct { type OutCommand struct {
github GitHub gitlab GitLab
writer io.Writer writer io.Writer
} }
func NewOutCommand(github GitHub, writer io.Writer) *OutCommand { func NewOutCommand(gitlab GitLab, writer io.Writer) *OutCommand {
return &OutCommand{ return &OutCommand{
github: github, gitlab: gitlab,
writer: writer, writer: writer,
} }
} }
@@ -38,6 +37,11 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err
tag = request.Params.TagPrefix + tag tag = request.Params.TagPrefix + tag
targetCommitish, err = c.fileContents(filepath.Join(sourceDir, request.Params.CommitishPath))
if err != nil {
return OutResponse{}, err
}
var body string var body string
bodySpecified := false bodySpecified := false
if request.Params.BodyPath != "" { if request.Params.BodyPath != "" {
@@ -49,82 +53,39 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err
} }
} }
targetCommitish := "" release := &gitlab.RepositoryRelease{
if request.Params.CommitishPath != "" { Name: gitlab.String(name),
targetCommitish, err = c.fileContents(filepath.Join(sourceDir, request.Params.CommitishPath)) TagName: gitlab.String(tag),
if err != nil { Body: gitlab.String(body),
return OutResponse{}, err TargetCommitish: gitlab.String(targetCommitish),
}
} }
draft := request.Source.Drafts tagExists := false
prerelease := false existingTag, err := c.gitlab.GetTag(tag)
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()
if err != nil { if err != nil {
return OutResponse{}, err //TODO: improve the check to be based on the specific error
tagExists = true
} }
var existingRelease *github.RepositoryRelease // create the tag first, as next sections assume the tag exists
for _, e := range existingReleases { if !tagExists {
if e.TagName != nil && *e.TagName == tag { tag, err := c.gitlab.CreateTag(targetCommitish, 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)
if err != nil { if err != nil {
return OutResponse{}, err 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 { for _, fileGlob := range params.Globs {
matches, err := filepath.Glob(filepath.Join(sourceDir, fileGlob)) matches, err := filepath.Glob(filepath.Join(sourceDir, fileGlob))
if err != nil { if err != nil {
@@ -136,13 +97,17 @@ func (c *OutCommand) Run(sourceDir string, request OutRequest) (OutResponse, err
} }
for _, filePath := range matches { for _, filePath := range matches {
err := c.upload(release, filePath) projectFile, err := c.UploadProjectFile(filePath)
if err != nil { if err != nil {
return OutResponse{}, err return OutResponse{}, err
} }
fileLinks = append(fileLinks, projectFile.Markdown)
} }
} }
// update the release
release, err = c.gitlab.UpdateRelease(tag, fileLinks.Join("\n"))
return OutResponse{ return OutResponse{
Version: versionFromRelease(release), Version: versionFromRelease(release),
Metadata: metadataFromRelease(release, ""), Metadata: metadataFromRelease(release, ""),
@@ -157,45 +122,3 @@ func (c *OutCommand) fileContents(path string) (string, error) {
return strings.TrimSpace(string(contents)), nil 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
}

View File

@@ -7,8 +7,8 @@ type Source struct {
// Deprecated; use Owner instead // Deprecated; use Owner instead
User string `json:"user"` User string `json:"user"`
GitHubAPIURL string `json:"github_api_url"` GitlabAPIURL string `json:"gitlab_api_url"`
GitHubUploadsURL string `json:"github_uploads_url"` GitlabUploadsURL string `json:"gitlab_uploads_url"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
Drafts bool `json:"drafts"` Drafts bool `json:"drafts"`
PreRelease bool `json:"pre_release"` PreRelease bool `json:"pre_release"`

View File

@@ -2,9 +2,6 @@ package resource
import ( import (
"regexp" "regexp"
"strconv"
"github.com/google/go-github/github"
) )
var defaultTagFilter = "^v?([^v].*)" var defaultTagFilter = "^v?([^v].*)"
@@ -31,11 +28,3 @@ func (vp *versionParser) parse(tag string) string {
} }
return "" return ""
} }
func versionFromRelease(release *github.RepositoryRelease) Version {
if *release.Draft {
return Version{ID: strconv.Itoa(*release.ID)}
} else {
return Version{Tag: *release.TagName}
}
}