diff --git a/go.mod b/go.mod index 5f46ed2a..ed6c20bd 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 + github.com/Masterminds/semver/v3 v3.1.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible github.com/aws/aws-sdk-go v1.48.6 @@ -31,7 +32,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-containerregistry v0.19.1 github.com/google/go-github/v50 v50.0.0 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/go-multierror v1.1.1 @@ -62,10 +63,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.4 gorm.io/gorm v1.25.7 - k8s.io/api v0.27.2 - k8s.io/apimachinery v0.27.2 - k8s.io/cli-runtime v0.27.1 - k8s.io/client-go v0.27.2 + k8s.io/api v0.29.2 + k8s.io/apimachinery v0.29.2 + k8s.io/cli-runtime v0.29.2 + k8s.io/client-go v0.29.2 k8s.io/component-base v0.27.2 k8s.io/kubectl v0.27.1 k8s.io/utils v0.0.0-20230726121419-3b25d923346b @@ -96,7 +97,9 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/go-git/v5 v5.11.0 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hashicorp/go-hclog v0.16.2 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -105,18 +108,19 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/oklog/run v1.0.0 // indirect 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 + github.com/xlab/treeprint v1.2.0 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect gopkg.in/ini.v1 v1.66.2 // indirect kcl-lang.io/lib v0.7.3 // indirect oras.land/oras-go v1.2.4 // indirect - sigs.k8s.io/kustomize/api v0.13.2 // indirect - sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect + sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect ) require ( @@ -170,13 +174,13 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -190,7 +194,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect @@ -279,7 +282,7 @@ require ( 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.16.0 // indirect + golang.org/x/tools v0.16.1 // 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 @@ -287,10 +290,10 @@ require ( google.golang.org/protobuf v1.31.0 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect oras.land/oras-go/v2 v2.3.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index d0260db9..e5c2c60e 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -235,11 +237,10 @@ 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/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -280,10 +281,9 @@ github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxF github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= @@ -340,7 +340,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -362,19 +361,21 @@ github.com/gonvenience/ytbx v1.3.0 h1:ZNOPdSrSwv1QwLsN9cd6u9z2t/XA/at67w87/LrgmN github.com/gonvenience/ytbx v1.3.0/go.mod h1:K2ZFl0mTkniFjkDWqlpDtx2lYnt97+jPkQU3Ixf4N+Q= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= 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= @@ -399,6 +400,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -497,7 +500,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -578,6 +580,8 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -688,7 +692,6 @@ 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= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -697,7 +700,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -725,8 +727,8 @@ github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtb 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= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= @@ -750,8 +752,8 @@ go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+p go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -845,7 +847,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -884,6 +885,7 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= @@ -917,8 +919,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.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/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= @@ -932,7 +934,6 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -950,7 +951,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= @@ -994,22 +995,22 @@ gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= -k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= +k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= +k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= -k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= -k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/cli-runtime v0.27.1 h1:MMzp5Q/Xmr5L1Lrowuc+Y/r95XINC6c6/fE3aN7JDRM= -k8s.io/cli-runtime v0.27.1/go.mod h1:tEbTB1XP/nTH3wujsi52bw91gWpErtWiS15R6CwYsAI= -k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= -k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/cli-runtime v0.29.2 h1:smfsOcT4QujeghsNjECKN3lwyX9AwcFU0nvJ7sFN3ro= +k8s.io/cli-runtime v0.29.2/go.mod h1:KLisYYfoqeNfO+MkTWvpqIyb1wpJmmFJhioA0xd4MW8= +k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= +k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kubectl v0.27.1 h1:9T5c5KdpburYiW8XKQSH0Uly1kMNE90aGSnbYUZNdcA= k8s.io/kubectl v0.27.1/go.mod h1:QsAkSmrRsKTPlAFzF8kODGDl4p35BIwQnc9XFhkcsy8= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= @@ -1032,11 +1033,11 @@ sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUT sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA= -sigs.k8s.io/kustomize/api v0.13.2/go.mod h1:DUp325VVMFVcQSq+ZxyDisA8wtldwHxLZbr1g94UHsw= -sigs.k8s.io/kustomize/kyaml v0.14.1 h1:c8iibius7l24G2wVAGZn/Va2wNys03GXLjYVIcFVxKA= -sigs.k8s.io/kustomize/kyaml v0.14.1/go.mod h1:AN1/IpawKilWD7V+YvQwRGUvuUOOWpjsHu6uHwonSF4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/apis/core/v1/appconfiguration.go b/pkg/apis/core/v1/appconfiguration.go index 3c8b3f07..507552b7 100644 --- a/pkg/apis/core/v1/appconfiguration.go +++ b/pkg/apis/core/v1/appconfiguration.go @@ -10,7 +10,7 @@ type Accessory map[string]interface{} // of experience from AntGroup in operating a large-scale internal developer platform and combines the best ideas and practices from the // community. // -// Note: AppConfiguration per se is not a Kusion Module +// Note: AppConfiguration per se is not a Kusion ModulePath // // Example: // import models.schema.v1 as ac diff --git a/pkg/cmd/mod/mod.go b/pkg/cmd/mod/mod.go index 061c8673..3bdccf12 100644 --- a/pkg/cmd/mod/mod.go +++ b/pkg/cmd/mod/mod.go @@ -16,7 +16,7 @@ var modLong = i18n.T(` // NewCmdMod returns an initialized Command instance for 'mod' sub command func NewCmdMod(streams genericclioptions.IOStreams) *cobra.Command { cmd := &cobra.Command{ - Use: "mod SUBCOMMAND", + Use: "mod", DisableFlagsInUseLine: true, Short: "Manage Kusion modules", Long: modLong, diff --git a/pkg/cmd/mod/mod_push.go b/pkg/cmd/mod/mod_push.go index 9e5ec6fc..44eea391 100644 --- a/pkg/cmd/mod/mod_push.go +++ b/pkg/cmd/mod/mod_push.go @@ -1,28 +1,358 @@ package mod import ( + "context" + "encoding/base64" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/Masterminds/semver/v3" "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" -) + "k8s.io/cli-runtime/pkg/genericiooptions" -type PushModOptions struct{} + cmdutil "kusionstack.io/kusion/pkg/cmd/util" + "kusionstack.io/kusion/pkg/oci" + ociclient "kusionstack.io/kusion/pkg/oci/client" + "kusionstack.io/kusion/pkg/oci/metadata" + "kusionstack.io/kusion/pkg/util/executable" + "kusionstack.io/kusion/pkg/util/gitutil" + "kusionstack.io/kusion/pkg/util/i18n" + "kusionstack.io/kusion/pkg/util/pretty" +) var ( - pushLong = `` - pushExample = `` + pushLong = i18n.T(` + The push command packages the module as an OCI artifact and pushes it to the + OCI registry using the version as the image tag.`) + + pushExample = i18n.T(` + # Push a module to GitHub Container Registry using a GitHub token + kusion mod push /path/to/module oci://ghcr.io/org/modules/app --version=1.0.0 --creds $GITHUB_TOKEN + + # Push a release candidate without marking it as the latest stable + kusion mod push /path/to/module oci://ghcr.io/modules/app --version=1.0.0-rc.1 --latest=false + + # Push a module with custom OCI annotations + kusion mod push /path/to/module oci://ghcr.io/org/modules/app --version=1.0.0 \ + --annotation='org.opencontainers.image.documentation=https://app.org/docs' + + # Push and sign a module with Cosign (the cosign binary must be present in PATH) + export COSIGN_PASSWORD=password + kusion mod push /path/to/module oci://ghcr.io/org/modules/app --version=1.0.0 \ + --sign=cosign --cosign-key=/path/to/cosign.key`) ) -// NewCmdPush returns an initialized Command instance for the 'mod push' sub command -func NewCmdPush(streams genericclioptions.IOStreams) *cobra.Command { +// LatestVersion is the tag name that +// denotes the latest stable version of a module. +const LatestVersion = "latest" + +// PushModFlags directly reflect the information that CLI is gathering via flags. They will be converted to +// PushModOptions, which reflect the runtime requirements for the command. +// +// This structure reduces the transformation to wiring and makes the logic itself easy to unit test. +type PushModFlags struct { + Version string + Latest bool + Annotations []string + Credentials string + Sign string + CosignKey string + InsecureRegistry bool + + genericiooptions.IOStreams +} + +// PushModOptions is a set of options that allows you to push module. This is the object reflects the +// runtime needs of a `mod push` command, making the logic itself easy to unit test. +type PushModOptions struct { + ModulePath string + OCIUrl string + Latest bool + Sign string + CosignKey string + + Client *ociclient.Client + Metadata metadata.Metadata + + genericiooptions.IOStreams +} + +// NewPushModFlags returns a default PushModFlags. +func NewPushModFlags(ioStreams genericiooptions.IOStreams) *PushModFlags { + return &PushModFlags{ + IOStreams: ioStreams, + Latest: true, + InsecureRegistry: false, + } +} + +// NewCmdPush returns an initialized Command instance for the 'mod push' sub command. +func NewCmdPush(ioStreams genericiooptions.IOStreams) *cobra.Command { + flags := NewPushModFlags(ioStreams) + cmd := &cobra.Command{ - Use: "", + Use: "push [MODULE PATH] [OCI REPOSITORY URL]", DisableFlagsInUseLine: true, - Short: "", + Short: "Push a module to OCI registry", Long: pushLong, Example: pushExample, Run: func(cmd *cobra.Command, args []string) { + o, err := flags.ToOptions(args, flags.IOStreams) + cmdutil.CheckErr(err) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) }, } + flags.AddFlags(cmd) + return cmd } + +// AddFlags registers flags for a cli. +func (flags *PushModFlags) AddFlags(cmd *cobra.Command) { + cmd.Flags().StringVarP(&flags.Version, "version", "v", flags.Version, "The version of the module e.g. '1.0.0' or '1.0.0-rc.1'.") + cmd.Flags().BoolVar(&flags.Latest, "latest", flags.Latest, "Tags the current version as the latest stable module version.") + cmd.Flags().StringVar(&flags.Credentials, "creds", flags.Credentials, "The credentials for the OCI registry in '[:]' format.") + cmd.Flags().StringVar(&flags.Sign, "sign", flags.Sign, "Signs the module with the specified provider.") + cmd.Flags().StringVar(&flags.CosignKey, "cosign-key", flags.CosignKey, "The Cosign private key for signing the module.") + cmd.Flags().BoolVar(&flags.InsecureRegistry, "insecure-registry", flags.InsecureRegistry, "If true, allows connecting to a OCI registry without TLS or with self-signed certificates.") + cmd.Flags().StringSliceVarP(&flags.Annotations, "annotations", "a", flags.Annotations, "Set custom OCI annotations in '=' format.") +} + +// ToOptions converts from CLI inputs to runtime inputs. +func (flags *PushModFlags) ToOptions(args []string, ioStreams genericiooptions.IOStreams) (*PushModOptions, error) { + if len(args) < 2 { + return nil, fmt.Errorf("path to module and OCI registry url are required") + } + + version := flags.Version + if _, err := semver.StrictNewVersion(version); err != nil { + return nil, fmt.Errorf("version is not in semver format: %w", err) + } + fullURL := fmt.Sprintf("%s:%s", args[1], version) + + // If creds in format, creds must be base64 encoded + if len(flags.Credentials) != 0 && !strings.Contains(flags.Credentials, ":") { + if _, err := base64.StdEncoding.DecodeString(flags.Credentials); err != nil { + return nil, fmt.Errorf("credentials must be base64 encoded") + } + } + + // Parse custom annotations + annotations, err := metadata.ParseAnnotations(flags.Annotations) + if err != nil { + return nil, err + } + annotations[metadata.AnnotationVersion] = version + + // Detect git repository to get basic git information + var info gitutil.Info + repoRoot, err := detectGitRepository(args[0]) + if err == nil && len(repoRoot) != 0 { + info = gitutil.Get(repoRoot) + } + + // Prepare metadata + meta := metadata.Metadata{ + Created: info.CommitDate, + Source: info.RemoteURL, + Revision: info.Commit, + Annotations: annotations, + } + if len(meta.Created) == 0 { + ct := time.Now().UTC() + meta.Created = ct.Format(time.RFC3339) + } + + // Construct OCI repository client + opts := []ociclient.ClientOption{ + ociclient.WithUserAgent(oci.UserAgent), + ociclient.WithCredentials(flags.Credentials), + ociclient.WithInsecure(flags.InsecureRegistry), + } + client := ociclient.NewClient(opts...) + + opt := &PushModOptions{ + ModulePath: args[0], + OCIUrl: fullURL, + Latest: flags.Latest, + Sign: flags.Sign, + CosignKey: flags.CosignKey, + Client: client, + Metadata: meta, + IOStreams: ioStreams, + } + + return opt, nil +} + +// Validate verifies if PushModOptions are valid and without conflicts. +func (o *PushModOptions) Validate() error { + if fs, err := os.Stat(o.ModulePath); err != nil || !fs.IsDir() { + return fmt.Errorf("no module found at path %s", o.ModulePath) + } + + // TODO: add oci url validation + return nil +} + +// Run executes the `mod push` command. +func (o *PushModOptions) Run() error { + // First build executable binary via compilation + // Create temp module dir for later tar operation + tempModuleDir, err := os.MkdirTemp("", filepath.Base(o.ModulePath)) + if err != nil { + return err + } + defer os.RemoveAll(tempModuleDir) + + sp := &pretty.SpinnerT + sp, _ = sp.Start("building generator program") + defer func() { + _ = sp.Stop() + }() + + generatorSourceDir := filepath.Join(o.ModulePath, "generator") + output := filepath.Join(tempModuleDir, "generator") + _, err = buildGenerator(generatorSourceDir, output, o.IOStreams) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + // Copy to temp module dir and push artifact to OCI repository + err = copyWithoutGeneratorSrc(o.ModulePath, tempModuleDir) + if err != nil { + return err + } + + sp.Info("pushing module") + digest, err := o.Client.Push(ctx, o.OCIUrl, tempModuleDir, o.Metadata, nil) + if err != nil { + return err + } + + // Tag latest version if required + if o.Latest { + if err = o.Client.Tag(ctx, digest, LatestVersion); err != nil { + return fmt.Errorf("tagging module version as latest failed: %w", err) + } + } + _ = sp.Stop() + + // Signs the module with specific provider + if len(o.Sign) != 0 { + err = oci.SignArtifact(o.Sign, digest, o.CosignKey) + if err != nil { + return err + } + } + + return nil +} + +// This function takes a file target to specify where to compile to. +// If `outfile` is "", the binary is compiled to a new temporary file. +// This function returns the path of the file that was produced. +func buildGenerator(generatorDirectory string, outfile string, ioStreams genericiooptions.IOStreams) (string, error) { + goFileSearchPattern := filepath.Join(generatorDirectory, "*.go") + if matches, err := filepath.Glob(goFileSearchPattern); err != nil || len(matches) == 0 { + return "", fmt.Errorf("no go source code files found for 'go build' matching %s", goFileSearchPattern) + } + + if outfile == "" { + // If no outfile is supplied, write the Go binary to a temporary file. + f, err := os.CreateTemp("", "generator.*") + if err != nil { + return "", fmt.Errorf("unable to create go program temp file: %w", err) + } + + if err := f.Close(); err != nil { + return "", fmt.Errorf("unable to close go program temp file: %w", err) + } + outfile = f.Name() + } + + gobin, err := executable.FindExecutable("go") + if err != nil { + return "", fmt.Errorf("unable to find 'go' executable: %w", err) + } + buildCmd := exec.Command(gobin, "build", "-o", outfile) + buildCmd.Dir = generatorDirectory + buildCmd.Stdout, buildCmd.Stderr = ioStreams.Out, ioStreams.ErrOut + + if err := buildCmd.Run(); err != nil { + return "", fmt.Errorf("unable to run `go build`: %w", err) + } + + return outfile, nil +} + +// detectGitRepository detects existence of .git with target path. +func detectGitRepository(path string) (string, error) { + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + for { + st, err := os.Stat(filepath.Join(path, ".git")) + if err == nil && st.IsDir() { + break + } + old := path + path = filepath.Dir(path) + if old == path { + return "", fmt.Errorf("could not detect git repository root") + } + } + return path, nil +} + +// copyWithoutGeneratorSrc copies module contents except for generator source code to dest dir. +func copyWithoutGeneratorSrc(modulePath, dstDir string) error { + return filepath.WalkDir(modulePath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if path == modulePath || strings.Contains(path, "generator") { + return nil + } + srcStat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return nil + } else { + return err + } + } + + if !srcStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", path) + } + + source, err := os.Open(path) + if err != nil { + return err + } + defer source.Close() + + dst := filepath.Join(dstDir, srcStat.Name()) + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + + _, err = io.Copy(destination, source) + return err + }) +} diff --git a/pkg/modules/proto/module.pb.go b/pkg/modules/proto/module.pb.go index 29817981..29b0d00a 100644 --- a/pkg/modules/proto/module.pb.go +++ b/pkg/modules/proto/module.pb.go @@ -220,8 +220,8 @@ var file_module_proto_goTypes = []interface{}{ (*GeneratorResponse)(nil), // 1: GeneratorResponse } var file_module_proto_depIdxs = []int32{ - 0, // 0: Module.Generate:input_type -> GeneratorRequest - 1, // 1: Module.Generate:output_type -> GeneratorResponse + 0, // 0: ModulePath.Generate:input_type -> GeneratorRequest + 1, // 1: ModulePath.Generate:output_type -> GeneratorResponse 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name diff --git a/pkg/modules/proto/module_grpc.pb.go b/pkg/modules/proto/module_grpc.pb.go index d5a684ab..12ac311d 100644 --- a/pkg/modules/proto/module_grpc.pb.go +++ b/pkg/modules/proto/module_grpc.pb.go @@ -18,7 +18,7 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 -// ModuleClient is the client API for Module service. +// ModuleClient is the client API for ModulePath service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ModuleClient interface { @@ -35,14 +35,14 @@ func NewModuleClient(cc grpc.ClientConnInterface) ModuleClient { func (c *moduleClient) Generate(ctx context.Context, in *GeneratorRequest, opts ...grpc.CallOption) (*GeneratorResponse, error) { out := new(GeneratorResponse) - err := c.cc.Invoke(ctx, "/Module/Generate", in, out, opts...) + err := c.cc.Invoke(ctx, "/ModulePath/Generate", in, out, opts...) if err != nil { return nil, err } return out, nil } -// ModuleServer is the server API for Module service. +// ModuleServer is the server API for ModulePath service. // All implementations must embed UnimplementedModuleServer // for forward compatibility type ModuleServer interface { @@ -80,7 +80,7 @@ func _Module_Generate_Handler(srv interface{}, ctx context.Context, dec func(int } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/Module/Generate", + FullMethod: "/ModulePath/Generate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ModuleServer).Generate(ctx, req.(*GeneratorRequest)) @@ -88,11 +88,11 @@ func _Module_Generate_Handler(srv interface{}, ctx context.Context, dec func(int return interceptor(ctx, in, info, handler) } -// Module_ServiceDesc is the grpc.ServiceDesc for Module service. +// Module_ServiceDesc is the grpc.ServiceDesc for ModulePath service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Module_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "Module", + ServiceName: "ModulePath", HandlerType: (*ModuleServer)(nil), Methods: []grpc.MethodDesc{ { diff --git a/pkg/oci/client/client.go b/pkg/oci/client/client.go index 8d74ec4c..fa1e2321 100644 --- a/pkg/oci/client/client.go +++ b/pkg/oci/client/client.go @@ -3,17 +3,19 @@ package client import ( "context" "fmt" + "strings" + "github.com/google/go-containerregistry/pkg/authn" "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" + CanonicalConfigMediaType types.MediaType = "application/vnd.io.kusion.config.v1+json" // CanonicalMediaTypePrefix is the suffix for OCI media type for the content layer. - CanonicalMediaTypePrefix types.MediaType = "application/vnd.io.kusionstack.content.v1" + CanonicalMediaTypePrefix types.MediaType = "application/vnd.io.kusion.content.v1" // CanonicalContentMediaType is the OCI media type for the content layer. CanonicalContentMediaType = types.MediaType(fmt.Sprintf("%s.tar+gzip", CanonicalMediaTypePrefix)) @@ -34,6 +36,34 @@ func WithUserAgent(userAgent string) ClientOption { } } +// WithCredentials sets authenticator for remote operations. +func WithCredentials(credentials string) ClientOption { + return func(o *ClientOptions) { + if len(credentials) == 0 { + return + } + + var authConfig authn.AuthConfig + parts := strings.SplitN(credentials, ":", 2) + + if len(parts) == 1 { + authConfig = authn.AuthConfig{RegistryToken: parts[0]} + } else { + authConfig = authn.AuthConfig{Username: parts[0], Password: parts[1]} + } + o.craneOptions = append(o.craneOptions, crane.WithAuth(authn.FromConfig(authConfig))) + } +} + +// WithInsecure returns a ClientOption which allows image references to be fetched without TLS. +func WithInsecure(insecure bool) ClientOption { + return func(o *ClientOptions) { + if insecure { + o.craneOptions = append(o.craneOptions, crane.Insecure) + } + } +} + // Client provides methods to interact with OCI registry. type Client struct { opts *ClientOptions diff --git a/pkg/oci/client/push.go b/pkg/oci/client/push.go index 19b1f81a..98c66f3e 100644 --- a/pkg/oci/client/push.go +++ b/pkg/oci/client/push.go @@ -14,6 +14,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" + "kusionstack.io/kusion/pkg/oci" meta "kusionstack.io/kusion/pkg/oci/metadata" ) @@ -23,10 +24,10 @@ import ( // - 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) +func (c *Client) Push(ctx context.Context, ociURL, sourceDir string, metadata meta.Metadata, ignorePaths []string) (string, error) { + ref, err := oci.ParseArtifactRef(ociURL) if err != nil { - return "", err + return "", fmt.Errorf("invalid OCI repository url: %w", err) } tmpDir, err := os.MkdirTemp("", "oci") @@ -55,12 +56,17 @@ func (c *Client) Push(ctx context.Context, registryURL, sourceDir string, metada return "", fmt.Errorf("creating content layer failed: %w", err) } - image, err = mutate.Append(image, mutate.Addendum{Layer: layer}) + image, err = mutate.Append(image, mutate.Addendum{ + Layer: layer, + Annotations: map[string]string{ + meta.AnnotationTitle: "artifact", + }, + }) if err != nil { return "", fmt.Errorf("appeding content to artifact failed: %w", err) } - if err := crane.Push(image, registryURL, c.optionsWithContext(ctx)...); err != nil { + if err := crane.Push(image, ref.String(), c.optionsWithContext(ctx)...); err != nil { return "", fmt.Errorf("pushing artifact failed: %w", err) } @@ -69,5 +75,6 @@ func (c *Client) Push(ctx context.Context, registryURL, sourceDir string, metada return "", fmt.Errorf("parsing artifact digest failed: %w", err) } - return ref.Context().Digest(digest.String()).String(), err + digestURL := ref.Context().Digest(digest.String()).String() + return fmt.Sprintf("%s%s", oci.OCIRepositoryPrefix, digestURL), nil } diff --git a/pkg/oci/client/tag.go b/pkg/oci/client/tag.go new file mode 100644 index 00000000..f23b338a --- /dev/null +++ b/pkg/oci/client/tag.go @@ -0,0 +1,20 @@ +package client + +import ( + "context" + "fmt" + + "github.com/google/go-containerregistry/pkg/crane" + + "kusionstack.io/kusion/pkg/oci" +) + +// Tag creates a new tag for the given artifact using the same OCI repository as the origin. +func (c *Client) Tag(ctx context.Context, registryURL, tag string) error { + ref, err := oci.ParseArtifactRef(registryURL) + if err != nil { + return fmt.Errorf("invalid URL: %w", err) + } + + return crane.Tag(ref.String(), tag, c.optionsWithContext(ctx)...) +} diff --git a/pkg/oci/constants.go b/pkg/oci/constants.go new file mode 100644 index 00000000..311452e8 --- /dev/null +++ b/pkg/oci/constants.go @@ -0,0 +1,6 @@ +package oci + +const ( + // UserAgent string used for OCI calls. + UserAgent = "kusion" +) diff --git a/pkg/oci/metadata/metadata.go b/pkg/oci/metadata/metadata.go index ee56027c..094419dc 100644 --- a/pkg/oci/metadata/metadata.go +++ b/pkg/oci/metadata/metadata.go @@ -1,5 +1,10 @@ package metadata +import ( + "fmt" + "strings" +) + const ( // AnnotationSource is the OpenContainers annotation for specifying // the upstream source of an OCI artifact. @@ -12,17 +17,25 @@ const ( // 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" + + // AnnotationVersion is the OpenContainers annotation for specifying + // the semantic version of an artifact. + AnnotationVersion = "org.opencontainers.image.version" + + // AnnotationTitle is the OpenContainers annotation for specifying + // the human-readable title of an artifact. + AnnotationTitle = "org.opencontainers.image.title" ) // 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"` + Created string + Source string + Revision string + Digest string + URL string + Annotations map[string]string } // ToAnnotations returns the OpenContainers annotations map. @@ -49,3 +62,18 @@ func MetadataFromAnnotations(annotations map[string]string) *Metadata { Annotations: annotations, } } + +// ParseAnnotations parses the annotations string in key=value format +// and returns the OpenContainers annotations. +func ParseAnnotations(annotationsStr []string) (map[string]string, error) { + annotations := map[string]string{} + for _, annotation := range annotationsStr { + kv := strings.Split(annotation, "=") + if len(kv) != 2 { + return annotations, fmt.Errorf("invalid annotation %s, must be in the format key=value", annotation) + } + annotations[kv[0]] = kv[1] + } + + return annotations, nil +} diff --git a/pkg/oci/client/ref.go b/pkg/oci/reference.go similarity index 76% rename from pkg/oci/client/ref.go rename to pkg/oci/reference.go index 59602b79..3eac5c08 100644 --- a/pkg/oci/client/ref.go +++ b/pkg/oci/reference.go @@ -1,4 +1,4 @@ -package client +package oci import ( "fmt" @@ -10,9 +10,9 @@ import ( // 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) { +// 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:////'") } diff --git a/pkg/oci/sign_artifact.go b/pkg/oci/sign_artifact.go new file mode 100644 index 00000000..b939a873 --- /dev/null +++ b/pkg/oci/sign_artifact.go @@ -0,0 +1,84 @@ +package oci + +import ( + "bufio" + "fmt" + "io" + "os" + "os/exec" + + "kusionstack.io/kusion/pkg/log" +) + +// Original copy from timoni with minimal change. + +// SignArtifact signs an OpenContainers artifact using the specified provider. +func SignArtifact(provider, registryURL, keyRef string) error { + ref, err := ParseArtifactRef(registryURL) + if err != nil { + return err + } + + switch provider { + case "cosign": + if err := SignCosign(ref.String(), keyRef); err != nil { + return err + } + default: + return fmt.Errorf("signer not supported: %s", provider) + } + return nil +} + +// SignCosign signs an image (`imageRef`) using a cosign private key (`keyRef`) +func SignCosign(imageRef, keyRef string) error { + cosignExecutable, err := exec.LookPath("cosign") + if err != nil { + return fmt.Errorf("executing cosign failed: %w", err) + } + + cosignCmd := exec.Command(cosignExecutable, []string{"sign"}...) + cosignCmd.Env = os.Environ() + + // if key is empty, use keyless mode + if keyRef != "" { + cosignCmd.Args = append(cosignCmd.Args, "--key", keyRef) + } + + cosignCmd.Args = append(cosignCmd.Args, "--yes") + cosignCmd.Args = append(cosignCmd.Args, imageRef) + + err = processCosignIO(cosignCmd) + if err != nil { + return err + } + + return cosignCmd.Wait() +} + +func processCosignIO(cosignCmd *exec.Cmd) error { + stdout, err := cosignCmd.StdoutPipe() + if err != nil { + log.Error(err, "cosign stdout pipe failed") + } + stderr, err := cosignCmd.StderrPipe() + if err != nil { + log.Error(err, "cosign stderr pipe failed") + } + + merged := io.MultiReader(stdout, stderr) + scanner := bufio.NewScanner(merged) + + if err := cosignCmd.Start(); err != nil { + return fmt.Errorf("executing cosign failed: %w", err) + } + + for scanner.Scan() { + log.Info("cosign: " + scanner.Text()) + } + if err := scanner.Err(); err != nil { + log.Error(err, "cosign stdout/stderr scanner failed") + } + + return nil +} diff --git a/pkg/util/executable/executable.go b/pkg/util/executable/executable.go new file mode 100644 index 00000000..6e052931 --- /dev/null +++ b/pkg/util/executable/executable.go @@ -0,0 +1,69 @@ +package executable + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +const unableToFindExecutableProgram = "unable to find executable program: %s" + +// FindExecutable attempts to find the needed executable in various locations on the +// filesystem, eventually resorting to searching in $PATH. +func FindExecutable(program string) (string, error) { + if runtime.GOOS == "windows" && !strings.HasSuffix(program, ".exe") { + program += ".exe" + } + // look in the same directory + cwd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("unable to get current working directory: %w", err) + } + + cwdProgram := filepath.Join(cwd, program) + if fileInfo, err := os.Stat(cwdProgram); !os.IsNotExist(err) && !fileInfo.Mode().IsDir() { + return cwdProgram, nil + } + + // look in potentials $GOPATH/bin + if goPath := os.Getenv("GOPATH"); len(goPath) > 0 { + // splitGoPath will return a list of paths in which to look for the binary. + // Because the GOPATH can take the form of multiple paths (https://golang.org/cmd/go/#hdr-GOPATH_environment_variable) + // we need to split the GOPATH, and look into each of the paths. + // If the GOPATH hold only one path, there will only be one element in the slice. + pathParts := splitGoPath(goPath, runtime.GOOS) + for _, pp := range pathParts { + goProgramPath := filepath.Join(pp, "bin", program) + fileInfo, err := os.Stat(goProgramPath) + if err != nil && !os.IsNotExist(err) { + return "", err + } + + if fileInfo != nil && !fileInfo.Mode().IsDir() { + return goProgramPath, nil + } + } + } + + // look in the $PATH somewhere + if fullPath, err := exec.LookPath(program); err == nil { + return fullPath, nil + } + + return "", fmt.Errorf(unableToFindExecutableProgram, program) +} + +func splitGoPath(goPath string, os string) []string { + var sep string + switch os { + case "windows": + sep = ";" + case "linux", "darwin": + sep = ":" + } + + return strings.Split(goPath, sep) +} diff --git a/pkg/util/executable/executable_test.go b/pkg/util/executable/executable_test.go new file mode 100644 index 00000000..1735cd40 --- /dev/null +++ b/pkg/util/executable/executable_test.go @@ -0,0 +1,36 @@ +package executable + +import "testing" + +func TestSplitGoPathShouldReturnExpected(t *testing.T) { + t.Parallel() + + tt := []struct { + path string + os string + expected int + }{ + { + path: "/home/user/go:/usr/local/go", + os: "linux", + expected: 2, + }, + { + path: "C:/Users/User/Documents/go;C:/Workspace/go", + os: "windows", + expected: 2, + }, + { + path: "/home/user/go", + os: "linux", + expected: 1, + }, + } + + for _, test := range tt { + paths := splitGoPath(test.path, test.os) + if len(paths) != test.expected { + t.Errorf("expected path length to be %d, got %d", test.expected, len(paths)) + } + } +} diff --git a/pkg/util/gitutil/info.go b/pkg/util/gitutil/info.go new file mode 100644 index 00000000..316b7229 --- /dev/null +++ b/pkg/util/gitutil/info.go @@ -0,0 +1,45 @@ +package gitutil + +import ( + "context" + "os/exec" + "strconv" + "strings" + "time" +) + +// Info contains basic information of Git repository. +type Info struct { + RemoteURL string + Commit string + CommitDate string +} + +// Get returns the overall codebase version. +func Get(repoRoot string) (info Info) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + tsCmd := exec.CommandContext(ctx, "git", "--no-pager", "log", "-1", `--format=%ct`) + tsCmd.Dir = repoRoot + if ts, err := tsCmd.Output(); err == nil && len(ts) > 1 { + if i, err := strconv.ParseInt(strings.TrimSuffix(string(ts), "\n"), 10, 64); err == nil { + d := time.Unix(i, 0) + info.CommitDate = d.Format(time.RFC3339) + } + } + + urlCmd := exec.CommandContext(ctx, "git", "config", "--get", "remote.origin.url") + urlCmd.Dir = repoRoot + if repo, err := urlCmd.Output(); err == nil && len(repo) > 1 { + info.RemoteURL = strings.TrimSuffix(string(repo), "\n") + } + + shaCmd := exec.CommandContext(ctx, "git", "show", "-s", "--format=%H") + shaCmd.Dir = repoRoot + if commit, err := shaCmd.Output(); err == nil && len(commit) > 1 { + info.Commit = strings.TrimSuffix(string(commit), "\n") + } + + return info +} diff --git a/pkg/util/gitutil/gitutil.go b/pkg/util/gitutil/utils.go similarity index 100% rename from pkg/util/gitutil/gitutil.go rename to pkg/util/gitutil/utils.go diff --git a/pkg/util/gitutil/gitutil_test.go b/pkg/util/gitutil/utils_test.go similarity index 100% rename from pkg/util/gitutil/gitutil_test.go rename to pkg/util/gitutil/utils_test.go