Skip to content

Commit

Permalink
dev: clean gen_github_action_config (#4847)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez committed Jun 26, 2024
1 parent 819ab93 commit bc3a56a
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 105 deletions.
2 changes: 1 addition & 1 deletion scripts/gen_github_action_config/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/golangci/golangci-lint/scripts/gen_github_action_config
go 1.21

require (
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc
github.com/shurcooL/githubv4 v0.0.0-20240429030203-be2daab69064
golang.org/x/oauth2 v0.21.0
)

Expand Down
4 changes: 2 additions & 2 deletions scripts/gen_github_action_config/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

226 changes: 124 additions & 102 deletions scripts/gen_github_action_config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,7 @@ import (
"golang.org/x/oauth2"
)

func main() {
if err := generate(context.Background()); err != nil {
log.Fatal(err)
}
}

func generate(ctx context.Context) error {
allReleases, err := fetchAllReleases(ctx)
if err != nil {
return fmt.Errorf("failed to fetch all releases: %w", err)
}

cfg, err := buildConfig(allReleases)
if err != nil {
return fmt.Errorf("failed to build config: %w", err)
}

if len(os.Args) != 2 { //nolint:gomnd
return fmt.Errorf("usage: go run .../main.go out-path.json")
}
outFile, err := os.Create(os.Args[1])
if err != nil {
return fmt.Errorf("failed to create output config file: %w", err)
}
defer outFile.Close()
enc := json.NewEncoder(outFile)
enc.SetIndent("", " ")
if err = enc.Encode(cfg); err != nil {
return fmt.Errorf("failed to json encode config: %w", err)
}

return nil
}
const noPatch = -1

type logInfo struct {
Warning string `json:",omitempty"`
Expand All @@ -72,79 +40,142 @@ type version struct {

func (v version) String() string {
ret := fmt.Sprintf("v%d.%d", v.major, v.minor)

if v.patch != noPatch {
ret += fmt.Sprintf(".%d", v.patch)
}

return ret
}

func (v *version) isAfterOrEq(vv *version) bool {
func (v version) isAfterOrEq(vv *version) bool {
if v.major != vv.major {
return v.major >= vv.major
}

if v.minor != vv.minor {
return v.minor >= vv.minor
}

return v.patch >= vv.patch
}

const noPatch = -1
type release struct {
TagName string
ReleaseAssets struct {
Nodes []releaseAsset
} `graphql:"releaseAssets(first: 50)"`
}

func parseVersion(s string) (*version, error) {
const vPrefix = "v"
if !strings.HasPrefix(s, vPrefix) {
return nil, fmt.Errorf("version should start with %q", vPrefix)
type releaseAsset struct {
DownloadURL string
}

func main() {
if err := generate(context.Background()); err != nil {
log.Fatal(err)
}
s = strings.TrimPrefix(s, vPrefix)
}

parts := strings.Split(s, ".")
func generate(ctx context.Context) error {
if len(os.Args) != 2 {
return fmt.Errorf("usage: go run .../main.go out-path.json")
}

var nums []int
for _, part := range parts {
num, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("failed to parse version part: %w", err)
}
nums = append(nums, num)
allReleases, err := fetchAllReleases(ctx)
if err != nil {
return fmt.Errorf("failed to fetch all releases: %w", err)
}

if len(nums) == 2 { //nolint:gomnd
return &version{major: nums[0], minor: nums[1], patch: noPatch}, nil
cfg, err := buildConfig(allReleases)
if err != nil {
return fmt.Errorf("failed to build config: %w", err)
}
if len(nums) == 3 { //nolint:gomnd
return &version{major: nums[0], minor: nums[1], patch: nums[2]}, nil

outFile, err := os.Create(os.Args[1])
if err != nil {
return fmt.Errorf("failed to create output config file: %w", err)
}

return nil, errors.New("invalid version format")
defer outFile.Close()

enc := json.NewEncoder(outFile)
enc.SetIndent("", " ")

if err = enc.Encode(cfg); err != nil {
return fmt.Errorf("failed to json encode config: %w", err)
}

return nil
}

func findLinuxAssetURL(ver *version, releaseAssets []releaseAsset) (string, error) {
pattern := fmt.Sprintf("golangci-lint-%d.%d.%d-linux-amd64.tar.gz", ver.major, ver.minor, ver.patch)
for _, relAsset := range releaseAssets {
if strings.HasSuffix(relAsset.DownloadURL, pattern) {
return relAsset.DownloadURL, nil
func fetchAllReleases(ctx context.Context) ([]release, error) {
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken == "" {
return nil, errors.New("no GITHUB_TOKEN environment variable")
}

client := githubv4.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken})))

var q struct {
Repository struct {
Releases struct {
Nodes []release
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"releases(first: 100, orderBy: { field: CREATED_AT, direction: DESC }, after: $releasesCursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}

vars := map[string]any{
"owner": githubv4.String("golangci"),
"name": githubv4.String("golangci-lint"),
"releasesCursor": (*githubv4.String)(nil),
}

var allReleases []release
for {
err := client.Query(ctx, &q, vars)
if err != nil {
return nil, fmt.Errorf("failed to fetch releases page from GitHub: %w", err)
}

releases := q.Repository.Releases
allReleases = append(allReleases, releases.Nodes...)

if !releases.PageInfo.HasNextPage {
break
}

vars["releasesCursor"] = githubv4.NewString(releases.PageInfo.EndCursor)
}
return "", fmt.Errorf("no matched asset url for pattern %q", pattern)

return allReleases, nil
}

func buildConfig(releases []release) (*actionConfig, error) {
versionToRelease := map[version]release{}

for _, rel := range releases {
ver, err := parseVersion(rel.TagName)
if err != nil {
return nil, fmt.Errorf("failed to parse release %s version: %w", rel.TagName, err)
}

if _, ok := versionToRelease[*ver]; ok {
return nil, fmt.Errorf("duplicate release %s", rel.TagName)
}

versionToRelease[*ver] = rel
}

maxPatchReleases := map[string]version{}

for ver := range versionToRelease {
key := fmt.Sprintf("v%d.%d", ver.major, ver.minor)

if mapVer, ok := maxPatchReleases[key]; !ok || ver.isAfterOrEq(&mapVer) {
maxPatchReleases[key] = ver
}
Expand All @@ -155,6 +186,7 @@ func buildConfig(releases []release) (*actionConfig, error) {

latestVersion := version{}
latestVersionConfig := versionConfig{}

for minorVersionedStr, maxPatchVersion := range maxPatchReleases {
if !maxPatchVersion.isAfterOrEq(&minAllowedVersion) {
minorVersionToConfig[minorVersionedStr] = versionConfig{
Expand All @@ -163,77 +195,67 @@ func buildConfig(releases []release) (*actionConfig, error) {
}
continue
}

maxPatchVersion := maxPatchVersion

assetURL, err := findLinuxAssetURL(&maxPatchVersion, versionToRelease[maxPatchVersion].ReleaseAssets.Nodes)
if err != nil {
return nil, fmt.Errorf("failed to find linux asset url for release %s: %w", maxPatchVersion, err)
}

minorVersionToConfig[minorVersionedStr] = versionConfig{
TargetVersion: maxPatchVersion.String(),
AssetURL: assetURL,
}

if maxPatchVersion.isAfterOrEq(&latestVersion) {
latestVersion = maxPatchVersion
latestVersionConfig.TargetVersion = maxPatchVersion.String()
latestVersionConfig.AssetURL = assetURL
}
}

minorVersionToConfig["latest"] = latestVersionConfig

return &actionConfig{MinorVersionToConfig: minorVersionToConfig}, nil
}

type release struct {
TagName string
ReleaseAssets struct {
Nodes []releaseAsset
} `graphql:"releaseAssets(first: 50)"`
}

type releaseAsset struct {
DownloadURL string
}
func findLinuxAssetURL(ver *version, releaseAssets []releaseAsset) (string, error) {
pattern := fmt.Sprintf("golangci-lint-%d.%d.%d-linux-amd64.tar.gz", ver.major, ver.minor, ver.patch)

func fetchAllReleases(ctx context.Context) ([]release, error) {
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken == "" {
return nil, errors.New("no GITHUB_TOKEN environment variable")
for _, relAsset := range releaseAssets {
if strings.HasSuffix(relAsset.DownloadURL, pattern) {
return relAsset.DownloadURL, nil
}
}
src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken})
httpClient := oauth2.NewClient(ctx, src)
client := githubv4.NewClient(httpClient)

var q struct {
Repository struct {
Releases struct {
Nodes []release
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"releases(first: 100, orderBy: { field: CREATED_AT, direction: DESC }, after: $releasesCursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
return "", fmt.Errorf("no matched asset url for pattern %q", pattern)
}

vars := map[string]any{
"owner": githubv4.String("golangci"),
"name": githubv4.String("golangci-lint"),
"releasesCursor": (*githubv4.String)(nil),
func parseVersion(s string) (*version, error) {
const vPrefix = "v"
if !strings.HasPrefix(s, vPrefix) {
return nil, fmt.Errorf("version %q should start with %q", s, vPrefix)
}

var allReleases []release
for {
err := client.Query(ctx, &q, vars)
parts := strings.Split(strings.TrimPrefix(s, vPrefix), ".")

var nums []int
for _, part := range parts {
num, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("failed to fetch releases page from GitHub: %w", err)
return nil, fmt.Errorf("failed to parse version %q: %w", s, err)
}
releases := q.Repository.Releases
allReleases = append(allReleases, releases.Nodes...)
if !releases.PageInfo.HasNextPage {
break
}
vars["releasesCursor"] = githubv4.NewString(releases.PageInfo.EndCursor)

nums = append(nums, num)
}

return allReleases, nil
switch len(nums) {
case 2:
return &version{major: nums[0], minor: nums[1], patch: noPatch}, nil
case 3:
return &version{major: nums[0], minor: nums[1], patch: nums[2]}, nil
default:
return nil, fmt.Errorf("invalid version format: %s", s)
}
}

0 comments on commit bc3a56a

Please sign in to comment.