From 69bb3177441b7d4d9e2928e564a61e36343e0a7c Mon Sep 17 00:00:00 2001 From: Ben Pearce Date: Tue, 23 May 2023 10:52:17 +1000 Subject: [PATCH] feat: tenant variables list (#225) feat: tenant variable update --- examples.md | 14 + go.mod | 18 +- go.sum | 52 +- pkg/cmd/project/variables/create/create.go | 17 +- pkg/cmd/project/variables/exclude/exclude.go | 2 +- pkg/cmd/project/variables/include/include.go | 2 +- pkg/cmd/project/variables/list/list.go | 1 - pkg/cmd/project/variables/shared/input.go | 169 ------ pkg/cmd/project/variables/update/update.go | 15 +- pkg/cmd/tenant/shared/shared.go | 12 + pkg/cmd/tenant/tenant.go | 4 +- pkg/cmd/tenant/variables/list/list.go | 261 ++++++++ pkg/cmd/tenant/variables/update/update.go | 557 ++++++++++++++++++ .../tenant/variables/update/update_test.go | 214 +++++++ pkg/cmd/tenant/variables/variables.go | 38 ++ pkg/question/selectors/selectors.go | 7 + pkg/question/shared/variables/variables.go | 229 +++++++ 17 files changed, 1381 insertions(+), 231 deletions(-) create mode 100644 pkg/cmd/tenant/variables/list/list.go create mode 100644 pkg/cmd/tenant/variables/update/update.go create mode 100644 pkg/cmd/tenant/variables/update/update_test.go create mode 100644 pkg/cmd/tenant/variables/variables.go create mode 100644 pkg/question/shared/variables/variables.go diff --git a/examples.md b/examples.md index 26e226db..4bf5872f 100644 --- a/examples.md +++ b/examples.md @@ -59,6 +59,20 @@ octopus tenant list -f json | jq --raw-output '.[] | select (.TenantTags[]? | co done ``` +# Creating a new tenant, linked to an existing project with variables + +``` +name='Mountain Vet Clinic' +abrev='mtn' +octopus tenant create --name "$name" --no-prompt +octopus tenant connect --tenant "$name" --project "Vet Clinic" --environment Staging --environment Production --no-prompt +octopus tenant variable update --tenant "$name" --library-variable-set "Tenant shared" --name "Tenant.Abbreviation" --value "$abrev" --no-prompt +octopus tenant variable update --tenant "$name" --project "Vet Clinic" --name "Tenant.Database.Name" --environment "Staging" --value "Staging$abrev" --no-prompt +octopus tenant variable update --tenant "$name" --project "Vet Clinic" --name "Tenant.Database.Name" --environment "Production" --value "$abrev" --no-prompt +octopus tenant variable update --tenant "$name" --project "Vet Clinic" --name "Tenant.Azure.ServicePlan.Sku.Code" --environment "Staging" --value "B1" --no-prompt +octopus tenant variable update --tenant "$name" --project "Vet Clinic" --name "Tenant.Azure.ServicePlan.Sku.Code" --environment "Production" --value "B4ms" --no-prompt +``` + # List all versions of all packages ``` diff --git a/go.mod b/go.mod index b47078c1..9d4a1742 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/AlecAivazis/survey/v2 v2.3.6 github.com/MakeNowJust/heredoc/v2 v2.0.1 - github.com/OctopusDeploy/go-octopusdeploy/v2 v2.20.0 + github.com/OctopusDeploy/go-octopusdeploy/v2 v2.25.0 github.com/bmatcuk/doublestar/v4 v4.4.0 github.com/briandowns/spinner v1.19.0 github.com/google/uuid v1.3.0 @@ -19,8 +19,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 - golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 - golang.org/x/term v0.3.0 + golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 + golang.org/x/term v0.4.0 ) require ( @@ -29,9 +29,9 @@ require ( github.com/dghubble/sling v1.4.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -51,9 +51,9 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - golang.org/x/crypto v0.4.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/crypto v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 16c27a47..08a04c22 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZ github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/OctopusDeploy/go-octopusdeploy/v2 v2.20.0 h1:3IpsaL5PJepA/5WEhUBn7GmXS2uT4GZ0n5N/iQV6JhQ= -github.com/OctopusDeploy/go-octopusdeploy/v2 v2.20.0/go.mod h1:++gnwUI5HaG31oDMaUXrNNjsXfmLn/zWyMmy/5GaFSA= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.25.0 h1:V/H9B3gg1cl5RKnD8tBBcbAH5YPZG2geFsjc9Wzs9Ns= +github.com/OctopusDeploy/go-octopusdeploy/v2 v2.25.0/go.mod h1:GZmFu6LmN8Yg0tEoZx3ytk9FnaH+84cWm7u5TdWZC6E= github.com/bmatcuk/doublestar/v4 v4.4.0 h1:LmAwNwhjEbYtyVLzjcP/XeVw4nhuScHGkF/XWXnvIic= github.com/bmatcuk/doublestar/v4 v4.4.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E= @@ -60,7 +60,6 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 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= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -83,14 +82,13 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -176,13 +174,10 @@ github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJIS github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -212,7 +207,6 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -223,9 +217,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/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/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 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/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= @@ -271,9 +263,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -284,8 +275,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w= -golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -340,7 +331,6 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -394,31 +384,27 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -564,7 +550,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba 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-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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -572,7 +557,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/cmd/project/variables/create/create.go b/pkg/cmd/project/variables/create/create.go index e64b1899..c42b0374 100644 --- a/pkg/cmd/project/variables/create/create.go +++ b/pkg/cmd/project/variables/create/create.go @@ -5,12 +5,13 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/cmd" - sharedVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" + sharedProjectVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" "github.com/OctopusDeploy/cli/pkg/cmd/tenant/shared" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/factory" "github.com/OctopusDeploy/cli/pkg/question" "github.com/OctopusDeploy/cli/pkg/question/selectors" + sharedVariable "github.com/OctopusDeploy/cli/pkg/question/shared/variables" "github.com/OctopusDeploy/cli/pkg/util" "github.com/OctopusDeploy/cli/pkg/util/flag" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" @@ -54,7 +55,7 @@ type CreateFlags struct { Value *flag.Flag[string] Type *flag.Flag[string] - *sharedVariable.ScopeFlags + *sharedProjectVariable.ScopeFlags IsPrompted *flag.Flag[bool] PromptLabel *flag.Flag[string] @@ -79,7 +80,7 @@ func NewCreateFlags() *CreateFlags { Value: flag.New[string](FlagValue, false), Description: flag.New[string](FlagDescription, false), Type: flag.New[string](FlagType, false), - ScopeFlags: sharedVariable.NewScopeFlags(), + ScopeFlags: sharedProjectVariable.NewScopeFlags(), IsPrompted: flag.New[bool](FlagPrompt, false), PromptLabel: flag.New[string](FlagPromptLabel, false), PromptDescription: flag.New[string](FlagPromptDescription, false), @@ -130,7 +131,7 @@ func NewCreateCmd(f factory.Factory) *cobra.Command { flags.StringVarP(&createFlags.Type.Value, createFlags.Type.Name, "t", "", fmt.Sprintf("The type of variable. Valid values are %s. Default is %s", strings.Join([]string{TypeText, TypeSensitive, TypeWorkerPool, TypeAwsAccount, TypeAzureAccount, TypeGoogleAccount, TypeCertificate}, ", "), TypeText)) flags.StringVar(&createFlags.Value.Value, createFlags.Value.Name, "", "The value to set on the variable") - sharedVariable.RegisterScopeFlags(cmd, createFlags.ScopeFlags) + sharedProjectVariable.RegisterScopeFlags(cmd, createFlags.ScopeFlags) flags.BoolVar(&createFlags.IsPrompted.Value, createFlags.IsPrompted.Name, false, "Make a prompted variable") flags.StringVar(&createFlags.PromptLabel.Value, createFlags.PromptLabel.Name, "", "The label for the prompted variable") flags.StringVar(&createFlags.PromptDescription.Value, createFlags.PromptDescription.Name, "", "Description for the prompted variable") @@ -158,7 +159,7 @@ func createRun(opts *CreateOptions) error { return err } - scope, err := sharedVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) + scope, err := sharedProjectVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) if err != nil { return err } @@ -309,7 +310,7 @@ func PromptMissing(opts *CreateOptions) error { if err != nil { return err } - opts.Value.Value, err = sharedVariable.PromptValue(opts.Ask, variableType, opts.VariableCallbacks) + opts.Value.Value, err = sharedVariable.PromptValue(opts.Ask, sharedVariable.VariableType(variableType), opts.VariableCallbacks, nil) if err != nil { return err } @@ -320,13 +321,13 @@ func PromptMissing(opts *CreateOptions) error { return err } - scope, err := sharedVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) + scope, err := sharedProjectVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) if err != nil { return err } if scope.IsEmpty() { - err = sharedVariable.PromptScopes(opts.Ask, projectVariables, opts.ScopeFlags, opts.IsPrompted.Value) + err = sharedProjectVariable.PromptScopes(opts.Ask, projectVariables, opts.ScopeFlags, opts.IsPrompted.Value) if err != nil { return err } diff --git a/pkg/cmd/project/variables/exclude/exclude.go b/pkg/cmd/project/variables/exclude/exclude.go index 9abe25f3..b3dbb982 100644 --- a/pkg/cmd/project/variables/exclude/exclude.go +++ b/pkg/cmd/project/variables/exclude/exclude.go @@ -4,12 +4,12 @@ import ( "fmt" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/cmd" - sharedVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" "github.com/OctopusDeploy/cli/pkg/cmd/tenant/shared" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/factory" "github.com/OctopusDeploy/cli/pkg/output" "github.com/OctopusDeploy/cli/pkg/question" + sharedVariable "github.com/OctopusDeploy/cli/pkg/question/shared/variables" "github.com/OctopusDeploy/cli/pkg/util" "github.com/OctopusDeploy/cli/pkg/util/flag" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" diff --git a/pkg/cmd/project/variables/include/include.go b/pkg/cmd/project/variables/include/include.go index 304ef088..110e2bc0 100644 --- a/pkg/cmd/project/variables/include/include.go +++ b/pkg/cmd/project/variables/include/include.go @@ -4,12 +4,12 @@ import ( "fmt" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/cmd" - sharedVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" "github.com/OctopusDeploy/cli/pkg/cmd/tenant/shared" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/factory" "github.com/OctopusDeploy/cli/pkg/output" "github.com/OctopusDeploy/cli/pkg/question" + sharedVariable "github.com/OctopusDeploy/cli/pkg/question/shared/variables" "github.com/OctopusDeploy/cli/pkg/util" "github.com/OctopusDeploy/cli/pkg/util/flag" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" diff --git a/pkg/cmd/project/variables/list/list.go b/pkg/cmd/project/variables/list/list.go index e4a7fbd2..2e6d5787 100644 --- a/pkg/cmd/project/variables/list/list.go +++ b/pkg/cmd/project/variables/list/list.go @@ -81,7 +81,6 @@ func listRun(cmd *cobra.Command, f factory.Factory, id string) error { return v.Name }, }) - } func getValue(v *variables.Variable) string { diff --git a/pkg/cmd/project/variables/shared/input.go b/pkg/cmd/project/variables/shared/input.go index 8712a75d..a942febe 100644 --- a/pkg/cmd/project/variables/shared/input.go +++ b/pkg/cmd/project/variables/shared/input.go @@ -3,118 +3,12 @@ package shared import ( "fmt" "github.com/AlecAivazis/survey/v2" - "github.com/OctopusDeploy/cli/pkg/cmd" "github.com/OctopusDeploy/cli/pkg/question" - "github.com/OctopusDeploy/cli/pkg/question/selectors" "github.com/OctopusDeploy/cli/pkg/util" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/certificates" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/resources" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" ) -type GetAccountsByTypeCallback func(accountType accounts.AccountType) ([]accounts.IAccount, error) -type GetAllWorkerPoolsCallback func() ([]*workerpools.WorkerPoolListResult, error) -type GetAllCertificatesCallback func() ([]*certificates.CertificateResource, error) -type GetProjectVariablesCallback func(projectId string) (*variables.VariableSet, error) -type GetVariableByIdCallback func(ownerId, variableId string) (*variables.Variable, error) -type GetAllLibraryVariableSetsCallback func() ([]*variables.LibraryVariableSet, error) - -type VariableCallbacks struct { - GetAccountsByType GetAccountsByTypeCallback - GetAllWorkerPools GetAllWorkerPoolsCallback - GetAllCertificates GetAllCertificatesCallback - GetProjectVariables GetProjectVariablesCallback - GetVariableById GetVariableByIdCallback -} - -func NewVariableCallbacks(dependencies *cmd.Dependencies) *VariableCallbacks { - return &VariableCallbacks{ - GetAccountsByType: func(accountType accounts.AccountType) ([]accounts.IAccount, error) { - return getAccountsByType(dependencies.Client, accountType) - }, - GetAllWorkerPools: func() ([]*workerpools.WorkerPoolListResult, error) { - return getAllWorkerPools(dependencies.Client) - }, - GetAllCertificates: func() ([]*certificates.CertificateResource, error) { - return getAllCertificates(dependencies.Client) - }, - GetProjectVariables: func(projectId string) (*variables.VariableSet, error) { - return getProjectVariables(dependencies.Client, projectId) - }, - GetVariableById: func(ownerId, variableId string) (*variables.Variable, error) { - return getVariableById(dependencies.Client, ownerId, variableId) - }, - } -} - -func PromptValue(ask question.Asker, variableType string, callbacks *VariableCallbacks) (string, error) { - var value string - switch variableType { - case "String": - if err := ask(&survey.Input{ - Message: "Value", - }, &value); err != nil { - return "", err - } - return value, nil - case "Sensitive": - if err := ask(&survey.Password{ - Message: "Value", - }, &value); err != nil { - return "", err - } - return value, nil - case "AmazonWebServicesAccount", "AzureAccount", "GoogleCloudAccount": - accountType, err := mapVariableTypeToAccountType(variableType) - if err != nil { - return "", err - } - accountsByType, err := callbacks.GetAccountsByType(accountType) - if err != nil { - return "", err - } - - selectedValue, err := selectors.ByName(ask, accountsByType, "Value") - if err != nil { - return "", err - } - return selectedValue.GetName(), nil - case "WorkerPool": - workerPools, err := callbacks.GetAllWorkerPools() - if err != nil { - return "", err - } - selectedValue, err := selectors.Select( - ask, - "Value", - func() ([]*workerpools.WorkerPoolListResult, error) { return workerPools, nil }, - func(item *workerpools.WorkerPoolListResult) string { return item.Name }) - if err != nil { - return "", err - } - return selectedValue.Name, nil - case "Certificate": - allCerts, err := callbacks.GetAllCertificates() - if err != nil { - return "", err - } - selectedValue, err := selectors.Select( - ask, - "Value", - func() ([]*certificates.CertificateResource, error) { return allCerts, nil }, - func(item *certificates.CertificateResource) string { return item.Name }) - if err != nil { - return "", err - } - return selectedValue.Name, nil - } - - return "", fmt.Errorf("error getting value") -} - func PromptScopes(asker question.Asker, projectVariables *variables.VariableSet, flags *ScopeFlags, isPrompted bool) error { var err error if util.Empty(flags.EnvironmentsScopes.Value) { @@ -178,66 +72,3 @@ func PromptScope(ask question.Asker, scopeDescription string, items []*resources return selectedItems, nil } - -func mapVariableTypeToAccountType(variableType string) (accounts.AccountType, error) { - switch variableType { - case "AmazonWebServicesAccount": - return accounts.AccountTypeAmazonWebServicesAccount, nil - case "AzureAccount": - return accounts.AccountTypeAzureServicePrincipal, nil - case "GoogleCloudAccount": - return accounts.AccountTypeGoogleCloudPlatformAccount, nil - default: - return accounts.AccountTypeNone, fmt.Errorf("variable type '%s' is not a valid account variable type", variableType) - - } -} - -func getAccountsByType(client *client.Client, accountType accounts.AccountType) ([]accounts.IAccount, error) { - accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ - AccountType: accountType, - }) - if err != nil { - return nil, err - } - items, err := accountResources.GetAllPages(client.Accounts.GetClient()) - if err != nil { - return nil, err - } - return items, nil -} - -func getAllCertificates(client *client.Client) ([]*certificates.CertificateResource, error) { - certs, err := client.Certificates.Get(certificates.CertificatesQuery{}) - if err != nil { - return nil, err - } - return certs.GetAllPages(client.Sling()) -} - -func getAllWorkerPools(client *client.Client) ([]*workerpools.WorkerPoolListResult, error) { - res, err := client.WorkerPools.GetAll() - if err != nil { - return nil, err - } - - return res, nil -} - -func getProjectVariables(client *client.Client, id string) (*variables.VariableSet, error) { - variableSet, err := client.Variables.GetAll(id) - return &variableSet, err -} - -func getVariableById(client *client.Client, ownerId string, variableId string) (*variables.Variable, error) { - return client.Variables.GetByID(ownerId, variableId) -} - -func GetAllLibraryVariableSets(client *client.Client) ([]*variables.LibraryVariableSet, error) { - res, err := client.LibraryVariableSets.GetAll() - if err != nil { - return nil, err - } - - return util.SliceFilter(res, func(item *variables.LibraryVariableSet) bool { return item.ContentType == "Variables" }), nil -} diff --git a/pkg/cmd/project/variables/update/update.go b/pkg/cmd/project/variables/update/update.go index 8e6c01c0..c8cefd83 100644 --- a/pkg/cmd/project/variables/update/update.go +++ b/pkg/cmd/project/variables/update/update.go @@ -5,13 +5,14 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/cmd" - sharedVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" + sharedProjectVariable "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" "github.com/OctopusDeploy/cli/pkg/cmd/tenant/shared" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/factory" "github.com/OctopusDeploy/cli/pkg/output" "github.com/OctopusDeploy/cli/pkg/question" "github.com/OctopusDeploy/cli/pkg/question/selectors" + sharedVariable "github.com/OctopusDeploy/cli/pkg/question/shared/variables" "github.com/OctopusDeploy/cli/pkg/util" "github.com/OctopusDeploy/cli/pkg/util/flag" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" @@ -35,7 +36,7 @@ type UpdateFlags struct { Value *flag.Flag[string] Unscoped *flag.Flag[bool] - *sharedVariable.ScopeFlags + *sharedProjectVariable.ScopeFlags } type UpdateOptions struct { @@ -53,7 +54,7 @@ func NewUpdateFlags() *UpdateFlags { Name: flag.New[string](FlagName, false), Value: flag.New[string](FlagValue, false), Unscoped: flag.New[bool](FlagUnscoped, false), - ScopeFlags: sharedVariable.NewScopeFlags(), + ScopeFlags: sharedProjectVariable.NewScopeFlags(), } } @@ -95,7 +96,7 @@ func NewUpdateCmd(f factory.Factory) *cobra.Command { flags.StringVarP(&updateFlags.Name.Value, updateFlags.Name.Name, "n", "", "The name of the variable") flags.StringVar(&updateFlags.Value.Value, updateFlags.Value.Name, "", "The value to set on the variable") flags.BoolVar(&updateFlags.Unscoped.Value, updateFlags.Unscoped.Name, false, "Remove all shared from the variable, cannot be used with shared") - sharedVariable.RegisterScopeFlags(cmd, updateFlags.ScopeFlags) + sharedProjectVariable.RegisterScopeFlags(cmd, updateFlags.ScopeFlags) return cmd } @@ -131,7 +132,7 @@ func updateRun(opts *UpdateOptions) error { opts.Value.Secure = true } - updatedScope, err := sharedVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) + updatedScope, err := sharedProjectVariable.ToVariableScope(projectVariables, opts.ScopeFlags, project) if err != nil { return err } @@ -237,7 +238,7 @@ func PromptMissing(opts *UpdateOptions) error { }, &updateValue) if updateValue { - opts.Value.Value, err = sharedVariable.PromptValue(opts.Ask, variable.Type, opts.VariableCallbacks) + opts.Value.Value, err = sharedVariable.PromptValue(opts.Ask, sharedVariable.VariableType(variable.Type), opts.VariableCallbacks, nil) if err != nil { return err } @@ -253,7 +254,7 @@ func PromptMissing(opts *UpdateOptions) error { case "unscope": opts.Unscoped.Value = true case "replace": - sharedVariable.PromptScopes(opts.Ask, projectVariables, opts.ScopeFlags, variable.Prompt != nil) + sharedProjectVariable.PromptScopes(opts.Ask, projectVariables, opts.ScopeFlags, variable.Prompt != nil) } } diff --git a/pkg/cmd/tenant/shared/shared.go b/pkg/cmd/tenant/shared/shared.go index 8cc8661b..fa4d873b 100644 --- a/pkg/cmd/tenant/shared/shared.go +++ b/pkg/cmd/tenant/shared/shared.go @@ -1,12 +1,14 @@ package shared import ( + "github.com/OctopusDeploy/cli/pkg/util" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/teams" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/users" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" ) type GetAllSpacesCallback func() ([]*spaces.Space, error) @@ -17,6 +19,7 @@ type GetTenantCallback func(identifier string) (*tenants.Tenant, error) type GetAllProjectsCallback func() ([]*projects.Project, error) type GetProjectCallback func(identifier string) (*projects.Project, error) type GetProjectProgression func(project *projects.Project) (*projects.Progression, error) +type GetAllLibraryVariableSetsCallback func() ([]*variables.LibraryVariableSet, error) func GetAllTeams(client client.Client) ([]*teams.Team, error) { res, err := client.Teams.Get(teams.TeamsQuery{IncludeSystem: true}) @@ -79,3 +82,12 @@ func GetProject(client *client.Client, identifier string) (*projects.Project, er return res, nil } + +func GetAllLibraryVariableSets(client *client.Client) ([]*variables.LibraryVariableSet, error) { + res, err := client.LibraryVariableSets.GetAll() + if err != nil { + return nil, err + } + + return util.SliceFilter(res, func(item *variables.LibraryVariableSet) bool { return item.ContentType == "Variables" }), nil +} diff --git a/pkg/cmd/tenant/tenant.go b/pkg/cmd/tenant/tenant.go index acd1b5b0..7bc025bc 100644 --- a/pkg/cmd/tenant/tenant.go +++ b/pkg/cmd/tenant/tenant.go @@ -2,13 +2,14 @@ package tenant import ( "github.com/MakeNowJust/heredoc/v2" + cmdClone "github.com/OctopusDeploy/cli/pkg/cmd/tenant/clone" cmdConnect "github.com/OctopusDeploy/cli/pkg/cmd/tenant/connect" cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/tenant/create" cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/tenant/delete" cmdDisconnect "github.com/OctopusDeploy/cli/pkg/cmd/tenant/disconnect" cmdList "github.com/OctopusDeploy/cli/pkg/cmd/tenant/list" cmdTag "github.com/OctopusDeploy/cli/pkg/cmd/tenant/tag" - cmdClone "github.com/OctopusDeploy/cli/pkg/cmd/tenant/clone" + cmdVariable "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables" cmdView "github.com/OctopusDeploy/cli/pkg/cmd/tenant/view" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/constants/annotations" @@ -38,6 +39,7 @@ func NewCmdTenant(f factory.Factory) *cobra.Command { cmd.AddCommand(cmdClone.NewCmdClone(f)) cmd.AddCommand(cmdDelete.NewCmdDelete(f)) cmd.AddCommand(cmdView.NewCmdView(f)) + cmd.AddCommand(cmdVariable.NewCmdVariables(f)) return cmd } diff --git a/pkg/cmd/tenant/variables/list/list.go b/pkg/cmd/tenant/variables/list/list.go new file mode 100644 index 00000000..8c92dd8b --- /dev/null +++ b/pkg/cmd/tenant/variables/list/list.go @@ -0,0 +1,261 @@ +package list + +import ( + "fmt" + "github.com/MakeNowJust/heredoc/v2" + "github.com/OctopusDeploy/cli/pkg/apiclient" + "github.com/OctopusDeploy/cli/pkg/constants" + "github.com/OctopusDeploy/cli/pkg/factory" + "github.com/OctopusDeploy/cli/pkg/output" + "github.com/OctopusDeploy/cli/pkg/question/selectors" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" + "github.com/spf13/cobra" +) + +const ( + LibraryVariableSetType = "Library" + ProjectType = "Project" +) + +type VariableValue struct { + Type string + OwnerName string + Name string + Value string + Label string + IsSensitive bool + IsDefaultValue bool + ScopeName string + HasMissingValue bool +} + +type VariableValueAsJson struct { + Type string + OwnerName string + Name string + Value string + Label string + IsSensitive bool + IsDefaultValue bool +} + +type VariableValueProjectAsJson struct { + *VariableValueAsJson + HasMissingValue bool + Environment string +} + +func NewVariableValueAsJson(v *VariableValue) *VariableValueAsJson { + return &VariableValueAsJson{ + Type: v.Type, + OwnerName: v.OwnerName, + Name: v.Name, + Value: v.Value, + IsSensitive: v.IsSensitive, + IsDefaultValue: v.IsDefaultValue, + } +} + +func NewVariableValueProjectAsJson(v *VariableValue) *VariableValueProjectAsJson { + return &VariableValueProjectAsJson{ + VariableValueAsJson: NewVariableValueAsJson(v), + Environment: v.ScopeName, + HasMissingValue: v.HasMissingValue, + } +} + +func NewCmdList(f factory.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List tenant variables", + Long: "List tenant variables in Octopus Deploy", + Example: heredoc.Docf(` + $ %[1]s tenant variable list + $ %[1]s tenant variable ls + `, constants.ExecutableName), + Aliases: []string{"ls"}, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("must supply tenant identifier") + } + return listRun(cmd, f, args[0]) + }, + } + + return cmd +} + +func listRun(cmd *cobra.Command, f factory.Factory, id string) error { + client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) + if err != nil { + return err + } + + tenant, err := client.Tenants.GetByIdentifier(id) + if err != nil { + return err + } + + vars, err := client.Tenants.GetVariables(tenant) + if err != nil { + return err + } + + missingVariablesResponse, err := client.Tenants.GetMissingVariables(variables.MissingVariablesQuery{TenantID: vars.TenantID}) + var missingVariables []variables.MissingVariable + if len(*missingVariablesResponse) > 0 { + missingVariables = (*missingVariablesResponse)[0].MissingVariables + } + var allVariableValues []*VariableValue + for _, element := range vars.LibraryVariables { + + variableValues := unwrapCommonVariables(element, missingVariables) + for _, v := range variableValues { + allVariableValues = append(allVariableValues, v) + } + } + + if err != nil { + return err + } + environmentMap, err := getEnvironmentMap(client) + if err != nil { + return err + } + for _, element := range vars.ProjectVariables { + variableValues := unwrapProjectVariables(element, environmentMap, missingVariables) + for _, v := range variableValues { + allVariableValues = append(allVariableValues, v) + } + } + + return output.PrintArray(allVariableValues, cmd, output.Mappers[*VariableValue]{ + Json: func(item *VariableValue) any { + if item.Type == LibraryVariableSetType { + return NewVariableValueAsJson(item) + } else { + return NewVariableValueProjectAsJson(item) + } + }, + Table: output.TableDefinition[*VariableValue]{ + Header: []string{"NAME", "LABEL", "TYPE", "OWNER", "ENVIRONMENT", "VALUE", "SENSITIVE", "DEFAULT VALUE"}, + Row: func(item *VariableValue) []string { + value := item.Value + if item.HasMissingValue { + value = output.Red("") + } + + return []string{output.Bold(item.Name), item.Label, item.Type, item.OwnerName, item.ScopeName, value, fmt.Sprint(item.IsSensitive), fmt.Sprint(item.IsDefaultValue)} + }, + }, + Basic: func(item *VariableValue) string { + return item.Name + }, + }) + + return nil +} + +func unwrapCommonVariables(variables variables.LibraryVariable, missingVariables []variables.MissingVariable) []*VariableValue { + var results []*VariableValue = nil + for _, template := range variables.Templates { + value, isDefault := getVariableValue(template, variables.Variables) + hasMissingValue := hasMissingCommonValue(missingVariables, variables.LibraryVariableSetID, template.ID) + actualValue := getDisplayValue(value) + + results = append(results, &VariableValue{ + Type: LibraryVariableSetType, + OwnerName: variables.LibraryVariableSetName, + Name: template.Name, + Value: actualValue, + Label: template.Label, + IsSensitive: value.IsSensitive, + IsDefaultValue: isDefault, + HasMissingValue: hasMissingValue, + ScopeName: "", + }) + } + + return results +} + +func unwrapProjectVariables(variables variables.ProjectVariable, environmentMap map[string]string, missingVariables []variables.MissingVariable) []*VariableValue { + var results []*VariableValue = nil + for _, template := range variables.Templates { + for environmentId, environmentVariables := range variables.Variables { + value, isDefault := getVariableValue(template, environmentVariables) + hasMissingValue := hasMissingProjectValue(missingVariables, variables.ProjectID, environmentId, template.ID) + displayValue := getDisplayValue(value) + + results = append(results, &VariableValue{ + Type: ProjectType, + OwnerName: variables.ProjectName, + Name: template.Name, + Value: displayValue, + Label: template.Label, + IsSensitive: value.IsSensitive, + IsDefaultValue: isDefault, + ScopeName: environmentMap[environmentId], + HasMissingValue: hasMissingValue, + }) + } + } + + return results +} + +func hasMissingProjectValue(missingVariables []variables.MissingVariable, projectID string, environmentID string, templateID string) bool { + for _, v := range missingVariables { + if v.ProjectID == projectID && v.EnvironmentID == environmentID && v.VariableTemplateID == templateID { + return true + } + } + + return false +} + +func hasMissingCommonValue(missingVariables []variables.MissingVariable, libraryVariableSetId string, templateId string) bool { + for _, v := range missingVariables { + if v.LibraryVariableSetID == v.LibraryVariableSetID && v.VariableTemplateID == templateId { + return true + } + } + return false +} + +func getVariableValue(template *actiontemplates.ActionTemplateParameter, values map[string]core.PropertyValue) (*core.PropertyValue, bool) { + if value, ok := values[template.ID]; ok { + return &value, false + } + + return template.DefaultValue, true +} + +func getEnvironmentMap(client *client.Client) (map[string]string, error) { + environmentMap := make(map[string]string) + allEnvs, err := selectors.GetAllEnvironments(client) + if err != nil { + return nil, err + } + for _, e := range allEnvs { + environmentMap[e.GetID()] = e.GetName() + } + return environmentMap, nil +} + +func getDisplayValue(value *core.PropertyValue) string { + var actualValue string + if value.IsSensitive { + if value.SensitiveValue.HasValue { + actualValue = "***" + } else { + actualValue = "" + } + } else { + actualValue = value.Value + } + return actualValue +} diff --git a/pkg/cmd/tenant/variables/update/update.go b/pkg/cmd/tenant/variables/update/update.go new file mode 100644 index 00000000..d1ad1683 --- /dev/null +++ b/pkg/cmd/tenant/variables/update/update.go @@ -0,0 +1,557 @@ +package update + +import ( + "fmt" + "github.com/MakeNowJust/heredoc/v2" + "github.com/OctopusDeploy/cli/pkg/cmd" + "github.com/OctopusDeploy/cli/pkg/cmd/tenant/shared" + "github.com/OctopusDeploy/cli/pkg/constants" + "github.com/OctopusDeploy/cli/pkg/factory" + "github.com/OctopusDeploy/cli/pkg/question/selectors" + sharedVariable "github.com/OctopusDeploy/cli/pkg/question/shared/variables" + "github.com/OctopusDeploy/cli/pkg/util" + "github.com/OctopusDeploy/cli/pkg/util/flag" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/certificates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" + "github.com/spf13/cobra" + "strings" +) + +const ( + FlagTenant = "tenant" + FlagProject = "project" + FlagEnvironment = "environment" + FlagLibraryVariableSet = "library-variable-set" + FlagName = "name" + FlagValue = "value" + + VariableOwnerTypeCommon = "common" + VariableOwnerTypeProject = "project" +) + +type UpdateFlags struct { + Tenant *flag.Flag[string] + Project *flag.Flag[string] + Environment *flag.Flag[string] + LibraryVariableSet *flag.Flag[string] + Name *flag.Flag[string] + Value *flag.Flag[string] +} + +type UpdateOptions struct { + *UpdateFlags + VariableId string + TemplateId string + *cmd.Dependencies + shared.GetProjectCallback + shared.GetAllProjectsCallback + shared.GetTenantCallback + shared.GetAllTenantsCallback + shared.GetAllLibraryVariableSetsCallback + selectors.GetAllEnvironmentsCallback + *sharedVariable.VariableCallbacks +} + +func NewUpdateFlags() *UpdateFlags { + return &UpdateFlags{ + Tenant: flag.New[string](FlagTenant, false), + Project: flag.New[string](FlagProject, false), + Environment: flag.New[string](FlagEnvironment, false), + LibraryVariableSet: flag.New[string](FlagLibraryVariableSet, false), + Name: flag.New[string](FlagName, false), + Value: flag.New[string](FlagValue, false), + } +} + +func NewUpdateOptions(flags *UpdateFlags, dependencies *cmd.Dependencies) *UpdateOptions { + return &UpdateOptions{ + UpdateFlags: flags, + Dependencies: dependencies, + GetProjectCallback: func(identifier string) (*projects.Project, error) { + return shared.GetProject(dependencies.Client, identifier) + }, + GetAllProjectsCallback: func() ([]*projects.Project, error) { return shared.GetAllProjects(dependencies.Client) }, + GetTenantCallback: func(identifier string) (*tenants.Tenant, error) { + return shared.GetTenant(dependencies.Client, identifier) + }, + GetAllTenantsCallback: func() ([]*tenants.Tenant, error) { return shared.GetAllTenants(dependencies.Client) }, + GetAllLibraryVariableSetsCallback: func() ([]*variables.LibraryVariableSet, error) { + return shared.GetAllLibraryVariableSets(dependencies.Client) + }, + GetAllEnvironmentsCallback: func() ([]*environments.Environment, error) { return selectors.GetAllEnvironments(dependencies.Client) }, + VariableCallbacks: sharedVariable.NewVariableCallbacks(dependencies), + } +} + +func NewCmdUpdate(f factory.Factory) *cobra.Command { + updateFlags := NewUpdateFlags() + cmd := &cobra.Command{ + Use: "update", + Short: "Update the value of a tenant variable", + Long: "Update the value of a tenant variable in Octopus Deploy", + Example: heredoc.Docf(` + $ %[1]s tenant variable update + $ %[1]s tenant variable update --tenant "Bobs Fish Shack" --name "site-name" --value "Bobs Fish Shack" --project "Awesome Web Site" --environment "Test" + $ %[1]s tenant variable update --tenant "Sallys Tackle Truck" --name dbPassword --value "12345" --library-variable-set "Shared Variables" + `, constants.ExecutableName), + RunE: func(c *cobra.Command, args []string) error { + opts := NewUpdateOptions(updateFlags, cmd.NewDependencies(f, c)) + + return updateRun(opts) + }, + } + + flags := cmd.Flags() + flags.StringVarP(&updateFlags.Tenant.Value, updateFlags.Tenant.Name, "t", "", "The tenant") + flags.StringVarP(&updateFlags.Project.Value, updateFlags.Project.Name, "p", "", "The project") + flags.StringVarP(&updateFlags.Environment.Value, updateFlags.Environment.Name, "e", "", "The environment") + flags.StringVarP(&updateFlags.LibraryVariableSet.Value, updateFlags.LibraryVariableSet.Name, "l", "", "The library variable set") + flags.StringVarP(&updateFlags.Name.Value, updateFlags.Name.Name, "n", "", "The name of the variable") + flags.StringVar(&updateFlags.Value.Value, updateFlags.Value.Name, "", "The value to set on the variable") + + return cmd +} + +func updateRun(opts *UpdateOptions) error { + if !opts.NoPrompt { + err := PromptMissing(opts) + if err != nil { + return err + } + } + + tenant, err := opts.GetTenantCallback(opts.Tenant.Value) + if err != nil { + return err + } + + vars, err := opts.GetTenantVariables(tenant) + if err != nil { + return err + } + + if opts.LibraryVariableSet.Value != "" { + opts.Value.Secure, err = updateCommonVariableValue(opts, vars) + if err != nil { + return err + } + } else { + environmentMap, err := getEnvironmentMap(opts.GetAllEnvironmentsCallback) + if err != nil { + return err + } + opts.Value.Secure, err = updateProjectVariableValue(opts, vars, environmentMap) + if err != nil { + return err + } + } + + _, err = opts.Client.Tenants.UpdateVariables(tenant, vars) + if err != nil { + return err + } + + _, err = fmt.Fprintf(opts.Out, "Successfully updated variable '%s' for tenant '%s'\n", opts.Name.Value, tenant.Name) + + if !opts.NoPrompt { + autoCmd := flag.GenerateAutomationCmd(opts.CmdPath, opts.Tenant, opts.Name, opts.Value, opts.Project, opts.LibraryVariableSet, opts.Environment) + fmt.Fprintf(opts.Out, "\nAutomation Command: %s\n", autoCmd) + } + + return nil +} + +func PromptMissing(opts *UpdateOptions) error { + var tenant *tenants.Tenant + var err error + if opts.Tenant.Value == "" { + tenant, err = selectors.Select(opts.Ask, "You have not specified a Tenant. Please select one:", opts.GetAllTenantsCallback, func(tenant *tenants.Tenant) string { + return tenant.Name + }) + if err != nil { + return err + } + + opts.Tenant.Value = tenant.Name + } else { + tenant, err = opts.GetTenantCallback(opts.Tenant.Value) + if err != nil { + return err + } + } + + var variableType = "" + if opts.LibraryVariableSet.Value != "" { + variableType = VariableOwnerTypeCommon + } else if opts.Project.Value != "" { + variableType = VariableOwnerTypeProject + } else { + selectedOption, err := selectors.SelectOptions(opts.Ask, "Which type of variable do you want to update?", getVariableTypeOptions) + if err != nil { + return err + } + variableType = selectedOption.Value + } + + variables, err := opts.GetTenantVariables(tenant) + if err != nil { + return err + } + + switch variableType { + case VariableOwnerTypeCommon: + if opts.LibraryVariableSet.Value == "" || opts.Name.Value == "" { + selectedVariable, err := PromptForVariable(opts, variables, findPossibleCommonVariables) + if err != nil { + return err + } + opts.LibraryVariableSet.Value = selectedVariable.Owner + + } + case VariableOwnerTypeProject: + if opts.Project.Value == "" || opts.Name.Value == "" { + selectedVariable, err := PromptForVariable(opts, variables, findPossibleProjectVariables) + if err != nil { + return err + } + opts.Project.Value = selectedVariable.Owner + } + + err = PromptForEnvironment(opts, variables) + if err != nil { + return err + } + } + + variableControlType, err := getVariableType(opts, variables) + + if opts.Value.Value == "" { + variableType, err := mapVariableControlTypeToVariableType(variableControlType) + if err != nil { + return err + } + template, err := findTemplateById(variables, opts.Name.Value) + if err != nil { + return err + } + value, err := sharedVariable.PromptValue(opts.Ask, variableType, opts.VariableCallbacks, template) + if err != nil { + return err + } + opts.Value.Value = value + } + + return nil +} + +func PromptForEnvironment(opts *UpdateOptions, variables *variables.TenantVariables) error { + if opts.Environment.Value == "" { + var environmentSelections []*selectors.SelectOption[string] + environmentMap, err := getEnvironmentMap(opts.GetAllEnvironmentsCallback) + if err != nil { + return err + } + + for _, p := range variables.ProjectVariables { + if strings.EqualFold(p.ProjectName, opts.Project.Value) { + for k, _ := range p.Variables { + environmentSelections = append(environmentSelections, selectors.NewSelectOption[string](environmentMap[k], environmentMap[k])) + } + } + } + + selectedEnvironment, err := selectors.SelectOptions(opts.Ask, "You have not specified an environment", func() []*selectors.SelectOption[string] { return environmentSelections }) + if err != nil { + return err + } + opts.Environment.Value = selectedEnvironment.Value + } + return nil +} + +func PromptForVariable(opts *UpdateOptions, tenantVariables *variables.TenantVariables, variableFilter func(opts *UpdateOptions, tenantVariables *variables.TenantVariables) []*PossibleVariable) (*PossibleVariable, error) { + possibleVariables := variableFilter(opts, tenantVariables) + selectedVariable, err := selectors.Select(opts.Ask, "You have not specified a variable", func() ([]*PossibleVariable, error) { return possibleVariables, nil }, func(variable *PossibleVariable) string { + return fmt.Sprintf("%s / %s", variable.Owner, variable.VariableName) + }) + if err != nil { + return nil, err + } + + opts.Name.Value = selectedVariable.VariableName + opts.VariableId = selectedVariable.ID + opts.TemplateId = selectedVariable.TemplateID + return selectedVariable, nil +} + +func mapVariableControlTypeToVariableType(controlType variables.ControlType) (sharedVariable.VariableType, error) { + switch controlType { + case variables.ControlTypeSingleLineText, variables.ControlTypeMultiLineText: + return sharedVariable.VariableTypeString, nil + case variables.ControlTypeSensitive: + return sharedVariable.VariableTypeSensitive, nil + case variables.ControlTypeCheckbox: + return sharedVariable.VariableTypeBoolean, nil + case variables.ControlTypeCertificate: + return sharedVariable.VariableTypeCertificate, nil + case variables.ControlTypeWorkerPool: + return sharedVariable.VariableTypeWorkerPool, nil + case variables.ControlTypeGoogleCloudAccount: + return sharedVariable.VariableTypeGoogleCloudAccount, nil + case variables.ControlTypeAwsAccount: + return sharedVariable.VariableTypeAwsAccount, nil + case variables.ControlTypeAzureAccount: + return sharedVariable.VariableTypeAzureAccount, nil + case variables.ControlTypeSelect: + return sharedVariable.VariableTypeSelect, nil + } + + return "", fmt.Errorf("cannot map control type '%s' to variable type", controlType) +} + +func getVariableType(opts *UpdateOptions, tenantVariables *variables.TenantVariables) (variables.ControlType, error) { + if opts.LibraryVariableSet.Value != "" { + for _, v := range tenantVariables.LibraryVariables { + if strings.EqualFold(v.LibraryVariableSetName, opts.LibraryVariableSet.Value) { + template, err := findTemplate(v.Templates, opts.Name.Value) + if err != nil { + return "", err + } + return variables.ControlType(template.DisplaySettings["Octopus.ControlType"]), nil + } + } + } else if opts.Project.Value != "" { + for _, v := range tenantVariables.ProjectVariables { + if strings.EqualFold(v.ProjectName, opts.Project.Value) { + template, err := findTemplate(v.Templates, opts.Name.Value) + if err != nil { + return "", err + } + return variables.ControlType(template.DisplaySettings["Octopus.ControlType"]), nil + } + } + } + + return "", fmt.Errorf("cannot find variable '%s'", opts.Name.Value) +} + +func findTemplate(templates []*actiontemplates.ActionTemplateParameter, variableName string) (*actiontemplates.ActionTemplateParameter, error) { + for _, t := range templates { + if strings.EqualFold(t.Name, variableName) { + return t, nil + } + } + + return nil, fmt.Errorf("cannot find variable called %s", variableName) +} + +func findTemplateById(variables *variables.TenantVariables, templateName string) (*actiontemplates.ActionTemplateParameter, error) { + for _, l := range variables.LibraryVariables { + t, _ := findTemplate(l.Templates, templateName) + if t != nil { + return t, nil + } + } + for _, p := range variables.ProjectVariables { + t, _ := findTemplate(p.Templates, templateName) + if t != nil { + return t, nil + } + } + + return nil, fmt.Errorf("cannot find template '%s'", templateName) +} + +func findPossibleCommonVariables(opts *UpdateOptions, tenantVariables *variables.TenantVariables) []*PossibleVariable { + var filteredVariables []*PossibleVariable + for _, l := range tenantVariables.LibraryVariables { + for _, t := range l.Templates { + if (opts.Name.Value == "" && opts.LibraryVariableSet.Value == "") || (opts.Name.Value != "" && strings.EqualFold(opts.Name.Value, t.Name)) || (opts.LibraryVariableSet.Value != "" && strings.EqualFold(l.LibraryVariableSetName, opts.LibraryVariableSet.Value)) { + filteredVariables = append(filteredVariables, &PossibleVariable{ + Owner: l.LibraryVariableSetName, + VariableName: t.Name, + TemplateID: t.GetID(), + }) + } + } + } + + return filteredVariables +} + +func findPossibleProjectVariables(opts *UpdateOptions, tenantVariables *variables.TenantVariables) []*PossibleVariable { + var filteredVariables []*PossibleVariable + for _, p := range tenantVariables.ProjectVariables { + for _, t := range p.Templates { + if (opts.Name.Value == "" && opts.Project.Value == "") || (opts.Name.Value != "" && strings.EqualFold(opts.Name.Value, t.Name)) || (opts.LibraryVariableSet.Value != "" && strings.EqualFold(p.ProjectName, opts.Project.Value)) { + filteredVariables = append(filteredVariables, &PossibleVariable{ + TemplateID: t.ID, + Owner: p.ProjectName, + VariableName: t.Name, + }) + } + } + } + + return filteredVariables +} +func updateProjectVariableValue(opts *UpdateOptions, vars *variables.TenantVariables, environmentMap map[string]string) (bool, error) { + var environmentId string + for id, environment := range environmentMap { + if strings.EqualFold(environment, opts.Environment.Value) || strings.EqualFold(id, opts.Environment.Value) { + environmentId = id + } + } + for _, v := range vars.ProjectVariables { + if strings.EqualFold(v.ProjectName, opts.Project.Value) { + t, err := findTemplate(v.Templates, opts.Name.Value) + if err != nil { + return false, err + } + value, err := convertValue(opts, t) + if err != nil { + return false, err + } + v.Variables[environmentId][t.ID] = *value + + return value.IsSensitive, nil + } + } + + return false, fmt.Errorf("unable to find requested variable") +} + +func updateCommonVariableValue(opts *UpdateOptions, vars *variables.TenantVariables) (bool, error) { + for _, v := range vars.LibraryVariables { + if strings.EqualFold(v.LibraryVariableSetName, opts.LibraryVariableSet.Value) { + t, err := findTemplate(v.Templates, opts.Name.Value) + if err != nil { + return false, err + } + value, err := convertValue(opts, t) + if err != nil { + return false, err + } + v.Variables[t.ID] = *value + + return value.IsSensitive, nil + } + } + + return false, fmt.Errorf("unable to find requested variable") +} + +func convertValue(opts *UpdateOptions, t *actiontemplates.ActionTemplateParameter) (*core.PropertyValue, error) { + variableType := variables.ControlType(t.DisplaySettings["Octopus.ControlType"]) + value := opts.Value.Value + var err error + switch variableType { + case variables.ControlTypeAwsAccount: + value, err = findAccount(opts, accounts.AccountTypeAmazonWebServicesAccount) + case variables.ControlTypeGoogleCloudAccount: + value, err = findAccount(opts, accounts.AccountTypeGoogleCloudPlatformAccount) + case variables.ControlTypeAzureAccount: + value, err = findAccount(opts, accounts.AccountTypeAzureServicePrincipal) + case variables.ControlTypeWorkerPool: + allWorkerPools, err := opts.GetAllWorkerPools() + if err != nil { + return nil, err + } + matchedWorkerPools := util.SliceFilter(allWorkerPools, func(p *workerpools.WorkerPoolListResult) bool { + return strings.EqualFold(p.Name, opts.Value.Value) || strings.EqualFold(p.ID, opts.Value.Value) || strings.EqualFold(p.Slug, opts.Value.Value) + }) + if util.Empty(matchedWorkerPools) { + return nil, fmt.Errorf("cannot find worker pool '%s'", opts.Value.Value) + } + if len(matchedWorkerPools) > 1 { + return nil, fmt.Errorf("matched multiple worker pools") + } + + value, err = matchedWorkerPools[0].ID, nil + case variables.ControlTypeCertificate: + allCertificates, err := opts.GetAllCertificates() + if err != nil { + return nil, err + } + matchedCertificate := util.SliceFilter(allCertificates, func(p *certificates.CertificateResource) bool { + return !p.IsExpired && (strings.EqualFold(p.Name, opts.Value.Value) || strings.EqualFold(p.ID, opts.Value.Value)) + }) + if util.Empty(matchedCertificate) { + return nil, fmt.Errorf("cannot find certifcate '%s'", opts.Value.Value) + } + if len(matchedCertificate) > 1 { + return nil, fmt.Errorf("matched multiple certificates") + } + + value, err = matchedCertificate[0].ID, nil + case variables.ControlTypeSelect: + selectionOptions := sharedVariable.GetSelectOptions(t) + for _, o := range selectionOptions { + if strings.EqualFold(o.Display, opts.Value.Value) || strings.EqualFold(o.Value, opts.Value.Value) { + value, err = o.Value, nil + break + } + } + + if value == "" { + err = fmt.Errorf("cannot match selection value '%s'", opts.Value.Value) + } + case variables.ControlTypeSensitive: + propertyValue := core.NewPropertyValue(value, true) + return &propertyValue, nil + } + + if err == nil { + propertyValue := core.NewPropertyValue(value, false) + return &propertyValue, nil + } + + return nil, fmt.Errorf("unable to convert value to correct variable type '%s'", variableType) +} + +func findAccount(opts *UpdateOptions, accountType accounts.AccountType) (string, error) { + accounts, err := opts.GetAccountsByType(accountType) + if err != nil { + return "", err + } + for _, a := range accounts { + if strings.EqualFold(a.GetName(), opts.Value.Value) || strings.EqualFold(a.GetID(), opts.Value.Value) || strings.EqualFold(a.GetSlug(), opts.Value.Value) { + return a.GetID(), nil + } + } + + return "", fmt.Errorf("cannot find %s account with called '%s'", accountType, opts.Value.Value) +} + +func getEnvironmentMap(getAllEnvironments selectors.GetAllEnvironmentsCallback) (map[string]string, error) { + environmentMap := make(map[string]string) + allEnvs, err := getAllEnvironments() + if err != nil { + return nil, err + } + for _, e := range allEnvs { + environmentMap[e.GetID()] = e.GetName() + } + return environmentMap, nil +} + +func getVariableTypeOptions() []*selectors.SelectOption[string] { + return []*selectors.SelectOption[string]{ + {Display: "Library/Common", Value: VariableOwnerTypeCommon}, + {Display: "Project", Value: VariableOwnerTypeProject}, + } +} + +type PossibleVariable struct { + ID string + TemplateID string + Owner string + VariableName string +} diff --git a/pkg/cmd/tenant/variables/update/update_test.go b/pkg/cmd/tenant/variables/update/update_test.go new file mode 100644 index 00000000..e1f76d75 --- /dev/null +++ b/pkg/cmd/tenant/variables/update/update_test.go @@ -0,0 +1,214 @@ +package update_test + +import ( + "github.com/OctopusDeploy/cli/pkg/cmd" + "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables/update" + "github.com/OctopusDeploy/cli/test/testutil" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPromptMissing_ProjectVariable_AllFlagsProvided(t *testing.T) { + pa := []*testutil.PA{} + + asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) + flags := update.NewUpdateFlags() + flags.Tenant.Value = "tenant name" + flags.Value.Value = "new value" + flags.Name.Value = "var name" + flags.Project.Value = "project name" + flags.Environment.Value = "dev environment" + opts := update.NewUpdateOptions(flags, &cmd.Dependencies{Ask: asker}) + opts.GetTenantVariables = func(tenant *tenants.Tenant) (*variables.TenantVariables, error) { + projectVars := variables.ProjectVariable{ + ProjectID: "Projects-1", + ProjectName: flags.Project.Value, + } + projectTemplate := createTemplate("var name", variables.ControlTypeSingleLineText, "templateId-1") + projectVars.Templates = append(projectVars.Templates, projectTemplate) + vars := variables.NewTenantVariables("Tenants-1") + vars.SpaceID = "Spaces-1" + vars.TenantName = "tenant name" + vars.ProjectVariables = make(map[string]variables.ProjectVariable) + vars.ProjectVariables["Projects-1"] = projectVars + return vars, nil + } + + opts.GetProjectCallback = func(identifier string) (*projects.Project, error) { + return projects.NewProject("Project", "Lifecycles-1", "ProjectGroups-1"), nil + } + + opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { + return tenants.NewTenant("tenant name"), nil + } + + err := update.PromptMissing(opts) + checkRemainingPrompts() + assert.NoError(t, err) +} + +func TestPromptMissing_LibraryVariable_AllFlagsProvided(t *testing.T) { + pa := []*testutil.PA{} + + asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) + flags := update.NewUpdateFlags() + flags.Tenant.Value = "tenant name" + flags.Value.Value = "new value" + flags.Name.Value = "var name" + flags.LibraryVariableSet.Value = "lvs" + opts := update.NewUpdateOptions(flags, &cmd.Dependencies{Ask: asker}) + opts.GetTenantVariables = func(tenant *tenants.Tenant) (*variables.TenantVariables, error) { + libraryVariables := variables.NewLibraryVariable() + libraryVariables.LibraryVariableSetID = "LibraryVariableSets-1" + libraryVariables.LibraryVariableSetName = "lvs" + libraryVariables.Templates = append(libraryVariables.Templates, createTemplate("var name", variables.ControlTypeSingleLineText, "templateId-1")) + vars := variables.NewTenantVariables("Tenants-1") + vars.SpaceID = "Spaces-1" + vars.TenantName = "tenant name" + vars.LibraryVariables = make(map[string]variables.LibraryVariable) + return vars, nil + } + + opts.GetAllLibraryVariableSetsCallback = func() ([]*variables.LibraryVariableSet, error) { + return []*variables.LibraryVariableSet{variables.NewLibraryVariableSet("lvs")}, nil + } + + opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { + return tenants.NewTenant("tenant name"), nil + } + + err := update.PromptMissing(opts) + checkRemainingPrompts() + assert.NoError(t, err) +} + +func TestPromptMissing_LibraryVariable_NoFlagsProvided(t *testing.T) { + pa := []*testutil.PA{ + testutil.NewSelectPrompt("You have not specified a Tenant. Please select one:", "", []string{"tenant name", "tenant name 2"}, "tenant name"), + testutil.NewSelectPrompt("Which type of variable do you want to update?", "", []string{"Library/Common", "Project"}, "Library/Common"), + testutil.NewSelectPrompt("You have not specified a variable", "", []string{"lvs / var name", "lvs / var name 2"}, "lvs / var name"), + testutil.NewInputPrompt("Value", "", "var value"), + } + asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) + flags := update.NewUpdateFlags() + opts := update.NewUpdateOptions(flags, &cmd.Dependencies{Ask: asker}) + + opts.GetTenantVariables = func(tenant *tenants.Tenant) (*variables.TenantVariables, error) { + libraryVariables := variables.NewLibraryVariable() + libraryVariables.LibraryVariableSetID = "LibraryVariableSets-1" + libraryVariables.LibraryVariableSetName = "lvs" + libraryVariables.Templates = append(libraryVariables.Templates, createTemplate("var name", variables.ControlTypeSingleLineText, "templateId-1")) + libraryVariables.Templates = append(libraryVariables.Templates, createTemplate("var name 2", variables.ControlTypeSingleLineText, "templateId-2")) + vars := variables.NewTenantVariables("Tenants-1") + vars.SpaceID = "Spaces-1" + vars.TenantName = "tenant name" + vars.LibraryVariables = make(map[string]variables.LibraryVariable) + vars.LibraryVariables[libraryVariables.LibraryVariableSetID] = *libraryVariables + return vars, nil + } + + opts.GetAllLibraryVariableSetsCallback = func() ([]*variables.LibraryVariableSet, error) { + return []*variables.LibraryVariableSet{variables.NewLibraryVariableSet("lvs")}, nil + } + + opts.GetAllTenantsCallback = func() ([]*tenants.Tenant, error) { + return []*tenants.Tenant{tenants.NewTenant("tenant name"), tenants.NewTenant("tenant name 2")}, nil + } + opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { + return tenants.NewTenant("tenant name"), nil + } + + err := update.PromptMissing(opts) + checkRemainingPrompts() + assert.Equal(t, "var value", flags.Value.Value) + assert.Equal(t, "lvs", flags.LibraryVariableSet.Value) + assert.Empty(t, flags.Project.Value) + assert.Equal(t, "var name", flags.Name.Value) + assert.Equal(t, "tenant name", flags.Tenant.Value) + assert.NoError(t, err) +} + +func TestPromptMissing_ProjectVariable_NoFlagsProvided(t *testing.T) { + pa := []*testutil.PA{ + testutil.NewSelectPrompt("You have not specified a Tenant. Please select one:", "", []string{"tenant name", "tenant name 2"}, "tenant name"), + testutil.NewSelectPrompt("Which type of variable do you want to update?", "", []string{"Library/Common", "Project"}, "Project"), + testutil.NewSelectPrompt("You have not specified a variable", "", []string{"Project 1 / project 1 var", "Project 2 / project 2 var"}, "Project 2 / project 2 var"), + testutil.NewSelectPrompt("You have not specified an environment", "", []string{"Staging", "Production"}, "Staging"), + testutil.NewInputPrompt("Value", "", "var value"), + } + asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) + flags := update.NewUpdateFlags() + opts := update.NewUpdateOptions(flags, &cmd.Dependencies{Ask: asker}) + + opts.GetTenantVariables = func(tenant *tenants.Tenant) (*variables.TenantVariables, error) { + project1Vars := variables.ProjectVariable{ + ProjectID: "Projects-1", + ProjectName: "Project 1", + Variables: make(map[string]map[string]core.PropertyValue), + } + project1Vars.Templates = append(project1Vars.Templates, createTemplate("project 1 var", variables.ControlTypeSingleLineText, "templateId-1")) + project1Vars.Variables["Environments-1"] = make(map[string]core.PropertyValue) + project1Vars.Variables["Environments-1"]["templateId-1"] = core.NewPropertyValue("", false) + project1Vars.Variables["Environments-2"] = make(map[string]core.PropertyValue) + project1Vars.Variables["Environments-2"]["templateId-1"] = core.NewPropertyValue("", false) + project2Vars := variables.ProjectVariable{ + ProjectID: "Projects-2", + ProjectName: "Project 2", + Variables: make(map[string]map[string]core.PropertyValue), + } + project2Vars.Templates = append(project2Vars.Templates, createTemplate("project 2 var", variables.ControlTypeSingleLineText, "templateId-2")) + project2Vars.Variables["Environments-1"] = make(map[string]core.PropertyValue) + project2Vars.Variables["Environments-1"]["templateId-2"] = core.NewPropertyValue("", false) + project2Vars.Variables["Environments-2"] = make(map[string]core.PropertyValue) + project2Vars.Variables["Environments-2"]["templateId-2"] = core.NewPropertyValue("", false) + + vars := variables.NewTenantVariables("Tenants-1") + vars.SpaceID = "Spaces-1" + vars.TenantName = "tenant name" + vars.ProjectVariables = make(map[string]variables.ProjectVariable) + vars.ProjectVariables["Projects-1"] = project1Vars + vars.ProjectVariables["Projects-2"] = project2Vars + + return vars, nil + } + opts.GetAllLibraryVariableSetsCallback = func() ([]*variables.LibraryVariableSet, error) { + return []*variables.LibraryVariableSet{variables.NewLibraryVariableSet("lvs")}, nil + } + + opts.GetAllTenantsCallback = func() ([]*tenants.Tenant, error) { + return []*tenants.Tenant{tenants.NewTenant("tenant name"), tenants.NewTenant("tenant name 2")}, nil + } + opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { + return tenants.NewTenant("tenant name"), nil + } + opts.GetAllEnvironmentsCallback = func() ([]*environments.Environment, error) { + staging := environments.NewEnvironment("Staging") + staging.ID = "Environments-1" + production := environments.NewEnvironment("Production") + production.ID = "Environments-2" + return []*environments.Environment{staging, production}, nil + } + + err := update.PromptMissing(opts) + checkRemainingPrompts() + assert.Equal(t, "var value", flags.Value.Value) + assert.Empty(t, flags.LibraryVariableSet.Value) + assert.Equal(t, "Project 2", flags.Project.Value) + assert.Equal(t, "project 2 var", flags.Name.Value) + assert.Equal(t, "tenant name", flags.Tenant.Value) + assert.NoError(t, err) +} + +func createTemplate(name string, controlType variables.ControlType, templateId string) *actiontemplates.ActionTemplateParameter { + template := actiontemplates.NewActionTemplateParameter() + template.Name = name + template.DisplaySettings = make(map[string]string) + template.DisplaySettings["Octopus.ControlType"] = string(controlType) + return template +} diff --git a/pkg/cmd/tenant/variables/variables.go b/pkg/cmd/tenant/variables/variables.go new file mode 100644 index 00000000..7f7646d4 --- /dev/null +++ b/pkg/cmd/tenant/variables/variables.go @@ -0,0 +1,38 @@ +package variables + +import ( + "github.com/MakeNowJust/heredoc/v2" + cmdList "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables/list" + cmdUpdate "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables/update" + "github.com/OctopusDeploy/cli/pkg/constants" + "github.com/OctopusDeploy/cli/pkg/constants/annotations" + "github.com/OctopusDeploy/cli/pkg/factory" + "github.com/spf13/cobra" +) + +func NewCmdVariables(f factory.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "variables ", + Aliases: []string{"variable"}, + Short: "Manage tenant variables", + Long: "Manage tenant variables in Octopus Deploy", + Example: heredoc.Docf(` + $ %[1]s tenant variables list --tenant "Bobs Wood Shop" + $ %[1]s tenant variables view --name "DatabaseName" --tenant "Bobs Wood Shop" + `, constants.ExecutableName), + Annotations: map[string]string{ + annotations.IsCore: "true", + }, + } + + //cmd.AddCommand(cmdUpdate.NewUpdateCmd(f)) + //cmd.AddCommand(cmdCreate.NewCreateCmd(f)) + cmd.AddCommand(cmdList.NewCmdList(f)) + cmd.AddCommand(cmdUpdate.NewCmdUpdate(f)) + //cmd.AddCommand(cmdView.NewCmdView(f)) + //cmd.AddCommand(cmdDelete.NewDeleteCmd(f)) + //cmd.AddCommand(cmdInclude.NewIncludeVariableSetCmd(f)) + //cmd.AddCommand(cmdExclude.NewExcludeVariableSetCmd(f)) + + return cmd +} diff --git a/pkg/question/selectors/selectors.go b/pkg/question/selectors/selectors.go index 72fe5e0f..1a2a5dcc 100644 --- a/pkg/question/selectors/selectors.go +++ b/pkg/question/selectors/selectors.go @@ -9,6 +9,13 @@ type SelectOption[T any] struct { Display string } +func NewSelectOption[T any](value any, display string) *SelectOption[T] { + return &SelectOption[T]{ + Value: value.(T), + Display: display, + } +} + type Nameable interface { GetName() string } diff --git a/pkg/question/shared/variables/variables.go b/pkg/question/shared/variables/variables.go new file mode 100644 index 00000000..b2b63618 --- /dev/null +++ b/pkg/question/shared/variables/variables.go @@ -0,0 +1,229 @@ +package variables + +import ( + "fmt" + "github.com/AlecAivazis/survey/v2" + "github.com/OctopusDeploy/cli/pkg/cmd" + "github.com/OctopusDeploy/cli/pkg/question" + "github.com/OctopusDeploy/cli/pkg/question/selectors" + "github.com/OctopusDeploy/cli/pkg/util" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/certificates" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" + "strings" +) + +type VariableType string + +const ( + VariableTypeString = VariableType("String") + VariableTypeSensitive = VariableType("Sensitive") + VariableTypeAwsAccount = VariableType("AmazonWebServicesAccount") + VariableTypeAzureAccount = VariableType("AzureAccount") + VariableTypeGoogleCloudAccount = VariableType("GoogleCloudAccount") + VariableTypeWorkerPool = VariableType("WorkerPool") + VariableTypeCertificate = VariableType("Certificate") + VariableTypeBoolean = VariableType("Boolean") + VariableTypeSelect = VariableType("Select") +) + +type GetAccountsByTypeCallback func(accountType accounts.AccountType) ([]accounts.IAccount, error) +type GetAllWorkerPoolsCallback func() ([]*workerpools.WorkerPoolListResult, error) +type GetAllCertificatesCallback func() ([]*certificates.CertificateResource, error) +type GetProjectVariablesCallback func(projectId string) (*variables.VariableSet, error) +type GetTenantVariablesCallback func(tenant *tenants.Tenant) (*variables.TenantVariables, error) +type GetVariableByIdCallback func(ownerId, variableId string) (*variables.Variable, error) +type GetAllLibraryVariableSetsCallback func() ([]*variables.LibraryVariableSet, error) + +type VariableCallbacks struct { + GetAccountsByType GetAccountsByTypeCallback + GetAllWorkerPools GetAllWorkerPoolsCallback + GetAllCertificates GetAllCertificatesCallback + GetProjectVariables GetProjectVariablesCallback + GetVariableById GetVariableByIdCallback + GetTenantVariables GetTenantVariablesCallback +} + +func NewVariableCallbacks(dependencies *cmd.Dependencies) *VariableCallbacks { + return &VariableCallbacks{ + GetAccountsByType: func(accountType accounts.AccountType) ([]accounts.IAccount, error) { + return getAccountsByType(dependencies.Client, accountType) + }, + GetAllWorkerPools: func() ([]*workerpools.WorkerPoolListResult, error) { + return getAllWorkerPools(dependencies.Client) + }, + GetAllCertificates: func() ([]*certificates.CertificateResource, error) { + return getAllCertificates(dependencies.Client) + }, + GetProjectVariables: func(projectId string) (*variables.VariableSet, error) { + return getProjectVariables(dependencies.Client, projectId) + }, + GetTenantVariables: func(tenant *tenants.Tenant) (*variables.TenantVariables, error) { + return getTenantVariables(dependencies.Client, tenant) + }, + GetVariableById: func(ownerId, variableId string) (*variables.Variable, error) { + return getVariableById(dependencies.Client, ownerId, variableId) + }, + } +} + +func PromptValue(ask question.Asker, variableType VariableType, callbacks *VariableCallbacks, template *actiontemplates.ActionTemplateParameter) (string, error) { + var value string + switch variableType { + case VariableTypeString: + if err := ask(&survey.Input{ + Message: "Value", + }, &value); err != nil { + return "", err + } + return value, nil + case VariableTypeSensitive: + if err := ask(&survey.Password{ + Message: "Value", + }, &value); err != nil { + return "", err + } + return value, nil + case VariableTypeAwsAccount, VariableTypeAzureAccount, VariableTypeGoogleCloudAccount: + accountType, err := mapVariableTypeToAccountType(variableType) + if err != nil { + return "", err + } + accountsByType, err := callbacks.GetAccountsByType(accountType) + if err != nil { + return "", err + } + + selectedValue, err := selectors.ByName(ask, accountsByType, "Value") + if err != nil { + return "", err + } + return selectedValue.GetName(), nil + case VariableTypeWorkerPool: + workerPools, err := callbacks.GetAllWorkerPools() + if err != nil { + return "", err + } + selectedValue, err := selectors.Select( + ask, + "Value", + func() ([]*workerpools.WorkerPoolListResult, error) { return workerPools, nil }, + func(item *workerpools.WorkerPoolListResult) string { return item.Name }) + if err != nil { + return "", err + } + return selectedValue.Name, nil + case VariableTypeCertificate: + allCerts, err := callbacks.GetAllCertificates() + if err != nil { + return "", err + } + selectedValue, err := selectors.Select( + ask, + "Value", + func() ([]*certificates.CertificateResource, error) { return allCerts, nil }, + func(item *certificates.CertificateResource) string { return item.Name }) + if err != nil { + return "", err + } + return selectedValue.Name, nil + case VariableTypeBoolean: + var response string + err := ask(&survey.Select{ + Message: "Select value", + Options: []string{"True", "False"}, // Yes/No would read more nicely, but doesn't fit well with cmdline which expects True/False + }, &response) + return response, err + case VariableTypeSelect: + response, err := selectors.SelectOptions(ask, "Selection option", func() []*selectors.SelectOption[string] { return GetSelectOptions(template) }) + if err != nil { + return "", err + } + return response.Value, nil + } + + return "", fmt.Errorf("error getting value") +} + +func mapVariableTypeToAccountType(variableType VariableType) (accounts.AccountType, error) { + switch variableType { + case VariableTypeAwsAccount: + return accounts.AccountTypeAmazonWebServicesAccount, nil + case VariableTypeAzureAccount: + return accounts.AccountTypeAzureServicePrincipal, nil + case VariableTypeGoogleCloudAccount: + return accounts.AccountTypeGoogleCloudPlatformAccount, nil + default: + return accounts.AccountTypeNone, fmt.Errorf("variable type '%s' is not a valid account variable type", variableType) + + } +} + +func getAccountsByType(client *client.Client, accountType accounts.AccountType) ([]accounts.IAccount, error) { + accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ + AccountType: accountType, + }) + if err != nil { + return nil, err + } + items, err := accountResources.GetAllPages(client.Accounts.GetClient()) + if err != nil { + return nil, err + } + return items, nil +} + +func getAllCertificates(client *client.Client) ([]*certificates.CertificateResource, error) { + certs, err := client.Certificates.Get(certificates.CertificatesQuery{}) + if err != nil { + return nil, err + } + return certs.GetAllPages(client.Sling()) +} + +func getAllWorkerPools(client *client.Client) ([]*workerpools.WorkerPoolListResult, error) { + res, err := client.WorkerPools.GetAll() + if err != nil { + return nil, err + } + + return res, nil +} + +func getProjectVariables(client *client.Client, id string) (*variables.VariableSet, error) { + variableSet, err := client.Variables.GetAll(id) + return &variableSet, err +} + +func getTenantVariables(client *client.Client, tenant *tenants.Tenant) (*variables.TenantVariables, error) { + tenantVariables, err := client.Tenants.GetVariables(tenant) + return tenantVariables, err +} + +func getVariableById(client *client.Client, ownerId string, variableId string) (*variables.Variable, error) { + return client.Variables.GetByID(ownerId, variableId) +} + +func GetAllLibraryVariableSets(client *client.Client) ([]*variables.LibraryVariableSet, error) { + res, err := client.LibraryVariableSets.GetAll() + if err != nil { + return nil, err + } + + return util.SliceFilter(res, func(item *variables.LibraryVariableSet) bool { return item.ContentType == "Variables" }), nil +} + +func GetSelectOptions(t *actiontemplates.ActionTemplateParameter) []*selectors.SelectOption[string] { + var selectionOptions []*selectors.SelectOption[string] + options := t.DisplaySettings["Octopus.SelectOptions"] + for _, l := range strings.Split(options, "\n") { + o := strings.Split(l, "|") + selectionOptions = append(selectionOptions, selectors.NewSelectOption[string](o[0], o[1])) + } + + return selectionOptions +}