github.com/kubri/kubri@v0.5.1-0.20240317001612-bda2aaef967e/integrations/apt/release.go (about)

     1  package apt
     2  
     3  import (
     4  	"crypto/md5"
     5  	"crypto/sha1"
     6  	"crypto/sha256"
     7  	"fmt"
     8  	"io/fs"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"golang.org/x/mod/semver"
    16  
    17  	"github.com/kubri/kubri/integrations/apt/deb"
    18  	"github.com/kubri/kubri/pkg/crypto/pgp"
    19  )
    20  
    21  func release(key *pgp.PrivateKey, algos CompressionAlgo, p []*Package) (string, error) {
    22  	dir, err := os.MkdirTemp("", "")
    23  	if err != nil {
    24  		return "", err
    25  	}
    26  
    27  	stable := make([]*Package, 0, len(p))
    28  	for _, pkg := range p {
    29  		if semver.Prerelease("v"+pkg.Version) == "" {
    30  			stable = append(stable, pkg)
    31  		}
    32  	}
    33  	if err = releaseSuite(key, algos, stable, "stable", dir); err != nil {
    34  		return "", err
    35  	}
    36  
    37  	// If not all packages are stable publish a separate `edge` dist.
    38  	if len(p) > len(stable) {
    39  		if err = releaseSuite(key, algos, p, "edge", dir); err != nil {
    40  			return "", err
    41  		}
    42  	}
    43  
    44  	if key != nil {
    45  		b, err := pgp.MarshalPublicKey(pgp.Public(key))
    46  		if err != nil {
    47  			return "", err
    48  		}
    49  		if err = os.WriteFile(filepath.Join(dir, "key.asc"), b, 0o600); err != nil {
    50  			return "", err
    51  		}
    52  	}
    53  
    54  	return dir, nil
    55  }
    56  
    57  func releaseSuite(key *pgp.PrivateKey, algos CompressionAlgo, p []*Package, suite, root string) error {
    58  	dir := filepath.Join(root, "dists", suite)
    59  
    60  	r := Releases{
    61  		Suite:      suite,
    62  		Codename:   suite,
    63  		Components: "main",
    64  	}
    65  
    66  	byArch := map[string][]*Package{}
    67  	for _, pkg := range p {
    68  		byArch[pkg.Architecture] = append(byArch[pkg.Architecture], pkg)
    69  	}
    70  
    71  	{
    72  		var as []string
    73  		for a, pkgs := range byArch {
    74  			if err := releaseArch(algos, pkgs, suite, a, dir); err != nil {
    75  				return err
    76  			}
    77  			as = append(as, a)
    78  		}
    79  		sort.Strings(as)
    80  		r.Architectures = strings.Join(as, " ")
    81  	}
    82  
    83  	dirfs := os.DirFS(dir)
    84  	err := fs.WalkDir(dirfs, ".", func(path string, d fs.DirEntry, err error) error {
    85  		if err != nil || d.IsDir() {
    86  			return err
    87  		}
    88  
    89  		b, err := fs.ReadFile(dirfs, path)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		r.MD5Sum += fmt.Sprintf("\n%x %d %s", md5.Sum(b), len(b), path)
    95  		r.SHA1 += fmt.Sprintf("\n%x %d %s", sha1.Sum(b), len(b), path)
    96  		r.SHA256 += fmt.Sprintf("\n%x %d %s", sha256.Sum256(b), len(b), path)
    97  
    98  		return nil
    99  	})
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	return writeRelease(dir, r, key)
   105  }
   106  
   107  func releaseArch(algos CompressionAlgo, p []*Package, suite, arch, root string) error {
   108  	r := Release{
   109  		Archive:      suite,
   110  		Suite:        suite,
   111  		Component:    "main",
   112  		Architecture: arch,
   113  	}
   114  
   115  	dir := filepath.Join(root, "main", "binary-"+arch)
   116  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   117  		return err
   118  	}
   119  	if err := writeFile(filepath.Join(dir, "Release"), r); err != nil {
   120  		return err
   121  	}
   122  	return writePackages(filepath.Join(dir, "Packages"), p, algos)
   123  }
   124  
   125  func writeFile(path string, v any) error {
   126  	f, err := os.Create(path)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	w, err := compress(filepath.Ext(path))(f)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	if err = deb.NewEncoder(w).Encode(v); err != nil {
   135  		return err
   136  	}
   137  	if err = w.Close(); err != nil {
   138  		return err
   139  	}
   140  	return f.Close()
   141  }
   142  
   143  func writePackages(path string, v []*Package, algos CompressionAlgo) error {
   144  	b, err := deb.Marshal(v)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	for _, ext := range compressionExtensions(algos) {
   150  		f, err := os.Create(path + ext)
   151  		if err != nil {
   152  			return err
   153  		}
   154  		w, err := compress(ext)(f)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		if _, err = w.Write(b); err != nil {
   159  			return err
   160  		}
   161  		if err = w.Close(); err != nil {
   162  			return err
   163  		}
   164  		if err = f.Close(); err != nil {
   165  			return err
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  var timeNow = time.Now //nolint:gochecknoglobals
   173  
   174  func writeRelease(dir string, r Releases, key *pgp.PrivateKey) error {
   175  	r.Date = timeNow().UTC()
   176  
   177  	b, err := deb.Marshal(r)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	if err = os.WriteFile(filepath.Join(dir, "Release"), b, 0o600); err != nil {
   183  		return err
   184  	}
   185  
   186  	if key != nil {
   187  		sig, err := pgp.Sign(key, b)
   188  		if err != nil {
   189  			return err
   190  		}
   191  		if err = os.WriteFile(filepath.Join(dir, "Release.gpg"), sig, 0o600); err != nil {
   192  			return err
   193  		}
   194  		b, err = pgp.SignText(key, b)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  
   200  	return os.WriteFile(filepath.Join(dir, "InRelease"), b, 0o600)
   201  }