Skip to content
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

WIP: Use gitlab API to get the latest version #928

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c
github.com/tmc/dot v0.0.0-20210901225022-f9bc17da75c0
github.com/xanzy/go-gitlab v0.105.0
go.lsp.dev/uri v0.3.0
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,8 @@ github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIq
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8=
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
github.com/xanzy/go-gitlab v0.105.0 h1:3nyLq0ESez0crcaM19o5S//SvezOQguuIHZ3wgX64hM=
github.com/xanzy/go-gitlab v0.105.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
Expand Down
5 changes: 3 additions & 2 deletions pkg/checks/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
o := update.New(ctx)
o.GithubReleaseQuery = true
o.ReleaseMonitoringQuery = true
o.GitlabReleaseQuery = true
o.ErrorMessages = make(map[string]string)
o.Logger = log.New(log.Writer(), "wolfictl check update: ", log.LstdFlags|log.Lmsgprefix)
checkErrors := make(lint.EvalRuleErrors, 0)
Expand Down Expand Up @@ -121,8 +122,8 @@

// ensure a backend has been configured
if c.Update.Enabled {
if c.Update.ReleaseMonitor == nil && c.Update.GitHubMonitor == nil {
addCheckError(checkErrors, fmt.Errorf("config %s has update config enabled but no release-monitor or github backend monitor configured, see examples in this repository", file))
if c.Update.ReleaseMonitor == nil && c.Update.GitHubMonitor == nil && c.Update.GitLabMonitor == nil {

Check failure on line 125 in pkg/checks/update.go

View workflow job for this annotation

GitHub Actions / lint

c.Update.GitLabMonitor undefined (type "chainguard.dev/melange/pkg/config".Update has no field or method GitLabMonitor) (typecheck)
addCheckError(checkErrors, fmt.Errorf("config %s has update config enabled but no release-monitor , github backend monitor or gitlab api monitor configured, see examples in this repository", file))
continue
}
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/cli/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type options struct {
pullRequestTitle string
dryRun bool
githubReleaseQuery bool
gitlabReleaseQuery bool
releaseMonitoringQuery bool
useGitSign bool
createIssues bool
Expand All @@ -38,6 +39,7 @@ func cmdUpdate() *cobra.Command {

cmd.Flags().BoolVar(&o.dryRun, "dry-run", false, "prints proposed package updates rather than creating a pull request")
cmd.Flags().BoolVar(&o.githubReleaseQuery, "github-release-query", true, "query the GitHub graphql API for latest releases")
cmd.Flags().BoolVar(&o.gitlabReleaseQuery, "gitlab-release-query", true, "query the GitLab REST API for latest releases version")
cmd.Flags().BoolVar(&o.releaseMonitoringQuery, "release-monitoring-query", true, "query https://release-monitoring.org/ API for latest releases")
cmd.Flags().StringArrayVar(&o.packageNames, "package-name", []string{}, "Optional: provide a specific package name to check for updates rather than searching all packages in a repo URI")
cmd.Flags().StringVar(&o.pullRequestBaseBranch, "pull-request-base-branch", "main", "base branch to create a pull request against")
Expand Down Expand Up @@ -74,6 +76,7 @@ func (o options) UpdateCmd(ctx context.Context, repoURI string) error {
updateContext.PullRequestTitle = o.pullRequestTitle
updateContext.ReleaseMonitoringQuery = o.releaseMonitoringQuery
updateContext.GithubReleaseQuery = o.githubReleaseQuery
updateContext.GitlabReleaseQuery = o.gitlabReleaseQuery
updateContext.UseGitSign = o.useGitSign
updateContext.CreateIssues = o.createIssues
updateContext.IssueLabels = o.issueLabels
Expand Down
4 changes: 2 additions & 2 deletions pkg/update/githubReleases.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ func (o GitHubReleaseOptions) getLatestVersion(packageNameHash string, versionRe
return nil
}

func (o GitHubReleaseOptions) shouldSkipVersion(v string) bool {
func shouldSkipVersion(v string) bool {
invalid := []string{"alpha", "beta", "rc", "pre"}
for _, i := range invalid {
if strings.Contains(strings.ToLower(v), i) {
Expand Down Expand Up @@ -639,7 +639,7 @@ func (o GitHubReleaseOptions) prepareVersion(nameHash, v, id string) (string, er
v = strings.ReplaceAll(v, c.Update.VersionSeparator, ".")
}

if o.shouldSkipVersion(v) {
if shouldSkipVersion(v) {
return "", nil
}

Expand Down
7 changes: 1 addition & 6 deletions pkg/update/githubReleases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package update
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -226,11 +225,7 @@ func TestGitHubReleaseOptions_isVersionPreRelease(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.version, func(t *testing.T) {
o := GitHubReleaseOptions{
Logger: log.New(log.Writer(), "test: ", log.LstdFlags|log.Lmsgprefix),
}

assert.Equalf(t, tt.skip, o.shouldSkipVersion(tt.version), "isVersionPreRelease(%v)", tt.version)
assert.Equalf(t, tt.skip, shouldSkipVersion(tt.version), "isVersionPreRelease(%v)", tt.version)
})
}
}
Expand Down
223 changes: 223 additions & 0 deletions pkg/update/gitlabReleases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package update

import (
"errors"
"fmt"
"log"
"os"
"strings"

"chainguard.dev/melange/pkg/config"
"github.com/Masterminds/semver/v3"
"github.com/wolfi-dev/wolfictl/pkg/melange"
"github.com/xanzy/go-gitlab"
)

const (
gitlabBaseURL = "https://gitlab.com"
)

type GitLabReleaseOptions struct {
PackageConfigs map[string]*melange.Packages
gitlabClient *gitlab.Client

Check failure on line 22 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
Logger *log.Logger

ErrorMessages map[string]string
}
type VersionComit struct {
Version string
Commit string
}

func NewGitlabReleaseOptions(packageConfigs map[string]*melange.Packages) GitLabReleaseOptions {

token := os.Getenv("GITLAB_TOKEN")
if token == "" {
log.Fatalf("GITLAB_TOKEN environment variable not set")
}

client, err := gitlab.NewClient(token, gitlab.WithBaseURL(gitlabBaseURL))

Check failure on line 39 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
if err != nil {
log.Fatalf("Failed to create gitlab client: %v", err)
}

o := GitLabReleaseOptions{
PackageConfigs: packageConfigs,
gitlabClient: client,
Logger: log.New(log.Writer(), "wolfictl check update: ", log.LstdFlags|log.Lmsgprefix),
ErrorMessages: make(map[string]string),
}

return o
}

func (o GitLabReleaseOptions) getLatestGitLabVersions() (map[string]NewVersionResults, map[string]string, error) {
if len(o.PackageConfigs) == 0 {
return nil, o.ErrorMessages, errors.New("No package configs provided")
}

releaseRepoList, tagRepoList := o.getSeparateRepoLists()

latestVersionResults := make(map[string]NewVersionResults)

if len(releaseRepoList) > 0 {
o.Logger.Println("Checking for latest new releases")
for packageName, identifier := range releaseRepoList {
o.Logger.Printf("Checking for latest release on %s using identifier %s\n", packageName, identifier)
listReleaseOption := &gitlab.ListReleasesOptions{

Check failure on line 67 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
ListOptions: gitlab.ListOptions{

Check failure on line 68 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
PerPage: 20,
Page: 1,
},
}
releases, resp, err := o.gitlabClient.Releases.ListReleases(identifier, listReleaseOption)
if err != nil || resp.StatusCode != 200 {
o.ErrorMessages[packageName] = fmt.Sprintf("failed to list releases for %s: %v", packageName, err)
continue
}
if len(releases) == 0 {
o.ErrorMessages[packageName] = fmt.Sprintf("No releases found for %s", packageName)
continue
}

// filter out releases that match the ignore regex patterns and other filters
allReleaseList := []VersionComit{}
for _, release := range releases {
allReleaseList = append(allReleaseList, VersionComit{
Version: release.TagName,
Commit: release.Commit.ID,
})
}
v, c, err := prepareLatestVersion(allReleaseList, &o.PackageConfigs[packageName].Config)
if err != nil {
o.ErrorMessages[packageName] = fmt.Sprintf("Failed to prepare version for %s: %v", packageName, err)
continue
}
latestVersionResults[packageName] = NewVersionResults{
Version: v,
Commit: c,
}
}
}

if len(tagRepoList) > 0 {
o.Logger.Println("Checking for latest new tags")
listTagsOption := &gitlab.ListTagsOptions{

Check failure on line 105 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
ListOptions: gitlab.ListOptions{

Check failure on line 106 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

undefined: gitlab (typecheck)
PerPage: 50,
Page: 1,
OrderBy: "version",
},
}
for packageName, identifier := range tagRepoList {
o.Logger.Printf("Checking for latest tag on %s using projectID %s\n", packageName, identifier)
tags, resp, err := o.gitlabClient.Tags.ListTags(identifier, listTagsOption)
if err != nil || resp.StatusCode != 200 {
o.ErrorMessages[packageName] = fmt.Sprintf("Failed to list tags for %s: %v", packageName, err)
continue
}
if len(tags) == 0 {
o.ErrorMessages[packageName] = fmt.Sprintf("No tags found for %s", packageName)
continue
}

// filter out releases that match the ignore regex patterns and other filters
allTagsList := []VersionComit{}
for _, tag := range tags {
allTagsList = append(allTagsList, VersionComit{
Version: tag.Name,
Commit: tag.Commit.ID,
})
}
v, c, err := prepareLatestVersion(allTagsList, &o.PackageConfigs[packageName].Config)
if err != nil {
o.ErrorMessages[packageName] = fmt.Sprintf("Failed to prepare version for %s: %v", packageName, err)
continue
}

latestVersionResults[packageName] = NewVersionResults{
Version: v,
Commit: c,
}
}
}

return latestVersionResults, o.ErrorMessages, nil
}

func prepareLatestVersion(versionList []VersionComit, packageConfig *config.Configuration) (latestVersion, commit string, err error) {
if len(versionList) == 0 {
return "", "", errors.New("no versions found, empty list of tags/releases")
}

glm := packageConfig.Update.GitLabMonitor

Check failure on line 153 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

packageConfig.Update.GitLabMonitor undefined (type "chainguard.dev/melange/pkg/config".Update has no field or method GitLabMonitor) (typecheck)
if glm == nil {
return "", "", errors.New("no gitlab update configuration found for package")
}

var highestVersion *semver.Version
for _, vc := range versionList {
// Check if version should be ignored based on regex patterns
ignore, err := ignoreVersions(packageConfig.Update.IgnoreRegexPatterns, vc.Version)
if err != nil {
return "", "", err
}
if ignore {
continue
}

// filters
if shouldSkipVersion(vc.Version) || (glm.TagFilterPrefix != "" && !strings.HasPrefix(vc.Version, glm.TagFilterPrefix)) || (glm.TagFilterContains != "" && !strings.Contains(vc.Version, glm.TagFilterContains)) {
continue
}

// Parse the version for comparison
currentVersion, err := semver.NewVersion(vc.Version)
if err != nil {
continue // Skip versions that cannot be parsed
}

// Compare and find the highest version
if highestVersion == nil || currentVersion.GreaterThan(highestVersion) {
highestVersion = currentVersion
latestVersion = vc.Version
commit = vc.Commit
}
}

if highestVersion == nil {
return "", "", errors.New("no latest version found")
}

// Apply transformations
if glm.StripPrefix != "" {
latestVersion = strings.TrimPrefix(latestVersion, glm.StripPrefix)
}
if glm.StripSuffix != "" {
latestVersion = strings.TrimSuffix(latestVersion, glm.StripSuffix)
}

transformedVersion, err := transformVersion(packageConfig.Update, latestVersion)
if err != nil {
return "", "", fmt.Errorf("failed to apply version transforms to %s. error: %s", latestVersion, err)
}

return transformedVersion, commit, nil
}

func (o GitLabReleaseOptions) getSeparateRepoLists() (releaseRepoList, tagRepoList map[string]string) {
tagRepoList = make(map[string]string)
releaseRepoList = make(map[string]string)
for _, pc := range o.PackageConfigs {
if monitor := pc.Config.Update.GitLabMonitor; monitor != nil {

Check failure on line 212 in pkg/update/gitlabReleases.go

View workflow job for this annotation

GitHub Actions / lint

pc.Config.Update.GitLabMonitor undefined (type "chainguard.dev/melange/pkg/config".Update has no field or method GitLabMonitor) (typecheck)
identifire := monitor.Identifier
if monitor.UseTags {
tagRepoList[pc.Config.Package.Name] = identifire
} else {
releaseRepoList[pc.Config.Package.Name] = identifire
}
}
}

return releaseRepoList, tagRepoList
}
Loading
Loading