feat(updater): add 'checkout' and 'upload_artifact' steps
Adds a new `checkout` step intended to replace `git checkout` by actually checking out the correct revision when running commands. Adds a new `upload_artifact` step that uploads an artifact to a package specific prefix. Primarily intended for supporting Go dependency archives, but could also be used for anything. Added `net-vpn/tailscale` using this new functionality.
This commit is contained in:
parent
f49156839f
commit
09d39ad36d
21 changed files with 659 additions and 186 deletions
|
@ -4,6 +4,8 @@ An automated ebuild updating system.
|
|||
|
||||
## Usage
|
||||
|
||||
**Requirements**: `docker`.
|
||||
|
||||
Create an `updater.yml` file in the root of your repository. Create a
|
||||
key for each package that should be managed by the updater.
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
|
||||
logger "github.com/charmbracelet/log"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config/packages"
|
||||
"github.com/jaredallard/overlay/.tools/internal/ebuild"
|
||||
updater "github.com/jaredallard/overlay/.tools/internal/resolver"
|
||||
"github.com/jaredallard/overlay/.tools/internal/steps"
|
||||
|
@ -42,8 +43,9 @@ var log = logger.NewWithOptions(os.Stderr, logger.Options{
|
|||
|
||||
// rootCmd is the root command used by cobra
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "updater",
|
||||
Use: "updater <package>",
|
||||
Short: "updater automatically updates ebuilds",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: entrypoint,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
@ -88,12 +90,27 @@ func getDefaultSteps() []steps.Step {
|
|||
func entrypoint(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
cfg, err := config.LoadConfig("updater.yml")
|
||||
cfg, err := config.LoadConfig(".updater.yml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
cfg = &config.Config{}
|
||||
}
|
||||
|
||||
for _, ce := range cfg {
|
||||
pkgs, err := packages.LoadPackages("packages.yml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load packages: %w", err)
|
||||
}
|
||||
|
||||
// If we have exactly one argument, we only want to update that
|
||||
// package.
|
||||
if len(args) == 1 {
|
||||
pkgName := args[0]
|
||||
if _, ok := pkgs[pkgName]; !ok {
|
||||
return fmt.Errorf("package not found in packages.yml: %s", pkgName)
|
||||
}
|
||||
pkgs = packages.List{pkgName: pkgs[pkgName]}
|
||||
}
|
||||
|
||||
for _, ce := range pkgs {
|
||||
log.With("name", ce.Name).With("resolver", ce.Resolver).Info("checking for updates")
|
||||
|
||||
ebuildDir := ce.Name
|
||||
|
@ -132,6 +149,7 @@ func entrypoint(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
executor := steps.NewExecutor(log, ceSteps, &steps.ExecutorInput{
|
||||
Config: cfg,
|
||||
OriginalEbuild: e,
|
||||
ExistingEbuilds: ebuilds,
|
||||
LatestVersion: latestVersion,
|
||||
|
|
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/docker/docker v25.0.3+incompatible
|
||||
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/minio/minio-go/v7 v7.0.70
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -23,17 +24,23 @@ require (
|
|||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
|
@ -41,6 +48,7 @@ require (
|
|||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect
|
||||
go.opentelemetry.io/otel v1.23.1 // indirect
|
||||
|
@ -48,9 +56,12 @@ require (
|
|||
go.opentelemetry.io/otel/metric v1.23.1 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.23.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.23.1 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
pault.ag/go/topsort v0.1.1 // indirect
|
||||
)
|
||||
|
|
|
@ -25,6 +25,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
|
|||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca h1:sWNMfkKG8GW1pGUyNlbsWq6f04pFgcsomY+Fly8XdB4=
|
||||
github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca/go.mod h1:Ar+qogA+fkjeUR18xJfFzrMSjfs/sCPO+yjVvhXpyEg=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
|
@ -38,18 +40,27 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
|
@ -61,6 +72,10 @@ github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
|
||||
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
|
@ -81,6 +96,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
|
@ -114,8 +131,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
|
@ -124,8 +141,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -135,9 +152,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
|
@ -162,6 +180,8 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7
|
|||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
|
|
|
@ -21,27 +21,32 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/steps"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Resolver is the resolver to use to determine if an update is available.
|
||||
type Resolver string
|
||||
// Config represents the configuration for the updater itself.
|
||||
type Config struct {
|
||||
// StepConfig contains configuration for steps that support
|
||||
// updater-wide configuration.
|
||||
StepConfig struct {
|
||||
// UploadArtifact contains the configuration for where the
|
||||
// 'upload_artifact' step should upload the artifact to.
|
||||
UploadArtifact struct {
|
||||
// Bucket is the S3 bucket to upload the artifact to.
|
||||
Bucket string `yaml:"bucket"`
|
||||
|
||||
// Contains the supported resolvers.
|
||||
const (
|
||||
// GitResolver is the git resolver.
|
||||
GitResolver Resolver = "git"
|
||||
// Host is the host of the S3 bucket.
|
||||
Host string `yaml:"host"`
|
||||
|
||||
// APTResolver is a version resolver powered by an APT repository.
|
||||
APTResolver Resolver = "apt"
|
||||
)
|
||||
|
||||
// Config is the configuration for the updater.
|
||||
type Config map[string]Ebuild
|
||||
// Prefix is the prefix to use for the artifact when storing it in
|
||||
// S3.
|
||||
Prefix string `yaml:"prefix"`
|
||||
} `yaml:"upload_artifact"`
|
||||
} `yaml:"step_config"`
|
||||
}
|
||||
|
||||
// LoadConfig loads the updater configuration from the provided path.
|
||||
func LoadConfig(path string) (Config, error) {
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open config file: %w", err)
|
||||
|
@ -52,102 +57,5 @@ func LoadConfig(path string) (Config, error) {
|
|||
if err := yaml.NewDecoder(f).Decode(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode config: %w", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Ebuild is an ebuild that should be updated by the updater.
|
||||
type Ebuild struct {
|
||||
// Name of the ebuild. This is only set when loaded from the config.
|
||||
// It is a readonly field.
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// Resolver to use to determine if an update is available.
|
||||
// Currently only "git" is supported.
|
||||
Resolver Resolver `yaml:"resolver"`
|
||||
|
||||
// GitOptions is the options for the git resolver.
|
||||
GitOptions GitOptions `yaml:"options"`
|
||||
|
||||
// APTOptions is the options for the APT resolver.
|
||||
APTOptions APTOptions `yaml:"options"`
|
||||
|
||||
// Steps are the steps to use to update the ebuild, if not set it
|
||||
// defaults to a copy the existing ebuild and regenerate the manifest.
|
||||
Steps steps.Steps `yaml:"steps"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals the ebuild configuration from YAML while
|
||||
// converting options into the appropriate type for the provided
|
||||
// resolver.
|
||||
func (e *Ebuild) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var raw struct {
|
||||
Resolver Resolver `yaml:"resolver"`
|
||||
Options yaml.Node `yaml:"options"`
|
||||
Steps steps.Steps
|
||||
}
|
||||
|
||||
if err := unmarshal(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Resolver = raw.Resolver
|
||||
e.Steps = raw.Steps
|
||||
|
||||
switch e.Resolver {
|
||||
case GitResolver:
|
||||
if err := raw.Options.Decode(&e.GitOptions); err != nil {
|
||||
return fmt.Errorf("failed to decode git options: %w", err)
|
||||
}
|
||||
case APTResolver:
|
||||
if err := raw.Options.Decode(&e.APTOptions); err != nil {
|
||||
return fmt.Errorf("failed to decode APT options: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported resolver: %s", e.Resolver)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals the configuration from YAML while carrying
|
||||
// over the ebuild name into the ebuild struct.
|
||||
func (c Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var ebuilds map[string]Ebuild
|
||||
|
||||
if err := unmarshal(&ebuilds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, ebuild := range ebuilds {
|
||||
ebuild.Name = name
|
||||
c[name] = ebuild
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GitOptions is the options for the git resolver.
|
||||
type GitOptions struct {
|
||||
// URL is the URL to the git repository. Must be a valid option to
|
||||
// 'git clone'.
|
||||
URL string `yaml:"url"`
|
||||
|
||||
// Tags denote if tags should be used as the version source.
|
||||
Tags bool `yaml:"tags"`
|
||||
}
|
||||
|
||||
// APTOptions contains the options for the APT resolver.
|
||||
type APTOptions struct {
|
||||
// Repository is the URL of the APT repository. Should match the
|
||||
// following format:
|
||||
// deb http://archive.ubuntu.com/ubuntu/ focal main
|
||||
Repository string `yaml:"repository"`
|
||||
|
||||
// Package is the name of the package to watch versions for.
|
||||
Package string `yaml:"package"`
|
||||
|
||||
// StripRelease is a boolean that denotes if extra release information
|
||||
// (in the context of a semver) should be stripped. Defaults to true.
|
||||
StripRelease *bool `yaml:"strip_release"`
|
||||
return &cfg, nil
|
||||
}
|
||||
|
|
155
.tools/internal/config/packages/packages.go
Normal file
155
.tools/internal/config/packages/packages.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Copyright (C) 2024 Jared Allard
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/steps"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Resolver is the resolver to use to determine if an update is available.
|
||||
type Resolver string
|
||||
|
||||
// Contains the supported resolvers.
|
||||
const (
|
||||
// GitResolver is the git resolver.
|
||||
GitResolver Resolver = "git"
|
||||
|
||||
// APTResolver is a version resolver powered by an APT repository.
|
||||
APTResolver Resolver = "apt"
|
||||
)
|
||||
|
||||
// List contains all of the packages that should be updated.
|
||||
type List map[string]Package
|
||||
|
||||
// Package is an package that should be updated by the updater.
|
||||
type Package struct {
|
||||
// Name of the ebuild. This is only set when loaded from the config.
|
||||
// It is a readonly field.
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// Resolver to use to determine if an update is available.
|
||||
// Currently only "git" is supported.
|
||||
Resolver Resolver `yaml:"resolver"`
|
||||
|
||||
// GitOptions is the options for the git resolver.
|
||||
GitOptions GitOptions `yaml:"options"`
|
||||
|
||||
// APTOptions is the options for the APT resolver.
|
||||
APTOptions APTOptions `yaml:"options"`
|
||||
|
||||
// Steps are the steps to use to update the ebuild, if not set it
|
||||
// defaults to a copy the existing ebuild and regenerate the manifest.
|
||||
Steps steps.Steps `yaml:"steps"`
|
||||
}
|
||||
|
||||
// LoadPackages returns a map of packages from the provided path.
|
||||
func LoadPackages(path string) (List, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open ebuilds file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pkgs := make(List)
|
||||
if err := yaml.NewDecoder(f).Decode(&pkgs); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode ebuilds: %w", err)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals the ebuild configuration from YAML while
|
||||
// converting options into the appropriate type for the provided
|
||||
// resolver.
|
||||
func (p *Package) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var raw struct {
|
||||
Resolver Resolver `yaml:"resolver"`
|
||||
Options yaml.Node `yaml:"options"`
|
||||
Steps steps.Steps
|
||||
}
|
||||
|
||||
if err := unmarshal(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Resolver = raw.Resolver
|
||||
p.Steps = raw.Steps
|
||||
|
||||
switch p.Resolver {
|
||||
case GitResolver:
|
||||
if err := raw.Options.Decode(&p.GitOptions); err != nil {
|
||||
return fmt.Errorf("failed to decode git options: %w", err)
|
||||
}
|
||||
case APTResolver:
|
||||
if err := raw.Options.Decode(&p.APTOptions); err != nil {
|
||||
return fmt.Errorf("failed to decode APT options: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported resolver: %s", p.Resolver)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshals the ebuilds from YAML while carrying the
|
||||
// name from the key into the ebuild name.
|
||||
func (l List) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
pkgs := make(map[string]Package)
|
||||
if err := unmarshal(&pkgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, ebuild := range pkgs {
|
||||
ebuild.Name = name
|
||||
l[name] = ebuild
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GitOptions is the options for the git resolver.
|
||||
type GitOptions struct {
|
||||
// URL is the URL to the git repository. Must be a valid option to
|
||||
// 'git clone'.
|
||||
URL string `yaml:"url"`
|
||||
|
||||
// Tags denote if tags should be used as the version source.
|
||||
Tags bool `yaml:"tags"`
|
||||
|
||||
// ConsiderPreReleases denotes if pre-releases should be considered,
|
||||
// when a tag is used and the version is able to be parsed as a
|
||||
// semver. Defaults to false.
|
||||
ConsiderPreReleases bool `yaml:"consider_pre_releases"`
|
||||
}
|
||||
|
||||
// APTOptions contains the options for the APT resolver.
|
||||
type APTOptions struct {
|
||||
// Repository is the URL of the APT repository. Should match the
|
||||
// following format:
|
||||
// deb http://archive.ubuntu.com/ubuntu/ focal main
|
||||
Repository string `yaml:"repository"`
|
||||
|
||||
// Package is the name of the package to watch versions for.
|
||||
Package string `yaml:"package"`
|
||||
|
||||
// StripRelease is a boolean that denotes if extra release information
|
||||
// (in the context of a semver) should be stripped. Defaults to true.
|
||||
StripRelease *bool `yaml:"strip_release"`
|
||||
}
|
|
@ -17,13 +17,13 @@ package updater
|
|||
|
||||
import (
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config/packages"
|
||||
"github.com/jaredallard/overlay/.tools/internal/resolver/apt"
|
||||
)
|
||||
|
||||
// getAPTVersion returns the latest version of an APT package based on
|
||||
// the config provided.
|
||||
func getAPTVersion(ce *config.Ebuild) (string, error) {
|
||||
func getAPTVersion(ce *packages.Package) (string, error) {
|
||||
v, err := apt.GetPackageVersion(apt.Lookup{
|
||||
SourcesEntry: ce.APTOptions.Repository,
|
||||
Package: ce.APTOptions.Package,
|
||||
|
|
|
@ -23,12 +23,13 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/config"
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config/packages"
|
||||
)
|
||||
|
||||
// getGitVersion returns the latest version available from a git
|
||||
// repository.
|
||||
func getGitVersion(ce *config.Ebuild) (string, error) {
|
||||
func getGitVersion(ce *packages.Package) (string, error) {
|
||||
dir, err := os.MkdirTemp("", "updater")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temporary directory: %w", err)
|
||||
|
@ -66,6 +67,16 @@ func getGitVersion(ce *config.Ebuild) (string, error) {
|
|||
|
||||
// Strip the "refs/tags/" prefix.
|
||||
tag := strings.TrimPrefix(fqTag, "refs/tags/")
|
||||
|
||||
// Attempt to parse as a semver, for other options.
|
||||
if sv, err := semver.ParseTolerant(tag); err == nil {
|
||||
isPreRelease := len(sv.Pre) > 0
|
||||
if isPreRelease && !ce.GitOptions.ConsiderPreReleases {
|
||||
// Skip the version if we're not considering pre-releases.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
newVersion = tag
|
||||
break
|
||||
}
|
||||
|
|
|
@ -20,15 +20,16 @@ package updater
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/config"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config/packages"
|
||||
)
|
||||
|
||||
// GetLatestVersion returns the latest version available for the given ebuild.
|
||||
func GetLatestVersion(ce *config.Ebuild) (string, error) {
|
||||
// GetLatestVersion returns the latest version available for the given
|
||||
// package.
|
||||
func GetLatestVersion(ce *packages.Package) (string, error) {
|
||||
switch ce.Resolver {
|
||||
case config.GitResolver:
|
||||
case packages.GitResolver:
|
||||
return getGitVersion(ce)
|
||||
case config.APTResolver:
|
||||
case packages.APTResolver:
|
||||
return getAPTVersion(ce)
|
||||
case "":
|
||||
return "", fmt.Errorf("no resolver specified")
|
||||
|
|
61
.tools/internal/steps/checkout_step.go
Normal file
61
.tools/internal/steps/checkout_step.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (C) 2024 Jared Allard
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package steps
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers"
|
||||
)
|
||||
|
||||
// CheckoutStep checks out a repository at the given path. If the Git
|
||||
// resolver was used, it defaults to the URL provided in the GitOptions.
|
||||
// Otherwise, it will use the URL provided.
|
||||
//
|
||||
// The latest version is used as the revision to checkout.
|
||||
type CheckoutStep struct {
|
||||
url string
|
||||
}
|
||||
|
||||
// NewCheckoutStep creates a new CheckoutStep from the provided input.
|
||||
func NewCheckoutStep(input any) (StepRunner, error) {
|
||||
url, ok := input.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected string, got %T", input)
|
||||
}
|
||||
|
||||
return &CheckoutStep{url}, nil
|
||||
}
|
||||
|
||||
// Run runs the provided command inside of the step runner.
|
||||
func (c CheckoutStep) Run(ctx context.Context, env Environment) (*StepOutput, error) {
|
||||
cmds := [][]string{
|
||||
{"git", "-c", "init.defaultBranch=main", "init", env.workDir},
|
||||
{"git", "remote", "add", "origin", c.url},
|
||||
{"git", "-c", "protocol.version=2", "fetch", "origin", "v" + env.in.LatestVersion},
|
||||
{"git", "reset", "--hard", "FETCH_HEAD"},
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
if err := stepshelpers.RunCommandInContainer(ctx, env.containerID, cmd...); err != nil {
|
||||
return nil, fmt.Errorf("failed to run command %v: %w", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
|
@ -23,6 +23,7 @@ import (
|
|||
"strings"
|
||||
|
||||
logger "github.com/charmbracelet/log"
|
||||
"github.com/jaredallard/overlay/.tools/internal/config"
|
||||
"github.com/jaredallard/overlay/.tools/internal/ebuild"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
@ -60,6 +61,10 @@ type Environment struct {
|
|||
// ExecutorInput is input to the executor. This should contain state
|
||||
// that existed before the executor was ran.
|
||||
type ExecutorInput struct {
|
||||
// Config is the configuration for the updater, which contains
|
||||
// configuration for some steps.
|
||||
Config *config.Config
|
||||
|
||||
// OriginalEbuild is the original ebuild that can be used for
|
||||
// generating a new one.
|
||||
OriginalEbuild *ebuild.Ebuild
|
||||
|
@ -91,7 +96,10 @@ func (e *Executor) Run(ctx context.Context) (*Results, error) {
|
|||
|
||||
// TODO(jaredallard): Use the Docker API for this, but for now the CLI
|
||||
// is much better.
|
||||
bid, err := exec.Command("docker", "run", "-d", "--rm", "--entrypoint", "sleep", "ghcr.io/jaredallard/overlay:updater", "infinity").Output()
|
||||
bid, err := exec.Command(
|
||||
"docker", "run", "-d", "--rm", "--entrypoint", "sleep",
|
||||
"ghcr.io/jaredallard/overlay:updater", "infinity",
|
||||
).Output()
|
||||
if err != nil {
|
||||
var execErr *exec.ExitError
|
||||
if errors.As(err, &execErr) {
|
||||
|
|
|
@ -45,7 +45,7 @@ type Steps []Step
|
|||
|
||||
// Step encapsulates a step that should be ran.
|
||||
type Step struct {
|
||||
// Args are the arguments that should be passed to the step.
|
||||
// Args are the arguments that were provided to the step.
|
||||
Args any
|
||||
|
||||
// Runner is the runner that runs this step.
|
||||
|
@ -55,7 +55,7 @@ type Step struct {
|
|||
// UnmarshalYAML unmarshals the steps from the YAML configuration file
|
||||
// turning them into their type safe representations.
|
||||
func (s *Steps) UnmarshalYAML(node *yaml.Node) error {
|
||||
var raw []map[string]any
|
||||
var raw []any
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -63,32 +63,47 @@ func (s *Steps) UnmarshalYAML(node *yaml.Node) error {
|
|||
// knownSteps map of key values to their respective steps.
|
||||
knownSteps := map[string]func(any) (StepRunner, error){
|
||||
"command": NewCommandStep,
|
||||
"checkout": NewCheckoutStep,
|
||||
"ebuild": NewEbuildStep,
|
||||
"original_ebuild": NewOriginalEbuildStep,
|
||||
"upload_artifact": NewUploadArtifactStep,
|
||||
}
|
||||
|
||||
for _, rawStep := range raw {
|
||||
// Find the first key that maps to a known step.
|
||||
var found bool
|
||||
for key := range knownSteps {
|
||||
if _, ok := rawStep[key]; ok {
|
||||
found = true
|
||||
var stepName string
|
||||
var stepData any
|
||||
|
||||
step, err := knownSteps[key](rawStep[key])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create step: %w", err)
|
||||
}
|
||||
switch rawStep := rawStep.(type) {
|
||||
case map[string]any:
|
||||
// If there's more than one key, fail.
|
||||
if len(rawStep) != 1 {
|
||||
return fmt.Errorf("expected one key on step, got %d", len(rawStep))
|
||||
}
|
||||
|
||||
*s = append(*s, Step{
|
||||
Args: rawStep[key],
|
||||
Runner: step,
|
||||
})
|
||||
// If it's a map, use the first key.
|
||||
for key, value := range rawStep {
|
||||
stepName = key
|
||||
stepData = value
|
||||
break
|
||||
}
|
||||
case string:
|
||||
// If it's just a string, then we use it as-is.
|
||||
stepName = rawStep
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("invalid step")
|
||||
|
||||
if _, ok := knownSteps[stepName]; !ok {
|
||||
return fmt.Errorf("unknown step: %s", stepName)
|
||||
}
|
||||
|
||||
step, err := knownSteps[stepName](stepData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create step: %w", err)
|
||||
}
|
||||
|
||||
*s = append(*s, Step{
|
||||
Args: stepData,
|
||||
Runner: step,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -81,6 +81,33 @@ func ReadFileInContainer(ctx context.Context, containerID, path string) ([]byte,
|
|||
return b, nil
|
||||
}
|
||||
|
||||
// StreamFileFromContainer streams a file from a container to the
|
||||
// provided writer. Because this function streams the file using the
|
||||
// exec package, the caller must call the returned wait function to wait
|
||||
// for the command to finish and clean up resources.
|
||||
//
|
||||
// The returned int64 is the size of the file being streamed.
|
||||
func StreamFileFromContainer(ctx context.Context, containerID, path string) (io.Reader, int64, func() error, error) {
|
||||
cmd := exec.CommandContext(ctx, "docker", "cp", fmt.Sprintf("%s:%s", containerID, path), "-")
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("failed to create stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("failed to start command: %w", err)
|
||||
}
|
||||
|
||||
// Process the output as a tar stream.
|
||||
t := tar.NewReader(stdout)
|
||||
th, err := t.Next()
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("failed to read tar: %w", err)
|
||||
}
|
||||
|
||||
return t, th.Size, cmd.Wait, nil
|
||||
}
|
||||
|
||||
// RunCommandInContainer runs a command inside of a container.
|
||||
func RunCommandInContainer(ctx context.Context, containerID string, origArgs ...string) error {
|
||||
args := []string{"exec", containerID, "bash", "-eo", "pipefail"}
|
||||
|
|
108
.tools/internal/steps/upload_artifact_step.go
Normal file
108
.tools/internal/steps/upload_artifact_step.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Copyright (C) 2024 Jared Allard
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package steps
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jaredallard/overlay/.tools/internal/steps/stepshelpers"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
// UploadArtifactStep takes the provided path and uploads it from the
|
||||
// container to a stable S3 bucket. This is used to store artifacts that
|
||||
// are required by ebuilds (e.g., Go dependency archives) and retrieve
|
||||
// them later.
|
||||
//
|
||||
// S3 configuration is provided by the environment. The file will be
|
||||
// stored using the following structure:
|
||||
//
|
||||
// <host>/<package_name>/<package_version>/<basename of path>
|
||||
type UploadArtifactStep struct {
|
||||
// path is the path to the artifact.
|
||||
path string
|
||||
}
|
||||
|
||||
// NewUploadArtifactStep creates a new UploadArtifactStep from the provided input.
|
||||
func NewUploadArtifactStep(input any) (StepRunner, error) {
|
||||
path, ok := input.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected string, got %T", input)
|
||||
}
|
||||
|
||||
return &UploadArtifactStep{path}, nil
|
||||
}
|
||||
|
||||
// Run runs the provided command inside of the step runner.
|
||||
func (e UploadArtifactStep) Run(ctx context.Context, env Environment) (*StepOutput, error) {
|
||||
if !filepath.IsAbs(e.path) {
|
||||
e.path = filepath.Join(env.workDir, e.path)
|
||||
}
|
||||
|
||||
// TODO(jaredallard): We should create the client once.
|
||||
s3Conf := env.in.Config.StepConfig.UploadArtifact
|
||||
|
||||
if s3Conf.Host == "" {
|
||||
return nil, fmt.Errorf("s3 host was not set in the config")
|
||||
}
|
||||
|
||||
if s3Conf.Bucket == "" {
|
||||
return nil, fmt.Errorf("s3 bucket was not set in the config")
|
||||
}
|
||||
|
||||
// Without a doubt, the worse code I've ever written.
|
||||
hostWithSchema := strings.TrimPrefix(strings.TrimPrefix(s3Conf.Host, "http://"), "https://")
|
||||
mc, err := minio.New(hostWithSchema, &minio.Options{
|
||||
Creds: credentials.NewEnvAWS(),
|
||||
Secure: strings.HasPrefix(s3Conf.Host, "https"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create minio client: %w", err)
|
||||
}
|
||||
|
||||
uploadFileName := filepath.Base(e.path)
|
||||
|
||||
// Example: <?prefix>/net-vpn/tailscale/1.66.1/deps.xz
|
||||
uploadPath := filepath.Join(
|
||||
s3Conf.Prefix, env.in.OriginalEbuild.Category, env.in.OriginalEbuild.Name, env.in.LatestVersion, uploadFileName,
|
||||
)
|
||||
|
||||
out, size, wait, err := stepshelpers.StreamFileFromContainer(ctx, env.containerID, e.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stream file from container: %w", err)
|
||||
}
|
||||
|
||||
env.log.With("path", uploadPath, "size", size).Info("uploading artifact")
|
||||
inf, err := mc.PutObject(ctx, s3Conf.Bucket, uploadPath, out, size, minio.PutObjectOptions{
|
||||
SendContentMd5: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload artifact: %w", err)
|
||||
}
|
||||
|
||||
if err := wait(); err != nil {
|
||||
return nil, fmt.Errorf("failed to wait for command to finish: %w", err)
|
||||
}
|
||||
|
||||
env.log.With("size", inf.Size).Info("uploaded artifact")
|
||||
|
||||
return nil, nil
|
||||
}
|
5
.updater.yml
Normal file
5
.updater.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
step_config:
|
||||
upload_artifact:
|
||||
host: https://c41358b3e2e8f5345933f0d433e3abef.r2.cloudflarestorage.com
|
||||
bucket: gentoo-rgst-io
|
||||
prefix: updater_artifacts
|
|
@ -7,3 +7,8 @@ RUN emerge-webrsync && \
|
|||
ACCEPT_KEYWORDS="~amd64 ~arm64" emerge -v app-portage/pycargoebuild && \
|
||||
emerge -v app-portage/gentoolkit && \
|
||||
eclean --deep packages && eclean --deep distfiles
|
||||
|
||||
# Install mise for things that might need it.
|
||||
RUN curl https://mise.run | sh
|
||||
ENV PATH="/root/.local/bin:/root/.local/share/mise/shims:${PATH}"
|
||||
RUN set -e; whoami; echo $HOME; mise --version
|
3
net-vpn/tailscale/Manifest
Normal file
3
net-vpn/tailscale/Manifest
Normal file
|
@ -0,0 +1,3 @@
|
|||
DIST deps.tar.xz 395317936 BLAKE2B 50b0e9bb784dd655ac5f2ce7aed0eddbac772511dc9e768c0005ef84b653c58719cabbb1e52c82cc8ecc257eed26cbc976d9df24e30bf6fc5cb871a21767499d SHA512 a9ae271d8272b54a0bfe91bc9d59fa3c2af31fa1565552f5fbd8517e5a7acbcad7fb45bb080a7876e45810394b8ac28d0815b7db3b58ac9e78a0b65d0693d62e
|
||||
DIST tailscale-1.66.0.tar.gz 2633400 BLAKE2B b089731d5412187d3ef9e3d0f7064143adc6baa8fd70622d2ce0c97d28420da1170d8f388fd4b1617dacd338360669a148f1a0f0f9f90914c77b0b0ff20b2e0e SHA512 5eab341a0d8208bd6915f0dd87d94a22e962527bc0c940b10f84fbcc82cfb9be979441b7367dced40a3adec4a72c0fdb0841db5790e0c24ae67f27f48fe93854
|
||||
DIST tailscale-1.66.1.tar.gz 2634211 BLAKE2B cbf90ee36545fa8ba302f872948dc92e735bfe690451428540bee10399d1db1cdbf050949a5f3dbc69a77596b6a6d94724eb43bf9a4ee78a5cee9ada03889a3e SHA512 46e226c651abd5dee248e49fcf40a0cea9de72fe9e330015299acd9ec1fc83e1f192948c26b803f0fe3404558471b374391f3cf9155ecb2257a2563f79f3278b
|
60
net-vpn/tailscale/tailscale-1.66.0.ebuild
Normal file
60
net-vpn/tailscale/tailscale-1.66.0.ebuild
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2020-2024 Gentoo Authors
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=8
|
||||
inherit go-module systemd tmpfiles
|
||||
|
||||
# These settings are obtained by running ./build_dist.sh shellvars in
|
||||
# the upstream repo.
|
||||
VERSION_MINOR="60"
|
||||
VERSION_SHORT="1.60.0"
|
||||
VERSION_LONG="1.60.0-tf4e3ee53e"
|
||||
VERSION_GIT_HASH="f4e3ee53ea4605d400df2ef6b6005b026661f96b"
|
||||
|
||||
DESCRIPTION="Tailscale vpn client"
|
||||
HOMEPAGE="https://tailscale.com"
|
||||
SRC_URI="https://github.com/tailscale/tailscale/archive/v${PV}.tar.gz -> ${P}.tar.gz"
|
||||
SRC_URI+=" https://gentoo.rgst.io/updater_artifacts/${CATEGORY}/${PN}/${PV}/deps.tar.xz"
|
||||
|
||||
LICENSE="MIT"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64 ~arm arm64 ~riscv ~x86"
|
||||
|
||||
RDEPEND="net-firewall/iptables"
|
||||
BDEPEND=">=dev-lang/go-1.21"
|
||||
|
||||
RESTRICT="test"
|
||||
|
||||
# This translates the build command from upstream's build_dist.sh to an
|
||||
# ebuild equivalent.
|
||||
build_dist() {
|
||||
ego build -tags xversion -ldflags "
|
||||
-X tailscale.com/version.Long=${VERSION_LONG}
|
||||
-X tailscale.com/version.Short=${VERSION_SHORT}
|
||||
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" "$@"
|
||||
}
|
||||
|
||||
src_compile() {
|
||||
build_dist ./cmd/tailscale
|
||||
build_dist ./cmd/tailscaled
|
||||
}
|
||||
|
||||
src_install() {
|
||||
dosbin tailscaled
|
||||
dobin tailscale
|
||||
|
||||
systemd_dounit cmd/tailscaled/tailscaled.service
|
||||
insinto /etc/default
|
||||
newins cmd/tailscaled/tailscaled.defaults tailscaled
|
||||
keepdir /var/lib/${PN}
|
||||
fperms 0750 /var/lib/${PN}
|
||||
|
||||
newtmpfiles "${FILESDIR}/${PN}.tmpfiles" ${PN}.conf
|
||||
|
||||
newinitd "${FILESDIR}/${PN}d.initd" ${PN}
|
||||
newconfd "${FILESDIR}/${PN}d.confd" ${PN}
|
||||
}
|
||||
|
||||
pkg_postinst() {
|
||||
tmpfiles_process ${PN}.conf
|
||||
}
|
67
packages.yml
Normal file
67
packages.yml
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Configure automatic updates for ebuilds.
|
||||
app-admin/chezmoi-bin:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/twpayne/chezmoi
|
||||
app-admin/1password:
|
||||
resolver: apt
|
||||
options:
|
||||
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
|
||||
package: 1password
|
||||
app-admin/op-cli-bin:
|
||||
resolver: apt
|
||||
options:
|
||||
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
|
||||
package: 1password-cli
|
||||
app-arch/7-zip:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/ip7z/7zip
|
||||
dev-util/mise:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/jdx/mise
|
||||
|
||||
# We have to regenerate the ebuild to get new crates and licenses to
|
||||
# be reflected, so we have to have custom steps.
|
||||
steps:
|
||||
- checkout: https://github.com/jdx/mise
|
||||
- original_ebuild: mise.ebuild
|
||||
- command: pycargoebuild -i mise.ebuild
|
||||
- ebuild: mise.ebuild
|
||||
net-im/armcord:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/ArmCord/ArmCord
|
||||
net-vpn/tailscale:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/tailscale/tailscale
|
||||
|
||||
# We have to generate a Go dependency archive and upload it to a
|
||||
# stable location, so we do that during this process.
|
||||
steps:
|
||||
- checkout: https://github.com/tailscale/tailscale
|
||||
- original_ebuild: new.ebuild
|
||||
- command: |-
|
||||
set -euxo pipefail
|
||||
|
||||
GO_VERSION=$(grep "^go" go.mod | awk '{ print $2 }' | awk -F '.' '{ print $1"."$2}')
|
||||
mise use -g golang@"${GO_VERSION}"
|
||||
|
||||
# Create the dependency tar.
|
||||
GOMODCACHE="${PWD}"/go-mod go mod download -modcacherw
|
||||
tar --create --file deps.tar go-mod
|
||||
xz --threads 0 deps.tar
|
||||
|
||||
# Get the shell variables and rewrite the ebuild to contain
|
||||
# them.
|
||||
eval "$(./build_dist.sh shellvars)"
|
||||
sed -i 's/VERSION_MINOR=".*"/VERSION_MINOR="'"${VERSION_MINOR}"'"/' new.ebuild
|
||||
sed -i 's/VERSION_SHORT=".*"/VERSION_SHORT="'"${VERSION_SHORT}"'"/' new.ebuild
|
||||
sed -i 's/VERSION_LONG=".*"/VERSION_LONG="'"${VERSION_LONG}"'"/' new.ebuild
|
||||
sed -i 's/VERSION_GIT_HASH=".*"/VERSION_GIT_HASH="'"${VERSION_GIT_HASH}"'"/' new.ebuild
|
||||
|
||||
sed -i 's|dev-lang/golang-.*|dev-lang/golang-${GO_VERSION}|' new.ebuild
|
||||
- upload_artifact: deps.tar.xz
|
||||
- ebuild: new.ebuild
|
23
update-base-image.sh
Executable file
23
update-base-image.sh
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rebuilds the base image used by the tools in this repository and
|
||||
# pushes it upstream.
|
||||
set -euo pipefail
|
||||
|
||||
# PUSH determines if we should push the image to the remote or not.
|
||||
PUSH=false
|
||||
if [[ "${1:-}" == "--push" ]]; then
|
||||
PUSH=true
|
||||
fi
|
||||
|
||||
args=(
|
||||
"--tag" "ghcr.io/jaredallard/overlay:updater"
|
||||
"$(pwd)"
|
||||
)
|
||||
|
||||
if [[ "$PUSH" == "true" ]]; then
|
||||
args+=("--platform" "linux/amd64,linux/arm64" "--push")
|
||||
else
|
||||
args+=("--load")
|
||||
fi
|
||||
|
||||
exec docker buildx build "${args[@]}"
|
35
updater.yml
35
updater.yml
|
@ -1,35 +0,0 @@
|
|||
# Configure automatic updates for ebuilds.
|
||||
app-admin/chezmoi-bin:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/twpayne/chezmoi
|
||||
app-admin/1password:
|
||||
resolver: apt
|
||||
options:
|
||||
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
|
||||
package: 1password
|
||||
app-admin/op-cli-bin:
|
||||
resolver: apt
|
||||
options:
|
||||
repository: "deb https://downloads.1password.com/linux/debian/amd64 stable main"
|
||||
package: 1password-cli
|
||||
app-arch/7-zip:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/ip7z/7zip
|
||||
dev-util/mise:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/jdx/mise
|
||||
|
||||
# We have to regenerate the ebuild to get new crates and licenses to
|
||||
# be reflected, so we have to have custom steps.
|
||||
steps:
|
||||
- command: git clone https://github.com/jdx/mise .
|
||||
- original_ebuild: mise.ebuild
|
||||
- command: pycargoebuild -i mise.ebuild
|
||||
- ebuild: mise.ebuild
|
||||
net-im/armcord:
|
||||
resolver: git
|
||||
options:
|
||||
url: https://github.com/ArmCord/ArmCord
|
Loading…
Add table
Reference in a new issue