From 91a7e653675be6eda765b283a83d4a3a796af84c Mon Sep 17 00:00:00 2001 From: Julien Castets Date: Tue, 22 Oct 2024 21:10:30 +0200 Subject: [PATCH] Add command "service unapplied-changes" --- CHANGES.md | 2 + docs/reference.md | 51 ++++++- go.mod | 12 +- go.sum | 33 ++++- pkg/koyeb/services.go | 9 ++ pkg/koyeb/services_show_unapplied_changes.go | 146 +++++++++++++++++++ 6 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 pkg/koyeb/services_show_unapplied_changes.go diff --git a/CHANGES.md b/CHANGES.md index 2cbdea3e..18c2de26 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ * Add the `--snapshot` flag to the volume creation command, to create a volume from a snapshot. - https://github.com/koyeb/koyeb-cli/pull/254/files - https://github.com/koyeb/koyeb-cli/pull/255/files +* Add command `koyeb service unapplied-changes ` to view the changes that have been made with `koyeb service update --save-only`. + - https://github.com/koyeb/koyeb-cli/pull/258 ## v5.1.0 (2024-09-26) diff --git a/docs/reference.md b/docs/reference.md index e725fc77..ee7f0c32 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -44,6 +44,7 @@ Koyeb CLI * [koyeb regional-deployments](#koyeb-regional-deployments) - Regional deployments * [koyeb secrets](#koyeb-secrets) - Secrets * [koyeb services](#koyeb-services) - Services +* [koyeb snapshots](#koyeb-snapshots) - Manage snapshots * [koyeb version](#koyeb-version) - Get version * [koyeb volumes](#koyeb-volumes) - Manage persistent volumes @@ -283,7 +284,7 @@ See examples of koyeb service create --help --checks-grace-period strings Set healthcheck grace period in seconds. Use the format =, for example --checks-grace-period 8080=10 - --deployment-strategy string Deployment strategy, either "rolling" (default), "canary", "blue-green" or "immediate". + --deployment-strategy STRATEGY Deployment strategy, either "rolling" (default), "blue-green" or "immediate". --docker string Docker image --docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times. --docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag. @@ -319,7 +320,7 @@ See examples of koyeb service create --help --privileged Whether the service container should run in privileged mode --regions strings Add a region where the service is deployed. You can specify this flag multiple times to deploy the service in multiple regions. To update a service and remove a region, prefix the region name with '!', for example --region '!par' - If the region is not specified on service creation, the service is deployed in fra + If the region is not specified on service creation, the service is deployed in was --routes strings Update service routes (available for services of type "web" only) using the format PATH[:PORT], for example '/foo:8080' PORT defaults to 8000 @@ -575,7 +576,7 @@ koyeb deploy / [flags] --checks-grace-period strings Set healthcheck grace period in seconds. Use the format =, for example --checks-grace-period 8080=10 - --deployment-strategy string Deployment strategy, either "rolling" (default), "canary", "blue-green" or "immediate". + --deployment-strategy STRATEGY Deployment strategy, either "rolling" (default), "blue-green" or "immediate". --env strings Update service environment variables using the format KEY=VALUE, for example --env FOO=bar To use the value of a secret as an environment variable, specify the secret name preceded by @, for example --env FOO=@bar To delete an environment variable, prefix its name with '!', for example --env '!FOO' @@ -591,7 +592,7 @@ koyeb deploy / [flags] --privileged Whether the service container should run in privileged mode --regions strings Add a region where the service is deployed. You can specify this flag multiple times to deploy the service in multiple regions. To update a service and remove a region, prefix the region name with '!', for example --region '!par' - If the region is not specified on service creation, the service is deployed in fra + If the region is not specified on service creation, the service is deployed in was --routes strings Update service routes (available for services of type "web" only) using the format PATH[:PORT], for example '/foo:8080' PORT defaults to 8000 @@ -1318,6 +1319,7 @@ Services * [koyeb services pause](#koyeb-services-pause) - Pause service * [koyeb services redeploy](#koyeb-services-redeploy) - Redeploy service * [koyeb services resume](#koyeb-services-resume) - Resume service +* [koyeb services unapplied-changes](#koyeb-services-unapplied-changes) - Show unapplied changes saved with the --save-only flag, which will be applied in the next deployment * [koyeb services update](#koyeb-services-update) - Update service ## koyeb services create @@ -1375,7 +1377,7 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp --checks-grace-period strings Set healthcheck grace period in seconds. Use the format =, for example --checks-grace-period 8080=10 - --deployment-strategy string Deployment strategy, either "rolling" (default), "canary", "blue-green" or "immediate". + --deployment-strategy STRATEGY Deployment strategy, either "rolling" (default), "blue-green" or "immediate". --docker string Docker image --docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times. --docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag. @@ -1411,7 +1413,7 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp --privileged Whether the service container should run in privileged mode --regions strings Add a region where the service is deployed. You can specify this flag multiple times to deploy the service in multiple regions. To update a service and remove a region, prefix the region name with '!', for example --region '!par' - If the region is not specified on service creation, the service is deployed in fra + If the region is not specified on service creation, the service is deployed in was --routes strings Update service routes (available for services of type "web" only) using the format PATH[:PORT], for example '/foo:8080' PORT defaults to 8000 @@ -1743,6 +1745,39 @@ koyeb services resume NAME [flags] +* [koyeb services](#koyeb-services) - Services + +## koyeb services unapplied-changes + +Show unapplied changes saved with the --save-only flag, which will be applied in the next deployment + +``` +koyeb services unapplied-changes SERVICE_NAME [flags] +``` + +### Options + +``` + -a, --app string Service application + -h, --help help for unapplied-changes +``` + +### Options inherited from parent commands + +``` + -c, --config string config file (default is $HOME/.koyeb.yaml) + -d, --debug enable the debug output + --debug-full do not hide sensitive information (tokens) in the debug output + --force-ascii only output ascii characters (no unicode emojis) + --full do not truncate output + --organization string organization ID + -o, --output output output format (yaml,json,table) + --token string API token + --url string url of the api (default "https://app.koyeb.com") +``` + + + * [koyeb services](#koyeb-services) - Services ## koyeb services update @@ -1795,7 +1830,7 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/' --checks-grace-period strings Set healthcheck grace period in seconds. Use the format =, for example --checks-grace-period 8080=10 - --deployment-strategy string Deployment strategy, either "rolling" (default), "canary", "blue-green" or "immediate". + --deployment-strategy STRATEGY Deployment strategy, either "rolling" (default), "blue-green" or "immediate". --docker string Docker image --docker-args strings Set arguments to the docker command. To provide multiple arguments, use the --docker-args flag multiple times. --docker-command string Set the docker CMD explicitly. To provide arguments to the command, use the --docker-args flag. @@ -1833,7 +1868,7 @@ $> koyeb service update myapp/myservice --port 80:tcp --route '!/' --privileged Whether the service container should run in privileged mode --regions strings Add a region where the service is deployed. You can specify this flag multiple times to deploy the service in multiple regions. To update a service and remove a region, prefix the region name with '!', for example --region '!par' - If the region is not specified on service creation, the service is deployed in fra + If the region is not specified on service creation, the service is deployed in was --routes strings Update service routes (available for services of type "web" only) using the format PATH[:PORT], for example '/foo:8080' PORT defaults to 8000 diff --git a/go.mod b/go.mod index 1b600b7b..1a0d8d6e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/koyeb/koyeb-cli -go 1.18 +go 1.21 + +toolchain go1.22.3 require ( github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de @@ -22,6 +24,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 + github.com/yudai/gojsondiff v1.0.0 golang.org/x/term v0.8.0 ) @@ -38,6 +41,8 @@ require ( github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -45,16 +50,19 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tcnksm/go-gitconfig v0.1.2 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index cba8b2be..408db6db 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -123,6 +124,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -152,6 +154,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= @@ -165,25 +168,28 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/koyeb/koyeb-api-client-go v0.0.0-20240626143115-aa41e51698e2 h1:5g98xW8nXOiZ6NPbJdbBXSA7ZCrrlfXhB0Ymw2sVZA0= -github.com/koyeb/koyeb-api-client-go v0.0.0-20240626143115-aa41e51698e2/go.mod h1:+oQfFj2WL3gi9Pb+UHbob4D7xaT52mPfKyH1UvWa4PQ= -github.com/koyeb/koyeb-api-client-go v0.0.0-20240820152208-34f63e4f1154 h1:kjRrGsSuKDr7eDCcsZwpCrQRQBEDrcyIlVUkZUalw50= -github.com/koyeb/koyeb-api-client-go v0.0.0-20240820152208-34f63e4f1154/go.mod h1:+oQfFj2WL3gi9Pb+UHbob4D7xaT52mPfKyH1UvWa4PQ= github.com/koyeb/koyeb-api-client-go v0.0.0-20240926125000-631dff710c71 h1:EXJWj7moiWNrt6OQNuM+lvlxRMvO2tsC52A7WT322hw= github.com/koyeb/koyeb-api-client-go v0.0.0-20240926125000-631dff710c71/go.mod h1:+oQfFj2WL3gi9Pb+UHbob4D7xaT52mPfKyH1UvWa4PQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= @@ -196,6 +202,7 @@ github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -218,9 +225,12 @@ github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= +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.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= @@ -252,6 +262,12 @@ github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4U github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -403,8 +419,10 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= @@ -569,12 +587,15 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/koyeb/services.go b/pkg/koyeb/services.go index 742c3205..19b9d6d0 100644 --- a/pkg/koyeb/services.go +++ b/pkg/koyeb/services.go @@ -76,6 +76,15 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp getServiceCmd.Flags().StringP("app", "a", "", "Service application") serviceCmd.AddCommand(getServiceCmd) + unappliedChangesCmd := &cobra.Command{ + Use: "unapplied-changes SERVICE_NAME", + Short: "Show unapplied changes saved with the --save-only flag, which will be applied in the next deployment", + Args: cobra.ExactArgs(1), + RunE: WithCLIContext(h.ShowUnappliedChanges), + } + unappliedChangesCmd.Flags().StringP("app", "a", "", "Service application") + serviceCmd.AddCommand(unappliedChangesCmd) + var since dates.HumanFriendlyDate logsServiceCmd := &cobra.Command{ Use: "logs NAME", diff --git a/pkg/koyeb/services_show_unapplied_changes.go b/pkg/koyeb/services_show_unapplied_changes.go new file mode 100644 index 00000000..b15e83a7 --- /dev/null +++ b/pkg/koyeb/services_show_unapplied_changes.go @@ -0,0 +1,146 @@ +package koyeb + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/koyeb/koyeb-api-client-go/api/v1/koyeb" + "github.com/koyeb/koyeb-cli/pkg/koyeb/errors" + "github.com/spf13/cobra" + + "github.com/yudai/gojsondiff" + "github.com/yudai/gojsondiff/formatter" +) + +func (h *ServiceHandler) ShowUnappliedChanges(ctx *CLIContext, cmd *cobra.Command, args []string) error { + serviceName, err := h.parseServiceName(cmd, args[0]) + if err != nil { + return err + } + + service, err := h.ResolveServiceArgs(ctx, serviceName) + if err != nil { + return err + } + + var stashedDeployment *koyeb.DeploymentListItem + var previousNonStashedDeployment *koyeb.DeploymentListItem + + page := int64(0) + offset := int64(0) + limit := int64(100) + // To display unapplied changes, we need to compare the last deployment of + // the service (which needs to be stashed0 with the next non-stashed + // deployment. + for { + res, resp, err := ctx.Client.DeploymentsApi. + ListDeployments(ctx.Context). + ServiceId(service). + Limit(strconv.FormatInt(limit, 10)). + Offset(strconv.FormatInt(offset, 10)). + Execute() + if err != nil { + return errors.NewCLIErrorFromAPIError( + fmt.Sprintf("Error while listing the deployments of the service `%s`", serviceName), + err, + resp, + ) + } + + // After the first iteration, stashedDeployment is set so we don't enter this condition again + if stashedDeployment == nil { + if len(res.Deployments) == 0 { + return &errors.CLIError{ + What: "Unable to show unapplied changes", + Why: "we couldn't find the latest deployment of your service", + Additional: []string{}, + Orig: nil, + Solution: "Use `koyeb service update` to update your service", + } + } + + // Last deployment is not stashed, render an empty diff + if res.Deployments[0].GetStatus() != koyeb.DEPLOYMENTSTATUS_STASHED { + // xx := NewShowDeploymentsDiff(jsondiff.Patch{}) + // ctx.Renderer.Render(xx) + return nil + } + stashedDeployment = &res.Deployments[0] + } + + for _, deployment := range res.Deployments { + if deployment.GetStatus() != koyeb.DEPLOYMENTSTATUS_STASHED { + previousNonStashedDeployment = &deployment + break + } + } + + page++ + offset = page * limit + if offset >= res.GetCount() { + break + } + } + + lhs, _ := json.Marshal(previousNonStashedDeployment.GetDefinition()) + rhs, _ := json.Marshal(stashedDeployment.GetDefinition()) + + diff, err := gojsondiff.New().Compare(lhs, rhs) + if err != nil { + return &errors.CLIError{ + What: "Unable to show unapplied changes", + Why: "unable to create the JSON diff", + Additional: []string{}, + Orig: err, + Solution: "Please, create an issue on https://github.com/koyeb/koyeb-cli/issues/new and provide your service ID", + } + } + + showUnappliedChangesReply := NewShowDeploymentsDiff(diff, lhs) + ctx.Renderer.Render(showUnappliedChangesReply) + return nil +} + +type ShowDeploymentsDiff struct { + diff gojsondiff.Diff + lhs []byte +} + +func NewShowDeploymentsDiff(diff gojsondiff.Diff, lhs []byte) *ShowDeploymentsDiff { + return &ShowDeploymentsDiff{diff, lhs} +} + +func (ShowDeploymentsDiff) Title() string { + return "Unapplied changes" +} + +func (r *ShowDeploymentsDiff) MarshalBinary() ([]byte, error) { + formatter := formatter.NewDeltaFormatter() + diffString, _ := formatter.Format(r.diff) + return []byte(diffString), nil +} + +func (r *ShowDeploymentsDiff) Headers() []string { + return []string{"diff"} +} + +func (r *ShowDeploymentsDiff) Fields() []map[string]string { + config := formatter.AsciiFormatterConfig{ + ShowArrayIndex: true, + Coloring: true, + } + + var aJson map[string]interface{} + _ = json.Unmarshal(r.lhs, &aJson) + + formatter := formatter.NewAsciiFormatter(aJson, config) + diffString, _ := formatter.Format(r.diff) + + fields := map[string]string{ + "diff": diffString, + } + + resp := []map[string]string{fields} + return resp +}