@@ -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
481630func (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}
0 commit comments