diff --git a/go.mod b/go.mod index 703e0b3b..270b6e8a 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/didi/gendry v1.7.0 github.com/djherbis/times v1.5.0 github.com/evanphx/json-patch v4.12.0+incompatible + github.com/fluxcd/pkg/sourceignore v0.5.0 + github.com/fluxcd/pkg/tar v0.4.0 github.com/go-git/go-git/v5 v5.11.0 github.com/go-sql-driver/mysql v1.7.0 github.com/go-test/deep v1.0.3 @@ -32,6 +34,7 @@ require ( github.com/gonvenience/wrap v1.1.0 github.com/gonvenience/ytbx v1.3.0 github.com/google/go-cmp v0.6.0 + github.com/google/go-containerregistry v0.19.0 github.com/google/go-github/v50 v50.0.0 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/go-multierror v1.1.1 @@ -43,14 +46,14 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/lucasb-eyer/go-colorful v1.0.3 github.com/mitchellh/hashstructure v1.0.0 - github.com/onsi/ginkgo/v2 v2.11.0 - github.com/onsi/gomega v1.27.10 + github.com/onsi/ginkgo/v2 v2.13.0 + github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.60 github.com/pulumi/pulumi/sdk/v3 v3.68.0 github.com/sergi/go-diff v1.3.1 github.com/spf13/afero v1.6.0 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/texttheater/golang-levenshtein v1.0.1 github.com/tidwall/gjson v1.17.0 @@ -89,6 +92,7 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 // indirect github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/deckarep/golang-set v1.7.1 // indirect github.com/go-errors/errors v1.4.2 // indirect @@ -106,6 +110,7 @@ require ( github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect gopkg.in/ini.v1 v1.66.2 // indirect @@ -161,9 +166,9 @@ require ( github.com/containerd/containerd v1.7.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/docker/cli v23.0.1+incompatible // indirect + github.com/docker/cli v24.0.0+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v23.0.3+incompatible // indirect + github.com/docker/docker v24.0.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -207,7 +212,7 @@ require ( github.com/hashicorp/hc-install v0.6.2 github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -280,12 +285,12 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/go.sum b/go.sum index 5ab82593..fe8f515b 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,7 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -190,6 +191,8 @@ github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkX github.com/containerd/containerd v1.7.5 h1:i9T9XpAWMe11BHMN7pu1BZqOGjXaKTPyz2v+KYOZgkY= github.com/containerd/containerd v1.7.5/go.mod h1:ieJNCSzASw2shSGYLHx8NAE7WsZ/gEigo5fQ78W5Zvw= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -209,12 +212,12 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= -github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= -github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= +github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= -github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= +github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -243,6 +246,10 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/fluxcd/pkg/sourceignore v0.5.0 h1:8ffSJCRIKsMpxXjGPVeRK3xhGUjuk+tFILf/+EODCVg= +github.com/fluxcd/pkg/sourceignore v0.5.0/go.mod h1:cJsXn+wYmRY3VamrtG9I3MBL2wjtns2bS7ARIht2XAQ= +github.com/fluxcd/pkg/tar v0.4.0 h1:SuXpfXBIcSJ5R/yqQi2CBxBmV/i/LH0agqNAh2PWBZg= +github.com/fluxcd/pkg/tar v0.4.0/go.mod h1:SyJBaQvuv2VA/rv4d1OHhCV6R8+9QKc9np193EzNHBc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -352,6 +359,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= +github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github/v50 v50.0.0 h1:gdO1AeuSZZK4iYWwVbjni7zg8PIQhp7QfmPunr016Jk= github.com/google/go-github/v50 v50.0.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -425,8 +434,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -562,16 +571,16 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= @@ -652,6 +661,7 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= @@ -662,8 +672,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -702,6 +712,9 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -812,8 +825,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -850,6 +863,7 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -898,8 +912,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/oci/client/build.go b/pkg/oci/client/build.go new file mode 100644 index 00000000..2838494d --- /dev/null +++ b/pkg/oci/client/build.go @@ -0,0 +1,176 @@ +package client + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fluxcd/pkg/sourceignore" +) + +// Build archives the given directory as a tarball to the given local path. +// While archiving, any environment specific data (for example, the user and group name) is stripped from file headers. +// Original copy from https://github.com/fluxcd/pkg/blob/main/oci/client/build.go +func (c *Client) Build(artifactPath, sourceDir string, ignorePaths []string) (err error) { + return build(artifactPath, sourceDir, ignorePaths) +} + +func build(artifactPath, sourceDir string, ignorePaths []string) (err error) { + absDir, err := filepath.Abs(sourceDir) + if err != nil { + return err + } + + dirStat, err := os.Stat(absDir) + if os.IsNotExist(err) { + return fmt.Errorf("invalid source dir path: %s", absDir) + } + + tf, err := os.CreateTemp(filepath.Split(absDir)) + if err != nil { + return err + } + tmpName := tf.Name() + defer func() { + if err != nil { + os.Remove(tmpName) + } + }() + + ignore := strings.Join(ignorePaths, "\n") + domain := strings.Split(filepath.Clean(absDir), string(filepath.Separator)) + ignorePatterns := sourceignore.ReadPatterns(strings.NewReader(ignore), domain) + matcher := sourceignore.NewMatcher(ignorePatterns) + filter := func(p string, fi os.FileInfo) bool { + return matcher.Match(strings.Split(p, string(filepath.Separator)), fi.IsDir()) + } + + sz := &writeCounter{} + mw := io.MultiWriter(tf, sz) + + gw := gzip.NewWriter(mw) + tw := tar.NewWriter(gw) + if err := filepath.Walk(absDir, func(p string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + // Ignore anything that is not a file or directories e.g. symlinks + if m := fi.Mode(); !(m.IsRegular() || m.IsDir()) { + return nil + } + + if len(ignorePatterns) > 0 && filter(p, fi) { + return nil + } + + header, err := tar.FileInfoHeader(fi, p) + if err != nil { + return err + } + if dirStat.IsDir() { + // The name needs to be modified to maintain directory structure + // as tar.FileInfoHeader only has access to the base name of the file. + // Ref: https://golang.org/src/archive/tar/common.go?#L6264 + // + // we only want to do this if a directory was passed in + relFilePath, err := filepath.Rel(absDir, p) + if err != nil { + return err + } + // Normalize file path so it works on windows + header.Name = filepath.ToSlash(relFilePath) + } + + // Remove any environment specific data. + header.Gid = 0 + header.Uid = 0 + header.Uname = "" + header.Gname = "" + header.ModTime = time.Time{} + header.AccessTime = time.Time{} + header.ChangeTime = time.Time{} + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if !fi.Mode().IsRegular() { + return nil + } + f, err := os.Open(p) + if err != nil { + f.Close() + return err + } + if _, err := io.Copy(tw, f); err != nil { + f.Close() + return err + } + return f.Close() + }); err != nil { + tw.Close() + gw.Close() + tf.Close() + return err + } + + if err := tw.Close(); err != nil { + gw.Close() + tf.Close() + return err + } + if err := gw.Close(); err != nil { + tf.Close() + return err + } + if err := tf.Close(); err != nil { + return err + } + + if err := os.Chmod(tmpName, 0o640); err != nil { + return err + } + + return renameWithFallback(tmpName, artifactPath) +} + +func renameWithFallback(src, dst string) error { + err := os.Rename(src, dst) + if err == nil { + return nil + } + + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + + f2, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return err + } + defer f2.Close() + + _, err = io.Copy(f2, f) + if err != nil { + return err + } + return nil +} + +type writeCounter struct { + written int64 +} + +func (wc *writeCounter) Write(p []byte) (int, error) { + n := len(p) + wc.written += int64(n) + return n, nil +} diff --git a/pkg/oci/client/build_test.go b/pkg/oci/client/build_test.go new file mode 100644 index 00000000..0e9d66d2 --- /dev/null +++ b/pkg/oci/client/build_test.go @@ -0,0 +1,138 @@ +package client + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/fluxcd/pkg/tar" + . "github.com/onsi/gomega" +) + +// Original copy from https://github.com/fluxcd/pkg/blob/main/oci/client/build_test.go +func TestBuild(t *testing.T) { + g := NewWithT(t) + + absPath := fmt.Sprintf("%s/deployment.yaml", t.TempDir()) + err := copyFile(absPath, "testdata/artifact/deployment.yaml") + g.Expect(err).To(BeNil()) + + absDir, err := filepath.Abs("testdata/artifact") + g.Expect(err).To(BeNil()) + + tests := []struct { + name string + path string + testDir string + ignorePath []string + expectErr bool + checkPaths []string + }{ + { + name: "non-existent path", + path: "testdata/non-existent", + expectErr: true, + }, + { + name: "existing path", + path: "testdata/artifact", + ignorePath: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + checkPaths: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + }, + { + name: "absolute directory path", + path: absDir, + ignorePath: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + checkPaths: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + }, + { + name: "existing path with leading slash", + path: "./testdata/artifact", + ignorePath: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + checkPaths: []string{"ignore.txt", "ignore-dir/", "!/deploy", "somedir/git"}, + }, + { + name: "relative file path", + path: "testdata/artifact/deployment.yaml", + testDir: "./", + checkPaths: []string{"!deployment.yaml"}, + }, + { + name: "absolute file path", + path: absPath, + testDir: "./", + checkPaths: []string{"!deployment.yaml"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + tmpDir := t.TempDir() + artifactPath := filepath.Join(tmpDir, "files.tar.gz") + + err := build(artifactPath, tt.path, tt.ignorePath) + if tt.expectErr { + g.Expect(err).To(HaveOccurred()) + return + } + + g.Expect(err).To(Not(HaveOccurred())) + + _, err = os.Stat(artifactPath) + g.Expect(err).ToNot(HaveOccurred()) + + b, err := os.ReadFile(artifactPath) + g.Expect(err).ToNot(HaveOccurred()) + + untarDir := t.TempDir() + err = tar.Untar(bytes.NewReader(b), untarDir, tar.WithMaxUntarSize(-1)) + g.Expect(err).To(BeNil()) + + checkPath(g, untarDir, tt.checkPaths) + }) + } +} + +// checkPath takes a directory and an array of files as its argument. For each item in the array, if a file name in the list +// is prefixed with an exclamation mark (!), it checks that the filepath exists else it checks that is doesn't exist. +func checkPath(g *WithT, dir string, paths []string) { + g.THelper() + + for _, path := range paths { + var shouldExist bool + if strings.HasPrefix(path, "!") { + shouldExist = true + path = path[1:] + } + + fullPath := filepath.Join(dir, path) + _, err := os.Stat(fullPath) + if shouldExist { + g.Expect(err).To(BeNil()) + continue + } + g.Expect(err).ToNot(BeNil()) + g.Expect(os.IsNotExist(err)).To(BeTrue()) + } +} + +func copyFile(dst, src string) error { + f, err := os.Create(dst) + if err != nil { + return fmt.Errorf("unable to create file: %w", err) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + _, err = io.Copy(f, source) + return err +} diff --git a/pkg/oci/client/client.go b/pkg/oci/client/client.go new file mode 100644 index 00000000..8d74ec4c --- /dev/null +++ b/pkg/oci/client/client.go @@ -0,0 +1,62 @@ +package client + +import ( + "context" + "fmt" + + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +var ( + // CanonicalConfigMediaType is the OCI media type for the config layer. + CanonicalConfigMediaType types.MediaType = "application/vnd.io.kusionstack.config.v1+json" + + // CanonicalMediaTypePrefix is the suffix for OCI media type for the content layer. + CanonicalMediaTypePrefix types.MediaType = "application/vnd.io.kusionstack.content.v1" + + // CanonicalContentMediaType is the OCI media type for the content layer. + CanonicalContentMediaType = types.MediaType(fmt.Sprintf("%s.tar+gzip", CanonicalMediaTypePrefix)) +) + +// ClientOptions are options for configuring the client behavior. +type ClientOptions struct { + craneOptions []crane.Option +} + +// ClientOption is a function for configuring ClientOptions. +type ClientOption func(o *ClientOptions) + +// WithUserAgent sets User-Agent header for any HTTP requests. +func WithUserAgent(userAgent string) ClientOption { + return func(o *ClientOptions) { + o.craneOptions = append(o.craneOptions, crane.WithUserAgent(userAgent)) + } +} + +// Client provides methods to interact with OCI registry. +type Client struct { + opts *ClientOptions +} + +// NewClient returns a client instance for use communicating with OCI registry. +func NewClient(options ...ClientOption) *Client { + client := &Client{ + opts: &ClientOptions{}, + } + + // Apply all options + for _, opt := range options { + opt(client.opts) + } + + return client +} + +// optionsWithContext returns the crane options for the given context. +func (c *Client) optionsWithContext(ctx context.Context) []crane.Option { + options := []crane.Option{ + crane.WithContext(ctx), + } + return append(options, c.opts.craneOptions...) +} diff --git a/pkg/oci/client/push.go b/pkg/oci/client/push.go new file mode 100644 index 00000000..19b1f81a --- /dev/null +++ b/pkg/oci/client/push.go @@ -0,0 +1,73 @@ +package client + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + + meta "kusionstack.io/kusion/pkg/oci/metadata" +) + +// Push takes care of the actual artifact push behavior. It performs following operations: +// - builds tarball from given directory also corresponding layer +// - adds this layer to an empty OpenContainers artifact +// - annotates the artifact with the given annotations +// - uploads the final artifact to the OCI registry +// - returns the digest URL of the upstream artifact +func (c *Client) Push(ctx context.Context, registryURL, sourceDir string, metadata meta.Metadata, ignorePaths []string) (string, error) { + ref, err := parseArtifactRef(registryURL) + if err != nil { + return "", err + } + + tmpDir, err := os.MkdirTemp("", "oci") + if err != nil { + return "", err + } + defer os.RemoveAll(tmpDir) + + tmpFile := filepath.Join(tmpDir, "artifact.tgz") + if err := c.Build(tmpFile, sourceDir, ignorePaths); err != nil { + return "", err + } + + // Add missing metadata + if metadata.Created == "" { + ct := time.Now().UTC() + metadata.Created = ct.Format(time.RFC3339) + } + + image := mutate.MediaType(empty.Image, types.OCIManifestSchema1) + image = mutate.ConfigMediaType(image, CanonicalConfigMediaType) + image = mutate.Annotations(image, metadata.ToAnnotations()).(v1.Image) + + layer, err := tarball.LayerFromFile(tmpFile, tarball.WithMediaType(CanonicalContentMediaType)) + if err != nil { + return "", fmt.Errorf("creating content layer failed: %w", err) + } + + image, err = mutate.Append(image, mutate.Addendum{Layer: layer}) + if err != nil { + return "", fmt.Errorf("appeding content to artifact failed: %w", err) + } + + if err := crane.Push(image, registryURL, c.optionsWithContext(ctx)...); err != nil { + return "", fmt.Errorf("pushing artifact failed: %w", err) + } + + digest, err := image.Digest() + if err != nil { + return "", fmt.Errorf("parsing artifact digest failed: %w", err) + } + + return ref.Context().Digest(digest.String()).String(), err +} diff --git a/pkg/oci/client/ref.go b/pkg/oci/client/ref.go new file mode 100644 index 00000000..59602b79 --- /dev/null +++ b/pkg/oci/client/ref.go @@ -0,0 +1,27 @@ +package client + +import ( + "fmt" + "strings" + + "github.com/google/go-containerregistry/pkg/name" +) + +// OCIRepositoryPrefix is the prefix used for OCIRepository URLs. +const OCIRepositoryPrefix = "oci://" + +// parseArtifactRef parses a string representing an OCI repository URL. +// If the string is not a valid representation of an OCI repository URL, parseArtifactRef returns an error. +func parseArtifactRef(ociURL string) (name.Reference, error) { + if !strings.HasPrefix(ociURL, OCIRepositoryPrefix) { + return nil, fmt.Errorf("URL must be in format 'oci:////'") + } + + url := strings.TrimPrefix(ociURL, OCIRepositoryPrefix) + ref, err := name.ParseReference(url) + if err != nil { + return nil, fmt.Errorf("'%s' invalid URL format: %w", ociURL, err) + } + + return ref, nil +} diff --git a/pkg/oci/client/testdata/artifact/deploy/repo.yaml b/pkg/oci/client/testdata/artifact/deploy/repo.yaml new file mode 100644 index 00000000..609af582 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/deploy/repo.yaml @@ -0,0 +1,8 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: HelmRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 2m + url: https://stefanprodan.github.io/podinfo \ No newline at end of file diff --git a/pkg/oci/client/testdata/artifact/deployment.yaml b/pkg/oci/client/testdata/artifact/deployment.yaml new file mode 100644 index 00000000..11de6936 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 \ No newline at end of file diff --git a/pkg/oci/client/testdata/artifact/ignore-dir/deployment.yaml b/pkg/oci/client/testdata/artifact/ignore-dir/deployment.yaml new file mode 100644 index 00000000..11de6936 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/ignore-dir/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 \ No newline at end of file diff --git a/pkg/oci/client/testdata/artifact/ignore.txt b/pkg/oci/client/testdata/artifact/ignore.txt new file mode 100644 index 00000000..0ecc15c8 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/ignore.txt @@ -0,0 +1 @@ +excluded file \ No newline at end of file diff --git a/pkg/oci/client/testdata/artifact/somedir/git/repo.yaml b/pkg/oci/client/testdata/artifact/somedir/git/repo.yaml new file mode 100644 index 00000000..609af582 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/somedir/git/repo.yaml @@ -0,0 +1,8 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: HelmRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 2m + url: https://stefanprodan.github.io/podinfo \ No newline at end of file diff --git a/pkg/oci/client/testdata/artifact/somedir/repo.yaml b/pkg/oci/client/testdata/artifact/somedir/repo.yaml new file mode 100644 index 00000000..609af582 --- /dev/null +++ b/pkg/oci/client/testdata/artifact/somedir/repo.yaml @@ -0,0 +1,8 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: HelmRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 2m + url: https://stefanprodan.github.io/podinfo \ No newline at end of file diff --git a/pkg/oci/metadata/metadata.go b/pkg/oci/metadata/metadata.go new file mode 100644 index 00000000..ee56027c --- /dev/null +++ b/pkg/oci/metadata/metadata.go @@ -0,0 +1,51 @@ +package metadata + +const ( + // AnnotationSource is the OpenContainers annotation for specifying + // the upstream source of an OCI artifact. + AnnotationSource = "org.opencontainers.image.source" + + // AnnotationRevision is the OpenContainers annotation for specifying + // the upstream source revision of an OCI artifact. + AnnotationRevision = "org.opencontainers.image.revision" + + // AnnotationCreated is the OpenContainers annotation for specifying + // the date and time on which the OCI artifact was built (RFC 3339). + AnnotationCreated = "org.opencontainers.image.created" +) + +// Metadata holds the upstream information about on artifact's source. +// https://github.com/opencontainers/image-spec/blob/main/annotations.md +type Metadata struct { + Created string `json:"created,omitempty"` + Source string `json:"source_url,omitempty"` + Revision string `json:"source_revision,omitempty"` + Digest string `json:"digest"` + URL string `json:"url"` + Annotations map[string]string `json:"annotations,omitempty"` +} + +// ToAnnotations returns the OpenContainers annotations map. +func (m *Metadata) ToAnnotations() map[string]string { + annotations := map[string]string{ + AnnotationCreated: m.Created, + AnnotationSource: m.Source, + AnnotationRevision: m.Revision, + } + + for k, v := range m.Annotations { + annotations[k] = v + } + + return annotations +} + +// MetadataFromAnnotations parses the OpenContainers annotations and returns a Metadata object. +func MetadataFromAnnotations(annotations map[string]string) *Metadata { + return &Metadata{ + Created: annotations[AnnotationCreated], + Source: annotations[AnnotationSource], + Revision: annotations[AnnotationRevision], + Annotations: annotations, + } +}