Skip to content

Commit e40566c

Browse files
authored
Merge pull request #1477 from mritunjaysharma394/fetch
adds git checkout fetch,update,test and yams the melange apkbuild yamls
2 parents 10a9185 + 9f1c1fa commit e40566c

6 files changed

Lines changed: 220 additions & 53 deletions

File tree

pkg/convert/apkbuild/apkbuild.go

Lines changed: 173 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"context"
55
"crypto/sha256"
66
"crypto/sha512"
7+
"encoding/json"
78
"fmt"
89
"io"
910
"net/http"
1011
"net/url"
1112
"os"
13+
"path"
1214
"path/filepath"
1315
"strconv"
1416
"strings"
@@ -17,6 +19,7 @@ import (
1719
rlhttp "chainguard.dev/melange/pkg/http"
1820
"chainguard.dev/melange/pkg/manifest"
1921
"github.com/chainguard-dev/clog"
22+
"github.com/chainguard-dev/yam/pkg/yam/formatted"
2023

2124
apkotypes "chainguard.dev/apko/pkg/build/types"
2225
"chainguard.dev/melange/pkg/config"
@@ -243,12 +246,135 @@ func (c Context) transitiveDependencyList(convertor ApkConvertor) []string {
243246
return dependencies
244247
}
245248

249+
// Helper function to check if a URL belongs to GitHub and extract the owner/repo
250+
func getGitHubIdentifierFromURL(packageURL string) (string, bool) {
251+
u, err := url.Parse(packageURL)
252+
if err != nil || u.Host != "github.com" {
253+
// Not a GitHub URL
254+
return "", false
255+
}
256+
// Extract the owner and repo from the URL path
257+
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
258+
if len(parts) < 2 {
259+
// Invalid GitHub URL format
260+
return "", false
261+
}
262+
owner, repo := parts[0], parts[1]
263+
return path.Join(owner, repo), true
264+
}
265+
266+
// Helper function to set up the update block based on the fetch source
267+
func (c *Context) setupUpdateBlock(packageURL string, packageVersion string, converter *ApkConvertor) {
268+
// Check if the package was fetched from GitHub
269+
if identifier, isGitHub := getGitHubIdentifierFromURL(packageURL); isGitHub {
270+
271+
// Enable GitHub monitoring
272+
converter.GeneratedMelangeConfig.Update = config.Update{
273+
Enabled: true,
274+
GitHubMonitor: &config.GitHubMonitor{
275+
Identifier: identifier, // Set the owner/repo identifier
276+
// To add logic to improve this check
277+
// StripPrefix: "v", // Strip "v" from tags like "v1.2.3"
278+
// TagFilterPrefix: "v", // Filter tags with a "v" prefix
279+
},
280+
}
281+
} else {
282+
// Fallback to release-monitoring.org if it's not a GitHub package
283+
converter.GeneratedMelangeConfig.Update = config.Update{
284+
Enabled: true,
285+
ReleaseMonitor: &config.ReleaseMonitor{
286+
Identifier: 12345, // Example ID, replace this with actual logic to get the ID
287+
},
288+
}
289+
}
290+
}
291+
292+
// Helper function to fetch the commit hash for a specific tag from a GitHub repository
293+
func getCommitForTagFromGitHub(repoURL, tag string) (string, error) {
294+
// Parse the repository URL to extract the owner and repo name
295+
u, err := url.Parse(repoURL)
296+
if err != nil {
297+
return "", fmt.Errorf("invalid repository URL: %w", err)
298+
}
299+
300+
// Assume the URL is in the form of "https://github.com/owner/repo"
301+
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
302+
if len(parts) != 2 {
303+
return "", fmt.Errorf("invalid GitHub repository URL format")
304+
}
305+
owner, repo := parts[0], parts[1]
306+
307+
// Build the API URL for fetching the tags in the repository
308+
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/refs/tags/%s", owner, repo, tag)
309+
310+
// Send the request to the GitHub API
311+
resp, err := http.Get(apiURL)
312+
if err != nil {
313+
return "", fmt.Errorf("error fetching tag information: %w", err)
314+
}
315+
defer resp.Body.Close()
316+
317+
// Parse the JSON response
318+
var tagResponse struct {
319+
Object struct {
320+
Sha string `json:"sha"`
321+
} `json:"object"`
322+
}
323+
if err := json.NewDecoder(resp.Body).Decode(&tagResponse); err != nil {
324+
return "", fmt.Errorf("error parsing GitHub response: %w", err)
325+
}
326+
327+
// Return the commit SHA associated with the tag
328+
return tagResponse.Object.Sha, nil
329+
}
330+
246331
// add pipeline fetch steps, validate checksums and generate mconvert expected sha
247-
func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) error {
332+
func (c *Context) buildFetchStep(ctx context.Context, converter ApkConvertor) error {
248333
log := clog.FromContext(ctx)
249334

250335
apkBuild := converter.Apkbuild
251336

337+
// Check if the package URL is available
338+
if apkBuild.Url != "" {
339+
// Check if the URL belongs to GitHub
340+
if _, isGitHub := getGitHubIdentifierFromURL(apkBuild.Url); isGitHub {
341+
// GitHub URL, proceed with git-checkout pipeline
342+
_, err := url.ParseRequestURI(apkBuild.Url)
343+
if err != nil {
344+
return fmt.Errorf("parsing URI %s: %w", apkBuild.Url, err)
345+
}
346+
347+
// Fetch the commit hash for the package version tag
348+
expectedCommit, err := getCommitForTagFromGitHub(apkBuild.Url, apkBuild.Pkgver) // Using the package version as the tag
349+
if err != nil {
350+
return fmt.Errorf("error fetching commit for tag: %w", err)
351+
}
352+
353+
// Create a basic git-checkout pipeline
354+
pipeline := config.Pipeline{
355+
Uses: "melange/git-checkout",
356+
With: map[string]string{
357+
"repository": apkBuild.Url,
358+
"tag": "${{package.version}}", // The version as the tag or branch reference
359+
"expected-commit": expectedCommit, // Use the dynamically fetched commit
360+
},
361+
}
362+
363+
// Add the pipeline to the generated configuration
364+
converter.GeneratedMelangeConfig.Pipeline = append(converter.GeneratedMelangeConfig.Pipeline, pipeline)
365+
366+
// Set up the update block based on the package source (GitHub or release-monitoring)
367+
c.setupUpdateBlock(apkBuild.Url, apkBuild.Pkgver, &converter)
368+
369+
log.Infof("Using git-checkout pipeline for package %s with repository %s and expected commit %s", converter.Pkgname, apkBuild.Url, expectedCommit)
370+
return nil
371+
} else {
372+
log.Infof("Package URL is not from GitHub, falling back to tar.gz method")
373+
}
374+
}
375+
376+
// Fallback to fetching tar.gz if URL is missing or not GitHub
377+
log.Infof("No valid GitHub URL found for package %s, using tar.gz method", converter.Pkgname)
252378
if len(apkBuild.Source) == 0 {
253379
log.Infof("skip adding pipeline for package %s, no source URL found", converter.Pkgname)
254380
return nil
@@ -257,7 +383,7 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
257383
return fmt.Errorf("no package version")
258384
}
259385

260-
// there can be multiple sources, let's add them all so, it's easier for users to remove from generated files if not needed
386+
// Loop over sources and add fetch steps for tarball
261387
for _, source := range apkBuild.Source {
262388
location := source.Location
263389

@@ -266,9 +392,14 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
266392
return fmt.Errorf("parsing URI %s: %w", location, err)
267393
}
268394

269-
req, _ := http.NewRequestWithContext(ctx, "GET", location, nil)
270-
resp, err := c.Client.Do(req)
395+
// Create a request using standard http.NewRequestWithContext
396+
req, err := http.NewRequestWithContext(ctx, "GET", location, nil)
397+
if err != nil {
398+
return fmt.Errorf("creating request for URI %s: %w", location, err)
399+
}
271400

401+
// Use RLHTTPClient to send the request with rate limiting
402+
resp, err := c.Client.Do(req)
272403
if err != nil {
273404
return fmt.Errorf("failed getting URI %s: %w", location, err)
274405
}
@@ -287,7 +418,7 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
287418

288419
var expectedSha string
289420
if !failed {
290-
// validate the source we are using matches the correct sha512 in the APKBIULD
421+
// Validate the source matches the sha512 in the APKBUILD
291422
validated := false
292423
for _, shas := range apkBuild.Sha512sums {
293424
if shas.Source == source.Filename {
@@ -300,7 +431,7 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
300431
}
301432
}
302433

303-
// now generate the 256 sha we need for a mconvert config
434+
// Now generate the 256 sha for the convert config
304435
if !validated {
305436
expectedSha = "SHA512 DOES NOT MATCH SOURCE - VALIDATE MANUALLY"
306437
log.Infof("source %s expected sha512 do not match!", source.Filename)
@@ -314,6 +445,7 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
314445
expectedSha = "FIXME - SOURCE URL NOT VALID"
315446
}
316447

448+
// Fallback to using the fetch pipeline with tarball location
317449
pipeline := config.Pipeline{
318450
Uses: "fetch",
319451
With: map[string]string{
@@ -322,6 +454,7 @@ func (c Context) buildFetchStep(ctx context.Context, converter ApkConvertor) err
322454
},
323455
}
324456
converter.GeneratedMelangeConfig.Pipeline = append(converter.GeneratedMelangeConfig.Pipeline, pipeline)
457+
325458
}
326459

327460
return nil
@@ -334,6 +467,22 @@ func (c ApkConvertor) mapconvert() {
334467
c.GeneratedMelangeConfig.Package.Version = c.Apkbuild.Pkgver
335468
c.GeneratedMelangeConfig.Package.Epoch = 0
336469

470+
// Add the version-check test block
471+
testPipeline := config.Pipeline{
472+
Name: "Verify " + c.Apkbuild.Pkgname + " installation, please improve the test as needed",
473+
Runs: fmt.Sprintf("%s --version || exit 1", c.Apkbuild.Pkgname), // Basic version check
474+
}
475+
476+
// Add the test block to the generated config
477+
testBlock := &config.Test{
478+
Pipeline: []config.Pipeline{
479+
testPipeline,
480+
},
481+
}
482+
483+
// Add the test block to the configuration
484+
c.GeneratedMelangeConfig.Test = testBlock
485+
337486
copyright := config.Copyright{
338487
License: c.Apkbuild.License,
339488
}
@@ -479,36 +628,38 @@ func contains(s []string, str string) bool {
479628
}
480629

481630
func (c ApkConvertor) write(ctx context.Context, orderNumber, outdir string) error {
482-
actual, err := yaml.Marshal(&c.GeneratedMelangeConfig)
483-
if err != nil {
484-
return fmt.Errorf("marshalling mconvert configuration: %w", err)
485-
}
486-
631+
// Ensure output directory exists
487632
if _, err := os.Stat(outdir); os.IsNotExist(err) {
488633
err = os.MkdirAll(outdir, os.ModePerm)
489634
if err != nil {
490635
return fmt.Errorf("creating output directory %s: %w", outdir, err)
491636
}
492637
}
493638

494-
// write the mconvert config, prefix with our guessed order along with zero to help users easily rename / reorder generated files
495-
mconvertFile := filepath.Join(outdir, orderNumber+"0-"+c.Apkbuild.Pkgname+".yaml")
496-
f, err := os.Create(mconvertFile)
639+
// Prepare the file path for the YAML output
640+
manifestFile := filepath.Join(outdir, fmt.Sprintf("%s0-%s.yaml", orderNumber, c.Apkbuild.Pkgname))
641+
f, err := os.Create(manifestFile)
497642
if err != nil {
498-
return fmt.Errorf("creating file %s: %w", mconvertFile, err)
643+
return fmt.Errorf("creating file %s: %w", manifestFile, err)
499644
}
500645
defer f.Close()
501646

502-
_, err = f.WriteString(fmt.Sprintf("# Generated from %s\n", c.GeneratedMelangeConfig.GeneratedFromComment))
503-
if err != nil {
504-
return fmt.Errorf("creating writing to file %s: %w", mconvertFile, err)
647+
// Write the initial comment to the YAML file
648+
if _, err := f.WriteString(fmt.Sprintf("# Generated from %s\n", c.GeneratedMelangeConfig.GeneratedFromComment)); err != nil {
649+
return fmt.Errorf("writing to file %s: %w", manifestFile, err)
505650
}
506651

507-
_, err = f.WriteString(string(actual))
508-
if err != nil {
509-
return fmt.Errorf("creating writing to file %s: %w", mconvertFile, err)
652+
// Marshal the configuration into a YAML node for formatting
653+
var n yaml.Node
654+
if err := n.Encode(c.GeneratedMelangeConfig); err != nil {
655+
return fmt.Errorf("encoding YAML to node: %w", err)
656+
}
657+
658+
// Use the formatted YAML encoder to write the YAML data
659+
if err := formatted.NewEncoder(f).AutomaticConfig().Encode(&n); err != nil {
660+
return fmt.Errorf("encoding formatted YAML to file %s: %w", manifestFile, err)
510661
}
511662

512-
clog.FromContext(ctx).Infof("Generated melange config: %s", mconvertFile)
663+
clog.FromContext(ctx).Infof("Generated melange config with update block: %s", manifestFile)
513664
return nil
514665
}

pkg/convert/apkbuild/apkbuild_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,14 @@ func Test_context_mapconvert(t *testing.T) {
281281

282282
assert.NoError(t, err)
283283

284+
// Ensure that the generated configuration contains the test block
285+
assert.NotNil(t, config.Test, "Test block should be created in the generated config")
286+
// assert.Nil(t, config.Test.Environment, "Expected environment to be nil or empty")
287+
288+
// Ensure that the test block contains the version-check pipeline
289+
assert.NotEmpty(t, config.Test.Pipeline, "Test pipeline should not be empty")
290+
291+
// Check that the generated YAML matches the expected YAML
284292
assert.YAMLEqf(t, string(expected), string(actual), "generated convert yaml not the same as expected")
285293
})
286294
}

pkg/convert/apkbuild/testdata/no_sub_packages.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@ package:
55
description: test package description
66
copyright:
77
- license: MIT
8+
89
environment: {}
10+
911
pipeline:
1012
- uses: autoconf/configure
13+
1114
- uses: autoconf/make
15+
1216
- uses: autoconf/make-install
17+
1318
- uses: strip
19+
20+
test:
21+
environment: {}
22+
pipeline:
23+
- name: Verify test-pkg installation, please improve the test as needed
24+
runs: test-pkg --version || exit 1
Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,7 @@
11
# Maintainer: Natanael Copa <ncopa@alpinelinux.org>
2-
pkgname=libxext
3-
pkgver=1.3.4
4-
pkgrel=1
5-
pkgdesc="X11 miscellaneous extensions library"
6-
url="rate_limitted_http://xorg.freedesktop.org/"
7-
arch="all"
8-
license="MIT"
9-
depends_dev="libxau-dev"
10-
makedepends="$depends_dev libx11-dev xorgproto util-macros xmlto"
11-
subpackages="$pkgname-dev $pkgname-doc"
12-
options="!check"
13-
source="https://www.x.org/releases/individual/lib/libXext-$pkgver.tar.bz2
14-
"
15-
2+
|-
3+
pkgname=libxext pkgver=1.3.4 pkgrel=1 pkgdesc="X11 miscellaneous extensions library" url="rate_limitted_http://xorg.freedesktop.org/" arch="all" license="MIT" depends_dev="libxau-dev" makedepends="$depends_dev libx11-dev xorgproto util-macros xmlto" subpackages="$pkgname-dev $pkgname-doc" options="!check" source="https://www.x.org/releases/individual/lib/libXext-$pkgver.tar.bz2 "
164
builddir="$srcdir"/libXext-$pkgver
17-
18-
build() {
19-
./configure \
20-
--build=$CBUILD \
21-
--host=$CHOST \
22-
--prefix=/usr \
23-
--sysconfdir=/etc \
24-
--with-xmlto \
25-
--without-fop
26-
make
27-
}
28-
29-
package() {
30-
make DESTDIR="$pkgdir" install
31-
}
32-
33-
sha512sums="09146397d95f80c04701be1cc0a9c580ab5a085842ac31d17dfb6d4c2e42b4253b89cba695e54444e520be359883a76ffd02f42484c9e2ba2c33a5a40c29df4a libXext-1.3.4.tar.bz2"
5+
build() { ./configure \ --build=$CBUILD \ --host=$CHOST \ --prefix=/usr \ --sysconfdir=/etc \ --with-xmlto \ --without-fop make }
6+
package() { make DESTDIR="$pkgdir" install }
7+
sha512sums="09146397d95f80c04701be1cc0a9c580ab5a085842ac31d17dfb6d4c2e42b4253b89cba695e54444e520be359883a76ffd02f42484c9e2ba2c33a5a40c29df4a libXext-1.3.4.tar.bz2"

0 commit comments

Comments
 (0)