From eb4672cd25fd5f751513035ef2ab1729e37f400f Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 3 Dec 2025 16:02:05 +1300 Subject: [PATCH 01/21] Initial implementation of interchangable builder --- cmd/skpr/package/command.go | 1 - go.mod | 23 +- go.sum | 46 ++- internal/buildpack/builder/builder.go | 98 ++---- internal/buildpack/builder/builder_test.go | 20 +- internal/buildpack/builder/docker/builder.go | 294 ++++++++++++++++++ internal/buildpack/types/types.go | 43 +++ internal/buildpack/utils/aws/ecr/ecr.go | 6 +- internal/buildpack/utils/finder/finder.go | 17 +- .../buildpack/utils/notation/utils/ecr.go | 47 --- .../{builder => utils/prefixer}/prefixer.go | 16 +- internal/client/config/user/config.go | 10 +- internal/command/mysql/image/pull/command.go | 10 +- internal/command/package/command.go | 35 +-- 14 files changed, 495 insertions(+), 171 deletions(-) create mode 100644 internal/buildpack/builder/docker/builder.go create mode 100644 internal/buildpack/types/types.go delete mode 100644 internal/buildpack/utils/notation/utils/ecr.go rename internal/buildpack/{builder => utils/prefixer}/prefixer.go (50%) diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index 5057969..fa9a7ad 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -47,7 +47,6 @@ func NewCommand() *cobra.Command { cmd.Flags().BoolVar(&command.PrintManifest, "print-manifest", command.PrintManifest, "Print the manifest to stdout.") cmd.Flags().StringVar(&command.PackageDir, "dir", ".skpr/package", "The location of the package directory.") cmd.Flags().StringSliceVar(&command.BuildArgs, "build-arg", []string{}, "Additional build arguments.") - cmd.Flags().StringVar(&command.Platform, "platform", "linux/amd64", "The platform to build for.") cmd.Flags().BoolVar(&command.Debug, "debug", command.Debug, "Enable debug output.") return cmd diff --git a/go.mod b/go.mod index d74b376..3508959 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/fang v0.4.4 + github.com/docker/docker v28.5.2+incompatible github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca github.com/fatih/color v1.18.0 github.com/fsouza/go-dockerclient v1.12.3 @@ -24,7 +25,9 @@ require ( github.com/jinzhu/now v1.1.5 github.com/jwalton/gchalk v1.3.0 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d - github.com/notaryproject/notation-go v1.3.2 + github.com/moby/go-archive v0.1.0 + github.com/moby/moby/api v1.52.0 + github.com/moby/patternmatcher v0.6.0 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 github.com/pkg/errors v0.9.1 github.com/skpr/api v1.3.2 @@ -39,7 +42,6 @@ require ( golang.org/x/term v0.37.0 google.golang.org/grpc v1.77.0 gopkg.in/yaml.v2 v2.4.0 - oras.land/oras-go/v2 v2.6.0 ) require ( @@ -72,12 +74,17 @@ require ( github.com/clipperhouse/displaywidth v0.6.2 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/docker v28.5.2+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jwalton/go-supportscolor v1.2.0 // indirect @@ -88,8 +95,7 @@ require ( github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.1.0 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect @@ -110,9 +116,16 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 46ed177..f66f350 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= @@ -82,6 +84,10 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -91,6 +97,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -103,8 +111,11 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsouza/go-dockerclient v1.12.3 h1:CEsX4/msyMEekHAR9Pf8XniZBtwGo0Kl+mLPQ/AnSys= github.com/fsouza/go-dockerclient v1.12.3/go.mod h1:gl0t2KUfrsLbm4tw5/ySsJkkFpi7Fz9gXzY2BKLEvZA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -125,6 +136,8 @@ github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -138,6 +151,10 @@ github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= @@ -157,8 +174,12 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg= +github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= @@ -183,12 +204,6 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/notaryproject/notation-core-go v1.3.0 h1:mWJaw1QBpBxpjLSiKOjzbZvB+xh2Abzk14FHWQ+9Kfs= -github.com/notaryproject/notation-core-go v1.3.0/go.mod h1:hzvEOit5lXfNATGNBT8UQRx2J6Fiw/dq/78TQL8aE64= -github.com/notaryproject/notation-go v1.3.2 h1:4223iLXOHhEV7ZPzIUJEwwMkhlgzoYFCsMJvSH1Chb8= -github.com/notaryproject/notation-go v1.3.2/go.mod h1:/1kuq5WuLF6Gaer5re0Z6HlkQRlKYO4EbWWT/L7J1Uw= -github.com/notaryproject/tspclient-go v1.0.0 h1:AwQ4x0gX8IHnyiZB1tggpn5NFqHpTEm1SDX8YNv4Dg4= -github.com/notaryproject/tspclient-go v1.0.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -203,6 +218,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= @@ -230,8 +247,14 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= @@ -240,6 +263,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= @@ -273,18 +298,23 @@ golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -293,5 +323,3 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= diff --git a/internal/buildpack/builder/builder.go b/internal/buildpack/builder/builder.go index 54d28a5..337af7f 100644 --- a/internal/buildpack/builder/builder.go +++ b/internal/buildpack/builder/builder.go @@ -3,16 +3,15 @@ package buildpack import ( "context" "fmt" - "io" - "strings" "time" - "github.com/egym-playground/go-prefix-writer/prefixer" docker "github.com/fsouza/go-dockerclient" "golang.org/x/sync/errgroup" + "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/buildpack/utils/image" - "github.com/skpr/cli/internal/color" + "github.com/skpr/cli/internal/buildpack/utils/prefixer" ) // DockerClientInterface provides an interface that allows us to test the builder. @@ -26,63 +25,30 @@ type Builder struct { dockerClient DockerClientInterface } -// BuildResponse is returned by the build operation. -type BuildResponse struct { - Images []Image `json:"images"` -} - -// Image build has been built. -type Image struct { - // Name of the image. - Name string `json:"name"` - // Tag used to push image. - Tag string `json:"tag"` -} - -// Params used for building the applications. -type Params struct { - Auth docker.AuthConfiguration - Writer io.Writer - Context string - Registry string - NoPush bool - Version string - BuildArgs map[string]string - Platform string -} - -// Dockerfiles the docker build files. -type Dockerfiles map[string]string - -const ( - // ImageNameCompile is used for compiling the application. - ImageNameCompile = "compile" - - // BuildArgCompileImage is used for referencing the compile image. - BuildArgCompileImage = "COMPILE_IMAGE" - // BuildArgVersion is used for providing the version identifier of the application. - BuildArgVersion = "SKPR_VERSION" -) - // NewBuilder creates a new Builder. -func NewBuilder(dockerClient DockerClientInterface) *Builder { - return &Builder{ - dockerClient: dockerClient, +func NewBuilder() (*Builder, error) { + dockerclient, err := docker.NewClientFromEnv() + if err != nil { + return nil, fmt.Errorf("failed to setup Docker client: %w", err) } + + return &Builder{ + dockerClient: dockerclient, + }, nil } // Build the images. -func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, error) { - var resp BuildResponse +func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { + var resp types.BuildResponse - compileDockerfile, ok := dockerfiles[ImageNameCompile] + compileDockerfile, ok := dockerfiles[types.ImageNameCompile] if !ok { - return resp, fmt.Errorf("%q is a required dockerfile", ImageNameCompile) + return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) } args := []docker.BuildArg{ { - Name: BuildArgVersion, + Name: types.BuildArgVersion, Value: params.Version, }, } @@ -98,12 +64,11 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, // We build the compile image first, as it is the base image for other images. compileBuild := docker.BuildImageOptions{ - Name: image.Name(params.Registry, params.Version, ImageNameCompile), + Name: image.Name(params.Registry, params.Version, types.ImageNameCompile), Dockerfile: compileDockerfile, ContextDir: params.Context, - OutputStream: prefixWithTime(params.Writer, ImageNameCompile, start), + OutputStream: prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start), BuildArgs: args, - Platform: params.Platform, } // We need to build the 'compile' image first. @@ -115,13 +80,13 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, fmt.Fprintf(params.Writer, "Built compile image in %s\n", time.Since(start).Round(time.Second)) // Remove compile from list of dockerfiles. - delete(dockerfiles, ImageNameCompile) + delete(dockerfiles, types.ImageNameCompile) // Adds compile image identifier to the runtime images as an arg. // That allows runtime images to copy over the compiled code. args = append(args, docker.BuildArg{ - Name: BuildArgCompileImage, - Value: image.Name(params.Registry, params.Version, ImageNameCompile), + Name: types.BuildArgCompileImage, + Value: image.Name(params.Registry, params.Version, types.ImageNameCompile), }) var builds []docker.BuildImageOptions @@ -131,22 +96,21 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, Name: image.Name(params.Registry, params.Version, imageName), Dockerfile: dockerfile, ContextDir: params.Context, - OutputStream: prefixWithTime(params.Writer, imageName, start), + OutputStream: prefixer.WrapWriterWithPrefixer(params.Writer, imageName, start), BuildArgs: args, - Platform: params.Platform, } // Add to the builder list. builds = append(builds, build) // Add to the manifest. - resp.Images = append(resp.Images, Image{ + resp.Images = append(resp.Images, types.Image{ Name: imageName, Tag: build.Name, }) } - bg, ctx := errgroup.WithContext(context.Background()) + bg, ctx := errgroup.WithContext(ctx) for _, build := range builds { // https://golang.org/doc/faq#closures_and_goroutines @@ -189,7 +153,12 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, }) } - pg, ctx := errgroup.WithContext(context.Background()) + pg, ctx := errgroup.WithContext(ctx) + + auth := docker.AuthConfiguration{ + Username: params.Auth.Username, + Password: params.Auth.Password, + } for _, push := range pushes { // https://golang.org/doc/faq#closures_and_goroutines @@ -203,7 +172,7 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, pg.Go(func() error { start = time.Now() - err = b.dockerClient.PushImage(push, params.Auth) + err = b.dockerClient.PushImage(push, auth) if err != nil { return err } @@ -223,8 +192,3 @@ func (b *Builder) Build(dockerfiles Dockerfiles, params Params) (BuildResponse, return resp, nil } - -// Helper function to prefix all output for a stream. -func prefixWithTime(w io.Writer, name string, start time.Time) io.Writer { - return prefixer.New(w, newPrefixer(color.Wrap(strings.ToUpper(name)), start).PrefixFunc()) -} diff --git a/internal/buildpack/builder/builder_test.go b/internal/buildpack/builder/builder_test.go index f376ae9..806512a 100644 --- a/internal/buildpack/builder/builder_test.go +++ b/internal/buildpack/builder/builder_test.go @@ -2,12 +2,14 @@ package buildpack import ( "bytes" + "context" "testing" - docker "github.com/fsouza/go-dockerclient" "github.com/stretchr/testify/assert" "github.com/skpr/cli/internal/buildpack/builder/mock" + "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/buildpack/utils/finder" ) func TestBuild(t *testing.T) { @@ -16,7 +18,7 @@ func TestBuild(t *testing.T) { dockerClient.BuildWg.Add(4) dockerClient.PushWg.Add(3) - dockerFiles := make(Dockerfiles) + dockerFiles := make(finder.Dockerfiles) dockerFiles["compile"] = ".skpr/package/compile/Dockerfile" dockerFiles["cli"] = ".skpr/package/cli/Dockerfile" dockerFiles["app"] = ".skpr/package/app/Dockerfile" @@ -24,21 +26,23 @@ func TestBuild(t *testing.T) { var b bytes.Buffer - params := Params{ + params := types.Params{ Writer: &b, Registry: "foo", Version: "222", Context: "bar", NoPush: false, - Auth: docker.AuthConfiguration{}, + Auth: types.Auth{}, } - builder := NewBuilder(dockerClient) - have, err := builder.Build(dockerFiles, params) + builder, err := NewBuilder() assert.NoError(t, err) - want := BuildResponse{ - Images: []Image{ + have, err := builder.Build(context.TODO(), dockerFiles, params) + assert.NoError(t, err) + + want := types.BuildResponse{ + Images: []types.Image{ { Name: "cli", Tag: "foo:222-cli", diff --git a/internal/buildpack/builder/docker/builder.go b/internal/buildpack/builder/docker/builder.go new file mode 100644 index 0000000..31e5f56 --- /dev/null +++ b/internal/buildpack/builder/docker/builder.go @@ -0,0 +1,294 @@ +package buildpack + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "os" + "time" + + "github.com/docker/docker/api/types/build" + imagetypes "github.com/docker/docker/api/types/image" + dockregistry "github.com/docker/docker/api/types/registry" + dockclient "github.com/docker/docker/client" + "github.com/moby/go-archive" + "github.com/moby/moby/api/types/jsonstream" + "github.com/moby/patternmatcher/ignorefile" + "golang.org/x/sync/errgroup" + + "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/buildpack/utils/finder" + "github.com/skpr/cli/internal/buildpack/utils/image" + "github.com/skpr/cli/internal/buildpack/utils/prefixer" +) + +// DockerClientInterface provides an interface that allows us to test the builder. +// This mirrors the subset of the official Docker SDK we use. +type DockerClientInterface interface { + ImageBuild(ctx context.Context, buildContext io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) + ImagePush(ctx context.Context, ref string, options imagetypes.PushOptions) (io.ReadCloser, error) +} + +// Builder is the docker image builder. +type Builder struct { + dockerClient DockerClientInterface +} + +// NewBuilder creates a new Builder. +func NewBuilder() (*Builder, error) { + dc, err := dockclient.NewClientWithOpts(dockclient.FromEnv, dockclient.WithAPIVersionNegotiation()) + if err != nil { + return nil, err + } + return &Builder{ + dockerClient: dc, + }, nil +} + +// Build the images. +func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { + var resp types.BuildResponse + + excludePatterns, err := loadIgnoreFilePatterns(params.IgnoreFile) + if err != nil { + return resp, fmt.Errorf("failed to parse ignore file: %w", err) + } + + compileDockerfile, ok := dockerfiles[types.ImageNameCompile] + if !ok { + return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) + } + + // Build args (Docker SDK expects map[string]*string) + buildArgs := map[string]*string{} + withArg := func(k, v string) { + val := v + buildArgs[k] = &val + } + withArg(types.BuildArgVersion, params.Version) + for k, v := range params.BuildArgs { + withArg(k, v) + } + + start := time.Now() + + // Build the compile image first; it's the base for others. + compileRef := image.Name(params.Registry, params.Version, types.ImageNameCompile) + + fmt.Fprintf(params.Writer, "Building image: %s\n", compileRef) + + localOut := prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start) + + if err := b.buildImage( + ctx, + params.Context, + excludePatterns, + build.ImageBuildOptions{ + Tags: []string{compileRef}, + Dockerfile: compileDockerfile, + Remove: true, + BuildArgs: buildArgs, + }, + localOut, + ); err != nil { + return resp, err + } + fmt.Fprintf(params.Writer, "Built %s image in %s\n", compileRef, time.Since(start).Round(time.Second)) + + // Remove compile from list of dockerfiles. + delete(dockerfiles, types.ImageNameCompile) + + // Adds compile image identifier to the runtime images as an arg. + withArg(types.BuildArgCompileImage, image.Name(params.Registry, params.Version, types.ImageNameCompile)) + + // Prepare runtime builds. + type pendingBuild struct { + name string + imageRef string + dockerfile string + } + var builds []pendingBuild + for imageName, dockerfile := range dockerfiles { + ref := image.Name(params.Registry, params.Version, imageName) + builds = append(builds, pendingBuild{name: imageName, imageRef: ref, dockerfile: dockerfile}) + resp.Images = append(resp.Images, types.Image{ + Name: imageName, + Tag: ref, + }) + } + + // Parallel runtime builds. + bg, ctx := errgroup.WithContext(ctx) + for _, pb := range builds { + pb := pb + + fmt.Fprintf(params.Writer, "Building image: %s\n", pb.imageRef) + + localStart := time.Now() + + localOut := prefixer.WrapWriterWithPrefixer(params.Writer, pb.name, localStart) + + bg.Go(func() error { + err := b.buildImage( + ctx, + params.Context, + excludePatterns, + build.ImageBuildOptions{ + Tags: []string{pb.imageRef}, + Dockerfile: pb.dockerfile, + Remove: true, + BuildArgs: buildArgs, + }, + localOut, + ) + if err != nil { + return err + } + fmt.Fprintf(params.Writer, "Built %s image in %s\n", pb.imageRef, time.Since(start).Round(time.Second)) + return nil + }) + } + if err := bg.Wait(); err != nil { + return resp, err + } + + if params.NoPush { + return resp, nil + } + + // Prepare pushes (skip compile). + type pendingPush struct { + name string + ref string // full "registry/repo:tag" + } + var pushes []pendingPush + for _, buildImage := range resp.Images { + pushes = append(pushes, pendingPush{ + name: buildImage.Name, + ref: fmt.Sprintf("%s:%s", params.Registry, image.Tag(params.Version, buildImage.Tag)), + }) + } + auth := dockregistry.AuthConfig{ + Username: params.Auth.Username, + Password: params.Auth.Password, + } + authHdr, err := encodeRegistryAuth(auth) + if err != nil { + return resp, fmt.Errorf("failed to encode registry auth: %w", err) + } + + // Parallel pushes. + pg, ctx := errgroup.WithContext(context.TODO()) + for _, p := range pushes { + p := p + fmt.Fprintf(params.Writer, "Pushing image: %s\n", p.ref) + out := prefixer.WrapWriterWithPrefixer(params.Writer, "push "+p.name, start) + + pg.Go(func() error { + localStart := time.Now() + rc, err := b.dockerClient.ImagePush(ctx, p.ref, imagetypes.PushOptions{ + RegistryAuth: authHdr, + }) + if err != nil { + return err + } + defer rc.Close() + + err = handleMessages(rc, out) + if err != nil { + return err + } + + fmt.Fprintf(params.Writer, "Pushed %s image in %s\n", p.ref, time.Since(localStart).Round(time.Second)) + return nil + }) + } + if err := pg.Wait(); err != nil { + return resp, err + } + + fmt.Fprintf(params.Writer, "Build complete in: %s\n", time.Since(start).Round(time.Second)) + return resp, nil +} + +// buildOne creates a tar build context from contextDir and streams the build output to out. +func (b *Builder) buildImage(ctx context.Context, contextDir string, contextExclude []string, opts build.ImageBuildOptions, out io.Writer) error { + buildCtx, err := archive.TarWithOptions(contextDir, &archive.TarOptions{ + ExcludePatterns: contextExclude, + }) + if err != nil { + return fmt.Errorf("failed to archive build context: %w", err) + } + + resp, err := b.dockerClient.ImageBuild(ctx, buildCtx, opts) + if err != nil { + return err + } + defer resp.Body.Close() + + err = handleMessages(resp.Body, out) + if err != nil { + return err + } + + return nil +} + +// encodeRegistryAuth converts a registrytypes.AuthConfig into the base64-encoded JSON +// expected by the Docker Engine API for ImagePush. +func encodeRegistryAuth(cfg dockregistry.AuthConfig) (string, error) { + b, err := json.Marshal(cfg) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(b), nil +} + +func handleMessages(in io.ReadCloser, out io.Writer) error { + // Stream the daemon's JSON log stream to the provided writer. + decoder := json.NewDecoder(in) + + for { + var msg jsonstream.Message + if err := decoder.Decode(&msg); err == io.EOF { + break + } else if err != nil { + return err + } + + // TODO: Possibly stream out progress. + + if msg.Stream != "" { + if _, err := io.WriteString(out, msg.Stream); err != nil { + return err + } + } + if msg.Error != nil { + return msg.Error + } + } + + return nil +} + +// Loads and returns a list of ignore file patterns from the specified file. +func loadIgnoreFilePatterns(filePath string) ([]string, error) { + f, err := os.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + + return nil, fmt.Errorf("unable to open ignore file: %w", err) + } + defer f.Close() + + patterns, err := ignorefile.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("error parsing ignore file: %w", err) + } + + return patterns, nil +} diff --git a/internal/buildpack/types/types.go b/internal/buildpack/types/types.go new file mode 100644 index 0000000..9829042 --- /dev/null +++ b/internal/buildpack/types/types.go @@ -0,0 +1,43 @@ +package types + +import "io" + +// BuildResponse is returned by the build operation. +type BuildResponse struct { + Images []Image `json:"images"` +} + +// Image build has been built. +type Image struct { + // Name of the image. + Name string `json:"name"` + // Tag used to push image. + Tag string `json:"tag"` +} + +type Auth struct { + Username string + Password string +} + +// Params used for building the applications. +type Params struct { + Auth Auth + Writer io.Writer + Context string + IgnoreFile string + Registry string + NoPush bool + Version string + BuildArgs map[string]string +} + +const ( + // ImageNameCompile is used for compiling the application. + ImageNameCompile = "compile" + + // BuildArgCompileImage is used for referencing the compile image. + BuildArgCompileImage = "COMPILE_IMAGE" + // BuildArgVersion is used for providing the version identifier of the application. + BuildArgVersion = "SKPR_VERSION" +) diff --git a/internal/buildpack/utils/aws/ecr/ecr.go b/internal/buildpack/utils/aws/ecr/ecr.go index 79e0b7a..ceb5c74 100644 --- a/internal/buildpack/utils/aws/ecr/ecr.go +++ b/internal/buildpack/utils/aws/ecr/ecr.go @@ -8,9 +8,9 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/ecr" - docker "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" + "github.com/skpr/cli/internal/buildpack/types" skprcredentials "github.com/skpr/cli/internal/client/credentials" ) @@ -25,8 +25,8 @@ func IsRegistry(registry string) bool { // UpgradeAuth to use an AWS IAM token for authentication.. // https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html -func UpgradeAuth(ctx context.Context, url string, creds skprcredentials.Credentials) (docker.AuthConfiguration, error) { - var auth docker.AuthConfiguration +func UpgradeAuth(ctx context.Context, url string, creds skprcredentials.Credentials) (types.Auth, error) { + var auth types.Auth region, err := extractRegionFromURL(url) if err != nil { diff --git a/internal/buildpack/utils/finder/finder.go b/internal/buildpack/utils/finder/finder.go index 31e7cc9..28d6f81 100644 --- a/internal/buildpack/utils/finder/finder.go +++ b/internal/buildpack/utils/finder/finder.go @@ -5,16 +5,17 @@ import ( "os" "path/filepath" "strings" - - "github.com/skpr/cli/internal/buildpack/builder" ) +// Dockerfiles the docker build files. +type Dockerfiles map[string]string + // dockerfileSuffix is the suffix for legacy dockerfiles. const dockerfileSuffix = ".dockerfile" // FindDockerfiles finds the dockerfiles in the specified directory. -func FindDockerfiles(packageDir string) (buildpack.Dockerfiles, error) { - dockerfiles := make(buildpack.Dockerfiles) +func FindDockerfiles(packageDir string) (Dockerfiles, error) { + dockerfiles := make(Dockerfiles) err := filepath.Walk(packageDir, func(path string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("failed accessing path %q: %w", path, err) @@ -34,6 +35,14 @@ func FindDockerfiles(packageDir string) (buildpack.Dockerfiles, error) { } return nil }) + + // Print deprecation notice. + for key, path := range dockerfiles { + if strings.HasSuffix(path, ".dockerfile") { + fmt.Printf("[DEPRECATED] Dockerfile location %q is deprecated. Use \"%s/%s/Dockerfile\" instead.\n", path, filepath.Dir(path), key) + } + } + if err != nil { return dockerfiles, err } diff --git a/internal/buildpack/utils/notation/utils/ecr.go b/internal/buildpack/utils/notation/utils/ecr.go deleted file mode 100644 index 3c769f3..0000000 --- a/internal/buildpack/utils/notation/utils/ecr.go +++ /dev/null @@ -1,47 +0,0 @@ -package utils - -import ( - "fmt" - - notationregistry "github.com/notaryproject/notation-go/registry" - "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" - "oras.land/oras-go/v2/registry/remote/auth" -) - -// GetNotationRepository creates notationregistry.Repository required to access artifacts in Amazon ECR for sign and verify operations. -// Inspired by https://github.com/aws/aws-signer-notation-plugin/blob/6e26cfd7711ad49b52c439e46bd1050c95865846/examples/utils/ecr.go -func GetNotationRepository(repository, token string) (notationregistry.Repository, error) { - ref, err := registry.ParseReference(repository) - if err != nil { - return nil, fmt.Errorf("failed to parse reference: %w", err) - } - - if err := ref.ValidateReferenceAsDigest(); err != nil { - return nil, fmt.Errorf("invalid reference: %w", err) - } - - authClient := &auth.Client{ - Credential: auth.StaticCredential(ref.Host(), auth.Credential{ - Username: "AWS", - Password: token, - }), - ClientID: "skpr", - } - - authClient.SetUserAgent("skpr") - - remoteRepo := &remote.Repository{ - Client: authClient, - Reference: ref, - } - - err = remoteRepo.SetReferrersCapability(false) - if err != nil { - return nil, err - } - - notationRepo := notationregistry.NewRepository(remoteRepo) - - return notationRepo, nil -} diff --git a/internal/buildpack/builder/prefixer.go b/internal/buildpack/utils/prefixer/prefixer.go similarity index 50% rename from internal/buildpack/builder/prefixer.go rename to internal/buildpack/utils/prefixer/prefixer.go index e784016..84cc22c 100644 --- a/internal/buildpack/builder/prefixer.go +++ b/internal/buildpack/utils/prefixer/prefixer.go @@ -1,8 +1,14 @@ -package buildpack +package prefixer import ( "fmt" + "io" + "strings" "time" + + "github.com/egym-playground/go-prefix-writer/prefixer" + + "github.com/skpr/cli/internal/color" ) // Prefixer for printing our build prefix. @@ -11,14 +17,18 @@ type Prefixer struct { start time.Time } -// New prefixer for printing out build prefix. -func newPrefixer(name string, start time.Time) Prefixer { +// NewPrefixer for printing out build prefix. +func NewPrefixer(name string, start time.Time) Prefixer { return Prefixer{ name: name, start: start, } } +func WrapWriterWithPrefixer(w io.Writer, name string, start time.Time) io.Writer { + return prefixer.New(w, NewPrefixer(color.Wrap(strings.ToUpper(name)), start).PrefixFunc()) +} + // PrefixFunc for dynamically calculating the prefix based on time since start. func (p Prefixer) PrefixFunc() func() string { return func() string { diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index 7b0badb..5237aa6 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -16,9 +16,17 @@ type Config struct { // ConfigExperimental holds experimental feature flags. type ConfigExperimental struct { - Trace bool `yaml:"trace,omitempty"` + Trace bool `yaml:"trace,omitempty"` + Builder ConfigPackageBuilder `yaml:"builder,omitempty"` } +type ConfigPackageBuilder string + +const ( + ConfigPackageBuilderLegacy ConfigPackageBuilder = "legacy" + ConfigPackageBuilderDocker ConfigPackageBuilder = "docker" +) + // Aliases maps alias names to commands. type Aliases map[string]string diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 86b3234..7100016 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/skpr/api/pb" + "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" skprlog "github.com/skpr/cli/internal/log" @@ -56,7 +57,7 @@ func (cmd *Command) Run(ctx context.Context) error { return fmt.Errorf("failed to get repository: %w", err) } - auth := docker.AuthConfiguration{ + auth := types.Auth{ Username: client.Credentials.Username, Password: client.Credentials.Password, } @@ -96,7 +97,12 @@ func (cmd *Command) Run(ctx context.Context) error { Tag: tag, } - err = dockerclient.PullImage(opts, auth) + clientAuth := docker.AuthConfiguration{ + Username: auth.Username, + Password: auth.Password, + } + + err = dockerclient.PullImage(opts, clientAuth) if err != nil { return err } diff --git a/internal/command/package/command.go b/internal/command/package/command.go index d40a0ed..25ee19c 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -5,17 +5,16 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" - "strings" - docker "github.com/fsouza/go-dockerclient" "github.com/skpr/api/pb" - buildpack "github.com/skpr/cli/internal/buildpack/builder" + buildpack "github.com/skpr/cli/internal/buildpack/builder/docker" + "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" "github.com/skpr/cli/internal/client/config" + "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/slice" ) @@ -23,10 +22,9 @@ import ( type Command struct { Region string PackageDir string - Params buildpack.Params + Params types.Params PrintManifest bool BuildArgs []string - Platform string Debug bool } @@ -63,12 +61,11 @@ func (cmd *Command) Run(ctx context.Context) error { cmd.Params.Registry = project.Registry.Application - cmd.Params.Auth = docker.AuthConfiguration{ + cmd.Params.Auth = types.Auth{ Username: client.Credentials.Username, Password: client.Credentials.Password, } - // @todo, Consider abstracting this if another registry + credentials pair is required. if ecr.IsRegistry(cmd.Params.Registry) { auth, err := ecr.UpgradeAuth(ctx, cmd.Params.Registry, client.Credentials) if err != nil { @@ -79,8 +76,13 @@ func (cmd *Command) Run(ctx context.Context) error { } } + // See if we're using default builder. + userConfig, _ := user.NewClient() + featureFlags, _ := userConfig.LoadFeatureFlags() + + fmt.Printf("builder: %s\n", featureFlags.Builder) + cmd.Params.Writer = os.Stderr - cmd.Params.Platform = cmd.Platform // Convert build args from slice to map. // eg. --build-arg=KEY=VALUE to map[string]string{"KEY": "VALUE"} @@ -98,21 +100,12 @@ func (cmd *Command) Run(ctx context.Context) error { } } - // Print deprecation notice. - for key, path := range dockerfiles { - if strings.HasSuffix(path, ".dockerfile") { - fmt.Printf("[DEPRECATED] Dockerfile location %q is deprecated. Use \"%s/%s/Dockerfile\" instead.\n", path, filepath.Dir(path), key) - } - } - - dockerclient, err := docker.NewClientFromEnv() + builder, err := buildpack.NewBuilder() if err != nil { - return fmt.Errorf("failed to setup Docker client: %w", err) + return err } - builder := buildpack.NewBuilder(dockerclient) - - resp, err := builder.Build(dockerfiles, cmd.Params) + resp, err := builder.Build(ctx, dockerfiles, cmd.Params) if err != nil { return err } From ab3f43999a8580ef19ffa0c8db3fcd2440b176ab Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 3 Dec 2025 16:43:49 +1300 Subject: [PATCH 02/21] Updating to an interface and using build flags --- internal/buildpack/builder/builder.go | 7 +---- internal/buildpack/builder/builder_test.go | 2 +- internal/buildpack/types/builder.go | 12 ++++++++ internal/command/package/command.go | 34 +++++++++++++++++----- 4 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 internal/buildpack/types/builder.go diff --git a/internal/buildpack/builder/builder.go b/internal/buildpack/builder/builder.go index 337af7f..cfe3f7c 100644 --- a/internal/buildpack/builder/builder.go +++ b/internal/buildpack/builder/builder.go @@ -26,12 +26,7 @@ type Builder struct { } // NewBuilder creates a new Builder. -func NewBuilder() (*Builder, error) { - dockerclient, err := docker.NewClientFromEnv() - if err != nil { - return nil, fmt.Errorf("failed to setup Docker client: %w", err) - } - +func NewBuilder(dockerclient DockerClientInterface) (*Builder, error) { return &Builder{ dockerClient: dockerclient, }, nil diff --git a/internal/buildpack/builder/builder_test.go b/internal/buildpack/builder/builder_test.go index 806512a..61da8c1 100644 --- a/internal/buildpack/builder/builder_test.go +++ b/internal/buildpack/builder/builder_test.go @@ -35,7 +35,7 @@ func TestBuild(t *testing.T) { Auth: types.Auth{}, } - builder, err := NewBuilder() + builder, err := NewBuilder(dockerClient) assert.NoError(t, err) have, err := builder.Build(context.TODO(), dockerFiles, params) diff --git a/internal/buildpack/types/builder.go b/internal/buildpack/types/builder.go new file mode 100644 index 0000000..511e655 --- /dev/null +++ b/internal/buildpack/types/builder.go @@ -0,0 +1,12 @@ +package types + +import ( + "context" + + "github.com/skpr/cli/internal/buildpack/utils/finder" +) + +// Builder interface for swapping out image builders. +type Builder interface { + Build(ctx context.Context, dockerfiles finder.Dockerfiles, params Params) (BuildResponse, error) +} diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 25ee19c..8e773ff 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -6,9 +6,11 @@ import ( "fmt" "os" + docker "github.com/fsouza/go-dockerclient" "github.com/skpr/api/pb" - buildpack "github.com/skpr/cli/internal/buildpack/builder/docker" + goclientbuilder "github.com/skpr/cli/internal/buildpack/builder" + dockerbuilder "github.com/skpr/cli/internal/buildpack/builder/docker" "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" @@ -76,12 +78,6 @@ func (cmd *Command) Run(ctx context.Context) error { } } - // See if we're using default builder. - userConfig, _ := user.NewClient() - featureFlags, _ := userConfig.LoadFeatureFlags() - - fmt.Printf("builder: %s\n", featureFlags.Builder) - cmd.Params.Writer = os.Stderr // Convert build args from slice to map. @@ -100,7 +96,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - builder, err := buildpack.NewBuilder() + builder, err := getBuilder() if err != nil { return err } @@ -155,3 +151,25 @@ func (cmd *Command) Run(ctx context.Context) error { return nil } + +func getBuilder() (types.Builder, error) { + // See if we're using default builder. + userConfig, _ := user.NewClient() + featureFlags, _ := userConfig.LoadFeatureFlags() + + if featureFlags.Builder == user.ConfigPackageBuilderDocker { + builder, err := dockerbuilder.NewBuilder() + return builder, err + } + + if featureFlags.Builder != "" && featureFlags.Builder != user.ConfigPackageBuilderLegacy { + return nil, fmt.Errorf("unknown builder: %s", featureFlags.Builder) + } + + dockerclient, err := docker.NewClientFromEnv() + if err != nil { + return nil, fmt.Errorf("failed to setup Docker client: %w", err) + } + + return goclientbuilder.NewBuilder(dockerclient) +} From 71d5e3c03681e97efff6c5fa4e2227fc61ac9bc6 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 3 Dec 2025 16:48:37 +1300 Subject: [PATCH 03/21] Refactor into appropriate boxes. Remove unused code --- internal/buildpack/builder/docker/builder.go | 9 +-------- internal/buildpack/builder/{ => goclient}/builder.go | 2 +- .../buildpack/builder/{ => goclient}/builder_test.go | 4 ++-- internal/buildpack/builder/{ => goclient}/mock/client.go | 0 internal/command/package/command.go | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) rename internal/buildpack/builder/{ => goclient}/builder.go (99%) rename internal/buildpack/builder/{ => goclient}/builder_test.go (94%) rename internal/buildpack/builder/{ => goclient}/mock/client.go (100%) diff --git a/internal/buildpack/builder/docker/builder.go b/internal/buildpack/builder/docker/builder.go index 31e5f56..7108155 100644 --- a/internal/buildpack/builder/docker/builder.go +++ b/internal/buildpack/builder/docker/builder.go @@ -24,16 +24,9 @@ import ( "github.com/skpr/cli/internal/buildpack/utils/prefixer" ) -// DockerClientInterface provides an interface that allows us to test the builder. -// This mirrors the subset of the official Docker SDK we use. -type DockerClientInterface interface { - ImageBuild(ctx context.Context, buildContext io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) - ImagePush(ctx context.Context, ref string, options imagetypes.PushOptions) (io.ReadCloser, error) -} - // Builder is the docker image builder. type Builder struct { - dockerClient DockerClientInterface + dockerClient *dockclient.Client } // NewBuilder creates a new Builder. diff --git a/internal/buildpack/builder/builder.go b/internal/buildpack/builder/goclient/builder.go similarity index 99% rename from internal/buildpack/builder/builder.go rename to internal/buildpack/builder/goclient/builder.go index cfe3f7c..900fa6e 100644 --- a/internal/buildpack/builder/builder.go +++ b/internal/buildpack/builder/goclient/builder.go @@ -1,4 +1,4 @@ -package buildpack +package goclient import ( "context" diff --git a/internal/buildpack/builder/builder_test.go b/internal/buildpack/builder/goclient/builder_test.go similarity index 94% rename from internal/buildpack/builder/builder_test.go rename to internal/buildpack/builder/goclient/builder_test.go index 61da8c1..ac324b7 100644 --- a/internal/buildpack/builder/builder_test.go +++ b/internal/buildpack/builder/goclient/builder_test.go @@ -1,4 +1,4 @@ -package buildpack +package goclient import ( "bytes" @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/skpr/cli/internal/buildpack/builder/mock" + "github.com/skpr/cli/internal/buildpack/builder/goclient/mock" "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/finder" ) diff --git a/internal/buildpack/builder/mock/client.go b/internal/buildpack/builder/goclient/mock/client.go similarity index 100% rename from internal/buildpack/builder/mock/client.go rename to internal/buildpack/builder/goclient/mock/client.go diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 8e773ff..cd950a7 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -9,8 +9,8 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/skpr/api/pb" - goclientbuilder "github.com/skpr/cli/internal/buildpack/builder" dockerbuilder "github.com/skpr/cli/internal/buildpack/builder/docker" + goclientbuilder "github.com/skpr/cli/internal/buildpack/builder/goclient" "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" From f29b0ae9eca2c31cce175131ea75fcbbcdb4d58b Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 10:11:25 +1300 Subject: [PATCH 04/21] Moving auth higher since it's used in two places --- internal/auth/auth.go | 6 ++++++ internal/buildpack/builder/goclient/builder_test.go | 3 ++- internal/buildpack/types/types.go | 13 ++++++------- internal/buildpack/utils/aws/ecr/ecr.go | 6 +++--- internal/command/mysql/image/pull/command.go | 4 ++-- internal/command/package/command.go | 3 ++- 6 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 internal/auth/auth.go diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..dd8ad03 --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,6 @@ +package auth + +type Auth struct { + Username string + Password string +} diff --git a/internal/buildpack/builder/goclient/builder_test.go b/internal/buildpack/builder/goclient/builder_test.go index ac324b7..bdf56ae 100644 --- a/internal/buildpack/builder/goclient/builder_test.go +++ b/internal/buildpack/builder/goclient/builder_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/builder/goclient/mock" "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/finder" @@ -32,7 +33,7 @@ func TestBuild(t *testing.T) { Version: "222", Context: "bar", NoPush: false, - Auth: types.Auth{}, + Auth: auth.Auth{}, } builder, err := NewBuilder(dockerClient) diff --git a/internal/buildpack/types/types.go b/internal/buildpack/types/types.go index 9829042..ce0040f 100644 --- a/internal/buildpack/types/types.go +++ b/internal/buildpack/types/types.go @@ -1,6 +1,10 @@ package types -import "io" +import ( + "io" + + "github.com/skpr/cli/internal/auth" +) // BuildResponse is returned by the build operation. type BuildResponse struct { @@ -15,14 +19,9 @@ type Image struct { Tag string `json:"tag"` } -type Auth struct { - Username string - Password string -} - // Params used for building the applications. type Params struct { - Auth Auth + Auth auth.Auth Writer io.Writer Context string IgnoreFile string diff --git a/internal/buildpack/utils/aws/ecr/ecr.go b/internal/buildpack/utils/aws/ecr/ecr.go index ceb5c74..57b9f22 100644 --- a/internal/buildpack/utils/aws/ecr/ecr.go +++ b/internal/buildpack/utils/aws/ecr/ecr.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" "github.com/pkg/errors" - "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/auth" skprcredentials "github.com/skpr/cli/internal/client/credentials" ) @@ -25,8 +25,8 @@ func IsRegistry(registry string) bool { // UpgradeAuth to use an AWS IAM token for authentication.. // https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html -func UpgradeAuth(ctx context.Context, url string, creds skprcredentials.Credentials) (types.Auth, error) { - var auth types.Auth +func UpgradeAuth(ctx context.Context, url string, creds skprcredentials.Credentials) (auth.Auth, error) { + var auth auth.Auth region, err := extractRegionFromURL(url) if err != nil { diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 7100016..ccc59c3 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/skpr/api/pb" - "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" skprlog "github.com/skpr/cli/internal/log" @@ -57,7 +57,7 @@ func (cmd *Command) Run(ctx context.Context) error { return fmt.Errorf("failed to get repository: %w", err) } - auth := types.Auth{ + auth := auth.Auth{ Username: client.Credentials.Username, Password: client.Credentials.Password, } diff --git a/internal/command/package/command.go b/internal/command/package/command.go index cd950a7..52df3b0 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -9,6 +9,7 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/skpr/api/pb" + "github.com/skpr/cli/internal/auth" dockerbuilder "github.com/skpr/cli/internal/buildpack/builder/docker" goclientbuilder "github.com/skpr/cli/internal/buildpack/builder/goclient" "github.com/skpr/cli/internal/buildpack/types" @@ -63,7 +64,7 @@ func (cmd *Command) Run(ctx context.Context) error { cmd.Params.Registry = project.Registry.Application - cmd.Params.Auth = types.Auth{ + cmd.Params.Auth = auth.Auth{ Username: client.Credentials.Username, Password: client.Credentials.Password, } From c0fbea438d9f100fcc85672e72d2acb9a79c11d1 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 13:09:14 +1300 Subject: [PATCH 05/21] More client reuse --- go.mod | 2 +- internal/buildpack/builder/docker/builder.go | 18 +--- .../buildpack/builder/goclient/mock/client.go | 2 +- internal/buildpack/utils/finder/finder.go | 2 +- internal/command/mysql/image/pull/command.go | 58 +++++++------ internal/docker/dockerclient/auth.go | 17 ++++ internal/docker/dockerclient/client.go | 83 +++++++++++++++++++ internal/docker/goclient/client.go | 66 +++++++++++++++ internal/docker/interface.go | 12 +++ 9 files changed, 218 insertions(+), 42 deletions(-) create mode 100644 internal/docker/dockerclient/auth.go create mode 100644 internal/docker/dockerclient/client.go create mode 100644 internal/docker/goclient/client.go create mode 100644 internal/docker/interface.go diff --git a/go.mod b/go.mod index 3508959..1f809d4 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/fang v0.4.4 + github.com/containerd/errdefs v1.0.0 github.com/docker/docker v28.5.2+incompatible github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca github.com/fatih/color v1.18.0 @@ -74,7 +75,6 @@ require ( github.com/clipperhouse/displaywidth v0.6.2 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect - github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/internal/buildpack/builder/docker/builder.go b/internal/buildpack/builder/docker/builder.go index 7108155..e29d20a 100644 --- a/internal/buildpack/builder/docker/builder.go +++ b/internal/buildpack/builder/docker/builder.go @@ -2,7 +2,6 @@ package buildpack import ( "context" - "encoding/base64" "encoding/json" "fmt" "io" @@ -11,7 +10,7 @@ import ( "github.com/docker/docker/api/types/build" imagetypes "github.com/docker/docker/api/types/image" - dockregistry "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/registry" dockclient "github.com/docker/docker/client" "github.com/moby/go-archive" "github.com/moby/moby/api/types/jsonstream" @@ -22,6 +21,7 @@ import ( "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/buildpack/utils/image" "github.com/skpr/cli/internal/buildpack/utils/prefixer" + "github.com/skpr/cli/internal/docker/dockerclient" ) // Builder is the docker image builder. @@ -163,11 +163,11 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par ref: fmt.Sprintf("%s:%s", params.Registry, image.Tag(params.Version, buildImage.Tag)), }) } - auth := dockregistry.AuthConfig{ + auth := registry.AuthConfig{ Username: params.Auth.Username, Password: params.Auth.Password, } - authHdr, err := encodeRegistryAuth(auth) + authHdr, err := dockerclient.EncodeRegistryAuth(auth) if err != nil { return resp, fmt.Errorf("failed to encode registry auth: %w", err) } @@ -229,16 +229,6 @@ func (b *Builder) buildImage(ctx context.Context, contextDir string, contextExcl return nil } -// encodeRegistryAuth converts a registrytypes.AuthConfig into the base64-encoded JSON -// expected by the Docker Engine API for ImagePush. -func encodeRegistryAuth(cfg dockregistry.AuthConfig) (string, error) { - b, err := json.Marshal(cfg) - if err != nil { - return "", err - } - return base64.URLEncoding.EncodeToString(b), nil -} - func handleMessages(in io.ReadCloser, out io.Writer) error { // Stream the daemon's JSON log stream to the provided writer. decoder := json.NewDecoder(in) diff --git a/internal/buildpack/builder/goclient/mock/client.go b/internal/buildpack/builder/goclient/mock/client.go index dc3072f..186e31d 100644 --- a/internal/buildpack/builder/goclient/mock/client.go +++ b/internal/buildpack/builder/goclient/mock/client.go @@ -6,7 +6,7 @@ import ( docker "github.com/fsouza/go-dockerclient" ) -// DockerClient provides a mock docker client. +// DockerClient provides a mock dockerclient client. type DockerClient struct { BuildWg sync.WaitGroup PushWg sync.WaitGroup diff --git a/internal/buildpack/utils/finder/finder.go b/internal/buildpack/utils/finder/finder.go index 28d6f81..89754b3 100644 --- a/internal/buildpack/utils/finder/finder.go +++ b/internal/buildpack/utils/finder/finder.go @@ -7,7 +7,7 @@ import ( "strings" ) -// Dockerfiles the docker build files. +// Dockerfiles the dockerclient build files. type Dockerfiles map[string]string // dockerfileSuffix is the suffix for legacy dockerfiles. diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index ccc59c3..ac5c69e 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -6,7 +6,6 @@ import ( "log/slog" "os" - docker "github.com/fsouza/go-dockerclient" "github.com/gosuri/uilive" "github.com/pkg/errors" "github.com/skpr/api/pb" @@ -14,6 +13,10 @@ import ( "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" + "github.com/skpr/cli/internal/client/config/user" + "github.com/skpr/cli/internal/docker" + "github.com/skpr/cli/internal/docker/dockerclient" + "github.com/skpr/cli/internal/docker/goclient" skprlog "github.com/skpr/cli/internal/log" ) @@ -70,13 +73,14 @@ func (cmd *Command) Run(ctx context.Context) error { } } - dockerclient, err := docker.NewClientFromEnv() + c, err := getClient(auth) if err != nil { - return errors.Wrap(err, "failed to setup Docker client") + return errors.Wrap(err, "failed to create Docker client") } writer := uilive.New() writer.Start() + defer writer.Stop() for _, database := range cmd.Params.Databases { tag := fmt.Sprintf("%s-%s", database, DefaultTagSuffix) @@ -86,45 +90,34 @@ func (cmd *Command) Run(ctx context.Context) error { logger.Info(fmt.Sprintf("Pulling: %s", imageName)) // Lookup the ID of the current image so we can delete it after we pull the image one. - cleanup, err := dockerclient.InspectImage(imageName) - if err != nil && !errors.Is(err, docker.ErrNoSuchImage) { + cleanupId, err := c.ImageId(context.TODO(), imageName) + if err != nil { return err } - opts := docker.PullImageOptions{ - OutputStream: writer, - Repository: getRepositoryResp.Repository, - Tag: tag, - } - - clientAuth := docker.AuthConfiguration{ - Username: auth.Username, - Password: auth.Password, - } - - err = dockerclient.PullImage(opts, clientAuth) + err = c.PullImage(context.TODO(), getRepositoryResp.Repository, tag, writer) if err != nil { return err } - // Check if there was an old images which - if cleanup == nil { + // Check if there was an old image before cleaning up. + if cleanupId == "" { continue } - current, err := dockerclient.InspectImage(imageName) + currentId, err := c.ImageId(context.TODO(), imageName) if err != nil { return err } // Don't cleanup the old image if it was the latest and never needed to be updated. - if cleanup.ID == current.ID { + if cleanupId == currentId { continue } - logger.Info(fmt.Sprintf("Cleaning up old image with: %s", cleanup.ID)) + logger.Info(fmt.Sprintf("Cleaning up old image with: %s", cleanupId)) - err = dockerclient.RemoveImage(cleanup.ID) + err = c.RemoveImage(context.TODO(), cleanupId) if err != nil { return err } @@ -132,7 +125,22 @@ func (cmd *Command) Run(ctx context.Context) error { logger.Info(fmt.Sprintf("Successfully pulled image: %s:%s", getRepositoryResp.Repository, tag)) } - writer.Stop() - return nil } + +func getClient(auth auth.Auth) (docker.DockerClient, error) { + // See if we're using default builder. + userConfig, _ := user.NewClient() + featureFlags, _ := userConfig.LoadFeatureFlags() + + if featureFlags.Builder == user.ConfigPackageBuilderDocker { + c, err := dockerclient.New(auth) + return c, err + } + + if featureFlags.Builder != "" && featureFlags.Builder != user.ConfigPackageBuilderLegacy { + return nil, fmt.Errorf("unknown docker client: %s", featureFlags.Builder) + } + + return goclient.New(auth) +} diff --git a/internal/docker/dockerclient/auth.go b/internal/docker/dockerclient/auth.go new file mode 100644 index 0000000..77cb6c9 --- /dev/null +++ b/internal/docker/dockerclient/auth.go @@ -0,0 +1,17 @@ +package dockerclient + +import ( + "encoding/base64" + "encoding/json" + + "github.com/docker/docker/api/types/registry" +) + +// EncodeRegistryAuth converts a registry.AuthConfig into the base64-encoded JSON expected by the Docker Engine API. +func EncodeRegistryAuth(cfg registry.AuthConfig) (string, error) { + b, err := json.Marshal(cfg) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(b), nil +} diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go new file mode 100644 index 0000000..e01ced2 --- /dev/null +++ b/internal/docker/dockerclient/client.go @@ -0,0 +1,83 @@ +package dockerclient + +import ( + "context" + "fmt" + "io" + + "github.com/containerd/errdefs" + imagetypes "github.com/docker/docker/api/types/image" + dockregistry "github.com/docker/docker/api/types/registry" + dockclient "github.com/docker/docker/client" + "github.com/pkg/errors" + + "github.com/skpr/cli/internal/auth" +) + +type Client struct { + Auth auth.Auth + Client *dockclient.Client +} + +func New(auth auth.Auth) (*Client, error) { + client, err := dockclient.NewClientWithOpts(dockclient.FromEnv, dockclient.WithAPIVersionNegotiation()) + if err != nil { + return nil, errors.Wrap(err, "failed to setup Docker client") + } + + return &Client{ + Auth: auth, + Client: client, + }, nil +} + +func (c *Client) ImageId(ctx context.Context, name string) (string, error) { + resp, err := c.Client.ImageInspect(ctx, name) + if err == nil { + return resp.ID, nil + } else if !errdefs.IsNotFound(err) { + return "", err + } + + return "", nil +} + +func (c *Client) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { + auth := dockregistry.AuthConfig{ + Username: c.Auth.Username, + Password: c.Auth.Password, + } + authHdr, err := EncodeRegistryAuth(auth) + if err != nil { + return errors.Wrap(err, "failed to encode registry auth") + } + + imageName := fmt.Sprintf("%s:%s", repository, tag) + + rc, err := c.Client.ImagePull(ctx, imageName, imagetypes.PullOptions{ + RegistryAuth: authHdr, + }) + if err != nil { + return err + } + + // Stream the pull progress to the UI writer. + if _, copyErr := io.Copy(writer, rc); copyErr != nil { + _ = rc.Close() + return copyErr + } + _ = rc.Close() + + return nil +} + +func (c *Client) RemoveImage(ctx context.Context, id string) error { + _, err := c.Client.ImageRemove(ctx, id, imagetypes.RemoveOptions{ + PruneChildren: true, + Force: false, + }) + if err != nil && !errdefs.IsNotFound(err) { + return err + } + return nil +} diff --git a/internal/docker/goclient/client.go b/internal/docker/goclient/client.go new file mode 100644 index 0000000..d23bcd7 --- /dev/null +++ b/internal/docker/goclient/client.go @@ -0,0 +1,66 @@ +package goclient + +import ( + "context" + "io" + + dockerclient "github.com/fsouza/go-dockerclient" + "github.com/pkg/errors" + + "github.com/skpr/cli/internal/auth" +) + +type Client struct { + Auth auth.Auth + Client *dockerclient.Client +} + +func New(auth auth.Auth) (*Client, error) { + client, err := dockerclient.NewClientFromEnv() + if err != nil { + return nil, errors.Wrap(err, "failed to setup Docker client") + } + + return &Client{ + Auth: auth, + Client: client, + }, nil +} + +func (c *Client) ImageId(ctx context.Context, name string) (string, error) { + resp, err := c.Client.InspectImage(name) + if err != nil && !errors.Is(err, dockerclient.ErrNoSuchImage) { + return "", err + } + + if resp == nil { + return "", nil + } + + return resp.ID, nil +} + +func (c *Client) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { + opts := dockerclient.PullImageOptions{ + OutputStream: writer, + Repository: repository, + Tag: tag, + } + + clientAuth := dockerclient.AuthConfiguration{ + Username: c.Auth.Username, + Password: c.Auth.Password, + } + + err := c.Client.PullImage(opts, clientAuth) + if err != nil { + return err + } + + return nil +} + +func (c *Client) RemoveImage(ctx context.Context, id string) error { + err := c.Client.RemoveImage(id) + return err +} diff --git a/internal/docker/interface.go b/internal/docker/interface.go new file mode 100644 index 0000000..a19376d --- /dev/null +++ b/internal/docker/interface.go @@ -0,0 +1,12 @@ +package docker + +import ( + "context" + "io" +) + +type DockerClient interface { + ImageId(ctx context.Context, name string) (string, error) + PullImage(ctx context.Context, repository, tag string, writer io.Writer) error + RemoveImage(ctx context.Context, id string) error +} From b0dea50a22d5ff1bfe06068f1bcb1542c6569172 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 19:13:51 +1300 Subject: [PATCH 06/21] Another snapshot --- go.mod | 8 +- go.sum | 32 ++--- internal/buildpack/builder/builder.go | 171 +++++++++++++++++++++++++ internal/command/package/command.go | 31 +++-- internal/docker/dockerclient/client.go | 105 ++++++++++++++- internal/docker/goclient/client.go | 31 ++++- internal/docker/interface.go | 2 + 7 files changed, 330 insertions(+), 50 deletions(-) create mode 100644 internal/buildpack/builder/builder.go diff --git a/go.mod b/go.mod index 1f809d4..4617fbf 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( ) require ( - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect @@ -79,7 +79,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -88,7 +88,7 @@ require ( github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jwalton/go-supportscolor v1.2.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -99,7 +99,7 @@ require ( github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect diff --git a/go.sum b/go.sum index f66f350..acb32bf 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 h1:D9PbaszZYp charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= @@ -91,8 +91,8 @@ github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmC github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -101,8 +101,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= 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/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca h1:sWNMfkKG8GW1pGUyNlbsWq6f04pFgcsomY+Fly8XdB4= @@ -124,8 +124,6 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -149,8 +147,8 @@ github.com/jwalton/gchalk v1.3.0/go.mod h1:ytRlj60R9f7r53IAElbpq4lVuPOPNg2J4tJcC github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8= github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -186,8 +184,8 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -210,7 +208,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -233,7 +230,6 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -266,20 +262,16 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 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/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -295,13 +287,10 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= @@ -320,6 +309,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/internal/buildpack/builder/builder.go b/internal/buildpack/builder/builder.go new file mode 100644 index 0000000..7b32712 --- /dev/null +++ b/internal/buildpack/builder/builder.go @@ -0,0 +1,171 @@ +package buildpack + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/moby/patternmatcher/ignorefile" + "golang.org/x/sync/errgroup" + + "github.com/skpr/cli/internal/buildpack/types" + "github.com/skpr/cli/internal/buildpack/utils/finder" + "github.com/skpr/cli/internal/buildpack/utils/image" + "github.com/skpr/cli/internal/buildpack/utils/prefixer" + "github.com/skpr/cli/internal/docker" +) + +// Builder is the docker image builder. +type Builder struct { + Client docker.DockerClient +} + +// NewBuilder creates a new Builder. +func NewBuilder(c docker.DockerClient) (*Builder, error) { + return &Builder{ + Client: c, + }, nil +} + +// Build the images. +func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { + var resp types.BuildResponse + + excludePatterns, err := loadIgnoreFilePatterns(params.IgnoreFile) + if err != nil { + return resp, fmt.Errorf("failed to parse ignore file: %w", err) + } + + compileDockerfile, ok := dockerfiles[types.ImageNameCompile] + if !ok { + return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) + } + + // Build the build args. + buildArgs := params.BuildArgs + buildArgs[types.BuildArgVersion] = params.Version + + start := time.Now() + + // Build the compile image first; it's the base for others. + compileRef := image.Name(params.Registry, params.Version, types.ImageNameCompile) + + fmt.Fprintf(params.Writer, "Building image: %s\n", compileRef) + + localOut := prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start) + err = b.Client.BuildImage(ctx, compileDockerfile, params.Context, compileRef, excludePatterns, buildArgs, localOut) + if err != nil { + return resp, err + } + + fmt.Fprintf(params.Writer, "Built %s image in %s\n", compileRef, time.Since(start).Round(time.Second)) + + // Remove compile from list of dockerfiles. + delete(dockerfiles, types.ImageNameCompile) + + // Adds compile image identifier to the runtime images as an arg. + buildArgs[types.BuildArgCompileImage] = compileRef + + // Prepare runtime builds. + type pendingBuild struct { + name string + imageRef string + dockerfile string + } + var builds []pendingBuild + for imageName, dockerfile := range dockerfiles { + ref := image.Name(params.Registry, params.Version, imageName) + builds = append(builds, pendingBuild{name: imageName, imageRef: ref, dockerfile: dockerfile}) + resp.Images = append(resp.Images, types.Image{ + Name: imageName, + Tag: ref, + }) + } + + // Parallel runtime builds. + bg, ctx := errgroup.WithContext(ctx) + for _, pb := range builds { + pb := pb + + fmt.Fprintf(params.Writer, "Building image: %s\n", pb.imageRef) + + localStart := time.Now() + localOut := prefixer.WrapWriterWithPrefixer(params.Writer, pb.name, localStart) + bg.Go(func() error { + err := b.Client.BuildImage(ctx, pb.dockerfile, params.Context, pb.imageRef, excludePatterns, buildArgs, localOut) + if err != nil { + return err + } + + fmt.Fprintf(params.Writer, "Built %s image in %s\n", pb.imageRef, time.Since(start).Round(time.Second)) + return nil + }) + } + if err := bg.Wait(); err != nil { + return resp, err + } + + if params.NoPush { + return resp, nil + } + + // Prepare pushes (skip compile). + type pendingPush struct { + Name string + Tag string // full "registry/repo:tag" + } + var pushes []pendingPush + for _, buildImage := range resp.Images { + pushes = append(pushes, pendingPush{ + Name: params.Registry, + Tag: image.Tag(params.Version, buildImage.Name), + }) + } + + // Parallel pushes. + pg, ctx := errgroup.WithContext(context.TODO()) + for _, p := range pushes { + p := p + + fmt.Fprintf(params.Writer, "Pushing image: %s:%s\n", p.Name, p.Tag) + out := prefixer.WrapWriterWithPrefixer(params.Writer, "push "+p.Name, start) + + pg.Go(func() error { + localStart := time.Now() + err := b.Client.PushImage(ctx, p.Name, p.Tag, out) + if err != nil { + return err + } + + fmt.Fprintf(params.Writer, "Pushed %s:%s image in %s\n", p.Name, p.Tag, time.Since(localStart).Round(time.Second)) + return nil + }) + } + if err := pg.Wait(); err != nil { + return resp, err + } + + fmt.Fprintf(params.Writer, "Build complete in: %s\n", time.Since(start).Round(time.Second)) + return resp, nil +} + +// Loads and returns a list of ignore file patterns from the specified file. +func loadIgnoreFilePatterns(filePath string) ([]string, error) { + f, err := os.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + + return nil, fmt.Errorf("unable to open ignore file: %w", err) + } + defer f.Close() + + patterns, err := ignorefile.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("error parsing ignore file: %w", err) + } + + return patterns, nil +} diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 52df3b0..f4dcf2e 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -6,18 +6,19 @@ import ( "fmt" "os" - docker "github.com/fsouza/go-dockerclient" "github.com/skpr/api/pb" "github.com/skpr/cli/internal/auth" - dockerbuilder "github.com/skpr/cli/internal/buildpack/builder/docker" - goclientbuilder "github.com/skpr/cli/internal/buildpack/builder/goclient" + dockerbuilder "github.com/skpr/cli/internal/buildpack/builder" "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" "github.com/skpr/cli/internal/client/config" "github.com/skpr/cli/internal/client/config/user" + "github.com/skpr/cli/internal/docker" + "github.com/skpr/cli/internal/docker/dockerclient" + "github.com/skpr/cli/internal/docker/goclient" "github.com/skpr/cli/internal/slice" ) @@ -40,6 +41,8 @@ func (cmd *Command) Run(ctx context.Context) error { } cmd.Params.Registry = fmt.Sprintf("localhost/skpr/%s", config.Project) + + cmd.Params.Auth = auth.Auth{} } else { ctx, client, err := client.New(ctx) if err != nil { @@ -97,7 +100,12 @@ func (cmd *Command) Run(ctx context.Context) error { } } - builder, err := getBuilder() + dc, err := getClient(cmd.Params.Auth) + if err != nil { + return err + } + + builder, err := dockerbuilder.NewBuilder(dc) if err != nil { return err } @@ -153,24 +161,19 @@ func (cmd *Command) Run(ctx context.Context) error { return nil } -func getBuilder() (types.Builder, error) { +func getClient(auth auth.Auth) (docker.DockerClient, error) { // See if we're using default builder. userConfig, _ := user.NewClient() featureFlags, _ := userConfig.LoadFeatureFlags() if featureFlags.Builder == user.ConfigPackageBuilderDocker { - builder, err := dockerbuilder.NewBuilder() - return builder, err + c, err := dockerclient.New(auth) + return c, err } if featureFlags.Builder != "" && featureFlags.Builder != user.ConfigPackageBuilderLegacy { - return nil, fmt.Errorf("unknown builder: %s", featureFlags.Builder) - } - - dockerclient, err := docker.NewClientFromEnv() - if err != nil { - return nil, fmt.Errorf("failed to setup Docker client: %w", err) + return nil, fmt.Errorf("unknown docker client: %s", featureFlags.Builder) } - return goclientbuilder.NewBuilder(dockerclient) + return goclient.New(auth) } diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go index e01ced2..eb3fd11 100644 --- a/internal/docker/dockerclient/client.go +++ b/internal/docker/dockerclient/client.go @@ -2,13 +2,17 @@ package dockerclient import ( "context" + "encoding/json" "fmt" "io" "github.com/containerd/errdefs" + "github.com/docker/docker/api/types/build" imagetypes "github.com/docker/docker/api/types/image" dockregistry "github.com/docker/docker/api/types/registry" dockclient "github.com/docker/docker/client" + "github.com/moby/go-archive" + "github.com/moby/moby/api/types/jsonstream" "github.com/pkg/errors" "github.com/skpr/cli/internal/auth" @@ -61,12 +65,37 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i return err } - // Stream the pull progress to the UI writer. - if _, copyErr := io.Copy(writer, rc); copyErr != nil { - _ = rc.Close() - return copyErr + err = handleMessages(rc, writer) + if err != nil { + return err + } + + return nil +} + +func (c *Client) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { + auth := dockregistry.AuthConfig{ + Username: c.Auth.Username, + Password: c.Auth.Password, + } + authHdr, err := EncodeRegistryAuth(auth) + if err != nil { + return errors.Wrap(err, "failed to encode registry auth") + } + + imageName := fmt.Sprintf("%s:%s", repository, tag) + + rc, err := c.Client.ImagePush(ctx, imageName, imagetypes.PushOptions{ + RegistryAuth: authHdr, + }) + if err != nil { + return err + } + + err = handleMessages(rc, writer) + if err != nil { + return err } - _ = rc.Close() return nil } @@ -81,3 +110,69 @@ func (c *Client) RemoveImage(ctx context.Context, id string) error { } return nil } + +func (c *Client) BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error { + buildCtx, err := archive.TarWithOptions(dockerContext, &archive.TarOptions{ + ExcludePatterns: excludePatterns, + }) + if err != nil { + return fmt.Errorf("failed to archive build context: %w", err) + } + + // Build args (Docker SDK expects map[string]*string) + args := map[string]*string{} + withArg := func(k, v string) { + val := v + args[k] = &val + } + for k, v := range buildArgs { + withArg(k, v) + } + + opts := build.ImageBuildOptions{ + Tags: []string{ref}, + Dockerfile: dockerfile, + Remove: true, + BuildArgs: args, + } + + resp, err := c.Client.ImageBuild(ctx, buildCtx, opts) + if err != nil { + return err + } + defer resp.Body.Close() + + err = handleMessages(resp.Body, writer) + if err != nil { + return err + } + + return nil +} + +func handleMessages(in io.ReadCloser, out io.Writer) error { + // Stream the daemon's JSON log stream to the provided writer. + decoder := json.NewDecoder(in) + + for { + var msg jsonstream.Message + if err := decoder.Decode(&msg); err == io.EOF { + break + } else if err != nil { + return err + } + + // TODO: Possibly stream out progress. + + if msg.Stream != "" { + if _, err := io.WriteString(out, msg.Stream); err != nil { + return err + } + } + if msg.Error != nil { + return msg.Error + } + } + + return nil +} diff --git a/internal/docker/goclient/client.go b/internal/docker/goclient/client.go index d23bcd7..0bdc820 100644 --- a/internal/docker/goclient/client.go +++ b/internal/docker/goclient/client.go @@ -2,6 +2,7 @@ package goclient import ( "context" + "fmt" "io" dockerclient "github.com/fsouza/go-dockerclient" @@ -45,6 +46,7 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i OutputStream: writer, Repository: repository, Tag: tag, + Context: ctx, } clientAuth := dockerclient.AuthConfiguration{ @@ -52,15 +54,34 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i Password: c.Auth.Password, } - err := c.Client.PullImage(opts, clientAuth) - if err != nil { - return err - } + return c.Client.PullImage(opts, clientAuth) +} - return nil +func (c *Client) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { + return fmt.Errorf("not implemented") } func (c *Client) RemoveImage(ctx context.Context, id string) error { err := c.Client.RemoveImage(id) return err } + +func (c *Client) BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error { + args := []dockerclient.BuildArg{} + for k, v := range buildArgs { + args = append(args, dockerclient.BuildArg{ + Name: k, + Value: v, + }) + } + + build := dockerclient.BuildImageOptions{ + Context: ctx, + Name: ref, + Dockerfile: dockerfile, + ContextDir: dockerContext, + OutputStream: writer, + BuildArgs: args, + } + return c.Client.BuildImage(build) +} diff --git a/internal/docker/interface.go b/internal/docker/interface.go index a19376d..e6a8a61 100644 --- a/internal/docker/interface.go +++ b/internal/docker/interface.go @@ -8,5 +8,7 @@ import ( type DockerClient interface { ImageId(ctx context.Context, name string) (string, error) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error + PushImage(ctx context.Context, repository, tag string, writer io.Writer) error RemoveImage(ctx context.Context, id string) error + BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error } From bd6a5079221ea3d71b93f400d85efc58c1ebb810 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 19:35:49 +1300 Subject: [PATCH 07/21] Implementing the mock docker client --- internal/buildpack/{builder => }/builder.go | 21 +- internal/buildpack/builder/docker/builder.go | 277 ------------------ .../buildpack/builder/goclient/builder.go | 189 ------------ .../buildpack/builder/goclient/mock/client.go | 50 ---- .../{builder/goclient => }/builder_test.go | 24 +- internal/buildpack/{types => }/types.go | 2 +- internal/buildpack/types/builder.go | 12 - internal/command/package/command.go | 5 +- internal/docker/mock/client.go | 51 ++++ 9 files changed, 76 insertions(+), 555 deletions(-) rename internal/buildpack/{builder => }/builder.go (85%) delete mode 100644 internal/buildpack/builder/docker/builder.go delete mode 100644 internal/buildpack/builder/goclient/builder.go delete mode 100644 internal/buildpack/builder/goclient/mock/client.go rename internal/buildpack/{builder/goclient => }/builder_test.go (77%) rename internal/buildpack/{types => }/types.go (98%) delete mode 100644 internal/buildpack/types/builder.go create mode 100644 internal/docker/mock/client.go diff --git a/internal/buildpack/builder/builder.go b/internal/buildpack/builder.go similarity index 85% rename from internal/buildpack/builder/builder.go rename to internal/buildpack/builder.go index 7b32712..1a3c442 100644 --- a/internal/buildpack/builder/builder.go +++ b/internal/buildpack/builder.go @@ -9,7 +9,6 @@ import ( "github.com/moby/patternmatcher/ignorefile" "golang.org/x/sync/errgroup" - "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/buildpack/utils/image" "github.com/skpr/cli/internal/buildpack/utils/prefixer" @@ -29,31 +28,31 @@ func NewBuilder(c docker.DockerClient) (*Builder, error) { } // Build the images. -func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { - var resp types.BuildResponse +func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params Params) (BuildResponse, error) { + var resp BuildResponse excludePatterns, err := loadIgnoreFilePatterns(params.IgnoreFile) if err != nil { return resp, fmt.Errorf("failed to parse ignore file: %w", err) } - compileDockerfile, ok := dockerfiles[types.ImageNameCompile] + compileDockerfile, ok := dockerfiles[ImageNameCompile] if !ok { - return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) + return resp, fmt.Errorf("%q is a required dockerfile", ImageNameCompile) } // Build the build args. buildArgs := params.BuildArgs - buildArgs[types.BuildArgVersion] = params.Version + buildArgs[BuildArgVersion] = params.Version start := time.Now() // Build the compile image first; it's the base for others. - compileRef := image.Name(params.Registry, params.Version, types.ImageNameCompile) + compileRef := image.Name(params.Registry, params.Version, ImageNameCompile) fmt.Fprintf(params.Writer, "Building image: %s\n", compileRef) - localOut := prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start) + localOut := prefixer.WrapWriterWithPrefixer(params.Writer, ImageNameCompile, start) err = b.Client.BuildImage(ctx, compileDockerfile, params.Context, compileRef, excludePatterns, buildArgs, localOut) if err != nil { return resp, err @@ -62,10 +61,10 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par fmt.Fprintf(params.Writer, "Built %s image in %s\n", compileRef, time.Since(start).Round(time.Second)) // Remove compile from list of dockerfiles. - delete(dockerfiles, types.ImageNameCompile) + delete(dockerfiles, ImageNameCompile) // Adds compile image identifier to the runtime images as an arg. - buildArgs[types.BuildArgCompileImage] = compileRef + buildArgs[BuildArgCompileImage] = compileRef // Prepare runtime builds. type pendingBuild struct { @@ -77,7 +76,7 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par for imageName, dockerfile := range dockerfiles { ref := image.Name(params.Registry, params.Version, imageName) builds = append(builds, pendingBuild{name: imageName, imageRef: ref, dockerfile: dockerfile}) - resp.Images = append(resp.Images, types.Image{ + resp.Images = append(resp.Images, Image{ Name: imageName, Tag: ref, }) diff --git a/internal/buildpack/builder/docker/builder.go b/internal/buildpack/builder/docker/builder.go deleted file mode 100644 index e29d20a..0000000 --- a/internal/buildpack/builder/docker/builder.go +++ /dev/null @@ -1,277 +0,0 @@ -package buildpack - -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "time" - - "github.com/docker/docker/api/types/build" - imagetypes "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/registry" - dockclient "github.com/docker/docker/client" - "github.com/moby/go-archive" - "github.com/moby/moby/api/types/jsonstream" - "github.com/moby/patternmatcher/ignorefile" - "golang.org/x/sync/errgroup" - - "github.com/skpr/cli/internal/buildpack/types" - "github.com/skpr/cli/internal/buildpack/utils/finder" - "github.com/skpr/cli/internal/buildpack/utils/image" - "github.com/skpr/cli/internal/buildpack/utils/prefixer" - "github.com/skpr/cli/internal/docker/dockerclient" -) - -// Builder is the docker image builder. -type Builder struct { - dockerClient *dockclient.Client -} - -// NewBuilder creates a new Builder. -func NewBuilder() (*Builder, error) { - dc, err := dockclient.NewClientWithOpts(dockclient.FromEnv, dockclient.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - return &Builder{ - dockerClient: dc, - }, nil -} - -// Build the images. -func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { - var resp types.BuildResponse - - excludePatterns, err := loadIgnoreFilePatterns(params.IgnoreFile) - if err != nil { - return resp, fmt.Errorf("failed to parse ignore file: %w", err) - } - - compileDockerfile, ok := dockerfiles[types.ImageNameCompile] - if !ok { - return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) - } - - // Build args (Docker SDK expects map[string]*string) - buildArgs := map[string]*string{} - withArg := func(k, v string) { - val := v - buildArgs[k] = &val - } - withArg(types.BuildArgVersion, params.Version) - for k, v := range params.BuildArgs { - withArg(k, v) - } - - start := time.Now() - - // Build the compile image first; it's the base for others. - compileRef := image.Name(params.Registry, params.Version, types.ImageNameCompile) - - fmt.Fprintf(params.Writer, "Building image: %s\n", compileRef) - - localOut := prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start) - - if err := b.buildImage( - ctx, - params.Context, - excludePatterns, - build.ImageBuildOptions{ - Tags: []string{compileRef}, - Dockerfile: compileDockerfile, - Remove: true, - BuildArgs: buildArgs, - }, - localOut, - ); err != nil { - return resp, err - } - fmt.Fprintf(params.Writer, "Built %s image in %s\n", compileRef, time.Since(start).Round(time.Second)) - - // Remove compile from list of dockerfiles. - delete(dockerfiles, types.ImageNameCompile) - - // Adds compile image identifier to the runtime images as an arg. - withArg(types.BuildArgCompileImage, image.Name(params.Registry, params.Version, types.ImageNameCompile)) - - // Prepare runtime builds. - type pendingBuild struct { - name string - imageRef string - dockerfile string - } - var builds []pendingBuild - for imageName, dockerfile := range dockerfiles { - ref := image.Name(params.Registry, params.Version, imageName) - builds = append(builds, pendingBuild{name: imageName, imageRef: ref, dockerfile: dockerfile}) - resp.Images = append(resp.Images, types.Image{ - Name: imageName, - Tag: ref, - }) - } - - // Parallel runtime builds. - bg, ctx := errgroup.WithContext(ctx) - for _, pb := range builds { - pb := pb - - fmt.Fprintf(params.Writer, "Building image: %s\n", pb.imageRef) - - localStart := time.Now() - - localOut := prefixer.WrapWriterWithPrefixer(params.Writer, pb.name, localStart) - - bg.Go(func() error { - err := b.buildImage( - ctx, - params.Context, - excludePatterns, - build.ImageBuildOptions{ - Tags: []string{pb.imageRef}, - Dockerfile: pb.dockerfile, - Remove: true, - BuildArgs: buildArgs, - }, - localOut, - ) - if err != nil { - return err - } - fmt.Fprintf(params.Writer, "Built %s image in %s\n", pb.imageRef, time.Since(start).Round(time.Second)) - return nil - }) - } - if err := bg.Wait(); err != nil { - return resp, err - } - - if params.NoPush { - return resp, nil - } - - // Prepare pushes (skip compile). - type pendingPush struct { - name string - ref string // full "registry/repo:tag" - } - var pushes []pendingPush - for _, buildImage := range resp.Images { - pushes = append(pushes, pendingPush{ - name: buildImage.Name, - ref: fmt.Sprintf("%s:%s", params.Registry, image.Tag(params.Version, buildImage.Tag)), - }) - } - auth := registry.AuthConfig{ - Username: params.Auth.Username, - Password: params.Auth.Password, - } - authHdr, err := dockerclient.EncodeRegistryAuth(auth) - if err != nil { - return resp, fmt.Errorf("failed to encode registry auth: %w", err) - } - - // Parallel pushes. - pg, ctx := errgroup.WithContext(context.TODO()) - for _, p := range pushes { - p := p - fmt.Fprintf(params.Writer, "Pushing image: %s\n", p.ref) - out := prefixer.WrapWriterWithPrefixer(params.Writer, "push "+p.name, start) - - pg.Go(func() error { - localStart := time.Now() - rc, err := b.dockerClient.ImagePush(ctx, p.ref, imagetypes.PushOptions{ - RegistryAuth: authHdr, - }) - if err != nil { - return err - } - defer rc.Close() - - err = handleMessages(rc, out) - if err != nil { - return err - } - - fmt.Fprintf(params.Writer, "Pushed %s image in %s\n", p.ref, time.Since(localStart).Round(time.Second)) - return nil - }) - } - if err := pg.Wait(); err != nil { - return resp, err - } - - fmt.Fprintf(params.Writer, "Build complete in: %s\n", time.Since(start).Round(time.Second)) - return resp, nil -} - -// buildOne creates a tar build context from contextDir and streams the build output to out. -func (b *Builder) buildImage(ctx context.Context, contextDir string, contextExclude []string, opts build.ImageBuildOptions, out io.Writer) error { - buildCtx, err := archive.TarWithOptions(contextDir, &archive.TarOptions{ - ExcludePatterns: contextExclude, - }) - if err != nil { - return fmt.Errorf("failed to archive build context: %w", err) - } - - resp, err := b.dockerClient.ImageBuild(ctx, buildCtx, opts) - if err != nil { - return err - } - defer resp.Body.Close() - - err = handleMessages(resp.Body, out) - if err != nil { - return err - } - - return nil -} - -func handleMessages(in io.ReadCloser, out io.Writer) error { - // Stream the daemon's JSON log stream to the provided writer. - decoder := json.NewDecoder(in) - - for { - var msg jsonstream.Message - if err := decoder.Decode(&msg); err == io.EOF { - break - } else if err != nil { - return err - } - - // TODO: Possibly stream out progress. - - if msg.Stream != "" { - if _, err := io.WriteString(out, msg.Stream); err != nil { - return err - } - } - if msg.Error != nil { - return msg.Error - } - } - - return nil -} - -// Loads and returns a list of ignore file patterns from the specified file. -func loadIgnoreFilePatterns(filePath string) ([]string, error) { - f, err := os.Open(filePath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - - return nil, fmt.Errorf("unable to open ignore file: %w", err) - } - defer f.Close() - - patterns, err := ignorefile.ReadAll(f) - if err != nil { - return nil, fmt.Errorf("error parsing ignore file: %w", err) - } - - return patterns, nil -} diff --git a/internal/buildpack/builder/goclient/builder.go b/internal/buildpack/builder/goclient/builder.go deleted file mode 100644 index 900fa6e..0000000 --- a/internal/buildpack/builder/goclient/builder.go +++ /dev/null @@ -1,189 +0,0 @@ -package goclient - -import ( - "context" - "fmt" - "time" - - docker "github.com/fsouza/go-dockerclient" - "golang.org/x/sync/errgroup" - - "github.com/skpr/cli/internal/buildpack/types" - "github.com/skpr/cli/internal/buildpack/utils/finder" - "github.com/skpr/cli/internal/buildpack/utils/image" - "github.com/skpr/cli/internal/buildpack/utils/prefixer" -) - -// DockerClientInterface provides an interface that allows us to test the builder. -type DockerClientInterface interface { - BuildImage(options docker.BuildImageOptions) error - PushImage(options docker.PushImageOptions, auth docker.AuthConfiguration) error -} - -// Builder is the docker image builder. -type Builder struct { - dockerClient DockerClientInterface -} - -// NewBuilder creates a new Builder. -func NewBuilder(dockerclient DockerClientInterface) (*Builder, error) { - return &Builder{ - dockerClient: dockerclient, - }, nil -} - -// Build the images. -func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, params types.Params) (types.BuildResponse, error) { - var resp types.BuildResponse - - compileDockerfile, ok := dockerfiles[types.ImageNameCompile] - if !ok { - return resp, fmt.Errorf("%q is a required dockerfile", types.ImageNameCompile) - } - - args := []docker.BuildArg{ - { - Name: types.BuildArgVersion, - Value: params.Version, - }, - } - - for k, v := range params.BuildArgs { - args = append(args, docker.BuildArg{ - Name: k, - Value: v, - }) - } - - start := time.Now() - - // We build the compile image first, as it is the base image for other images. - compileBuild := docker.BuildImageOptions{ - Name: image.Name(params.Registry, params.Version, types.ImageNameCompile), - Dockerfile: compileDockerfile, - ContextDir: params.Context, - OutputStream: prefixer.WrapWriterWithPrefixer(params.Writer, types.ImageNameCompile, start), - BuildArgs: args, - } - - // We need to build the 'compile' image first. - fmt.Fprintf(params.Writer, "Building image: %s\n", compileBuild.Name) - err := b.dockerClient.BuildImage(compileBuild) - if err != nil { - return resp, err - } - fmt.Fprintf(params.Writer, "Built compile image in %s\n", time.Since(start).Round(time.Second)) - - // Remove compile from list of dockerfiles. - delete(dockerfiles, types.ImageNameCompile) - - // Adds compile image identifier to the runtime images as an arg. - // That allows runtime images to copy over the compiled code. - args = append(args, docker.BuildArg{ - Name: types.BuildArgCompileImage, - Value: image.Name(params.Registry, params.Version, types.ImageNameCompile), - }) - - var builds []docker.BuildImageOptions - - for imageName, dockerfile := range dockerfiles { - build := docker.BuildImageOptions{ - Name: image.Name(params.Registry, params.Version, imageName), - Dockerfile: dockerfile, - ContextDir: params.Context, - OutputStream: prefixer.WrapWriterWithPrefixer(params.Writer, imageName, start), - BuildArgs: args, - } - - // Add to the builder list. - builds = append(builds, build) - - // Add to the manifest. - resp.Images = append(resp.Images, types.Image{ - Name: imageName, - Tag: build.Name, - }) - } - - bg, ctx := errgroup.WithContext(ctx) - - for _, build := range builds { - // https://golang.org/doc/faq#closures_and_goroutines - build := build - - // Allows us to cancel build executions. - build.Context = ctx - - fmt.Fprintf(params.Writer, "Building image: %s\n", build.Name) - - bg.Go(func() error { - start = time.Now() - - err := b.dockerClient.BuildImage(build) - if err != nil { - return err - } - - fmt.Fprintf(params.Writer, "Built %s image in %s\n", build.Name, time.Since(start).Round(time.Second)) - - return nil - }) - } - - err = bg.Wait() - if err != nil { - return resp, err - } - - if params.NoPush { - return resp, nil - } - - var pushes []docker.PushImageOptions - - for _, buildImage := range resp.Images { - pushes = append(pushes, docker.PushImageOptions{ - Name: params.Registry, - Tag: image.Tag(params.Version, buildImage.Name), - }) - } - - pg, ctx := errgroup.WithContext(ctx) - - auth := docker.AuthConfiguration{ - Username: params.Auth.Username, - Password: params.Auth.Password, - } - - for _, push := range pushes { - // https://golang.org/doc/faq#closures_and_goroutines - push := push - - // Allows us to cancel push executions. - push.Context = ctx - - fmt.Fprintf(params.Writer, "Pushing image: %s:%s\n", push.Name, push.Tag) - - pg.Go(func() error { - start = time.Now() - - err = b.dockerClient.PushImage(push, auth) - if err != nil { - return err - } - - fmt.Fprintf(params.Writer, "Pushed %s:%s image in %s\n", push.Name, push.Tag, time.Since(start).Round(time.Second)) - - return nil - }) - } - - err = pg.Wait() - if err != nil { - return resp, err - } - - fmt.Fprintf(params.Writer, "Build complete in: %s\n", time.Since(start).Round(time.Second)) - - return resp, nil -} diff --git a/internal/buildpack/builder/goclient/mock/client.go b/internal/buildpack/builder/goclient/mock/client.go deleted file mode 100644 index 186e31d..0000000 --- a/internal/buildpack/builder/goclient/mock/client.go +++ /dev/null @@ -1,50 +0,0 @@ -package mock - -import ( - "sync" - - docker "github.com/fsouza/go-dockerclient" -) - -// DockerClient provides a mock dockerclient client. -type DockerClient struct { - BuildWg sync.WaitGroup - PushWg sync.WaitGroup - buildNum int - pushNum int -} - -// BuildImage implements the interface. -func (c *DockerClient) BuildImage(options docker.BuildImageOptions) error { - c.BuildWg.Done() - c.buildNum++ - return nil -} - -// PushImage implements the interface. -func (c *DockerClient) PushImage(options docker.PushImageOptions, auth docker.AuthConfiguration) error { - c.PushWg.Done() - c.pushNum++ - return nil -} - -// InspectImage implements the interface. -func (c *DockerClient) InspectImage(name string) (*docker.Image, error) { - return &docker.Image{ - RepoDigests: []string{ - "sha@111222333444555666", - }, - }, nil -} - -// BuildCount returns the build count. -func (c *DockerClient) BuildCount() int { - c.BuildWg.Wait() - return c.buildNum -} - -// PushCount returns the push count. -func (c *DockerClient) PushCount() int { - c.PushWg.Wait() - return c.pushNum -} diff --git a/internal/buildpack/builder/goclient/builder_test.go b/internal/buildpack/builder_test.go similarity index 77% rename from internal/buildpack/builder/goclient/builder_test.go rename to internal/buildpack/builder_test.go index bdf56ae..028a414 100644 --- a/internal/buildpack/builder/goclient/builder_test.go +++ b/internal/buildpack/builder_test.go @@ -1,4 +1,4 @@ -package goclient +package buildpack import ( "bytes" @@ -8,9 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/skpr/cli/internal/auth" - "github.com/skpr/cli/internal/buildpack/builder/goclient/mock" - "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/finder" + "github.com/skpr/cli/internal/docker/mock" ) func TestBuild(t *testing.T) { @@ -27,13 +26,14 @@ func TestBuild(t *testing.T) { var b bytes.Buffer - params := types.Params{ - Writer: &b, - Registry: "foo", - Version: "222", - Context: "bar", - NoPush: false, - Auth: auth.Auth{}, + params := Params{ + Writer: &b, + Registry: "foo", + Version: "222", + Context: "bar", + NoPush: false, + BuildArgs: map[string]string{}, + Auth: auth.Auth{}, } builder, err := NewBuilder(dockerClient) @@ -42,8 +42,8 @@ func TestBuild(t *testing.T) { have, err := builder.Build(context.TODO(), dockerFiles, params) assert.NoError(t, err) - want := types.BuildResponse{ - Images: []types.Image{ + want := BuildResponse{ + Images: []Image{ { Name: "cli", Tag: "foo:222-cli", diff --git a/internal/buildpack/types/types.go b/internal/buildpack/types.go similarity index 98% rename from internal/buildpack/types/types.go rename to internal/buildpack/types.go index ce0040f..53e55cb 100644 --- a/internal/buildpack/types/types.go +++ b/internal/buildpack/types.go @@ -1,4 +1,4 @@ -package types +package buildpack import ( "io" diff --git a/internal/buildpack/types/builder.go b/internal/buildpack/types/builder.go deleted file mode 100644 index 511e655..0000000 --- a/internal/buildpack/types/builder.go +++ /dev/null @@ -1,12 +0,0 @@ -package types - -import ( - "context" - - "github.com/skpr/cli/internal/buildpack/utils/finder" -) - -// Builder interface for swapping out image builders. -type Builder interface { - Build(ctx context.Context, dockerfiles finder.Dockerfiles, params Params) (BuildResponse, error) -} diff --git a/internal/command/package/command.go b/internal/command/package/command.go index f4dcf2e..28759f6 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -7,10 +7,9 @@ import ( "os" "github.com/skpr/api/pb" + dockerbuilder "github.com/skpr/cli/internal/buildpack" "github.com/skpr/cli/internal/auth" - dockerbuilder "github.com/skpr/cli/internal/buildpack/builder" - "github.com/skpr/cli/internal/buildpack/types" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" @@ -26,7 +25,7 @@ import ( type Command struct { Region string PackageDir string - Params types.Params + Params dockerbuilder.Params PrintManifest bool BuildArgs []string Debug bool diff --git a/internal/docker/mock/client.go b/internal/docker/mock/client.go new file mode 100644 index 0000000..b9ca0f4 --- /dev/null +++ b/internal/docker/mock/client.go @@ -0,0 +1,51 @@ +package mock + +import ( + "context" + "io" + "sync" +) + +// DockerClient provides a mock dockerclient client. +type DockerClient struct { + BuildWg sync.WaitGroup + PushWg sync.WaitGroup + buildNum int + pushNum int +} + +func (c *DockerClient) ImageId(ctx context.Context, name string) (string, error) { + return "sha@111222333444555666", nil +} + +func (c *DockerClient) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { + return nil +} + +func (c *DockerClient) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { + c.PushWg.Done() + c.pushNum++ + return nil +} + +func (c *DockerClient) RemoveImage(ctx context.Context, id string) error { + return nil +} + +func (c *DockerClient) BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error { + c.BuildWg.Done() + c.buildNum++ + return nil +} + +// BuildCount returns the build count. +func (c *DockerClient) BuildCount() int { + c.BuildWg.Wait() + return c.buildNum +} + +// PushCount returns the push count. +func (c *DockerClient) PushCount() int { + c.PushWg.Wait() + return c.pushNum +} From 4a237927c1c9b991ff28460022c8bcc9686ef2fa Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 19:44:06 +1300 Subject: [PATCH 08/21] Reusable factory --- internal/client/config/user/config.go | 9 +++-- internal/command/mysql/image/pull/command.go | 22 +---------- internal/command/package/command.go | 22 +---------- internal/docker/factory.go | 40 ++++++++++++++++++++ internal/docker/interface.go | 14 ------- 5 files changed, 47 insertions(+), 60 deletions(-) create mode 100644 internal/docker/factory.go delete mode 100644 internal/docker/interface.go diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index 5237aa6..86403f5 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -16,15 +16,16 @@ type Config struct { // ConfigExperimental holds experimental feature flags. type ConfigExperimental struct { - Trace bool `yaml:"trace,omitempty"` - Builder ConfigPackageBuilder `yaml:"builder,omitempty"` + Trace bool `yaml:"trace,omitempty"` + DockerClient ConfigPackageBuilder `yaml:"docker-client,omitempty"` } type ConfigPackageBuilder string const ( - ConfigPackageBuilderLegacy ConfigPackageBuilder = "legacy" - ConfigPackageBuilderDocker ConfigPackageBuilder = "docker" + ConfigPackageClientLegacy ConfigPackageBuilder = "legacy" + ConfigPackageClientDocker ConfigPackageBuilder = "docker" + ConfigPackageClientMock ConfigPackageBuilder = "mock" ) // Aliases maps alias names to commands. diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index ac5c69e..413cb8e 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -13,10 +13,7 @@ import ( "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" - "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" - "github.com/skpr/cli/internal/docker/dockerclient" - "github.com/skpr/cli/internal/docker/goclient" skprlog "github.com/skpr/cli/internal/log" ) @@ -73,7 +70,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - c, err := getClient(auth) + c, err := docker.GetClientFromUserConfig(auth) if err != nil { return errors.Wrap(err, "failed to create Docker client") } @@ -127,20 +124,3 @@ func (cmd *Command) Run(ctx context.Context) error { return nil } - -func getClient(auth auth.Auth) (docker.DockerClient, error) { - // See if we're using default builder. - userConfig, _ := user.NewClient() - featureFlags, _ := userConfig.LoadFeatureFlags() - - if featureFlags.Builder == user.ConfigPackageBuilderDocker { - c, err := dockerclient.New(auth) - return c, err - } - - if featureFlags.Builder != "" && featureFlags.Builder != user.ConfigPackageBuilderLegacy { - return nil, fmt.Errorf("unknown docker client: %s", featureFlags.Builder) - } - - return goclient.New(auth) -} diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 28759f6..f0077af 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -14,10 +14,7 @@ import ( "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" "github.com/skpr/cli/internal/client/config" - "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" - "github.com/skpr/cli/internal/docker/dockerclient" - "github.com/skpr/cli/internal/docker/goclient" "github.com/skpr/cli/internal/slice" ) @@ -99,7 +96,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - dc, err := getClient(cmd.Params.Auth) + dc, err := docker.GetClientFromUserConfig(cmd.Params.Auth) if err != nil { return err } @@ -159,20 +156,3 @@ func (cmd *Command) Run(ctx context.Context) error { return nil } - -func getClient(auth auth.Auth) (docker.DockerClient, error) { - // See if we're using default builder. - userConfig, _ := user.NewClient() - featureFlags, _ := userConfig.LoadFeatureFlags() - - if featureFlags.Builder == user.ConfigPackageBuilderDocker { - c, err := dockerclient.New(auth) - return c, err - } - - if featureFlags.Builder != "" && featureFlags.Builder != user.ConfigPackageBuilderLegacy { - return nil, fmt.Errorf("unknown docker client: %s", featureFlags.Builder) - } - - return goclient.New(auth) -} diff --git a/internal/docker/factory.go b/internal/docker/factory.go new file mode 100644 index 0000000..3f7ed1c --- /dev/null +++ b/internal/docker/factory.go @@ -0,0 +1,40 @@ +package docker + +import ( + "context" + "fmt" + "io" + + "github.com/skpr/cli/internal/auth" + "github.com/skpr/cli/internal/client/config/user" + "github.com/skpr/cli/internal/docker/dockerclient" + "github.com/skpr/cli/internal/docker/goclient" +) + +type DockerClient interface { + ImageId(ctx context.Context, name string) (string, error) + PullImage(ctx context.Context, repository, tag string, writer io.Writer) error + PushImage(ctx context.Context, repository, tag string, writer io.Writer) error + RemoveImage(ctx context.Context, id string) error + BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error +} + +func GetClientFromUserConfig(auth auth.Auth) (DockerClient, error) { + // See if we're using default builder. + userConfig, _ := user.NewClient() + featureFlags, _ := userConfig.LoadFeatureFlags() + + if featureFlags.DockerClient == user.ConfigPackageClientDocker { + c, err := dockerclient.New(auth) + return c, err + } else if featureFlags.DockerClient == user.ConfigPackageClientMock { + c, err := dockerclient.New(auth) + return c, err + } + + if featureFlags.DockerClient != "" && featureFlags.DockerClient != user.ConfigPackageClientLegacy { + return nil, fmt.Errorf("unknown docker client: %s", featureFlags.DockerClient) + } + + return goclient.New(auth) +} diff --git a/internal/docker/interface.go b/internal/docker/interface.go deleted file mode 100644 index e6a8a61..0000000 --- a/internal/docker/interface.go +++ /dev/null @@ -1,14 +0,0 @@ -package docker - -import ( - "context" - "io" -) - -type DockerClient interface { - ImageId(ctx context.Context, name string) (string, error) - PullImage(ctx context.Context, repository, tag string, writer io.Writer) error - PushImage(ctx context.Context, repository, tag string, writer io.Writer) error - RemoveImage(ctx context.Context, id string) error - BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error -} From bab73fdff554a009b82a7adfc610990b607fc5af Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 19:44:59 +1300 Subject: [PATCH 09/21] Using a better naming scheme --- internal/command/mysql/image/pull/command.go | 2 +- internal/command/package/command.go | 2 +- internal/docker/factory.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 413cb8e..a4e637d 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -70,7 +70,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - c, err := docker.GetClientFromUserConfig(auth) + c, err := docker.NewClientFromUserConfig(auth) if err != nil { return errors.Wrap(err, "failed to create Docker client") } diff --git a/internal/command/package/command.go b/internal/command/package/command.go index f0077af..e4304d7 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -96,7 +96,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - dc, err := docker.GetClientFromUserConfig(cmd.Params.Auth) + dc, err := docker.NewClientFromUserConfig(cmd.Params.Auth) if err != nil { return err } diff --git a/internal/docker/factory.go b/internal/docker/factory.go index 3f7ed1c..a8628bd 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -19,7 +19,7 @@ type DockerClient interface { BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error } -func GetClientFromUserConfig(auth auth.Auth) (DockerClient, error) { +func NewClientFromUserConfig(auth auth.Auth) (DockerClient, error) { // See if we're using default builder. userConfig, _ := user.NewClient() featureFlags, _ := userConfig.LoadFeatureFlags() From 1473489976baf42af25e6c4801fecfb31c5eab40 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 19:58:11 +1300 Subject: [PATCH 10/21] Fixing linting issue. --- internal/buildpack/builder_test.go | 4 ++-- internal/client/config/user/config.go | 2 +- internal/command/mysql/image/pull/tag_test.go | 2 +- internal/command/package/command.go | 6 +++--- internal/docker/factory.go | 16 +++++++++------- internal/docker/{mock => mockclient}/client.go | 4 ++-- 6 files changed, 18 insertions(+), 16 deletions(-) rename internal/docker/{mock => mockclient}/client.go (94%) diff --git a/internal/buildpack/builder_test.go b/internal/buildpack/builder_test.go index 028a414..87a3f65 100644 --- a/internal/buildpack/builder_test.go +++ b/internal/buildpack/builder_test.go @@ -9,12 +9,12 @@ import ( "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/finder" - "github.com/skpr/cli/internal/docker/mock" + "github.com/skpr/cli/internal/docker/mockclient" ) func TestBuild(t *testing.T) { - dockerClient := &mock.DockerClient{} + dockerClient := &mockclient.DockerClient{} dockerClient.BuildWg.Add(4) dockerClient.PushWg.Add(3) diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index 86403f5..26c1542 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -25,7 +25,7 @@ type ConfigPackageBuilder string const ( ConfigPackageClientLegacy ConfigPackageBuilder = "legacy" ConfigPackageClientDocker ConfigPackageBuilder = "docker" - ConfigPackageClientMock ConfigPackageBuilder = "mock" + ConfigPackageClientMock ConfigPackageBuilder = "mockclient" ) // Aliases maps alias names to commands. diff --git a/internal/command/mysql/image/pull/tag_test.go b/internal/command/mysql/image/pull/tag_test.go index 799de11..c6f943e 100644 --- a/internal/command/mysql/image/pull/tag_test.go +++ b/internal/command/mysql/image/pull/tag_test.go @@ -12,7 +12,7 @@ import ( // ImageListClient is a client for mocking the ImageListClient type. type ImageListMockClient struct{} -// ImageList is a mock implementation of the ImageListClient interface. +// ImageList is a mockclient implementation of the ImageListClient interface. func (c *ImageListMockClient) ImageList(ctx context.Context, in *pb.ImageListRequest, opts ...grpc.CallOption) (*pb.ImageListResponse, error) { return &pb.ImageListResponse{ List: []*pb.ImageStatus{ diff --git a/internal/command/package/command.go b/internal/command/package/command.go index e4304d7..8269bcc 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -7,9 +7,9 @@ import ( "os" "github.com/skpr/api/pb" - dockerbuilder "github.com/skpr/cli/internal/buildpack" "github.com/skpr/cli/internal/auth" + "github.com/skpr/cli/internal/buildpack" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" @@ -22,7 +22,7 @@ import ( type Command struct { Region string PackageDir string - Params dockerbuilder.Params + Params buildpack.Params PrintManifest bool BuildArgs []string Debug bool @@ -101,7 +101,7 @@ func (cmd *Command) Run(ctx context.Context) error { return err } - builder, err := dockerbuilder.NewBuilder(dc) + builder, err := buildpack.NewBuilder(dc) if err != nil { return err } diff --git a/internal/docker/factory.go b/internal/docker/factory.go index a8628bd..0dd4dda 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -9,6 +9,7 @@ import ( "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker/dockerclient" "github.com/skpr/cli/internal/docker/goclient" + "github.com/skpr/cli/internal/docker/mockclient" ) type DockerClient interface { @@ -24,15 +25,16 @@ func NewClientFromUserConfig(auth auth.Auth) (DockerClient, error) { userConfig, _ := user.NewClient() featureFlags, _ := userConfig.LoadFeatureFlags() - if featureFlags.DockerClient == user.ConfigPackageClientDocker { - c, err := dockerclient.New(auth) - return c, err - } else if featureFlags.DockerClient == user.ConfigPackageClientMock { - c, err := dockerclient.New(auth) - return c, err + switch featureFlags.DockerClient { + case user.ConfigPackageClientLegacy: + return goclient.New(auth) + case user.ConfigPackageClientDocker: + return dockerclient.New(auth) + case user.ConfigPackageClientMock: + return &mockclient.DockerClient{}, nil } - if featureFlags.DockerClient != "" && featureFlags.DockerClient != user.ConfigPackageClientLegacy { + if featureFlags.DockerClient != "" { return nil, fmt.Errorf("unknown docker client: %s", featureFlags.DockerClient) } diff --git a/internal/docker/mock/client.go b/internal/docker/mockclient/client.go similarity index 94% rename from internal/docker/mock/client.go rename to internal/docker/mockclient/client.go index b9ca0f4..33a36fa 100644 --- a/internal/docker/mock/client.go +++ b/internal/docker/mockclient/client.go @@ -1,4 +1,4 @@ -package mock +package mockclient import ( "context" @@ -6,7 +6,7 @@ import ( "sync" ) -// DockerClient provides a mock dockerclient client. +// DockerClient provides a mockclient client. type DockerClient struct { BuildWg sync.WaitGroup PushWg sync.WaitGroup From a3ef965f20755292c8f974639cd52cb1d35a5767 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 20:23:37 +1300 Subject: [PATCH 11/21] Updating the mock to work with config --- cmd/skpr/package/command.go | 9 +++++++++ internal/buildpack/builder_test.go | 4 +--- internal/client/config/user/config.go | 2 +- internal/command/mysql/image/pull/tag_test.go | 2 +- internal/docker/factory.go | 2 +- internal/docker/mockclient/client.go | 16 ++++++++-------- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index fa9a7ad..49e6739 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -1,6 +1,7 @@ package pkg import ( + "github.com/skpr/cli/internal/client/config/user" "github.com/spf13/cobra" skprcommand "github.com/skpr/cli/internal/command" @@ -49,5 +50,13 @@ func NewCommand() *cobra.Command { cmd.Flags().StringSliceVar(&command.BuildArgs, "build-arg", []string{}, "Additional build arguments.") cmd.Flags().BoolVar(&command.Debug, "debug", command.Debug, "Enable debug output.") + // See if we're using default builder. + userConfig, _ := user.NewClient() + featureFlags, _ := userConfig.LoadFeatureFlags() + + if featureFlags.DockerClient == user.ConfigPackageClientDocker { + cmd.Flags().StringVar(&command.Params.IgnoreFile, "ignore-file", ".dockerignore", "A file containing patterns to exclude from the build context.") + } + return cmd } diff --git a/internal/buildpack/builder_test.go b/internal/buildpack/builder_test.go index 87a3f65..9cf61c9 100644 --- a/internal/buildpack/builder_test.go +++ b/internal/buildpack/builder_test.go @@ -14,9 +14,7 @@ import ( func TestBuild(t *testing.T) { - dockerClient := &mockclient.DockerClient{} - dockerClient.BuildWg.Add(4) - dockerClient.PushWg.Add(3) + dockerClient := mockclient.New() dockerFiles := make(finder.Dockerfiles) dockerFiles["compile"] = ".skpr/package/compile/Dockerfile" diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index 26c1542..86403f5 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -25,7 +25,7 @@ type ConfigPackageBuilder string const ( ConfigPackageClientLegacy ConfigPackageBuilder = "legacy" ConfigPackageClientDocker ConfigPackageBuilder = "docker" - ConfigPackageClientMock ConfigPackageBuilder = "mockclient" + ConfigPackageClientMock ConfigPackageBuilder = "mock" ) // Aliases maps alias names to commands. diff --git a/internal/command/mysql/image/pull/tag_test.go b/internal/command/mysql/image/pull/tag_test.go index c6f943e..799de11 100644 --- a/internal/command/mysql/image/pull/tag_test.go +++ b/internal/command/mysql/image/pull/tag_test.go @@ -12,7 +12,7 @@ import ( // ImageListClient is a client for mocking the ImageListClient type. type ImageListMockClient struct{} -// ImageList is a mockclient implementation of the ImageListClient interface. +// ImageList is a mock implementation of the ImageListClient interface. func (c *ImageListMockClient) ImageList(ctx context.Context, in *pb.ImageListRequest, opts ...grpc.CallOption) (*pb.ImageListResponse, error) { return &pb.ImageListResponse{ List: []*pb.ImageStatus{ diff --git a/internal/docker/factory.go b/internal/docker/factory.go index 0dd4dda..3e10c42 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -31,7 +31,7 @@ func NewClientFromUserConfig(auth auth.Auth) (DockerClient, error) { case user.ConfigPackageClientDocker: return dockerclient.New(auth) case user.ConfigPackageClientMock: - return &mockclient.DockerClient{}, nil + return mockclient.New(), nil } if featureFlags.DockerClient != "" { diff --git a/internal/docker/mockclient/client.go b/internal/docker/mockclient/client.go index 33a36fa..13f8245 100644 --- a/internal/docker/mockclient/client.go +++ b/internal/docker/mockclient/client.go @@ -3,17 +3,21 @@ package mockclient import ( "context" "io" - "sync" ) -// DockerClient provides a mockclient client. +// DockerClient provides a mock client. type DockerClient struct { - BuildWg sync.WaitGroup - PushWg sync.WaitGroup buildNum int pushNum int } +func New() *DockerClient { + return &DockerClient{ + buildNum: 0, + pushNum: 0, + } +} + func (c *DockerClient) ImageId(ctx context.Context, name string) (string, error) { return "sha@111222333444555666", nil } @@ -23,7 +27,6 @@ func (c *DockerClient) PullImage(ctx context.Context, repository, tag string, wr } func (c *DockerClient) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { - c.PushWg.Done() c.pushNum++ return nil } @@ -33,19 +36,16 @@ func (c *DockerClient) RemoveImage(ctx context.Context, id string) error { } func (c *DockerClient) BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error { - c.BuildWg.Done() c.buildNum++ return nil } // BuildCount returns the build count. func (c *DockerClient) BuildCount() int { - c.BuildWg.Wait() return c.buildNum } // PushCount returns the push count. func (c *DockerClient) PushCount() int { - c.PushWg.Wait() return c.pushNum } From 43ab2bf96b422bb7be5f4cde94775e46dddd6069 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Thu, 4 Dec 2025 20:55:14 +1300 Subject: [PATCH 12/21] Better handling of messages --- internal/docker/dockerclient/client.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go index eb3fd11..ca71e3c 100644 --- a/internal/docker/dockerclient/client.go +++ b/internal/docker/dockerclient/client.go @@ -64,6 +64,7 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i if err != nil { return err } + defer rc.Close() err = handleMessages(rc, writer) if err != nil { @@ -91,6 +92,7 @@ func (c *Client) PushImage(ctx context.Context, repository, tag string, writer i if err != nil { return err } + defer rc.Close() err = handleMessages(rc, writer) if err != nil { @@ -163,7 +165,11 @@ func handleMessages(in io.ReadCloser, out io.Writer) error { } // TODO: Possibly stream out progress. - + if msg.Status != "" { + if _, err := io.WriteString(out, msg.Status+"\n"); err != nil { + return err + } + } if msg.Stream != "" { if _, err := io.WriteString(out, msg.Stream); err != nil { return err From 8283711c49237ae2838b506705a91acd673cccdb Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Fri, 5 Dec 2025 09:52:26 +1300 Subject: [PATCH 13/21] Cleaning up more of goclient. --- internal/buildpack/builder.go | 20 +++++++++++--------- internal/buildpack/utils/image/build.go | 4 ++-- internal/docker/dockerclient/client.go | 9 +++++---- internal/docker/factory.go | 4 ++-- internal/docker/goclient/client.go | 22 ++++++++++++++++------ internal/docker/mockclient/client.go | 4 ++-- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/internal/buildpack/builder.go b/internal/buildpack/builder.go index 1a3c442..e77a644 100644 --- a/internal/buildpack/builder.go +++ b/internal/buildpack/builder.go @@ -48,7 +48,7 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par start := time.Now() // Build the compile image first; it's the base for others. - compileRef := image.Name(params.Registry, params.Version, ImageNameCompile) + compileRef := image.Name(params.Registry, image.Tag(params.Version, ImageNameCompile)) fmt.Fprintf(params.Writer, "Building image: %s\n", compileRef) @@ -74,7 +74,7 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par } var builds []pendingBuild for imageName, dockerfile := range dockerfiles { - ref := image.Name(params.Registry, params.Version, imageName) + ref := image.Name(params.Registry, image.Tag(params.Version, imageName)) builds = append(builds, pendingBuild{name: imageName, imageRef: ref, dockerfile: dockerfile}) resp.Images = append(resp.Images, Image{ Name: imageName, @@ -111,14 +111,16 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par // Prepare pushes (skip compile). type pendingPush struct { - Name string - Tag string // full "registry/repo:tag" + Name string + Registry string + Tag string } var pushes []pendingPush for _, buildImage := range resp.Images { pushes = append(pushes, pendingPush{ - Name: params.Registry, - Tag: image.Tag(params.Version, buildImage.Name), + Name: buildImage.Name, + Registry: params.Registry, + Tag: image.Tag(params.Version, buildImage.Name), }) } @@ -127,17 +129,17 @@ func (b *Builder) Build(ctx context.Context, dockerfiles finder.Dockerfiles, par for _, p := range pushes { p := p - fmt.Fprintf(params.Writer, "Pushing image: %s:%s\n", p.Name, p.Tag) + fmt.Fprintf(params.Writer, "Pushing image: %s:%s\n", p.Registry, p.Tag) out := prefixer.WrapWriterWithPrefixer(params.Writer, "push "+p.Name, start) pg.Go(func() error { localStart := time.Now() - err := b.Client.PushImage(ctx, p.Name, p.Tag, out) + err := b.Client.PushImage(ctx, p.Registry, p.Tag, out) if err != nil { return err } - fmt.Fprintf(params.Writer, "Pushed %s:%s image in %s\n", p.Name, p.Tag, time.Since(localStart).Round(time.Second)) + fmt.Fprintf(params.Writer, "Pushed %s:%s image in %s\n", p.Registry, p.Tag, time.Since(localStart).Round(time.Second)) return nil }) } diff --git a/internal/buildpack/utils/image/build.go b/internal/buildpack/utils/image/build.go index a217691..8181119 100644 --- a/internal/buildpack/utils/image/build.go +++ b/internal/buildpack/utils/image/build.go @@ -5,8 +5,8 @@ import ( ) // Name of the Docker image. -func Name(registry, version, suffix string) string { - return fmt.Sprintf("%s:%s", registry, Tag(version, suffix)) +func Name(registry, tag string) string { + return fmt.Sprintf("%s:%s", registry, tag) } // Tag assigned to a Docker image. diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go index ca71e3c..da3fe24 100644 --- a/internal/docker/dockerclient/client.go +++ b/internal/docker/dockerclient/client.go @@ -14,6 +14,7 @@ import ( "github.com/moby/go-archive" "github.com/moby/moby/api/types/jsonstream" "github.com/pkg/errors" + "github.com/skpr/cli/internal/buildpack/utils/image" "github.com/skpr/cli/internal/auth" ) @@ -46,7 +47,7 @@ func (c *Client) ImageId(ctx context.Context, name string) (string, error) { return "", nil } -func (c *Client) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { +func (c *Client) PullImage(ctx context.Context, registry, tag string, writer io.Writer) error { auth := dockregistry.AuthConfig{ Username: c.Auth.Username, Password: c.Auth.Password, @@ -56,7 +57,7 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i return errors.Wrap(err, "failed to encode registry auth") } - imageName := fmt.Sprintf("%s:%s", repository, tag) + imageName := image.Name(registry, tag) rc, err := c.Client.ImagePull(ctx, imageName, imagetypes.PullOptions{ RegistryAuth: authHdr, @@ -74,7 +75,7 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i return nil } -func (c *Client) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { +func (c *Client) PushImage(ctx context.Context, registry, tag string, writer io.Writer) error { auth := dockregistry.AuthConfig{ Username: c.Auth.Username, Password: c.Auth.Password, @@ -84,7 +85,7 @@ func (c *Client) PushImage(ctx context.Context, repository, tag string, writer i return errors.Wrap(err, "failed to encode registry auth") } - imageName := fmt.Sprintf("%s:%s", repository, tag) + imageName := image.Name(registry, tag) rc, err := c.Client.ImagePush(ctx, imageName, imagetypes.PushOptions{ RegistryAuth: authHdr, diff --git a/internal/docker/factory.go b/internal/docker/factory.go index 3e10c42..25035e2 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -14,8 +14,8 @@ import ( type DockerClient interface { ImageId(ctx context.Context, name string) (string, error) - PullImage(ctx context.Context, repository, tag string, writer io.Writer) error - PushImage(ctx context.Context, repository, tag string, writer io.Writer) error + PullImage(ctx context.Context, registry, tag string, writer io.Writer) error + PushImage(ctx context.Context, registry, tag string, writer io.Writer) error RemoveImage(ctx context.Context, id string) error BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error } diff --git a/internal/docker/goclient/client.go b/internal/docker/goclient/client.go index 0bdc820..3f8d26a 100644 --- a/internal/docker/goclient/client.go +++ b/internal/docker/goclient/client.go @@ -2,12 +2,10 @@ package goclient import ( "context" - "fmt" "io" dockerclient "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" - "github.com/skpr/cli/internal/auth" ) @@ -41,10 +39,10 @@ func (c *Client) ImageId(ctx context.Context, name string) (string, error) { return resp.ID, nil } -func (c *Client) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { +func (c *Client) PullImage(ctx context.Context, registry, tag string, writer io.Writer) error { opts := dockerclient.PullImageOptions{ OutputStream: writer, - Repository: repository, + Repository: registry, Tag: tag, Context: ctx, } @@ -57,8 +55,20 @@ func (c *Client) PullImage(ctx context.Context, repository, tag string, writer i return c.Client.PullImage(opts, clientAuth) } -func (c *Client) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { - return fmt.Errorf("not implemented") +func (c *Client) PushImage(ctx context.Context, registry, tag string, writer io.Writer) error { + opts := dockerclient.PushImageOptions{ + Context: ctx, + OutputStream: writer, + Name: registry, + Tag: tag, + } + + clientAuth := dockerclient.AuthConfiguration{ + Username: c.Auth.Username, + Password: c.Auth.Password, + } + + return c.Client.PushImage(opts, clientAuth) } func (c *Client) RemoveImage(ctx context.Context, id string) error { diff --git a/internal/docker/mockclient/client.go b/internal/docker/mockclient/client.go index 13f8245..28fd809 100644 --- a/internal/docker/mockclient/client.go +++ b/internal/docker/mockclient/client.go @@ -22,11 +22,11 @@ func (c *DockerClient) ImageId(ctx context.Context, name string) (string, error) return "sha@111222333444555666", nil } -func (c *DockerClient) PullImage(ctx context.Context, repository, tag string, writer io.Writer) error { +func (c *DockerClient) PullImage(ctx context.Context, registry, tag string, writer io.Writer) error { return nil } -func (c *DockerClient) PushImage(ctx context.Context, repository, tag string, writer io.Writer) error { +func (c *DockerClient) PushImage(ctx context.Context, registry, tag string, writer io.Writer) error { c.pushNum++ return nil } From b1c69f1de6586675f9c01e376d9b8708c6932de4 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Fri, 5 Dec 2025 09:52:32 +1300 Subject: [PATCH 14/21] Cleaning up more of goclient. --- cmd/skpr/package/command.go | 2 +- internal/docker/dockerclient/client.go | 2 +- internal/docker/goclient/client.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index 49e6739..5eae5ee 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -1,9 +1,9 @@ package pkg import ( - "github.com/skpr/cli/internal/client/config/user" "github.com/spf13/cobra" + "github.com/skpr/cli/internal/client/config/user" skprcommand "github.com/skpr/cli/internal/command" v1package "github.com/skpr/cli/internal/command/package" ) diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go index da3fe24..4719b3d 100644 --- a/internal/docker/dockerclient/client.go +++ b/internal/docker/dockerclient/client.go @@ -14,9 +14,9 @@ import ( "github.com/moby/go-archive" "github.com/moby/moby/api/types/jsonstream" "github.com/pkg/errors" - "github.com/skpr/cli/internal/buildpack/utils/image" "github.com/skpr/cli/internal/auth" + "github.com/skpr/cli/internal/buildpack/utils/image" ) type Client struct { diff --git a/internal/docker/goclient/client.go b/internal/docker/goclient/client.go index 3f8d26a..44f0d44 100644 --- a/internal/docker/goclient/client.go +++ b/internal/docker/goclient/client.go @@ -6,6 +6,7 @@ import ( dockerclient "github.com/fsouza/go-dockerclient" "github.com/pkg/errors" + "github.com/skpr/cli/internal/auth" ) From 9bc2422db85e70c74f51b12b58a28cac0cb04baf Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Fri, 5 Dec 2025 10:57:05 +1300 Subject: [PATCH 15/21] Image is up to date message. Remove progress stream for now --- internal/command/mysql/image/pull/command.go | 1 + internal/docker/dockerclient/client.go | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index a4e637d..994de72 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -109,6 +109,7 @@ func (cmd *Command) Run(ctx context.Context) error { // Don't cleanup the old image if it was the latest and never needed to be updated. if cleanupId == currentId { + logger.Info(fmt.Sprintf("Image is up to date: %s", imageName)) continue } diff --git a/internal/docker/dockerclient/client.go b/internal/docker/dockerclient/client.go index 4719b3d..b878115 100644 --- a/internal/docker/dockerclient/client.go +++ b/internal/docker/dockerclient/client.go @@ -166,11 +166,6 @@ func handleMessages(in io.ReadCloser, out io.Writer) error { } // TODO: Possibly stream out progress. - if msg.Status != "" { - if _, err := io.WriteString(out, msg.Status+"\n"); err != nil { - return err - } - } if msg.Stream != "" { if _, err := io.WriteString(out, msg.Stream); err != nil { return err From 42e78d2b324a493223e00a2228200c9ef4452b2c Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Fri, 5 Dec 2025 11:03:23 +1300 Subject: [PATCH 16/21] Improved output order --- internal/command/mysql/image/pull/command.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 994de72..67b055a 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -97,6 +97,8 @@ func (cmd *Command) Run(ctx context.Context) error { return err } + logger.Info(fmt.Sprintf("Successfully pulled image: %s:%s", getRepositoryResp.Repository, tag)) + // Check if there was an old image before cleaning up. if cleanupId == "" { continue @@ -109,7 +111,6 @@ func (cmd *Command) Run(ctx context.Context) error { // Don't cleanup the old image if it was the latest and never needed to be updated. if cleanupId == currentId { - logger.Info(fmt.Sprintf("Image is up to date: %s", imageName)) continue } @@ -119,8 +120,6 @@ func (cmd *Command) Run(ctx context.Context) error { if err != nil { return err } - - logger.Info(fmt.Sprintf("Successfully pulled image: %s:%s", getRepositoryResp.Repository, tag)) } return nil From b02e98eef74815f43dd0eccfb733b363905444b8 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Fri, 5 Dec 2025 11:15:53 +1300 Subject: [PATCH 17/21] Moving the messages around so it works a bit better --- internal/command/mysql/image/pull/command.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 67b055a..056ea08 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -97,20 +97,19 @@ func (cmd *Command) Run(ctx context.Context) error { return err } - logger.Info(fmt.Sprintf("Successfully pulled image: %s:%s", getRepositoryResp.Repository, tag)) - - // Check if there was an old image before cleaning up. - if cleanupId == "" { - continue - } - currentId, err := c.ImageId(context.TODO(), imageName) if err != nil { return err } - // Don't cleanup the old image if it was the latest and never needed to be updated. if cleanupId == currentId { + logger.Info(fmt.Sprintf("Image is up to date: %s", imageName)) + } else { + logger.Info(fmt.Sprintf("Successfully pulled image: %s", imageName)) + } + + // If it's a fresh image or the same image as the current one, skip deleting it. + if cleanupId == "" || cleanupId == currentId { continue } From 6975b8a2f6c22ff4e4b4cdf98461b335a637f70f Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 17 Dec 2025 09:10:22 +1300 Subject: [PATCH 18/21] Updating dependencies --- go.mod | 59 +++++++++++++++++++++++++++++---------------------------- go.sum | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 4617fbf..accd3b4 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ require ( charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 github.com/aquasecurity/table v1.11.0 - github.com/aws/aws-sdk-go-v2 v1.40.1 - github.com/aws/aws-sdk-go-v2/config v1.32.3 - github.com/aws/aws-sdk-go-v2/credentials v1.19.3 - github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.15 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.16 - github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2 + github.com/aws/aws-sdk-go-v2 v1.41.0 + github.com/aws/aws-sdk-go-v2/config v1.32.5 + github.com/aws/aws-sdk-go-v2/credentials v1.19.5 + github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.16 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17 + github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/fang v0.4.4 github.com/containerd/errdefs v1.0.0 @@ -37,10 +37,10 @@ require ( github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 - golang.org/x/crypto v0.45.0 - golang.org/x/oauth2 v0.33.0 + golang.org/x/crypto v0.46.0 + golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 - golang.org/x/term v0.37.0 + golang.org/x/term v0.38.0 google.golang.org/grpc v1.77.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -49,23 +49,24 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbles v0.21.0 // indirect - github.com/charmbracelet/colorprofile v0.4.0 // indirect + github.com/charmbracelet/colorprofile v0.4.1 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692 // indirect - github.com/charmbracelet/x/ansi v0.11.2 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20251215102126-8518113293e1 // indirect + github.com/charmbracelet/x/ansi v0.11.3 // indirect github.com/charmbracelet/x/cellbuf v0.0.14 // indirect github.com/charmbracelet/x/exp/charmtone v0.0.0-20251215102626-e0db08df7383 // indirect github.com/charmbracelet/x/exp/golden v0.0.0-20251215102626-e0db08df7383 // indirect @@ -90,7 +91,7 @@ require ( github.com/jwalton/go-supportscolor v1.2.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect @@ -100,7 +101,7 @@ require ( github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/mango v0.2.0 // indirect @@ -117,16 +118,16 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - golang.org/x/net v0.47.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index acb32bf..4870d0e 100644 --- a/go.sum +++ b/go.sum @@ -14,36 +14,64 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= +github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= +github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.15 h1:1dVctbat9Rie0Aw9QjcDr/dPfDzR3r1B/NmMAuL0+VI= github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.15/go.mod h1:1eKTGnKGdPdif+PFUn874AO6UHCxjnNXM8HQZUOz1Pw= +github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.16 h1:SEjKS691gctDMITrxLPxovXxEE4CXb7yOqfjgG10X8g= +github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.16/go.mod h1:SJttU0860z9g/ZFlAGI1vV4w44NLO6PKDHO3jFu3Jd4= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.16 h1:h0aEsnYFvKVHhCjxJ2hYVMiwWdHb9z2/H7KTwSPSPqM= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.16/go.mod h1:ZFDOOw1B3HGqicw7n/SXqMZT/zwLpjk56OczL5GW7xI= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17 h1:kYAxFlyBhmhdjel6MNFf5lYQlTcMUOXPC33mor8rFz0= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17/go.mod h1:NSRHRisUPKx5y8RD+HpeCjIn8SYz5m6HhNGkd0GLB1o= github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2 h1:2Mdcg3Rphkj48toLpLrckQ9T0ce08GrMfaL0l2anzLY= github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2/go.mod h1:gwUKatMqrynzKA8L0MDAlvAvGB7LIzmTm6uFRqD+4CU= +github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4 h1:4THfkydiKvFeOFlfY1ABHe4Nsj+Jy6S6tHBqUAojY0M= +github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4/go.mod h1:8n8vVvu7LzveA0or4iWQwNndJStpKOX4HiVHM5jax2U= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -52,20 +80,28 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.4.0 h1:OyLBfRcKbGa/a92Qh7jQQmoToGYqM1vmq0rdVm2Qwh4= github.com/charmbracelet/colorprofile v0.4.0/go.mod h1:o630BguBH8XPdFI/NcDGPCt5vmitfqYQSCJ1s1mi5Oc= +github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= +github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY= github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692 h1:r/3jQZ1LjWW6ybp8HHfhrKrwHIWiJhUuY7wwYIWZulQ= github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692/go.mod h1:Y8B4DzWeTb0ama8l3+KyopZtkE8fZjwRQ3aEAPEXHE0= +github.com/charmbracelet/ultraviolet v0.0.0-20251215102126-8518113293e1 h1:0uVEMbe5L80n1fbNAz+WSO9mEWIau6iEo6umDMstzis= +github.com/charmbracelet/ultraviolet v0.0.0-20251215102126-8518113293e1/go.mod h1:Ns3cOzzY9hEFFeGxB6VpfgRnqOJZJFhQAPfRxPqflQs= github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs= github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg= +github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI= +github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI= github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA= github.com/charmbracelet/x/exp/charmtone v0.0.0-20251215102626-e0db08df7383 h1:xGojlO6kHCDB1k6DolME79LG0u90TzVd8atGhmxFRIo= @@ -159,6 +195,8 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -188,6 +226,8 @@ github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -245,31 +285,45 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -287,6 +341,8 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -297,10 +353,14 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1: google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 8d56181d56ba7793539d6a4927038ed205f1762c Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 17 Dec 2025 09:57:32 +1300 Subject: [PATCH 19/21] Passing the feature flags through each of the levels --- .mise.toml | 1 - cmd/skpr/main.go | 18 +++--- cmd/skpr/mysql/command.go | 5 +- cmd/skpr/mysql/image/command.go | 5 +- cmd/skpr/mysql/image/pull/command.go | 4 +- cmd/skpr/package/command.go | 7 +- go.sum | 68 ++------------------ internal/auth/auth.go | 1 + internal/command/mysql/image/pull/command.go | 12 ++-- internal/command/package/command.go | 4 +- internal/docker/factory.go | 6 +- 11 files changed, 36 insertions(+), 95 deletions(-) diff --git a/.mise.toml b/.mise.toml index d486777..81cf9a3 100644 --- a/.mise.toml +++ b/.mise.toml @@ -32,7 +32,6 @@ depends = ["vendor"] [tasks."lint"] description = "Linting automatically checks code for errors and style issues" run = "golangci-lint run" -depends = ["tidy"] [tasks.test] description = "Checks to verify code correctness" diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index 3f6c19f..6a47e5e 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -80,6 +80,13 @@ func main() { os.Exit(1) } + // Experimental commands. + featureFlags, err := userConfig.LoadFeatureFlags() + if err != nil { + fmt.Println("Failed to load feature flags:", err) + os.Exit(1) + } + skprcommand.AddGroupsToCommand(cmd) cmd.AddCommand(alias.NewCommand()) @@ -99,8 +106,8 @@ func main() { cmd.AddCommand(login.NewCommand()) cmd.AddCommand(logout.NewCommand()) cmd.AddCommand(logs.NewCommand()) - cmd.AddCommand(mysql.NewCommand()) - cmd.AddCommand(pkg.NewCommand()) + cmd.AddCommand(mysql.NewCommand(featureFlags)) + cmd.AddCommand(pkg.NewCommand(featureFlags)) cmd.AddCommand(purge.NewCommand()) cmd.AddCommand(release.NewCommand()) cmd.AddCommand(restore.NewCommand()) @@ -117,13 +124,6 @@ func main() { HiddenDefaultCmd: true, } - // Experimental commands. - featureFlags, err := userConfig.LoadFeatureFlags() - if err != nil { - fmt.Println("Failed to load feature flags:", err) - os.Exit(1) - } - if featureFlags.Trace { cmd.AddCommand(trace.NewCommand()) } diff --git a/cmd/skpr/mysql/command.go b/cmd/skpr/mysql/command.go index 542041e..bfc29c5 100644 --- a/cmd/skpr/mysql/command.go +++ b/cmd/skpr/mysql/command.go @@ -6,6 +6,7 @@ import ( "github.com/skpr/cli/cmd/skpr/mysql/backup" img "github.com/skpr/cli/cmd/skpr/mysql/image" "github.com/skpr/cli/cmd/skpr/mysql/restore" + "github.com/skpr/cli/internal/client/config/user" skprcommand "github.com/skpr/cli/internal/command" ) @@ -14,7 +15,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'mysql' sub command -func NewCommand() *cobra.Command { +func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { cmd := &cobra.Command{ Use: "mysql", DisableFlagsInUseLine: true, @@ -23,7 +24,7 @@ func NewCommand() *cobra.Command { GroupID: skprcommand.GroupDataStorage, } - cmd.AddCommand(img.NewCommand()) + cmd.AddCommand(img.NewCommand(featureFlags)) cmd.AddCommand(backup.NewCommand()) cmd.AddCommand(restore.NewCommand()) diff --git a/cmd/skpr/mysql/image/command.go b/cmd/skpr/mysql/image/command.go index 3adf3ad..b44fe27 100644 --- a/cmd/skpr/mysql/image/command.go +++ b/cmd/skpr/mysql/image/command.go @@ -6,6 +6,7 @@ import ( "github.com/skpr/cli/cmd/skpr/mysql/image/create" "github.com/skpr/cli/cmd/skpr/mysql/image/list" "github.com/skpr/cli/cmd/skpr/mysql/image/pull" + "github.com/skpr/cli/internal/client/config/user" ) var ( @@ -20,7 +21,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'image' sub command -func NewCommand() *cobra.Command { +func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { cmd := &cobra.Command{ Use: "image", DisableFlagsInUseLine: true, @@ -31,7 +32,7 @@ func NewCommand() *cobra.Command { cmd.AddCommand(create.NewCommand()) cmd.AddCommand(list.NewCommand()) - cmd.AddCommand(pull.NewCommand()) + cmd.AddCommand(pull.NewCommand(featureFlags)) return cmd } diff --git a/cmd/skpr/mysql/image/pull/command.go b/cmd/skpr/mysql/image/pull/command.go index 1f994c7..299640e 100644 --- a/cmd/skpr/mysql/image/pull/command.go +++ b/cmd/skpr/mysql/image/pull/command.go @@ -3,6 +3,7 @@ package pull import ( "github.com/spf13/cobra" + "github.com/skpr/cli/internal/client/config/user" v1pull "github.com/skpr/cli/internal/command/mysql/image/pull" ) @@ -15,7 +16,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'list' sub command -func NewCommand() *cobra.Command { +func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { command := v1pull.Command{} cmd := &cobra.Command{ @@ -27,6 +28,7 @@ func NewCommand() *cobra.Command { Example: cmdExample, RunE: func(cmd *cobra.Command, args []string) error { command.Params.Environment = args[0] + command.Params.FeatureFlags = featureFlags if len(args) > 1 { command.Params.Databases = args[1:] diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index 5eae5ee..c811eb1 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -24,7 +24,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'package' sub command -func NewCommand() *cobra.Command { +func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { command := v1package.Command{} cmd := &cobra.Command{ @@ -38,6 +38,7 @@ func NewCommand() *cobra.Command { GroupID: skprcommand.GroupLifecycle, RunE: func(cmd *cobra.Command, args []string) error { command.Params.Version = args[0] + command.FeatureFlags = featureFlags return command.Run(cmd.Context()) }, } @@ -50,10 +51,6 @@ func NewCommand() *cobra.Command { cmd.Flags().StringSliceVar(&command.BuildArgs, "build-arg", []string{}, "Additional build arguments.") cmd.Flags().BoolVar(&command.Debug, "debug", command.Debug, "Enable debug output.") - // See if we're using default builder. - userConfig, _ := user.NewClient() - featureFlags, _ := userConfig.LoadFeatureFlags() - if featureFlags.DockerClient == user.ConfigPackageClientDocker { cmd.Flags().StringVar(&command.Params.IgnoreFile, "ignore-file", ".dockerignore", "A file containing patterns to exclude from the build context.") } diff --git a/go.sum b/go.sum index 4870d0e..0159980 100644 --- a/go.sum +++ b/go.sum @@ -12,64 +12,36 @@ github.com/aquasecurity/table v1.11.0 h1:SzgCAv7dZcv/gyAyzxorS6OgEk7w/WU5iT2pStI github.com/aquasecurity/table v1.11.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= -github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= -github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= -github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.15 h1:1dVctbat9Rie0Aw9QjcDr/dPfDzR3r1B/NmMAuL0+VI= -github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.15/go.mod h1:1eKTGnKGdPdif+PFUn874AO6UHCxjnNXM8HQZUOz1Pw= github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.16 h1:SEjKS691gctDMITrxLPxovXxEE4CXb7yOqfjgG10X8g= github.com/aws/aws-sdk-go-v2/service/cognitoidentity v1.33.16/go.mod h1:SJttU0860z9g/ZFlAGI1vV4w44NLO6PKDHO3jFu3Jd4= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.16 h1:h0aEsnYFvKVHhCjxJ2hYVMiwWdHb9z2/H7KTwSPSPqM= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.16/go.mod h1:ZFDOOw1B3HGqicw7n/SXqMZT/zwLpjk56OczL5GW7xI= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17 h1:kYAxFlyBhmhdjel6MNFf5lYQlTcMUOXPC33mor8rFz0= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17/go.mod h1:NSRHRisUPKx5y8RD+HpeCjIn8SYz5m6HhNGkd0GLB1o= -github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2 h1:2Mdcg3Rphkj48toLpLrckQ9T0ce08GrMfaL0l2anzLY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.54.2/go.mod h1:gwUKatMqrynzKA8L0MDAlvAvGB7LIzmTm6uFRqD+4CU= github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4 h1:4THfkydiKvFeOFlfY1ABHe4Nsj+Jy6S6tHBqUAojY0M= github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4/go.mod h1:8n8vVvu7LzveA0or4iWQwNndJStpKOX4HiVHM5jax2U= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= @@ -86,20 +58,14 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.0 h1:OyLBfRcKbGa/a92Qh7jQQmoToGYqM1vmq0rdVm2Qwh4= -github.com/charmbracelet/colorprofile v0.4.0/go.mod h1:o630BguBH8XPdFI/NcDGPCt5vmitfqYQSCJ1s1mi5Oc= github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY= github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692 h1:r/3jQZ1LjWW6ybp8HHfhrKrwHIWiJhUuY7wwYIWZulQ= -github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692/go.mod h1:Y8B4DzWeTb0ama8l3+KyopZtkE8fZjwRQ3aEAPEXHE0= github.com/charmbracelet/ultraviolet v0.0.0-20251215102126-8518113293e1 h1:0uVEMbe5L80n1fbNAz+WSO9mEWIau6iEo6umDMstzis= github.com/charmbracelet/ultraviolet v0.0.0-20251215102126-8518113293e1/go.mod h1:Ns3cOzzY9hEFFeGxB6VpfgRnqOJZJFhQAPfRxPqflQs= -github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs= -github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg= github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI= github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI= github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4= @@ -193,11 +159,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -224,8 +187,6 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -283,45 +244,31 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= @@ -333,14 +280,11 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= @@ -351,14 +295,10 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/auth/auth.go b/internal/auth/auth.go index dd8ad03..d94d348 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -1,5 +1,6 @@ package auth +// Auth is a consistent object for docker authentication (package, pull) as each client does it differently. type Auth struct { Username string Password string diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 056ea08..063d2aa 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -13,6 +13,7 @@ import ( "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" + "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" skprlog "github.com/skpr/cli/internal/log" ) @@ -29,10 +30,11 @@ type Command struct { // Params provided to this command. type Params struct { - Environment string - Databases []string - ID string - Tag string + Environment string + Databases []string + ID string + Tag string + FeatureFlags user.ConfigExperimental } // Run the command. @@ -70,7 +72,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - c, err := docker.NewClientFromUserConfig(auth) + c, err := docker.NewClientFromUserConfig(auth, cmd.Params.FeatureFlags) if err != nil { return errors.Wrap(err, "failed to create Docker client") } diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 8269bcc..45ba67a 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -14,6 +14,7 @@ import ( "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" "github.com/skpr/cli/internal/client/config" + "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" "github.com/skpr/cli/internal/slice" ) @@ -26,6 +27,7 @@ type Command struct { PrintManifest bool BuildArgs []string Debug bool + FeatureFlags user.ConfigExperimental } // Run the command. @@ -96,7 +98,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - dc, err := docker.NewClientFromUserConfig(cmd.Params.Auth) + dc, err := docker.NewClientFromUserConfig(cmd.Params.Auth, cmd.FeatureFlags) if err != nil { return err } diff --git a/internal/docker/factory.go b/internal/docker/factory.go index 25035e2..de1bbb1 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -20,11 +20,7 @@ type DockerClient interface { BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error } -func NewClientFromUserConfig(auth auth.Auth) (DockerClient, error) { - // See if we're using default builder. - userConfig, _ := user.NewClient() - featureFlags, _ := userConfig.LoadFeatureFlags() - +func NewClientFromUserConfig(auth auth.Auth, featureFlags user.ConfigExperimental) (DockerClient, error) { switch featureFlags.DockerClient { case user.ConfigPackageClientLegacy: return goclient.New(auth) From a3db007b56652d4477c6421f532a38842ad8ab1a Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 17 Dec 2025 11:29:41 +1300 Subject: [PATCH 20/21] Refactoring passing the docker client ID around everywhere --- cmd/skpr/main.go | 4 ++-- cmd/skpr/mysql/command.go | 6 ++--- cmd/skpr/mysql/image/command.go | 6 ++--- cmd/skpr/mysql/image/pull/command.go | 6 ++--- cmd/skpr/package/command.go | 8 +++---- internal/client/config/user/config.go | 14 ++++-------- internal/command/mysql/image/pull/command.go | 15 ++++++------- internal/command/package/command.go | 5 ++--- internal/docker/factory.go | 23 +++++++++++++------- 9 files changed, 43 insertions(+), 44 deletions(-) diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index 6a47e5e..c95e94b 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -106,8 +106,8 @@ func main() { cmd.AddCommand(login.NewCommand()) cmd.AddCommand(logout.NewCommand()) cmd.AddCommand(logs.NewCommand()) - cmd.AddCommand(mysql.NewCommand(featureFlags)) - cmd.AddCommand(pkg.NewCommand(featureFlags)) + cmd.AddCommand(mysql.NewCommand(featureFlags.DockerClient)) + cmd.AddCommand(pkg.NewCommand(featureFlags.DockerClient)) cmd.AddCommand(purge.NewCommand()) cmd.AddCommand(release.NewCommand()) cmd.AddCommand(restore.NewCommand()) diff --git a/cmd/skpr/mysql/command.go b/cmd/skpr/mysql/command.go index bfc29c5..bff86d9 100644 --- a/cmd/skpr/mysql/command.go +++ b/cmd/skpr/mysql/command.go @@ -6,8 +6,8 @@ import ( "github.com/skpr/cli/cmd/skpr/mysql/backup" img "github.com/skpr/cli/cmd/skpr/mysql/image" "github.com/skpr/cli/cmd/skpr/mysql/restore" - "github.com/skpr/cli/internal/client/config/user" skprcommand "github.com/skpr/cli/internal/command" + "github.com/skpr/cli/internal/docker" ) var ( @@ -15,7 +15,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'mysql' sub command -func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { +func NewCommand(clientId docker.DockerClientId) *cobra.Command { cmd := &cobra.Command{ Use: "mysql", DisableFlagsInUseLine: true, @@ -24,7 +24,7 @@ func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { GroupID: skprcommand.GroupDataStorage, } - cmd.AddCommand(img.NewCommand(featureFlags)) + cmd.AddCommand(img.NewCommand(clientId)) cmd.AddCommand(backup.NewCommand()) cmd.AddCommand(restore.NewCommand()) diff --git a/cmd/skpr/mysql/image/command.go b/cmd/skpr/mysql/image/command.go index b44fe27..cd77362 100644 --- a/cmd/skpr/mysql/image/command.go +++ b/cmd/skpr/mysql/image/command.go @@ -6,7 +6,7 @@ import ( "github.com/skpr/cli/cmd/skpr/mysql/image/create" "github.com/skpr/cli/cmd/skpr/mysql/image/list" "github.com/skpr/cli/cmd/skpr/mysql/image/pull" - "github.com/skpr/cli/internal/client/config/user" + "github.com/skpr/cli/internal/docker" ) var ( @@ -21,7 +21,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'image' sub command -func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { +func NewCommand(clientId docker.DockerClientId) *cobra.Command { cmd := &cobra.Command{ Use: "image", DisableFlagsInUseLine: true, @@ -32,7 +32,7 @@ func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { cmd.AddCommand(create.NewCommand()) cmd.AddCommand(list.NewCommand()) - cmd.AddCommand(pull.NewCommand(featureFlags)) + cmd.AddCommand(pull.NewCommand(clientId)) return cmd } diff --git a/cmd/skpr/mysql/image/pull/command.go b/cmd/skpr/mysql/image/pull/command.go index 299640e..e4a82e7 100644 --- a/cmd/skpr/mysql/image/pull/command.go +++ b/cmd/skpr/mysql/image/pull/command.go @@ -3,8 +3,8 @@ package pull import ( "github.com/spf13/cobra" - "github.com/skpr/cli/internal/client/config/user" v1pull "github.com/skpr/cli/internal/command/mysql/image/pull" + "github.com/skpr/cli/internal/docker" ) var ( @@ -16,7 +16,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'list' sub command -func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { +func NewCommand(clientId docker.DockerClientId) *cobra.Command { command := v1pull.Command{} cmd := &cobra.Command{ @@ -28,7 +28,7 @@ func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { Example: cmdExample, RunE: func(cmd *cobra.Command, args []string) error { command.Params.Environment = args[0] - command.Params.FeatureFlags = featureFlags + command.ClientId = clientId if len(args) > 1 { command.Params.Databases = args[1:] diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index c811eb1..6345b55 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -3,9 +3,9 @@ package pkg import ( "github.com/spf13/cobra" - "github.com/skpr/cli/internal/client/config/user" skprcommand "github.com/skpr/cli/internal/command" v1package "github.com/skpr/cli/internal/command/package" + "github.com/skpr/cli/internal/docker" ) var ( @@ -24,7 +24,7 @@ var ( ) // NewCommand creates a new cobra.Command for 'package' sub command -func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { +func NewCommand(clientId docker.DockerClientId) *cobra.Command { command := v1package.Command{} cmd := &cobra.Command{ @@ -38,7 +38,7 @@ func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { GroupID: skprcommand.GroupLifecycle, RunE: func(cmd *cobra.Command, args []string) error { command.Params.Version = args[0] - command.FeatureFlags = featureFlags + command.ClientId = clientId return command.Run(cmd.Context()) }, } @@ -51,7 +51,7 @@ func NewCommand(featureFlags user.ConfigExperimental) *cobra.Command { cmd.Flags().StringSliceVar(&command.BuildArgs, "build-arg", []string{}, "Additional build arguments.") cmd.Flags().BoolVar(&command.Debug, "debug", command.Debug, "Enable debug output.") - if featureFlags.DockerClient == user.ConfigPackageClientDocker { + if clientId == docker.ClientIdDocker { cmd.Flags().StringVar(&command.Params.IgnoreFile, "ignore-file", ".dockerignore", "A file containing patterns to exclude from the build context.") } diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index 86403f5..4962be3 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -6,6 +6,8 @@ import ( "path/filepath" "gopkg.in/yaml.v2" + + "github.com/skpr/cli/internal/docker" ) // Config represents the persistent user configuration. @@ -16,18 +18,10 @@ type Config struct { // ConfigExperimental holds experimental feature flags. type ConfigExperimental struct { - Trace bool `yaml:"trace,omitempty"` - DockerClient ConfigPackageBuilder `yaml:"docker-client,omitempty"` + Trace bool `yaml:"trace,omitempty"` + DockerClient docker.DockerClientId `yaml:"docker-client,omitempty"` } -type ConfigPackageBuilder string - -const ( - ConfigPackageClientLegacy ConfigPackageBuilder = "legacy" - ConfigPackageClientDocker ConfigPackageBuilder = "docker" - ConfigPackageClientMock ConfigPackageBuilder = "mock" -) - // Aliases maps alias names to commands. type Aliases map[string]string diff --git a/internal/command/mysql/image/pull/command.go b/internal/command/mysql/image/pull/command.go index 063d2aa..bdf7380 100644 --- a/internal/command/mysql/image/pull/command.go +++ b/internal/command/mysql/image/pull/command.go @@ -13,7 +13,6 @@ import ( "github.com/skpr/cli/internal/auth" "github.com/skpr/cli/internal/buildpack/utils/aws/ecr" "github.com/skpr/cli/internal/client" - "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" skprlog "github.com/skpr/cli/internal/log" ) @@ -25,16 +24,16 @@ const ( // Command to pull a database image. type Command struct { - Params Params + Params Params + ClientId docker.DockerClientId } // Params provided to this command. type Params struct { - Environment string - Databases []string - ID string - Tag string - FeatureFlags user.ConfigExperimental + Environment string + Databases []string + ID string + Tag string } // Run the command. @@ -72,7 +71,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - c, err := docker.NewClientFromUserConfig(auth, cmd.Params.FeatureFlags) + c, err := docker.NewClientFromUserConfig(auth, cmd.ClientId) if err != nil { return errors.Wrap(err, "failed to create Docker client") } diff --git a/internal/command/package/command.go b/internal/command/package/command.go index 45ba67a..88df5b4 100644 --- a/internal/command/package/command.go +++ b/internal/command/package/command.go @@ -14,7 +14,6 @@ import ( "github.com/skpr/cli/internal/buildpack/utils/finder" "github.com/skpr/cli/internal/client" "github.com/skpr/cli/internal/client/config" - "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker" "github.com/skpr/cli/internal/slice" ) @@ -27,7 +26,7 @@ type Command struct { PrintManifest bool BuildArgs []string Debug bool - FeatureFlags user.ConfigExperimental + ClientId docker.DockerClientId } // Run the command. @@ -98,7 +97,7 @@ func (cmd *Command) Run(ctx context.Context) error { } } - dc, err := docker.NewClientFromUserConfig(cmd.Params.Auth, cmd.FeatureFlags) + dc, err := docker.NewClientFromUserConfig(cmd.Params.Auth, cmd.ClientId) if err != nil { return err } diff --git a/internal/docker/factory.go b/internal/docker/factory.go index de1bbb1..0ed418d 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -6,7 +6,6 @@ import ( "io" "github.com/skpr/cli/internal/auth" - "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/docker/dockerclient" "github.com/skpr/cli/internal/docker/goclient" "github.com/skpr/cli/internal/docker/mockclient" @@ -20,18 +19,26 @@ type DockerClient interface { BuildImage(ctx context.Context, dockerfile string, dockerContext string, ref string, excludePatterns []string, buildArgs map[string]string, writer io.Writer) error } -func NewClientFromUserConfig(auth auth.Auth, featureFlags user.ConfigExperimental) (DockerClient, error) { - switch featureFlags.DockerClient { - case user.ConfigPackageClientLegacy: +type DockerClientId string + +const ( + ClientIdLegacy DockerClientId = "legacy" + ClientIdDocker DockerClientId = "docker" + ClientIdMock DockerClientId = "mock" +) + +func NewClientFromUserConfig(auth auth.Auth, clientId DockerClientId) (DockerClient, error) { + switch clientId { + case ClientIdLegacy: return goclient.New(auth) - case user.ConfigPackageClientDocker: + case ClientIdDocker: return dockerclient.New(auth) - case user.ConfigPackageClientMock: + case ClientIdMock: return mockclient.New(), nil } - if featureFlags.DockerClient != "" { - return nil, fmt.Errorf("unknown docker client: %s", featureFlags.DockerClient) + if clientId != "" { + return nil, fmt.Errorf("unknown docker client: %s", clientId) } return goclient.New(auth) From 2c7a1a135114c245e28022c96ff718b41393aae5 Mon Sep 17 00:00:00 2001 From: Nathan ter Bogt Date: Wed, 17 Dec 2025 11:33:46 +1300 Subject: [PATCH 21/21] Catch the constant names too --- cmd/skpr/package/command.go | 2 +- internal/docker/factory.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/skpr/package/command.go b/cmd/skpr/package/command.go index 6345b55..d69bd72 100644 --- a/cmd/skpr/package/command.go +++ b/cmd/skpr/package/command.go @@ -51,7 +51,7 @@ func NewCommand(clientId docker.DockerClientId) *cobra.Command { cmd.Flags().StringSliceVar(&command.BuildArgs, "build-arg", []string{}, "Additional build arguments.") cmd.Flags().BoolVar(&command.Debug, "debug", command.Debug, "Enable debug output.") - if clientId == docker.ClientIdDocker { + if clientId == docker.DockerClientIdDocker { cmd.Flags().StringVar(&command.Params.IgnoreFile, "ignore-file", ".dockerignore", "A file containing patterns to exclude from the build context.") } diff --git a/internal/docker/factory.go b/internal/docker/factory.go index 0ed418d..291f7f7 100644 --- a/internal/docker/factory.go +++ b/internal/docker/factory.go @@ -22,18 +22,18 @@ type DockerClient interface { type DockerClientId string const ( - ClientIdLegacy DockerClientId = "legacy" - ClientIdDocker DockerClientId = "docker" - ClientIdMock DockerClientId = "mock" + DockerClientIdLegacy DockerClientId = "legacy" + DockerClientIdDocker DockerClientId = "docker" + DockerClientIdMock DockerClientId = "mock" ) func NewClientFromUserConfig(auth auth.Auth, clientId DockerClientId) (DockerClient, error) { switch clientId { - case ClientIdLegacy: + case DockerClientIdLegacy: return goclient.New(auth) - case ClientIdDocker: + case DockerClientIdDocker: return dockerclient.New(auth) - case ClientIdMock: + case DockerClientIdMock: return mockclient.New(), nil }