From 2accd75569136a9ec05c50a717fa6bd21ae107fd Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:27:20 -0300 Subject: [PATCH 01/18] feat add profiles command --- cmd/azion/main.go | 6 +- go.sum | 68 --------- messages/login/errors.go | 1 + messages/login/messages.go | 6 + messages/profile/errors.go | 15 ++ messages/profile/messages.go | 32 ++++ pkg/cmd/create/create.go | 2 + pkg/cmd/create/profile/profile.go | 138 +++++++++++++++++ pkg/cmd/delete/delete.go | 2 + pkg/cmd/delete/profile/profile.go | 141 ++++++++++++++++++ pkg/cmd/deploy/deploy.go | 7 +- pkg/cmd/deploy/deploy_test.go | 2 +- pkg/cmd/list/storage/object/object.go | 5 +- pkg/cmd/login/login.go | 45 ++++-- pkg/cmd/login/terminal_test.go | 9 ++ pkg/cmd/logout/logout.go | 13 +- pkg/cmd/logout/logout_test.go | 4 +- pkg/cmd/profiles/profiles.go | 81 ++++++++++ pkg/cmd/reset/reset.go | 13 +- pkg/cmd/reset/reset_test.go | 4 +- pkg/cmd/root/pre_command.go | 94 +++++++++--- pkg/cmd/root/pre_command_test.go | 2 +- pkg/cmd/root/root.go | 6 +- pkg/cmd/whoami/whoami.go | 7 +- pkg/cmd/whoami/whoami_test.go | 138 ++++++++--------- pkg/cmdutil/factory.go | 18 +++ pkg/config/config.go | 5 + pkg/config/config_test.go | 5 + pkg/metric/count.go | 5 +- pkg/metric/segment.go | 19 ++- pkg/schedule/schedule.go | 52 ++++++- pkg/token/models.go | 4 + pkg/token/token.go | 68 +++++++-- pkg/token/token_test.go | 22 +-- pkg/v3commands/deploy/deploy.go | 8 +- pkg/v3commands/deploy/deploy_test.go | 14 +- .../list/edge_storage/object/object.go | 5 +- pkg/v3commands/login/login.go | 45 ++++-- pkg/v3commands/login/login_test.go | 9 ++ pkg/v3commands/login/terminal_test.go | 9 ++ pkg/v3commands/logout/logout.go | 11 +- pkg/v3commands/logout/logout_test.go | 5 +- pkg/v3commands/reset/reset.go | 11 +- pkg/v3commands/reset/reset_test.go | 5 +- pkg/v3commands/whoami/whoami.go | 7 +- pkg/v3commands/whoami/whoami_test.go | 4 +- pkg/vulcan/vulcan.go | 15 +- pkg/vulcan/vulcan_test.go | 2 +- utils/errors.go | 5 + 49 files changed, 912 insertions(+), 282 deletions(-) create mode 100644 messages/profile/errors.go create mode 100644 messages/profile/messages.go create mode 100644 pkg/cmd/create/profile/profile.go create mode 100644 pkg/cmd/delete/profile/profile.go create mode 100644 pkg/cmd/profiles/profiles.go diff --git a/cmd/azion/main.go b/cmd/azion/main.go index 571bec8d5..d4913ac39 100644 --- a/cmd/azion/main.go +++ b/cmd/azion/main.go @@ -2,6 +2,7 @@ package main import ( "net/http" + "path" "time" cmd "github.com/aziontech/azion-cli/pkg/cmd/root" @@ -18,13 +19,16 @@ func main() { Timeout: 50 * time.Second, } - tok, _ := token.ReadSettings() + profiles, settingsPath, _ := token.ReadProfiles() + activeProfile := path.Join(settingsPath, profiles.Name) + tok, _ := token.ReadSettings(profiles.Name) viper.SetEnvPrefix("AZIONCLI") viper.AutomaticEnv() viper.SetDefault("token", tok.Token) viper.SetDefault("api_url", constants.ApiURL) viper.SetDefault("api_v4_url", constants.ApiV4URL) viper.SetDefault("storage_url", constants.StorageApiURL) + viper.SetDefault("active_profile", activeProfile) factory := &cmdutil.Factory{ HttpClient: httpClient, diff --git a/go.sum b/go.sum index 22e2d3839..5867089a7 100644 --- a/go.sum +++ b/go.sum @@ -50,110 +50,44 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4= -github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= -github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= -github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2 v1.39.3 h1:h7xSsanJ4EQJXG5iuW4UqgP7qBopLpj84mpkNx3wPjM= github.com/aws/aws-sdk-go-v2 v1.39.3/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= -github.com/aws/aws-sdk-go-v2/config v1.31.8 h1:kQjtOLlTU4m4A64TsRcqwNChhGCwaPBt+zCQt/oWsHU= -github.com/aws/aws-sdk-go-v2/config v1.31.8/go.mod h1:QPpc7IgljrKwH0+E6/KolCgr4WPLerURiU592AYzfSY= -github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= -github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= github.com/aws/aws-sdk-go-v2/config v1.31.14 h1:kj/KpDqvt0UqcEL3WOvCykE9QUpBb6b23hQdnXe+elo= github.com/aws/aws-sdk-go-v2/config v1.31.14/go.mod h1:X5PaY6QCzViihn/ru7VxnIamcJQrG9NSeTxuSKm2YtU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.13 h1:gkpEm65/ZfrGJ3wbFH++Ki7DyaWtsWbK9idX6OXCo2E= -github.com/aws/aws-sdk-go-v2/credentials v1.18.13/go.mod h1:eVTHz1yI2/WIlXTE8f70mcrSxNafXD5sJpTIM9f+kmo= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I= github.com/aws/aws-sdk-go-v2/credentials v1.18.18 h1:5AfxTvDN0AJoA7rg/yEc0sHhl6/B9fZ+NtiQuOjWGQM= github.com/aws/aws-sdk-go-v2/credentials v1.18.18/go.mod h1:m9mE1mJ1s7zI6rrt7V3RQU2SCgUbNaphlfqEksLp+Fs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10 h1:UuGVOX48oP4vgQ36oiKmW9RuSeT8jlgQgBFQD+HUiHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10/go.mod h1:vM/Ini41PzvudT4YkQyE/+WiQJiQ6jzeDyU8pQKwCac= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 h1:mj/bdWleWEh81DtpdHKkw41IrS+r3uw1J/VQtbwYYp8= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10/go.mod h1:7+oEMxAZWP8gZCyjcm9VicI0M61Sx4DJtcGfKYv2yKQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 h1:wh+/mn57yhUrFtLIxyFPh2RgxgQz/u+Yrf7hiHGHqKY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10/go.mod h1:7zirD+ryp5gitJJ2m1BBux56ai8RIRDykXZrJSp540w= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10 h1:FHw90xCTsofzk6vjU808TSuDtDfOOKPNdz5Weyc3tUI= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10/go.mod h1:n8jdIE/8F3UYkg8O4IGkQpn2qUmapg/1K1yl29/uf/c= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 h1:X0FveUndcZ3lKbSpIC6rMYGRiQTcUVRNH6X4yYtIrlU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1 h1:ne+eepnDB2Wh5lHKzELgEncIqeVlQ1rSF9fEa4r5I+A= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1/go.mod h1:u0Jkg0L+dcG1ozUq21uFElmpbmjBnhHR5DELHIme4wg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10 h1:DRND0dkCKtJzCj4Xl4OpVbXZgfttY5q712H9Zj7qc/0= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10/go.mod h1:tGGNmJKOTernmR2+VJ0fCzQRurcPZj9ut60Zu5Fi6us= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10 h1:DA+Hl5adieRyFvE7pCvBWm3VOZTRexGVkXw33SUqNoY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10/go.mod h1:L+A89dH3/gr8L4ecrdzuXUYd1znoko6myzndVGZx/DA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 h1:+RpGuaQ72qnU83qBKVwxkznewEdAGhIWo/PQCmkhhog= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 h1:mUI3b885qJgfqKDUSj6RgbRqLdX0wGmg8ruM03zNfQA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4/go.mod h1:6v8ukAxc7z4x4oBjGUsLnH7KGLY9Uhcgij19UJNkiMg= github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6 h1:Hcb4yllr4GTOHC/BKjEklxWhciWMHIqzeCI9oYf1OIk= github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6/go.mod h1:N/iojY+8bW3MYol9NUMuKimpSbPEur75cuI1SmtonFM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 h1:7PKX3VYsZ8LUWceVRuv0+PU+E7OtQb1lgmi5vmUE9CM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.3/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= github.com/aws/aws-sdk-go-v2/service/sso v1.29.7 h1:fspVFg6qMx0svs40YgRmE7LZXh9VRZvTT35PfdQR6FM= github.com/aws/aws-sdk-go-v2/service/sso v1.29.7/go.mod h1:BQTKL3uMECaLaUV3Zc2L4Qybv8C6BIXjuu1dOPyxTQs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.5 h1:gBBZmSuIySGqDLtXdZiYpwyzbJKXQD2jjT0oDY6ywbo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.5/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2 h1:scVnW+NLXasGOhy7HhkdT9AGb6kjgW7fJ5xYkUaqHs0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2/go.mod h1:FRNCY3zTEWZXBKm2h5UBUPvCVDOecTad9KhynDyGBc0= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 h1:PR00NXRYgY4FWHqOGx3fC3lhVKjsp1GdloDv2ynMSd8= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.4/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= github.com/aws/aws-sdk-go-v2/service/sts v1.38.8 h1:xSL4IV19pKDASL2fjWXRfTGmZddPiPPZNPpbv6uZQZY= github.com/aws/aws-sdk-go-v2/service/sts v1.38.8/go.mod h1:L1xxV3zAdB+qVrVW/pBIrIAnHFWHo6FBbFe4xOGsG/o= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/aziontech/azionapi-v4-go-sdk-dev v0.71.0 h1:GwS8mCmhh/8tgNSGGma0ic/WOr3rmmpOiBFaSQmvn5o= -github.com/aziontech/azionapi-v4-go-sdk-dev v0.71.0/go.mod h1:kJOyMICpeA+2xeo7/trGKGzMLdBMRKXM9A270vknSbI= github.com/aziontech/azionapi-v4-go-sdk-dev v0.80.0 h1:q4Ma0fp/zcN8zzHtiOjOx+EaB/xK/vmlIMMXSAP4V8Y= github.com/aziontech/azionapi-v4-go-sdk-dev v0.80.0/go.mod h1:kJOyMICpeA+2xeo7/trGKGzMLdBMRKXM9A270vknSbI= github.com/aziontech/go-thoth v0.0.0-20240228144710-d061a88cc39f h1:b0IX6tpiiG+QzCVOBqwYEHP5gPeeESq57A5ZXiYyDS4= @@ -209,8 +143,6 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= -github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= diff --git a/messages/login/errors.go b/messages/login/errors.go index be438c0a3..c0dc5f5d8 100644 --- a/messages/login/errors.go +++ b/messages/login/errors.go @@ -7,4 +7,5 @@ var ( ErrorInvalidLogin = errors.New("Invalid login method") ErrorTokenCreateInvalid = errors.New("Invalid token detected. The generated token appears to be corrupted or expired. Please check your authentication credentials and generate a new token to proceed.") ErrorServerClosed = errors.New("Error while serving server for browser login") + ErrorGetProfileName = errors.New("failed to get profile name: %w") ) diff --git a/messages/login/messages.go b/messages/login/messages.go index 647663744..8f5403983 100644 --- a/messages/login/messages.go +++ b/messages/login/messages.go @@ -19,4 +19,10 @@ const ( //browser VisitMsg = "Please visit https://console.azion.com/login?next=cli&callback_port=%d in case it did not open automatically\n" BrowserMsg = "You may now close this page and return to your terminal" + + // profile creation + QuestionCreateProfile = "Would you like to create a new profile for this login? (Y/n)" + AskProfileName = "Enter a name for the new profile:" + ProfileCreated = "Profile '%s' created successfully" + TokenSavedToProfile = "Token saved to profile '%s'" ) diff --git a/messages/profile/errors.go b/messages/profile/errors.go new file mode 100644 index 000000000..3862d6764 --- /dev/null +++ b/messages/profile/errors.go @@ -0,0 +1,15 @@ +package profile + +import "errors" + +var ( + ErrorCreate = errors.New("Failed to create the profile: %w") + ErrorReadFile = errors.New("Failed to read the file: %w") + ErrorUnmarshalFile = errors.New("Failed to unmarshal the file: %w") + ErrorReadDir = errors.New("Failed to read the directory: %w") + ErrorSwitchProfile = errors.New("Failed to switch profile: %w") + ErrorDeleteProfile = errors.New("Failed to delete the profile: %w") + ErrorProfileNotFound = errors.New("Profile '%s' not found") + ErrorCannotDeleteDefault = errors.New("Cannot delete the 'default' profile") + ErrorDeleteToken = errors.New("Failed to delete token: %w") +) diff --git a/messages/profile/messages.go b/messages/profile/messages.go new file mode 100644 index 000000000..1014e6b19 --- /dev/null +++ b/messages/profile/messages.go @@ -0,0 +1,32 @@ +package profile + +var ( + UsageCreate = "profile" + + CreateShortDescription = "Create a new profile" + CreateLongDescription = "Create a new profile setting up all the required fields" + CreateFlagHelp = "Displays more information about the create profile subcommand" + FlagName = "Name of the new profile" + FlagToken = "Token for the new profile" + FlagFile = "Path to the toml file containing the settings for the new profile" + QuestionToken = "Would you like to set a token for the new profile? (Y/n)" + QuestionProvideToken = "Would you like to provide a token for the new profile? If answer is no, the CLI will create a new token for you (Y/n)" + FieldToken = "Please inform a token for the new profile" + FieldProfileName = "Please inform a name for the new profile" + QuestionCollectMetrics = "To better understand user needs and enhance our application, we gather anonymous data. Do you agree to participate? (Y/n)" + CreateOutputSuccess = "Profile '%s' created successfully" + + UsageProfiles = "profiles" + ProfilesShortDescription = "Manage profiles" + ProfilesLongDescription = "Manage profiles that you have configured" + ProfilesFlagHelp = "Displays more information about the profiles command" + SwitchSuccessful = "Profile switched successfully" + + UsageDelete = "profile" + DeleteShortDescription = "Delete a profile" + DeleteLongDescription = "Delete a profile and all its associated data" + DeleteFlagHelp = "Displays more information about the delete profile subcommand" + DeleteOutputSuccess = "Profile '%s' deleted successfully" + QuestionDeleteProfile = "Choose a profile to delete:" + ConfirmDeleteProfile = "Are you sure you want to delete profile '%s'? This action cannot be undone (Y/n)" +) diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go index a4ab64f25..f2a193b7f 100644 --- a/pkg/cmd/create/create.go +++ b/pkg/cmd/create/create.go @@ -10,6 +10,7 @@ import ( functionInstance "github.com/aziontech/azion-cli/pkg/cmd/create/function_instance" origin "github.com/aziontech/azion-cli/pkg/cmd/create/origin" token "github.com/aziontech/azion-cli/pkg/cmd/create/personal_token" + profile "github.com/aziontech/azion-cli/pkg/cmd/create/profile" rulesEngine "github.com/aziontech/azion-cli/pkg/cmd/create/rules_engine" edgeStorage "github.com/aziontech/azion-cli/pkg/cmd/create/storage" "github.com/aziontech/azion-cli/pkg/cmd/create/variables" @@ -46,6 +47,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(workloaddeployment.NewCmd(f)) cmd.AddCommand(edgeConnector.NewCmd(f)) cmd.AddCommand(functionInstance.NewCmd(f)) + cmd.AddCommand(profile.NewCmd(f)) cmd.Flags().BoolP("help", "h", false, msg.FlagHelp) return cmd diff --git a/pkg/cmd/create/profile/profile.go b/pkg/cmd/create/profile/profile.go new file mode 100644 index 000000000..fd0888b7c --- /dev/null +++ b/pkg/cmd/create/profile/profile.go @@ -0,0 +1,138 @@ +package profile + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/MakeNowJust/heredoc" + msg "github.com/aziontech/azion-cli/messages/profile" + api "github.com/aziontech/azion-cli/pkg/api/personal_token" + "github.com/aziontech/azion-cli/pkg/cmdutil" + "github.com/aziontech/azion-cli/pkg/output" + "github.com/aziontech/azion-cli/pkg/token" + "github.com/aziontech/azion-cli/utils" + "github.com/pelletier/go-toml/v2" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +type Fields struct { + File string + Name string + Token string +} + +var confirmFn = utils.Confirm + +func NewCmd(f *cmdutil.Factory) *cobra.Command { + fields := &Fields{} + + cmd := &cobra.Command{ + Use: msg.UsageCreate, + Short: msg.CreateShortDescription, + Long: msg.CreateLongDescription, + SilenceUsage: true, + SilenceErrors: true, + Example: heredoc.Doc(` + $ azion create profile --file "create.toml" + $ azion create profile" + `), + RunE: func(cmd *cobra.Command, args []string) error { + settings := &token.Settings{} + if !cmd.Flags().Changed("name") { + profileName, err := utils.AskInput(msg.FieldProfileName) + if err != nil { + return utils.ErrorParseResponse + } + fields.Name = profileName + } + + if cmd.Flags().Changed("file") { + fileData, err := os.ReadFile(fields.File) + if err != nil { + return fmt.Errorf(msg.ErrorReadFile.Error(), err) + } + + err = toml.Unmarshal(fileData, settings) + if err != nil { + return fmt.Errorf(msg.ErrorUnmarshalFile.Error(), err) + } + } else { + authorize := confirmFn(f.GlobalFlagAll, msg.QuestionCollectMetrics, true) + if authorize { + settings.AuthorizeMetricsCollection = 1 + } else { + settings.AuthorizeMetricsCollection = 2 + } + + if !cmd.Flags().Changed("personal-token") { + authorizeToken := confirmFn(f.GlobalFlagAll, msg.QuestionToken, true) + if authorizeToken { + provideToken := confirmFn(f.GlobalFlagAll, msg.QuestionProvideToken, true) + if provideToken { + tokenString, err := utils.AskInput(msg.FieldToken) + if err != nil { + return utils.ErrorParseResponse + } + fields.Token = tokenString + } else { + // Create new token via API + request := api.Request{} + request.SetName(fields.Name) + request.SetExpiresAt(time.Now().Add(8760 * time.Hour)) + response, err := api.NewClient(f.HttpClient, f.Config.GetString("api_url"), f.Config.GetString("token")).Create(context.Background(), &request) + if err != nil { + return fmt.Errorf(msg.ErrorCreate.Error(), err) + } + fields.Token = response.GetKey() + settings.UUID = response.GetUuid() + } + } + } + + // Validate token if one was provided or created + if fields.Token != "" { + t := token.New(&token.Config{ + Client: f.HttpClient, + Out: f.IOStreams.Out, + }) + valid, user, err := t.Validate(&fields.Token) + if err != nil { + return err + } + + if !valid { + return utils.ErrorInvalidToken + } + settings.Token = fields.Token + settings.ClientId = user.Results.ClientID + settings.Email = user.Results.Email + } + } + + if err := token.WriteSettings(*settings, fields.Name); err != nil { + return err + } + + profileOut := output.GeneralOutput{ + Msg: fmt.Sprintf(msg.CreateOutputSuccess, fields.Name), + Out: f.IOStreams.Out, + } + return output.Print(&profileOut) + }, + } + + flags := cmd.Flags() + addFlags(flags, fields) + + return cmd +} + +func addFlags(flags *pflag.FlagSet, fields *Fields) { + flags.BoolP("help", "h", false, msg.CreateFlagHelp) + flags.StringVar(&fields.File, "file", "", msg.FlagFile) + flags.StringVar(&fields.Name, "name", "", msg.FlagName) + flags.StringVar(&fields.Token, "personal-token", "", msg.FlagToken) +} diff --git a/pkg/cmd/delete/delete.go b/pkg/cmd/delete/delete.go index 9c3881ace..12ed32b4a 100644 --- a/pkg/cmd/delete/delete.go +++ b/pkg/cmd/delete/delete.go @@ -10,6 +10,7 @@ import ( functionInstance "github.com/aziontech/azion-cli/pkg/cmd/delete/function_instance" origin "github.com/aziontech/azion-cli/pkg/cmd/delete/origin" token "github.com/aziontech/azion-cli/pkg/cmd/delete/personal_token" + profile "github.com/aziontech/azion-cli/pkg/cmd/delete/profile" rulesEngine "github.com/aziontech/azion-cli/pkg/cmd/delete/rules_engine" storage "github.com/aziontech/azion-cli/pkg/cmd/delete/storage" "github.com/aziontech/azion-cli/pkg/cmd/delete/variables" @@ -44,6 +45,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(storage.NewCmd(f)) cmd.AddCommand(connector.NewCmd(f)) cmd.AddCommand(functionInstance.NewCmd(f)) + cmd.AddCommand(profile.NewCmd(f)) cmd.Flags().BoolP("help", "h", false, msg.FlagHelp) return cmd diff --git a/pkg/cmd/delete/profile/profile.go b/pkg/cmd/delete/profile/profile.go new file mode 100644 index 000000000..9e675c167 --- /dev/null +++ b/pkg/cmd/delete/profile/profile.go @@ -0,0 +1,141 @@ +package profile + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/MakeNowJust/heredoc" + msg "github.com/aziontech/azion-cli/messages/profile" + api "github.com/aziontech/azion-cli/pkg/api/personal_token" + "github.com/aziontech/azion-cli/pkg/cmdutil" + "github.com/aziontech/azion-cli/pkg/config" + "github.com/aziontech/azion-cli/pkg/output" + "github.com/aziontech/azion-cli/pkg/token" + "github.com/aziontech/azion-cli/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +type Fields struct { + Name string +} + +var confirmFn = utils.Confirm + +func NewCmd(f *cmdutil.Factory) *cobra.Command { + fields := &Fields{} + + cmd := &cobra.Command{ + Use: msg.UsageDelete, + Short: msg.DeleteShortDescription, + Long: msg.DeleteLongDescription, + SilenceUsage: true, + SilenceErrors: true, + Example: heredoc.Doc(` + $ azion delete profile --name "my-profile" + $ azion delete profile + `), + RunE: func(cmd *cobra.Command, args []string) error { + var profileToDelete string + + if !cmd.Flags().Changed("name") { + // List profiles and let user choose + dir := config.Dir() + entries, err := os.ReadDir(dir.Dir) + if err != nil { + return fmt.Errorf("%w", msg.ErrorReadDir) + } + + var profileNames []string + for _, entry := range entries { + if entry.IsDir() && !strings.HasPrefix(entry.Name(), "tempclonesamples") { + profileNames = append(profileNames, entry.Name()) + } + } + + if len(profileNames) == 0 { + return fmt.Errorf("No profiles found") + } + + prompt := &survey.Select{ + Message: msg.QuestionDeleteProfile, + Options: profileNames, + PageSize: len(profileNames), + } + + err = survey.AskOne(prompt, &profileToDelete) + if err != nil { + return err + } + } else { + profileToDelete = fields.Name + } + + // Prevent deletion of default profile + if profileToDelete == "default" { + return msg.ErrorCannotDeleteDefault + } + + // Confirm deletion + confirmDelete := confirmFn(false, fmt.Sprintf(msg.ConfirmDeleteProfile, profileToDelete), true) + if !confirmDelete { + return fmt.Errorf("Profile deletion cancelled") + } + + // Check if profile exists + dir := config.Dir() + profilePath := filepath.Join(dir.Dir, profileToDelete) + if _, err := os.Stat(profilePath); os.IsNotExist(err) { + return fmt.Errorf("Profile '%s' not found", profileToDelete) + } + + // Try to delete token if UUID exists + settings, err := token.ReadSettings(profileToDelete) + if err == nil && settings.UUID != "" { + client := api.NewClient(f.HttpClient, f.Config.GetString("api_url"), settings.Token) + err = client.Delete(context.Background(), settings.UUID) + if err != nil { + // Log warning but don't fail the profile deletion + fmt.Fprintf(f.IOStreams.Out, "Warning: Failed to delete token from server: %v\n", err) + } + } + + // Delete the profile directory and all its contents + err = os.RemoveAll(profilePath) + if err != nil { + return fmt.Errorf("Failed to delete the profile: %w", err) + } + + // Check if deleted profile was the active one + currentProfile, _, err := token.ReadProfiles() + if err == nil && currentProfile.Name == profileToDelete { + // Set active profile to "default" + defaultProfile := token.Profile{Name: "default"} + err = token.WriteProfiles(defaultProfile) + if err != nil { + fmt.Fprintf(f.IOStreams.Out, "Warning: Failed to set active profile to default: %v\n", err) + } + } + + profileOut := output.GeneralOutput{ + Msg: fmt.Sprintf(msg.DeleteOutputSuccess, profileToDelete), + Out: f.IOStreams.Out, + } + return output.Print(&profileOut) + }, + } + + flags := cmd.Flags() + addFlags(flags, fields) + + return cmd +} + +func addFlags(flags *pflag.FlagSet, fields *Fields) { + flags.BoolP("help", "h", false, msg.DeleteFlagHelp) + flags.StringVar(&fields.Name, "name", "", msg.FlagName) +} diff --git a/pkg/cmd/deploy/deploy.go b/pkg/cmd/deploy/deploy.go index c8c072ef6..ebcea03e4 100644 --- a/pkg/cmd/deploy/deploy.go +++ b/pkg/cmd/deploy/deploy.go @@ -51,7 +51,7 @@ type DeployCmd struct { OpenBrowser func(f *cmdutil.Factory, urlConsoleDeploy string, cmd *DeployCmd) error CaptureLogs func(execId string, token string, cmd *DeployCmd) error CheckToken func(f *cmdutil.Factory) error - ReadSettings func() (token.Settings, error) + ReadSettings func(path string) (token.Settings, error) UploadFiles func(f *cmdutil.Factory, conf *contracts.AzionApplicationOptions, msgs *[]string, pathStatic, bucket string, cmd *DeployCmd, settings token.Settings) error OpenBrowserFunc func(input string) error } @@ -144,6 +144,7 @@ func (cmd *DeployCmd) ExternalRun(f *cmdutil.Factory, configPath string, sync, l } func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { + activeProfile := f.GetActiveProfile() if DryRun { dryStructure := dryrun.NewDryrunCmd(f) @@ -169,7 +170,7 @@ func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { return err } - settings, err := cmd.ReadSettings() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -209,7 +210,7 @@ func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { settings.S3SecretKey = creds.Data.GetSecretKey() settings.S3Bucket = nameBucket - err = token.WriteSettings(settings) + err = token.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/cmd/deploy/deploy_test.go b/pkg/cmd/deploy/deploy_test.go index 42484b0b1..279b03850 100644 --- a/pkg/cmd/deploy/deploy_test.go +++ b/pkg/cmd/deploy/deploy_test.go @@ -46,7 +46,7 @@ func MockFileReader(path string) ([]byte, error) { return nil, errors.New("file not found") } -func MockReadSettings() (token.Settings, error) { +func MockReadSettings(path string) (token.Settings, error) { return token.Settings{Token: "123321", S3AccessKey: "122221", S3SecretKey: "3333322222", S3Bucket: "bucketname"}, nil } diff --git a/pkg/cmd/list/storage/object/object.go b/pkg/cmd/list/storage/object/object.go index 3dffad079..4559dc5b8 100644 --- a/pkg/cmd/list/storage/object/object.go +++ b/pkg/cmd/list/storage/object/object.go @@ -57,8 +57,9 @@ func (b *Objects) RunE(cmd *cobra.Command, args []string) error { func (b *Objects) PrintTable(client *api.Client) error { c := context.Background() + activeProfile := b.Factory.GetActiveProfile() - settings, err := token.ReadSettings() + settings, err := token.ReadSettings(activeProfile) if err != nil { return err } @@ -82,7 +83,7 @@ func (b *Objects) PrintTable(client *api.Client) error { } settings.ContinuationToken = resp.GetContinuationToken() - err = token.WriteSettings(settings) + err = token.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/cmd/login/login.go b/pkg/cmd/login/login.go index 450b28276..f40ab24b7 100644 --- a/pkg/cmd/login/login.go +++ b/pkg/cmd/login/login.go @@ -22,8 +22,12 @@ import ( var ( username, password, tokenValue, uuid string userInfo token.UserInfo + createNewProfile bool + newProfileName string ) +var confirmFn = utils.Confirm + type login struct { factory *cmdutil.Factory askOne func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error @@ -63,6 +67,15 @@ func cmd(l *login) *cobra.Command { $ azion login --username fulanodasilva@gmail.com --password "senhasecreta" `), RunE: func(cmd *cobra.Command, args []string) error { + createNewProfile = confirmFn(l.factory.GlobalFlagAll, msg.QuestionCreateProfile, true) + if createNewProfile { + profileNameInput, err := utils.AskInput(msg.AskProfileName) + if err != nil { + return fmt.Errorf(msg.ErrorGetProfileName.Error(), err) + } + newProfileName = profileNameInput + } + answer, err := l.selectLoginMode() if err != nil { return err @@ -133,17 +146,31 @@ func (l *login) saveSettings() error { S3Bucket: "", } - byteSettings, err := l.marshalToml(settings) - if err != nil { - logger.Debug("Error while marshalling toml file", zap.Error(err)) - return err + var profileName string + if createNewProfile { + profileName = newProfileName + + err := token.WriteSettings(settings, profileName) + if err != nil { + return err + } + + profile := token.Profile{Name: profileName} + err = token.WriteProfiles(profile) + if err != nil { + return fmt.Errorf("failed to set new profile as active: %w", err) + } + + fmt.Fprintf(l.factory.IOStreams.Out, msg.ProfileCreated+"\n", profileName) + } else { + profileName = l.factory.GetActiveProfile() + err := token.WriteSettings(settings, profileName) + if err != nil { + return err + } } - _, err = l.token.Save(byteSettings) - if err != nil { - logger.Debug("Error while saving settings", zap.Error(err)) - return err - } + fmt.Fprintf(l.factory.IOStreams.Out, msg.TokenSavedToProfile+"\n", profileName) return nil } diff --git a/pkg/cmd/login/terminal_test.go b/pkg/cmd/login/terminal_test.go index 2478cb221..ee3ed6ee5 100644 --- a/pkg/cmd/login/terminal_test.go +++ b/pkg/cmd/login/terminal_test.go @@ -103,6 +103,15 @@ func Test_login_terminalLogin(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Mock confirmFn to return false (don't create new profile) + originalConfirmFn := confirmFn + confirmFn = func(globalFlagAll bool, msg string, defaultYes bool) bool { + return false + } + defer func() { + confirmFn = originalConfirmFn + }() + mock := &httpmock.Registry{} mock.Register(tt.register.matcher, tt.register.reponder) f, _, _ := testutils.NewFactory(mock) diff --git a/pkg/cmd/logout/logout.go b/pkg/cmd/logout/logout.go index 3f1a0ec64..c9f3cb12d 100644 --- a/pkg/cmd/logout/logout.go +++ b/pkg/cmd/logout/logout.go @@ -16,8 +16,8 @@ import ( type LogoutCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) - WriteSettings func(token.Settings) error + ReadSettings func(string) (token.Settings, error) + WriteSettings func(token.Settings, string) error DeleteToken func(context.Context, string) error } @@ -42,7 +42,7 @@ func NewCobraCmd(logout *LogoutCmd, f *cmdutil.Factory) *cobra.Command { $ azion logout --help `), RunE: func(cmd *cobra.Command, args []string) error { - return logout.run() + return logout.run(f) }, } @@ -55,8 +55,9 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { return NewCobraCmd(NewLogoutCmd(f), f) } -func (cmd *LogoutCmd) run() error { - settings, err := cmd.ReadSettings() +func (cmd *LogoutCmd) run(f *cmdutil.Factory) error { + activeProfile := f.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -70,7 +71,7 @@ func (cmd *LogoutCmd) run() error { settings.UUID = "" settings.Token = "" - err = cmd.WriteSettings(settings) + err = cmd.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/cmd/logout/logout_test.go b/pkg/cmd/logout/logout_test.go index 82c2c15d0..01736f4b4 100644 --- a/pkg/cmd/logout/logout_test.go +++ b/pkg/cmd/logout/logout_test.go @@ -85,11 +85,11 @@ func TestLogout(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { + mockReadSettings := func(path string) (token.Settings, error) { return tt.tokenSettings, tt.mockReadError } - mockWriteSettings := func(settings token.Settings) error { + mockWriteSettings := func(settings token.Settings, path string) error { return tt.mockWriteError } diff --git a/pkg/cmd/profiles/profiles.go b/pkg/cmd/profiles/profiles.go new file mode 100644 index 000000000..9dcdacce4 --- /dev/null +++ b/pkg/cmd/profiles/profiles.go @@ -0,0 +1,81 @@ +package profiles + +import ( + "fmt" + "os" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/MakeNowJust/heredoc" + msg "github.com/aziontech/azion-cli/messages/profile" + "github.com/aziontech/azion-cli/pkg/cmdutil" + "github.com/aziontech/azion-cli/pkg/config" + "github.com/aziontech/azion-cli/pkg/token" + "github.com/aziontech/azion-cli/utils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var confirmFn = utils.Confirm + +func NewCmd(f *cmdutil.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: msg.UsageProfiles, + Short: msg.ProfilesShortDescription, + Long: msg.ProfilesLongDescription, + SilenceUsage: true, + SilenceErrors: true, + Example: heredoc.Doc(` + $ azion profiles + `), + RunE: func(cmd *cobra.Command, args []string) error { + dir := config.Dir() + entries, err := os.ReadDir(dir.Dir) + if err != nil { + return fmt.Errorf(msg.ErrorReadDir.Error(), err) + } + + var profileNames []string + for _, entry := range entries { + if entry.IsDir() && !strings.HasPrefix(entry.Name(), "tempclonesamples") { + profileNames = append(profileNames, entry.Name()) + } + } + + prompt := &survey.Select{ + Message: "Choose a profile:", + Options: profileNames, + PageSize: len(profileNames), + } + + var answer string + err = survey.AskOne(prompt, &answer) + if err != nil { + return err + } + + profile, _, err := token.ReadProfiles() + if err != nil { + return err + } + + profile.Name = answer + + err = token.WriteProfiles(profile) + if err != nil { + return err + } + + return nil + }, + } + + flags := cmd.Flags() + addFlags(flags) + + return cmd +} + +func addFlags(flags *pflag.FlagSet) { + flags.BoolP("help", "h", false, msg.ProfilesFlagHelp) +} diff --git a/pkg/cmd/reset/reset.go b/pkg/cmd/reset/reset.go index af4c3d81d..86bf824a4 100644 --- a/pkg/cmd/reset/reset.go +++ b/pkg/cmd/reset/reset.go @@ -16,8 +16,8 @@ import ( type ResetCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) - WriteSettings func(token.Settings) error + ReadSettings func(string) (token.Settings, error) + WriteSettings func(token.Settings, string) error DeleteToken func(context.Context, string) error } @@ -42,7 +42,7 @@ func NewCobraCmd(reset *ResetCmd, f *cmdutil.Factory) *cobra.Command { $ azion reset --help `), RunE: func(cmd *cobra.Command, args []string) error { - return reset.run() + return reset.run(f) }, } cobraCmd.Flags().BoolP("help", "h", false, msg.FLAGHELP) @@ -53,8 +53,9 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { return NewCobraCmd(NewResetCmd(f), f) } -func (cmd *ResetCmd) run() error { - settings, err := cmd.ReadSettings() +func (cmd *ResetCmd) run(f *cmdutil.Factory) error { + activeProfile := f.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -67,7 +68,7 @@ func (cmd *ResetCmd) run() error { } settings = token.Settings{} - err = cmd.WriteSettings(settings) + err = cmd.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/cmd/reset/reset_test.go b/pkg/cmd/reset/reset_test.go index 7a3c43258..6d2677a72 100644 --- a/pkg/cmd/reset/reset_test.go +++ b/pkg/cmd/reset/reset_test.go @@ -85,11 +85,11 @@ func TestReset(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { + mockReadSettings := func(string) (token.Settings, error) { return tt.tokenSettings, tt.mockReadError } - mockWriteSettings := func(settings token.Settings) error { + mockWriteSettings := func(settings token.Settings, path string) error { return tt.mockWriteError } diff --git a/pkg/cmd/root/pre_command.go b/pkg/cmd/root/pre_command.go index 4a90edbd6..68f95011e 100644 --- a/pkg/cmd/root/pre_command.go +++ b/pkg/cmd/root/pre_command.go @@ -2,6 +2,8 @@ package root import ( "fmt" + "os" + "path/filepath" "strconv" "strings" "time" @@ -18,7 +20,6 @@ import ( "github.com/aziontech/azion-cli/pkg/metric" "github.com/aziontech/azion-cli/pkg/token" "github.com/aziontech/azion-cli/utils" - "github.com/pelletier/go-toml" "github.com/spf13/cobra" ) @@ -30,6 +31,10 @@ type OSInfo struct { // doPreCommandCheck carry out all pre-cmd checks needed func doPreCommandCheck(cmd *cobra.Command, fact *factoryRoot) error { + // Check if profiles.json exists, create it with default profile if not + if err := ensureProfilesExist(); err != nil { + return err + } // Apply timeout based on the parsed flags timeout, err := cmd.Flags().GetInt("timeout") @@ -50,30 +55,39 @@ func doPreCommandCheck(cmd *cobra.Command, fact *factoryRoot) error { } } - settings, err := token.ReadSettings() - if err != nil { - return err - } - fact.globalSettings = &settings - t := token.New(&token.Config{ Client: fact.factory.HttpClient, Out: fact.factory.IOStreams.Out, }) if cmd.Flags().Changed("token") { - if err := checkTokenSent(fact, fact.globalSettings, t); err != nil { + // When token is provided, use empty settings initially to avoid creating base settings.toml + emptySettings := token.Settings{} + if err := checkTokenSent(fact, &emptySettings, t); err != nil { return err } - } + // fact.globalSettings is set inside checkTokenSent, so we can proceed + } else { + // Only read existing settings if no token is being provided + activeProfile := fact.factory.GetActiveProfile() + settings, err := token.ReadSettings(activeProfile) + if err != nil { + return err + } + fact.globalSettings = &settings - if err := checkAuthorizeMetricsCollection(cmd, fact.factory.GlobalFlagAll, fact.globalSettings); err != nil { - return err + // Only check metrics authorization if no token was provided + if err := checkAuthorizeMetricsCollection(cmd, fact.factory.GlobalFlagAll, fact.globalSettings, activeProfile); err != nil { + return err + } } //both verifications occurs if 24 hours have passed since the last execution - if err := checkForUpdateAndMetrics(version.BinVersion, fact.factory, fact.globalSettings); err != nil { - return err + // Skip update check when token is being provided to avoid creating base settings + if !cmd.Flags().Changed("token") && fact.globalSettings != nil { + if err := checkForUpdateAndMetrics(version.BinVersion, fact.factory, fact.globalSettings); err != nil { + return err + } } return nil @@ -93,6 +107,9 @@ func checkTokenSent(fact *factoryRoot, settings *token.Settings, tokenStr *token return utils.ErrorInvalidToken } + // When setting a token, always use "default" profile to avoid config system initialization + activeProfile := "default" + strToken := token.Settings{ Token: fact.tokenFlag, ClientId: user.Results.ClientID, @@ -103,18 +120,21 @@ func checkTokenSent(fact *factoryRoot, settings *token.Settings, tokenStr *token S3Bucket: "", } - bStrToken, err := toml.Marshal(strToken) - if err != nil { - return err - } - - filePath, err := tokenStr.Save(bStrToken) + // Save token to the active profile's settings + err = token.WriteSettings(strToken, activeProfile) if err != nil { return err } fact.globalSettings = &strToken + // Create a profile-aware file path for the message + dir := config.Dir() + if activeProfile != "" { + dir.Dir = filepath.Join(dir.Dir, activeProfile) + } + filePath := filepath.Join(dir.Dir, dir.Settings) + logger.FInfo(fact.factory.IOStreams.Out, fmt.Sprintf(msg.TokenSavedIn, filePath)) logger.FInfo(fact.factory.IOStreams.Out, msg.TokenUsedIn+"\n") return nil @@ -122,6 +142,7 @@ func checkTokenSent(fact *factoryRoot, settings *token.Settings, tokenStr *token func checkForUpdateAndMetrics(cVersion string, f *cmdutil.Factory, settings *token.Settings) error { logger.Debug("Verifying if an update is required") + activeProfile := f.GetActiveProfile() // checks if 24 hours have passed since the last check if time.Since(settings.LastCheck) < 24*time.Hour && !settings.LastCheck.IsZero() { return nil @@ -129,7 +150,7 @@ func checkForUpdateAndMetrics(cVersion string, f *cmdutil.Factory, settings *tok // checks if user is Logged in before sending metrics if verifyUserInfo(settings) { - metric.Send(settings) + metric.Send(settings, activeProfile) } git := github.NewGithub() @@ -153,7 +174,6 @@ func checkForUpdateAndMetrics(cVersion string, f *cmdutil.Factory, settings *tok } if latestVersion > currentVersion { - // Parse the published_at date publishedTime, err := time.Parse(time.RFC3339, publishedAt) if err != nil { logger.Debug("Failed to parse published_at date", zap.Error(err)) @@ -175,7 +195,7 @@ func checkForUpdateAndMetrics(cVersion string, f *cmdutil.Factory, settings *tok // Update the last update check time settings.LastCheck = time.Now() - if err := token.WriteSettings(*settings); err != nil { + if err := token.WriteSettings(*settings, activeProfile); err != nil { return err } @@ -218,7 +238,7 @@ func format(input string) (int, error) { var confirmFn = utils.Confirm // 0 = authorization was not asked yet, 1 = accepted, 2 = denied -func checkAuthorizeMetricsCollection(cmd *cobra.Command, globalFlagAll bool, settings *token.Settings) error { +func checkAuthorizeMetricsCollection(cmd *cobra.Command, globalFlagAll bool, settings *token.Settings, activeProfile string) error { if settings.AuthorizeMetricsCollection > 0 || cmd.Name() == "completion" { return nil } @@ -230,7 +250,7 @@ func checkAuthorizeMetricsCollection(cmd *cobra.Command, globalFlagAll bool, set settings.AuthorizeMetricsCollection = 2 } - if err := token.WriteSettings(*settings); err != nil { + if err := token.WriteSettings(*settings, activeProfile); err != nil { return err } @@ -244,3 +264,29 @@ func verifyUserInfo(settings *token.Settings) bool { return false } + +// function that ensures the profiles.json file exists +func ensureProfilesExist() error { + dir := config.Dir() + profilesPath := filepath.Join(dir.Dir, dir.Profiles) + + if _, err := os.Stat(profilesPath); err == nil { + return nil + } else if !os.IsNotExist(err) { + return fmt.Errorf(utils.ErrorCheckingProfilesFile.Error(), err) + } + + defaultProfile := token.Profile{ + Name: "default", + } + if err := os.MkdirAll(dir.Dir, 0755); err != nil { + return fmt.Errorf(utils.ErrorCreatingConfigDirectory.Error(), err) + } + + if err := token.WriteProfiles(defaultProfile); err != nil { + return fmt.Errorf(utils.ErrorCreatingDefaultProfiles.Error(), err) + } + + logger.Debug("Created default profiles.json with default profile") + return nil +} diff --git a/pkg/cmd/root/pre_command_test.go b/pkg/cmd/root/pre_command_test.go index 50aa30049..352d41b99 100644 --- a/pkg/cmd/root/pre_command_test.go +++ b/pkg/cmd/root/pre_command_test.go @@ -68,7 +68,7 @@ func TestCheckAuthorizeMetricsCollection(t *testing.T) { confirmFn = func(globalFlagAll bool, msg string, defaultValue bool) bool { return tt.mockConfirm } - err := checkAuthorizeMetricsCollection(cmd, tt.globalFlagAll, &settings) + err := checkAuthorizeMetricsCollection(cmd, tt.globalFlagAll, &settings, "default") assert.Equal(t, tt.expectedErr, err) assert.Equal(t, tt.expectedSettings.AuthorizeMetricsCollection, settings.AuthorizeMetricsCollection) }) diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 941cb840b..f7401d83a 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -18,6 +18,7 @@ import ( "github.com/aziontech/azion-cli/pkg/cmd/login" "github.com/aziontech/azion-cli/pkg/cmd/logout" logcmd "github.com/aziontech/azion-cli/pkg/cmd/logs" + "github.com/aziontech/azion-cli/pkg/cmd/profiles" "github.com/aziontech/azion-cli/pkg/cmd/purge" "github.com/aziontech/azion-cli/pkg/cmd/reset" "github.com/aziontech/azion-cli/pkg/cmd/rollback" @@ -151,6 +152,7 @@ func (fact *factoryRoot) setV3Cmds(cobraCmd *cobra.Command) { cobraCmd.AddCommand(v3reset.NewCmd(fact.factory)) cobraCmd.AddCommand(v3sync.NewCmd(fact.factory)) cobraCmd.AddCommand(v3rollback.NewCmd(fact.factory)) + cobraCmd.AddCommand(profiles.NewCmd(fact.factory)) } func (fact *factoryRoot) setCmds(cobraCmd *cobra.Command) { @@ -177,6 +179,7 @@ func (fact *factoryRoot) setCmds(cobraCmd *cobra.Command) { cobraCmd.AddCommand(rollback.NewCmd(fact.factory)) cobraCmd.AddCommand(clone.NewCmd(fact.factory)) cobraCmd.AddCommand(warmup.NewCmd(fact.factory)) + cobraCmd.AddCommand(profiles.NewCmd(fact.factory)) } func (fact *factoryRoot) CmdRoot() cmdutil.Command { @@ -239,7 +242,8 @@ func Execute(f *factoryRoot) { // 1 = authorize; anything different than 1 means that the user did not authorize metrics collection, or did not answer the question yet if f.globalSettings != nil { if f.globalSettings.AuthorizeMetricsCollection == 1 { - errMetrics := metric.TotalCommandsCount(cmd, f.commandName, executionTime, err) + activeProfile := f.factory.GetActiveProfile() + errMetrics := metric.TotalCommandsCount(cmd, f.commandName, executionTime, err, activeProfile) if errMetrics != nil { logger.Debug("Error while saving metrics", zap.Error(err)) } diff --git a/pkg/cmd/whoami/whoami.go b/pkg/cmd/whoami/whoami.go index 9661c0509..213e5e228 100644 --- a/pkg/cmd/whoami/whoami.go +++ b/pkg/cmd/whoami/whoami.go @@ -14,7 +14,7 @@ import ( type WhoamiCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) + ReadSettings func(string) (token.Settings, error) F *cmdutil.Factory } @@ -50,7 +50,8 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { } func (cmd *WhoamiCmd) run() error { - settings, err := cmd.ReadSettings() + activeProfile := cmd.F.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -59,7 +60,7 @@ func (cmd *WhoamiCmd) run() error { return msg.ErrorNotLoggedIn } - msg := fmt.Sprintf(" Client ID: %s\n Email: %s\n", settings.ClientId, settings.Email) + msg := fmt.Sprintf(" Client ID: %s\n Email: %s\n Active Profile: %s\n", settings.ClientId, settings.Email, activeProfile) whoamiOut := output.GeneralOutput{ Msg: msg, diff --git a/pkg/cmd/whoami/whoami_test.go b/pkg/cmd/whoami/whoami_test.go index 4cfe6a1ae..c0653615a 100644 --- a/pkg/cmd/whoami/whoami_test.go +++ b/pkg/cmd/whoami/whoami_test.go @@ -1,83 +1,83 @@ package whoami import ( - "errors" - "testing" + "errors" + "testing" - msg "github.com/aziontech/azion-cli/messages/whoami" - "github.com/aziontech/azion-cli/pkg/httpmock" - "github.com/aziontech/azion-cli/pkg/logger" - "github.com/aziontech/azion-cli/pkg/testutils" - "github.com/aziontech/azion-cli/pkg/token" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" + msg "github.com/aziontech/azion-cli/messages/whoami" + "github.com/aziontech/azion-cli/pkg/httpmock" + "github.com/aziontech/azion-cli/pkg/logger" + "github.com/aziontech/azion-cli/pkg/testutils" + "github.com/aziontech/azion-cli/pkg/token" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" ) func TestWhoami(t *testing.T) { - logger.New(zapcore.DebugLevel) + logger.New(zapcore.DebugLevel) - tests := []struct { - name string - tokenSettings token.Settings - mockReadError error - expectedOutput string - expectedError error - }{ - { - name: "whoami - logged in", - tokenSettings: token.Settings{ - Email: "test@example.com", - ClientId: "abcd-1234", - }, - mockReadError: nil, - expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n", - expectedError: nil, - }, - { - name: "whoami - not logged in", - tokenSettings: token.Settings{ - Email: "", - }, - mockReadError: nil, - expectedOutput: "", - expectedError: msg.ErrorNotLoggedIn, - }, - { - name: "whoami - failed to read settings", - tokenSettings: token.Settings{}, - mockReadError: errors.New("failed to get token dir"), - expectedOutput: "", - expectedError: errors.New("failed to get token dir"), - }, - } + tests := []struct { + name string + tokenSettings token.Settings + mockReadError error + expectedOutput string + expectedError error + }{ + { + name: "whoami - logged in", + tokenSettings: token.Settings{ + Email: "test@example.com", + ClientId: "abcd-1234", + }, + mockReadError: nil, + expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n Active Profile: \n", + expectedError: nil, + }, + { + name: "whoami - not logged in", + tokenSettings: token.Settings{ + Email: "", + }, + mockReadError: nil, + expectedOutput: "", + expectedError: msg.ErrorNotLoggedIn, + }, + { + name: "whoami - failed to read settings", + tokenSettings: token.Settings{}, + mockReadError: errors.New("failed to get token dir"), + expectedOutput: "", + expectedError: errors.New("failed to get token dir"), + }, + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { - return tt.tokenSettings, tt.mockReadError - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockReadSettings := func(path string) (token.Settings, error) { + return tt.tokenSettings, tt.mockReadError + } - mock := &httpmock.Registry{} + mock := &httpmock.Registry{} - f, out, _ := testutils.NewFactory(mock) - f.Flags.NoColor = true + f, out, _ := testutils.NewFactory(mock) + f.Flags.NoColor = true - whoamiCmd := &WhoamiCmd{ - Io: f.IOStreams, - ReadSettings: mockReadSettings, - F: f, - } - cmd := NewCobraCmd(whoamiCmd, f) + whoamiCmd := &WhoamiCmd{ + Io: f.IOStreams, + ReadSettings: mockReadSettings, + F: f, + } + cmd := NewCobraCmd(whoamiCmd, f) - _, err := cmd.ExecuteC() - if tt.expectedError != nil { - require.Error(t, err) - assert.Equal(t, tt.expectedError.Error(), err.Error()) - } else { - require.NoError(t, err) - assert.Equal(t, tt.expectedOutput, out.String()) - } - }) - } + _, err := cmd.ExecuteC() + if tt.expectedError != nil { + require.Error(t, err) + assert.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedOutput, out.String()) + } + }) + } } diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index 1fd50e8bc..3626a8a8b 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -2,6 +2,8 @@ package cmdutil import ( "net/http" + "path/filepath" + "strings" "github.com/aziontech/azion-cli/pkg/config" "github.com/aziontech/azion-cli/pkg/iostreams" @@ -22,3 +24,19 @@ type Flags struct { Format string `json:"-" yaml:"-" toml:"-"` NoColor bool `json:"-" yaml:"-" toml:"-"` } + +// GetActiveProfile returns the active profile name from config, defaulting to "default" if empty +func (f *Factory) GetActiveProfile() string { + activeProfile := f.Config.GetString("active_profile") + + if activeProfile == "" { + return "default" + } + + // If the value looks like a path, extract just the profile name (last component) + if strings.Contains(activeProfile, "/") { + return filepath.Base(activeProfile) + } + + return activeProfile +} diff --git a/pkg/config/config.go b/pkg/config/config.go index e528e2c43..50bea6607 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -13,11 +13,13 @@ const ( DEFAULT_SETTINGS = "settings.toml" DEFAULT_METRICS = "metrics.json" DEFAULT_SCHEDULE = "schedule.json" + DEFAULT_PROFILES = "profiles.json" ) var ( pathDir string = DEFAULT_DIR pathSettings string = DEFAULT_SETTINGS + pathProfiles string = DEFAULT_PROFILES ) type Config interface { @@ -31,6 +33,7 @@ func SetPath(path string) error { pathDir = filepath.Dir(path) pathSettings = filepath.Base(path) + pathProfiles = DEFAULT_PROFILES return nil } @@ -44,6 +47,7 @@ type DirPath struct { Settings string Metrics string Schedule string + Profiles string } func Dir() DirPath { @@ -56,6 +60,7 @@ func Dir() DirPath { dirPath := DirPath{ Dir: filepath.Join(home, pathDir), Settings: pathSettings, + Profiles: pathProfiles, Metrics: DEFAULT_METRICS, Schedule: DEFAULT_SCHEDULE, } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 55b86e340..39061082e 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -37,6 +37,7 @@ func TestSetPath(t *testing.T) { // Reset to defaults before each test pathDir = DEFAULT_DIR pathSettings = DEFAULT_SETTINGS + pathProfiles = DEFAULT_PROFILES err := SetPath(tt.path) if err != tt.wantErr { @@ -75,6 +76,7 @@ func TestGetPath(t *testing.T) { // Reset to defaults before each test pathDir = DEFAULT_DIR pathSettings = DEFAULT_SETTINGS + pathProfiles = DEFAULT_PROFILES if tt.setPath != "" { err := SetPath(tt.setPath) @@ -105,6 +107,7 @@ func TestDir(t *testing.T) { wantDir: DirPath{ Dir: filepath.Join(home, DEFAULT_DIR), Settings: DEFAULT_SETTINGS, + Profiles: DEFAULT_PROFILES, Metrics: DEFAULT_METRICS, Schedule: DEFAULT_SCHEDULE, }, @@ -117,6 +120,7 @@ func TestDir(t *testing.T) { wantDir: DirPath{ Dir: "/home/user/.azion", Settings: "settings.toml", + Profiles: DEFAULT_PROFILES, Metrics: DEFAULT_METRICS, Schedule: DEFAULT_SCHEDULE, }, @@ -129,6 +133,7 @@ func TestDir(t *testing.T) { // Reset to defaults before each test pathDir = DEFAULT_DIR pathSettings = DEFAULT_SETTINGS + pathProfiles = DEFAULT_PROFILES // Set environment variable for HOME if tt.envHome != "" { diff --git a/pkg/metric/count.go b/pkg/metric/count.go index fc4f49d61..c28a5b321 100644 --- a/pkg/metric/count.go +++ b/pkg/metric/count.go @@ -24,7 +24,7 @@ type command struct { Shell string } -func TotalCommandsCount(cmd cmdutil.Command, commandName string, executionTime float64, errExec error) error { +func TotalCommandsCount(cmd cmdutil.Command, commandName string, executionTime float64, errExec error, profile string) error { if commandName == "" { return nil } @@ -35,6 +35,9 @@ func TotalCommandsCount(cmd cmdutil.Command, commandName string, executionTime f } dir := config.Dir() + if profile != "" { + dir.Dir = filepath.Join(dir.Dir, profile) + } ignoredWords := map[string]bool{ "__complete": true, "completion": true, diff --git a/pkg/metric/segment.go b/pkg/metric/segment.go index a7c9bab55..c4bb5b3fc 100644 --- a/pkg/metric/segment.go +++ b/pkg/metric/segment.go @@ -17,13 +17,16 @@ import ( const SEGMENT_KEY = "Irg63QfdvWpoANAVeCBEwfxXBKvoSSzt" -func location() string { +func location(profile string) string { dir := config.Dir() + if profile != "" { + dir.Dir = filepath.Join(dir.Dir, profile) + } return filepath.Join(dir.Dir, dir.Metrics) } -func readLocalMetrics() map[string]command { - file, err := os.OpenFile(location(), os.O_RDWR|os.O_CREATE, 0666) +func readLocalMetrics(profile string) map[string]command { + file, err := os.OpenFile(location(profile), os.O_RDWR|os.O_CREATE, 0666) if err != nil { return nil } @@ -43,11 +46,11 @@ func readLocalMetrics() map[string]command { return data } -func Send(settings *token.Settings) { +func Send(settings *token.Settings, profile string) { client := analytics.New(SEGMENT_KEY) defer client.Close() - metrics := readLocalMetrics() + metrics := readLocalMetrics(profile) os := runtime.GOOS arch := runtime.GOARCH @@ -75,12 +78,12 @@ func Send(settings *token.Settings) { } } - clean() + clean(profile) } // cleans metrics location and rewrites the file with empty content -func clean() { - err := os.WriteFile(location(), []byte{}, 0666) +func clean(profile string) { + err := os.WriteFile(location(profile), []byte{}, 0666) if err != nil { return } diff --git a/pkg/schedule/schedule.go b/pkg/schedule/schedule.go index ea35bf918..ac93e54e7 100644 --- a/pkg/schedule/schedule.go +++ b/pkg/schedule/schedule.go @@ -90,6 +90,19 @@ func (s *Factory) createFileSchedule(shedules []Schedule) error { return s.WriteFile(path, b, os.FileMode(os.O_CREATE)) } +func (s *Factory) createFileScheduleForProfile(schedules []Schedule, profile string) error { + b, err := s.MarshalIndent(schedules, " ", " ") + if err != nil { + return err + } + configPath := s.Dir() + if profile != "" { + configPath.Dir = filepath.Join(configPath.Dir, profile) + } + path := s.Join(configPath.Dir, configPath.Schedule) + return s.WriteFile(path, b, 0666) +} + func (s *Factory) readFileSchedule() ([]Schedule, error) { configPath := config.Dir() schedules := []Schedule{} @@ -122,9 +135,44 @@ func (s *Factory) readFileSchedule() ([]Schedule, error) { return schedules, nil } +func (s *Factory) readFileScheduleForProfile(profile string) ([]Schedule, error) { + configPath := config.Dir() + if profile != "" { + configPath.Dir = filepath.Join(configPath.Dir, profile) + } + schedules := []Schedule{} + + path := s.Join(configPath.Dir, configPath.Schedule) + + // Checks if the file exists in the given path + if _, err := s.Stat(path); s.IsNotExist(err) { + data, err := s.Marshal(&schedules) + if err != nil { + return nil, err + } + + if err := s.WriteFile(path, data, 0666); err != nil { + return nil, err + } + return schedules, nil + } + + file, err := s.ReadFile(path) + if err != nil { + return nil, err + } + + err = s.Unmarshal(file, &schedules) + if err != nil { + return nil, err + } + return schedules, nil +} + func ExecSchedules(factory *cmdutil.Factory) { logger.Debug("Exec Schedules") - schedules, err := factoryShedule.readFileSchedule() + activeProfile := factory.GetActiveProfile() + schedules, err := factoryShedule.readFileScheduleForProfile(activeProfile) if err != nil { logger.Debug("Error while reading the schedule", zap.Error(err)) return @@ -142,7 +190,7 @@ func ExecSchedules(factory *cmdutil.Factory) { } } - if err := factoryShedule.createFileSchedule(scheds); err != nil { + if err := factoryShedule.createFileScheduleForProfile(scheds, activeProfile); err != nil { logger.Debug("Scheduling error", zap.Error(err)) } } diff --git a/pkg/token/models.go b/pkg/token/models.go index a2ef15b8c..790d56f04 100644 --- a/pkg/token/models.go +++ b/pkg/token/models.go @@ -25,6 +25,10 @@ type UserInfo struct { } `json:"results"` } +type Profile struct { + Name string +} + type Settings struct { Token string UUID string diff --git a/pkg/token/token.go b/pkg/token/token.go index 6ebbedbda..d1f033ef1 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" - "github.com/aziontech/azion-cli/messages/root" "github.com/aziontech/azion-cli/pkg/logger" "github.com/aziontech/azion-cli/utils" "github.com/pelletier/go-toml/v2" @@ -30,7 +29,7 @@ func New(c *Config) *Token { return &Token{ client: c.Client, Endpoint: constants.AuthURL, - filePath: filepath.Join(dir.Dir, dir.Settings), + filePath: filepath.Join(dir.Dir, dir.Settings), //TODO: here out: c.Out, } } @@ -118,13 +117,17 @@ func (t *Token) Create(b64 string) (*Response, error) { return &result, nil } -func WriteSettings(settings Settings) error { +func WriteSettings(settings Settings, subdir string) error { dir := config.Dir() b, err := toml.Marshal(settings) if err != nil { return err } + if subdir != "" { + dir.Dir = filepath.Join(dir.Dir, subdir) + } + // Check if the directory exists, create it if not if err := os.MkdirAll(dir.Dir, 0777); err != nil { return fmt.Errorf("Error creating directory: %w", err) @@ -137,23 +140,60 @@ func WriteSettings(settings Settings) error { return nil } -func ReadSettings() (Settings, error) { +func ReadProfiles() (Profile, string, error) { + dir := config.Dir() + filePath := filepath.Join(dir.Dir, dir.Profiles) + + fileData, err := os.ReadFile(filePath) + if err != nil { + return Profile{}, "", err + } + + var profile Profile + err = toml.Unmarshal(fileData, &profile) + if err != nil { + return Profile{}, "", fmt.Errorf("Failed parse byte to struct profile: %w", err) + } + + settingsPath := filepath.Join(dir.Dir, dir.Profiles) + + return profile, settingsPath, nil +} + +func WriteProfiles(profile Profile) error { + dir := config.Dir() + b, err := toml.Marshal(profile) + if err != nil { + return err + } + + if err := os.MkdirAll(dir.Dir, 0777); err != nil { + return fmt.Errorf("Error creating directory: %w", err) + } + + if err := os.WriteFile(filepath.Join(dir.Dir, dir.Profiles), b, 0777); err != nil { + return fmt.Errorf(utils.ErrorWriteSettings.Error(), err) + } + + return nil +} + +func ReadSettings(path string) (Settings, error) { dir := config.Dir() + if path != "" { + dir.Dir = filepath.Join(dir.Dir, path) + } filePath := filepath.Join(dir.Dir, dir.Settings) // Check if the file exists if _, err := os.Stat(filePath); os.IsNotExist(err) { - // File does not exist, create it with default settings - if config.GetPath() == config.DEFAULT_DIR { - defaultSettings := Settings{} - err := WriteSettings(defaultSettings) - if err != nil { - return Settings{}, fmt.Errorf("failed to create settings file: %w", err) - } - return defaultSettings, nil + // File does not exist, create it with default settings for the profile + defaultSettings := Settings{} + err := WriteSettings(defaultSettings, path) + if err != nil { + return Settings{}, fmt.Errorf(utils.ErrorWriteSettings.Error(), err) } - - return Settings{}, root.ErrorReadFileSettingsToml + return defaultSettings, nil } // Read the file diff --git a/pkg/token/token_test.go b/pkg/token/token_test.go index 315fba4e0..9f634830d 100644 --- a/pkg/token/token_test.go +++ b/pkg/token/token_test.go @@ -129,13 +129,13 @@ func Test_WriteSettings(t *testing.T) { t.Fatalf("SetPath() error: %s;", errSetPath.Error()) } - err := WriteSettings(settings) + err := WriteSettings(settings, "test") if err != nil { t.Fatalf("WriteSettings() = %v; want nil", err) } dir := config.Dir() - data, err := os.ReadFile(filepath.Join(dir.Dir, dir.Settings)) + data, err := os.ReadFile(filepath.Join(dir.Dir, "test", dir.Settings)) require.NoError(t, err) var readSettings Settings @@ -162,12 +162,12 @@ func Test_ReadSettings(t *testing.T) { UUID: "uuidValue", } - err := WriteSettings(expectedSettings) + err := WriteSettings(expectedSettings, "test") if err != nil { t.Fatalf("WriteSettings() error: %s", err) } - settings, err := ReadSettings() + settings, err := ReadSettings("test") if err != nil { t.Fatalf("ReadSettings() = %v; want nil", err) } @@ -177,19 +177,21 @@ func Test_ReadSettings(t *testing.T) { } }) - t.Run("read settings from non-existing file", func(t *testing.T) { + t.Run("read settings from non-existing file creates default", func(t *testing.T) { errSetPath := config.SetPath("/tmp/testazion/nonexistent.toml") if errSetPath != nil { t.Fatalf("SetPath() error: %s;", errSetPath.Error()) } - settings, err := ReadSettings() - if err == nil { - t.Fatalf("ReadSettings() error = nil; want non-nil error") + settings, err := ReadSettings("nonexistent") + if err != nil { + t.Fatalf("ReadSettings() error = %v; want nil", err) } - if settings != (Settings{}) { - t.Errorf("ReadSettings() = %v; want empty settings", settings) + // Should return default empty settings + expectedSettings := Settings{} + if settings != expectedSettings { + t.Errorf("ReadSettings() = %v; want %v", settings, expectedSettings) } }) } diff --git a/pkg/v3commands/deploy/deploy.go b/pkg/v3commands/deploy/deploy.go index 0200fcbd4..babc686fc 100644 --- a/pkg/v3commands/deploy/deploy.go +++ b/pkg/v3commands/deploy/deploy.go @@ -50,7 +50,7 @@ type DeployCmd struct { OpenBrowser func(f *cmdutil.Factory, urlConsoleDeploy string, cmd *DeployCmd) error CaptureLogs func(execId string, token string, cmd *DeployCmd) error CheckToken func(f *cmdutil.Factory) error - ReadSettings func() (token.Settings, error) + ReadSettings func(string) (token.Settings, error) UploadFiles func(f *cmdutil.Factory, conf *contracts.AzionApplicationOptionsV3, msgs *[]string, pathStatic, bucket string, cmd *DeployCmd, settings token.Settings) error OpenBrowserFunc func(input string) error } @@ -137,7 +137,7 @@ func (cmd *DeployCmd) ExternalRun(f *cmdutil.Factory, configPath string, local b } func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { - + activeProfile := f.GetActiveProfile() if DryRun { dryStructure := dryrun.NewDryrunCmd(f) pathWorkingDir, err := cmd.GetWorkDir() @@ -162,7 +162,7 @@ func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { return err } - settings, err := cmd.ReadSettings() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -202,7 +202,7 @@ func (cmd *DeployCmd) Run(f *cmdutil.Factory) error { settings.S3SecretKey = creds.Data.GetSecretKey() settings.S3Bucket = nameBucket - err = token.WriteSettings(settings) + err = token.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/v3commands/deploy/deploy_test.go b/pkg/v3commands/deploy/deploy_test.go index ee3d89c1d..d717b0a7a 100644 --- a/pkg/v3commands/deploy/deploy_test.go +++ b/pkg/v3commands/deploy/deploy_test.go @@ -46,7 +46,7 @@ func MockFileReader(path string) ([]byte, error) { return nil, errors.New("file not found") } -func MockReadSettings() (token.Settings, error) { +func MockReadSettings(string) (token.Settings, error) { return token.Settings{Token: "123321", S3AccessKey: "122221", S3SecretKey: "3333322222", S3Bucket: "bucketname"}, nil } @@ -120,8 +120,12 @@ func TestDeploy_Run(t *testing.T) { cmd.OpenBrowser = MockOpenBrowser cmd.CaptureLogs = MockCaptureLogs cmd.CheckToken = MockCheckToken - cmd.ReadSettings = func() (token.Settings, error) { - return token.Settings{}, nil + cmd.ReadSettings = func(string) (token.Settings, error) { + return token.Settings{ + S3AccessKey: "test-access-key", + S3SecretKey: "test-secret-key", + S3Bucket: "test-bucket", + }, nil } cmd.UploadFiles = func(f *cmdutil.Factory, conf *contracts.AzionApplicationOptionsV3, msgs *[]string, pathStatic, bucket string, cmd *DeployCmd, settings token.Settings) error { return nil @@ -175,11 +179,11 @@ func TestDeploy_Run(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mock := &httpmock.Registry{} mock.Register( - httpmock.REST(http.MethodPost, "v4/edge_storage/buckets"), + httpmock.REST(http.MethodPost, "edge_storage/buckets"), httpmock.JSONFromFile("fixtures/response.json"), ) mock.Register( - httpmock.REST(http.MethodPost, "v4/edge_storage/s3-credentials"), + httpmock.REST(http.MethodPost, "edge_storage/s3-credentials"), httpmock.JSONFromFile("fixtures/responses3.json"), ) diff --git a/pkg/v3commands/list/edge_storage/object/object.go b/pkg/v3commands/list/edge_storage/object/object.go index 5fdb71e68..899f91b52 100644 --- a/pkg/v3commands/list/edge_storage/object/object.go +++ b/pkg/v3commands/list/edge_storage/object/object.go @@ -57,8 +57,9 @@ func (b *Objects) RunE(cmd *cobra.Command, args []string) error { func (b *Objects) PrintTable(client *api.Client) error { c := context.Background() + activeProfile := b.Factory.GetActiveProfile() - settings, err := token.ReadSettings() + settings, err := token.ReadSettings(activeProfile) if err != nil { return err } @@ -82,7 +83,7 @@ func (b *Objects) PrintTable(client *api.Client) error { } settings.ContinuationToken = resp.GetContinuationToken() - err = token.WriteSettings(settings) + err = token.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/v3commands/login/login.go b/pkg/v3commands/login/login.go index 02fc7ffa9..e12b6c0d7 100644 --- a/pkg/v3commands/login/login.go +++ b/pkg/v3commands/login/login.go @@ -23,8 +23,12 @@ import ( var ( username, password, tokenValue, uuid string userInfo token.UserInfo + createNewProfile bool + newProfileName string ) +var confirmFn = utils.Confirm + type login struct { factory *cmdutil.Factory askOne func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error @@ -64,6 +68,15 @@ func cmd(l *login) *cobra.Command { $ azion login --username fulanodasilva@gmail.com --password "senhasecreta" `), RunE: func(cmd *cobra.Command, args []string) error { + createNewProfile = confirmFn(l.factory.GlobalFlagAll, msg.QuestionCreateProfile, true) + if createNewProfile { + profileNameInput, err := utils.AskInput(msg.AskProfileName) + if err != nil { + return fmt.Errorf(msg.ErrorGetProfileName.Error(), err) + } + newProfileName = profileNameInput + } + answer, err := l.selectLoginMode() if err != nil { return err @@ -134,17 +147,31 @@ func (l *login) saveSettings() error { S3Bucket: "", } - byteSettings, err := l.marshalToml(settings) - if err != nil { - logger.Debug("Error while marshalling toml file", zap.Error(err)) - return err + var profileName string + if createNewProfile { + profileName = newProfileName + + err := token.WriteSettings(settings, profileName) + if err != nil { + return err + } + + profile := token.Profile{Name: profileName} + err = token.WriteProfiles(profile) + if err != nil { + return fmt.Errorf("failed to set new profile as active: %w", err) + } + + fmt.Fprintf(l.factory.IOStreams.Out, msg.ProfileCreated+"\n", profileName) + } else { + profileName = l.factory.GetActiveProfile() + err := token.WriteSettings(settings, profileName) + if err != nil { + return err + } } - _, err = l.token.Save(byteSettings) - if err != nil { - logger.Debug("Error while saving settings", zap.Error(err)) - return err - } + fmt.Fprintf(l.factory.IOStreams.Out, msg.TokenSavedToProfile+"\n", profileName) return nil } diff --git a/pkg/v3commands/login/login_test.go b/pkg/v3commands/login/login_test.go index 924fe1897..49af86e1e 100644 --- a/pkg/v3commands/login/login_test.go +++ b/pkg/v3commands/login/login_test.go @@ -84,6 +84,15 @@ func Test_cmd(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Mock confirmFn to return false (don't create new profile) + originalConfirmFn := confirmFn + confirmFn = func(globalFlagAll bool, msg string, defaultYes bool) bool { + return false + } + defer func() { + confirmFn = originalConfirmFn + }() + cmd := tt.cmd(tt.args.l) if err := cmd.Execute(); (err != nil) != tt.wantErr { t.Errorf("error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/v3commands/login/terminal_test.go b/pkg/v3commands/login/terminal_test.go index 2478cb221..ee3ed6ee5 100644 --- a/pkg/v3commands/login/terminal_test.go +++ b/pkg/v3commands/login/terminal_test.go @@ -103,6 +103,15 @@ func Test_login_terminalLogin(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Mock confirmFn to return false (don't create new profile) + originalConfirmFn := confirmFn + confirmFn = func(globalFlagAll bool, msg string, defaultYes bool) bool { + return false + } + defer func() { + confirmFn = originalConfirmFn + }() + mock := &httpmock.Registry{} mock.Register(tt.register.matcher, tt.register.reponder) f, _, _ := testutils.NewFactory(mock) diff --git a/pkg/v3commands/logout/logout.go b/pkg/v3commands/logout/logout.go index 75f6385c6..ec082e415 100644 --- a/pkg/v3commands/logout/logout.go +++ b/pkg/v3commands/logout/logout.go @@ -16,9 +16,10 @@ import ( type LogoutCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) - WriteSettings func(token.Settings) error + ReadSettings func(string) (token.Settings, error) + WriteSettings func(token.Settings, string) error DeleteToken func(context.Context, string) error + Factory *cmdutil.Factory } func NewLogoutCmd(f *cmdutil.Factory) *LogoutCmd { @@ -30,6 +31,7 @@ func NewLogoutCmd(f *cmdutil.Factory) *LogoutCmd { client := api.NewClient(f.HttpClient, f.Config.GetString("api_url"), f.Config.GetString("token")) return client.Delete(ctx, uuid) }, + Factory: f, } } @@ -56,7 +58,8 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { } func (cmd *LogoutCmd) run() error { - settings, err := cmd.ReadSettings() + activeProfile := cmd.Factory.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -70,7 +73,7 @@ func (cmd *LogoutCmd) run() error { settings.UUID = "" settings.Token = "" - err = cmd.WriteSettings(settings) + err = cmd.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/v3commands/logout/logout_test.go b/pkg/v3commands/logout/logout_test.go index 82c2c15d0..75cea0e15 100644 --- a/pkg/v3commands/logout/logout_test.go +++ b/pkg/v3commands/logout/logout_test.go @@ -85,11 +85,11 @@ func TestLogout(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { + mockReadSettings := func(string) (token.Settings, error) { return tt.tokenSettings, tt.mockReadError } - mockWriteSettings := func(settings token.Settings) error { + mockWriteSettings := func(settings token.Settings, path string) error { return tt.mockWriteError } @@ -106,6 +106,7 @@ func TestLogout(t *testing.T) { ReadSettings: mockReadSettings, WriteSettings: mockWriteSettings, DeleteToken: mockDeleteToken, + Factory: f, } cmd := NewCobraCmd(logoutCmd, f) diff --git a/pkg/v3commands/reset/reset.go b/pkg/v3commands/reset/reset.go index e43c1278e..fd56dc4d5 100644 --- a/pkg/v3commands/reset/reset.go +++ b/pkg/v3commands/reset/reset.go @@ -16,9 +16,10 @@ import ( type ResetCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) - WriteSettings func(token.Settings) error + ReadSettings func(string) (token.Settings, error) + WriteSettings func(token.Settings, string) error DeleteToken func(context.Context, string) error + Factory *cmdutil.Factory } func NewResetCmd(f *cmdutil.Factory) *ResetCmd { @@ -30,6 +31,7 @@ func NewResetCmd(f *cmdutil.Factory) *ResetCmd { client := api.NewClient(f.HttpClient, f.Config.GetString("api_url"), f.Config.GetString("token")) return client.Delete(ctx, uuid) }, + Factory: f, } } @@ -54,7 +56,8 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { } func (cmd *ResetCmd) run() error { - settings, err := cmd.ReadSettings() + activeProfile := cmd.Factory.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -67,7 +70,7 @@ func (cmd *ResetCmd) run() error { } settings = token.Settings{} - err = cmd.WriteSettings(settings) + err = cmd.WriteSettings(settings, activeProfile) if err != nil { return err } diff --git a/pkg/v3commands/reset/reset_test.go b/pkg/v3commands/reset/reset_test.go index 7a3c43258..b4ad785e8 100644 --- a/pkg/v3commands/reset/reset_test.go +++ b/pkg/v3commands/reset/reset_test.go @@ -85,11 +85,11 @@ func TestReset(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { + mockReadSettings := func(string) (token.Settings, error) { return tt.tokenSettings, tt.mockReadError } - mockWriteSettings := func(settings token.Settings) error { + mockWriteSettings := func(settings token.Settings, path string) error { return tt.mockWriteError } @@ -105,6 +105,7 @@ func TestReset(t *testing.T) { ReadSettings: mockReadSettings, WriteSettings: mockWriteSettings, DeleteToken: mockDeleteToken, + Factory: f, } cmd := NewCobraCmd(resetCmd, f) diff --git a/pkg/v3commands/whoami/whoami.go b/pkg/v3commands/whoami/whoami.go index 72cd51bc7..a4921e638 100644 --- a/pkg/v3commands/whoami/whoami.go +++ b/pkg/v3commands/whoami/whoami.go @@ -14,7 +14,7 @@ import ( type WhoamiCmd struct { Io *iostreams.IOStreams - ReadSettings func() (token.Settings, error) + ReadSettings func(string) (token.Settings, error) F *cmdutil.Factory } @@ -50,7 +50,8 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { } func (cmd *WhoamiCmd) run() error { - settings, err := cmd.ReadSettings() + activeProfile := cmd.F.GetActiveProfile() + settings, err := cmd.ReadSettings(activeProfile) if err != nil { return err } @@ -59,7 +60,7 @@ func (cmd *WhoamiCmd) run() error { return msg.ErrorNotLoggedIn } - msg := fmt.Sprintf(" Client ID: %s\n Email: %s\n", settings.ClientId, settings.Email) + msg := fmt.Sprintf(" Client ID: %s\n Email: %s\n Active Profile: %s\n", settings.ClientId, settings.Email, activeProfile) whoamiOut := output.GeneralOutput{ Msg: msg, Out: cmd.Io.Out, diff --git a/pkg/v3commands/whoami/whoami_test.go b/pkg/v3commands/whoami/whoami_test.go index f0c3f4160..5fb6e7bd4 100644 --- a/pkg/v3commands/whoami/whoami_test.go +++ b/pkg/v3commands/whoami/whoami_test.go @@ -31,7 +31,7 @@ func TestWhoami(t *testing.T) { ClientId: "abcd-1234", }, mockReadError: nil, - expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n", + expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n Active Profile: \n", expectedError: nil, }, { @@ -54,7 +54,7 @@ func TestWhoami(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockReadSettings := func() (token.Settings, error) { + mockReadSettings := func(string) (token.Settings, error) { return tt.tokenSettings, tt.mockReadError } diff --git a/pkg/vulcan/vulcan.go b/pkg/vulcan/vulcan.go index 3caa3ee82..b2b12af06 100644 --- a/pkg/vulcan/vulcan.go +++ b/pkg/vulcan/vulcan.go @@ -8,7 +8,6 @@ import ( "github.com/aziontech/azion-cli/pkg/cmdutil" "github.com/aziontech/azion-cli/pkg/logger" "github.com/aziontech/azion-cli/pkg/token" - "github.com/pelletier/go-toml" "go.uber.org/zap" ) @@ -22,7 +21,7 @@ var ( type VulcanPkg struct { Command func(flags, params string, f *cmdutil.Factory) string CheckVulcanMajor func(currentVersion string, f *cmdutil.Factory, vulcan *VulcanPkg) error - ReadSettings func() (token.Settings, error) + ReadSettings func(string) (token.Settings, error) } func NewVulcan() *VulcanPkg { @@ -53,6 +52,7 @@ func command(flags, params string, f *cmdutil.Factory) string { } func checkVulcanMajor(currentVersion string, f *cmdutil.Factory, vulcan *VulcanPkg) error { + activeProfile := f.GetActiveProfile() parts := strings.Split(currentVersion, ".") // strings.Split will always return at least one element, so parts will always be len>0 // to avoid this, I am checking if version is empty. If so, I just use an empty slice @@ -67,7 +67,7 @@ func checkVulcanMajor(currentVersion string, f *cmdutil.Factory, vulcan *VulcanP return err } - config, err := vulcan.ReadSettings() + config, err := vulcan.ReadSettings(activeProfile) if err != nil { return err } @@ -77,14 +77,7 @@ func checkVulcanMajor(currentVersion string, f *cmdutil.Factory, vulcan *VulcanP } config.LastVulcanVersion = currentVersion - client := token.New(&token.Config{Client: f.HttpClient}) - byteSettings, err := toml.Marshal(config) - if err != nil { - logger.Debug("Error while marshalling settings.toml", zap.Error(err)) - return err - } - - _, err = client.Save(byteSettings) + err = token.WriteSettings(config, activeProfile) if err != nil { logger.Debug("Error while saving settings", zap.Error(err)) return err diff --git a/pkg/vulcan/vulcan_test.go b/pkg/vulcan/vulcan_test.go index fa8e37c45..1664bdc8c 100644 --- a/pkg/vulcan/vulcan_test.go +++ b/pkg/vulcan/vulcan_test.go @@ -152,7 +152,7 @@ func TestCheckVulcanMajor(t *testing.T) { t.Run(tt.name, func(t *testing.T) { f, _, _ := testutils.NewFactory(nil) vul := NewVulcan() - vul.ReadSettings = func() (token.Settings, error) { + vul.ReadSettings = func(string) (token.Settings, error) { return token.Settings{}, nil } err := vul.CheckVulcanMajor(tt.args.currentVersion, f, vul) diff --git a/utils/errors.go b/utils/errors.go index acc84ee40..96203364b 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -48,7 +48,12 @@ var ( ErrorMinTlsVersion = errors.New("This is not a valid TLS Version. Run azion edge_applications --help for more information") ErrorNameInUse = errors.New("The name you've selected is already in use by another resource. Please choose a different name. Run 'azion list [resource]' to see all your resources") ErrorCancelledContextInput = errors.New("Execution interrupted by the user. All interactions of this flow were lost.") + ErrorWriteProfiles = errors.New("Failed to write profiles.toml file: %w") + ErrorReadProfiles = errors.New("Failed to read profiles.toml file: %w") ErrorWriteSettings = errors.New("Failed to write settings.toml file: %w") + ErrorCheckingProfilesFile = errors.New("error checking profiles file: %w") + ErrorCreatingConfigDirectory = errors.New("error creating config directory: %w") + ErrorCreatingDefaultProfiles = errors.New("error creating default profiles.json: %w") ) const ( From c1baee5648f9d6aead9d5f2b47a25cd9c3aff12b Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:47:56 -0300 Subject: [PATCH 02/18] refactor: update message packages --- messages/login/errors.go | 3 ++- messages/profile/errors.go | 1 + messages/profile/messages.go | 2 ++ pkg/cmd/build/build_test.go | 4 ++++ pkg/cmd/build/run_test.go | 6 ++++++ pkg/cmd/build/vulcan_test.go | 6 ++++++ pkg/cmd/delete/profile/profile.go | 18 +++++------------- pkg/cmd/login/login.go | 2 +- pkg/cmd/root/pre_command.go | 12 +++++------- pkg/v3commands/login/login.go | 2 +- utils/errors.go | 6 +++--- 11 files changed, 36 insertions(+), 26 deletions(-) diff --git a/messages/login/errors.go b/messages/login/errors.go index c0dc5f5d8..b9fc7f8cc 100644 --- a/messages/login/errors.go +++ b/messages/login/errors.go @@ -7,5 +7,6 @@ var ( ErrorInvalidLogin = errors.New("Invalid login method") ErrorTokenCreateInvalid = errors.New("Invalid token detected. The generated token appears to be corrupted or expired. Please check your authentication credentials and generate a new token to proceed.") ErrorServerClosed = errors.New("Error while serving server for browser login") - ErrorGetProfileName = errors.New("failed to get profile name: %w") + ErrorGetProfileName = errors.New("Failed to get profile name: %w") + ErrorSetActiveProfile = errors.New("Failed to set new profile as active: %w") ) diff --git a/messages/profile/errors.go b/messages/profile/errors.go index 3862d6764..6c0126978 100644 --- a/messages/profile/errors.go +++ b/messages/profile/errors.go @@ -12,4 +12,5 @@ var ( ErrorProfileNotFound = errors.New("Profile '%s' not found") ErrorCannotDeleteDefault = errors.New("Cannot delete the 'default' profile") ErrorDeleteToken = errors.New("Failed to delete token: %w") + ErrorDeleteCancelled = errors.New("Profile deletion cancelled") ) diff --git a/messages/profile/messages.go b/messages/profile/messages.go index 1014e6b19..5cb532e34 100644 --- a/messages/profile/messages.go +++ b/messages/profile/messages.go @@ -29,4 +29,6 @@ var ( DeleteOutputSuccess = "Profile '%s' deleted successfully" QuestionDeleteProfile = "Choose a profile to delete:" ConfirmDeleteProfile = "Are you sure you want to delete profile '%s'? This action cannot be undone (Y/n)" + WarningDeleteToken = "Warning: Failed to delete token from server: %v" + WarningSetActiveProfile = "Warning: Failed to set active profile to default: %v" ) diff --git a/pkg/cmd/build/build_test.go b/pkg/cmd/build/build_test.go index 4c05840fb..137a33093 100644 --- a/pkg/cmd/build/build_test.go +++ b/pkg/cmd/build/build_test.go @@ -9,6 +9,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -21,6 +22,7 @@ func TestNewCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), } NewCmd(f) } @@ -84,6 +86,7 @@ func TestBuildCmd_ExternalRun(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -127,6 +130,7 @@ func TestNewBuildCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), } NewBuildCmd(f) } diff --git a/pkg/cmd/build/run_test.go b/pkg/cmd/build/run_test.go index 994203d07..64557dfbb 100644 --- a/pkg/cmd/build/run_test.go +++ b/pkg/cmd/build/run_test.go @@ -10,6 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -71,6 +72,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -111,6 +113,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -155,6 +158,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -200,6 +204,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -245,6 +250,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ diff --git a/pkg/cmd/build/vulcan_test.go b/pkg/cmd/build/vulcan_test.go index b09169ceb..68b8f4743 100644 --- a/pkg/cmd/build/vulcan_test.go +++ b/pkg/cmd/build/vulcan_test.go @@ -12,6 +12,7 @@ import ( "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" vulcanPkg "github.com/aziontech/azion-cli/pkg/vulcan" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -75,6 +76,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -134,6 +136,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -194,6 +197,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -254,6 +258,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -314,6 +319,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ diff --git a/pkg/cmd/delete/profile/profile.go b/pkg/cmd/delete/profile/profile.go index 9e675c167..deadfcb84 100644 --- a/pkg/cmd/delete/profile/profile.go +++ b/pkg/cmd/delete/profile/profile.go @@ -75,49 +75,41 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { profileToDelete = fields.Name } - // Prevent deletion of default profile if profileToDelete == "default" { return msg.ErrorCannotDeleteDefault } - // Confirm deletion confirmDelete := confirmFn(false, fmt.Sprintf(msg.ConfirmDeleteProfile, profileToDelete), true) if !confirmDelete { - return fmt.Errorf("Profile deletion cancelled") + return msg.ErrorDeleteCancelled } - // Check if profile exists dir := config.Dir() profilePath := filepath.Join(dir.Dir, profileToDelete) if _, err := os.Stat(profilePath); os.IsNotExist(err) { - return fmt.Errorf("Profile '%s' not found", profileToDelete) + return fmt.Errorf(msg.ErrorProfileNotFound.Error(), profileToDelete) } - // Try to delete token if UUID exists settings, err := token.ReadSettings(profileToDelete) if err == nil && settings.UUID != "" { client := api.NewClient(f.HttpClient, f.Config.GetString("api_url"), settings.Token) err = client.Delete(context.Background(), settings.UUID) if err != nil { - // Log warning but don't fail the profile deletion - fmt.Fprintf(f.IOStreams.Out, "Warning: Failed to delete token from server: %v\n", err) + fmt.Fprintf(f.IOStreams.Out, msg.WarningDeleteToken+"\n", err) } } - // Delete the profile directory and all its contents err = os.RemoveAll(profilePath) if err != nil { - return fmt.Errorf("Failed to delete the profile: %w", err) + return fmt.Errorf(msg.ErrorDeleteProfile.Error(), err) } - // Check if deleted profile was the active one currentProfile, _, err := token.ReadProfiles() if err == nil && currentProfile.Name == profileToDelete { - // Set active profile to "default" defaultProfile := token.Profile{Name: "default"} err = token.WriteProfiles(defaultProfile) if err != nil { - fmt.Fprintf(f.IOStreams.Out, "Warning: Failed to set active profile to default: %v\n", err) + fmt.Fprintf(f.IOStreams.Out, msg.WarningSetActiveProfile+"\n", err) } } diff --git a/pkg/cmd/login/login.go b/pkg/cmd/login/login.go index f40ab24b7..31ff47909 100644 --- a/pkg/cmd/login/login.go +++ b/pkg/cmd/login/login.go @@ -158,7 +158,7 @@ func (l *login) saveSettings() error { profile := token.Profile{Name: profileName} err = token.WriteProfiles(profile) if err != nil { - return fmt.Errorf("failed to set new profile as active: %w", err) + return fmt.Errorf(msg.ErrorSetActiveProfile.Error(), err) } fmt.Fprintf(l.factory.IOStreams.Out, msg.ProfileCreated+"\n", profileName) diff --git a/pkg/cmd/root/pre_command.go b/pkg/cmd/root/pre_command.go index 68f95011e..e19fcc678 100644 --- a/pkg/cmd/root/pre_command.go +++ b/pkg/cmd/root/pre_command.go @@ -61,14 +61,12 @@ func doPreCommandCheck(cmd *cobra.Command, fact *factoryRoot) error { }) if cmd.Flags().Changed("token") { - // When token is provided, use empty settings initially to avoid creating base settings.toml emptySettings := token.Settings{} if err := checkTokenSent(fact, &emptySettings, t); err != nil { return err } // fact.globalSettings is set inside checkTokenSent, so we can proceed } else { - // Only read existing settings if no token is being provided activeProfile := fact.factory.GetActiveProfile() settings, err := token.ReadSettings(activeProfile) if err != nil { @@ -82,8 +80,6 @@ func doPreCommandCheck(cmd *cobra.Command, fact *factoryRoot) error { } } - //both verifications occurs if 24 hours have passed since the last execution - // Skip update check when token is being provided to avoid creating base settings if !cmd.Flags().Changed("token") && fact.globalSettings != nil { if err := checkForUpdateAndMetrics(version.BinVersion, fact.factory, fact.globalSettings); err != nil { return err @@ -107,8 +103,11 @@ func checkTokenSent(fact *factoryRoot, settings *token.Settings, tokenStr *token return utils.ErrorInvalidToken } - // When setting a token, always use "default" profile to avoid config system initialization - activeProfile := "default" + // When setting a token, use "default" profile if another one was not configured yet + activeProfile := fact.factory.GetActiveProfile() + if activeProfile == "" { + activeProfile = "default" + } strToken := token.Settings{ Token: fact.tokenFlag, @@ -128,7 +127,6 @@ func checkTokenSent(fact *factoryRoot, settings *token.Settings, tokenStr *token fact.globalSettings = &strToken - // Create a profile-aware file path for the message dir := config.Dir() if activeProfile != "" { dir.Dir = filepath.Join(dir.Dir, activeProfile) diff --git a/pkg/v3commands/login/login.go b/pkg/v3commands/login/login.go index e12b6c0d7..33ff224ce 100644 --- a/pkg/v3commands/login/login.go +++ b/pkg/v3commands/login/login.go @@ -159,7 +159,7 @@ func (l *login) saveSettings() error { profile := token.Profile{Name: profileName} err = token.WriteProfiles(profile) if err != nil { - return fmt.Errorf("failed to set new profile as active: %w", err) + return fmt.Errorf(msg.ErrorSetActiveProfile.Error(), err) } fmt.Fprintf(l.factory.IOStreams.Out, msg.ProfileCreated+"\n", profileName) diff --git a/utils/errors.go b/utils/errors.go index 96203364b..b997c27b9 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -51,9 +51,9 @@ var ( ErrorWriteProfiles = errors.New("Failed to write profiles.toml file: %w") ErrorReadProfiles = errors.New("Failed to read profiles.toml file: %w") ErrorWriteSettings = errors.New("Failed to write settings.toml file: %w") - ErrorCheckingProfilesFile = errors.New("error checking profiles file: %w") - ErrorCreatingConfigDirectory = errors.New("error creating config directory: %w") - ErrorCreatingDefaultProfiles = errors.New("error creating default profiles.json: %w") + ErrorCheckingProfilesFile = errors.New("Failed to check profiles file: %w") + ErrorCreatingConfigDirectory = errors.New("Failed to create config directory: %w") + ErrorCreatingDefaultProfiles = errors.New("Failed to create default profiles.json: %w") ) const ( From ebed602d395ce99f733cd84e018bb60d596343ba Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:58:16 -0300 Subject: [PATCH 03/18] tests: update init unit tests --- pkg/cmd/build/utils_test.go | 4 ++++ pkg/cmd/init/init_test.go | 11 +++++++++++ pkg/v3commands/build/build_test.go | 4 ++++ pkg/v3commands/build/run_test.go | 6 ++++++ pkg/v3commands/build/utils_test.go | 4 ++++ pkg/v3commands/build/vulcan_test.go | 6 ++++++ pkg/v3commands/init/init_test.go | 11 +++++++++++ 7 files changed, 46 insertions(+) diff --git a/pkg/cmd/build/utils_test.go b/pkg/cmd/build/utils_test.go index f2fcc9c6b..bbf398a94 100644 --- a/pkg/cmd/build/utils_test.go +++ b/pkg/cmd/build/utils_test.go @@ -10,6 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -68,6 +69,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -103,6 +105,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ @@ -138,6 +141,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, }, args: args{ diff --git a/pkg/cmd/init/init_test.go b/pkg/cmd/init/init_test.go index dba372571..c47f87771 100644 --- a/pkg/cmd/init/init_test.go +++ b/pkg/cmd/init/init_test.go @@ -22,6 +22,7 @@ import ( "github.com/aziontech/azion-cli/pkg/testutils" "github.com/go-git/go-git/v5" "github.com/spf13/cobra" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -101,6 +102,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -176,6 +178,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -252,6 +255,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: true, name: "", @@ -328,6 +332,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -404,6 +409,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -480,6 +486,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -558,6 +565,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -636,6 +644,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -817,6 +826,7 @@ func Test_initCmd_deps(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, commandRunInteractive: func(f *cmdutil.Factory, comm string) error { return nil @@ -848,6 +858,7 @@ func Test_initCmd_deps(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, commandRunInteractive: func(f *cmdutil.Factory, comm string) error { return errors.New("error depsInstall") diff --git a/pkg/v3commands/build/build_test.go b/pkg/v3commands/build/build_test.go index b548dc576..e7473de81 100644 --- a/pkg/v3commands/build/build_test.go +++ b/pkg/v3commands/build/build_test.go @@ -9,6 +9,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" +"github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -21,6 +22,7 @@ func TestNewCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), } NewCmd(f) } @@ -84,6 +86,7 @@ func TestBuildCmd_ExternalRun(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -127,6 +130,7 @@ func TestNewBuildCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), } NewBuildCmd(f) } diff --git a/pkg/v3commands/build/run_test.go b/pkg/v3commands/build/run_test.go index 6f0aa65b8..1ed6f4672 100644 --- a/pkg/v3commands/build/run_test.go +++ b/pkg/v3commands/build/run_test.go @@ -10,6 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" +"github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -71,6 +72,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -111,6 +113,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -156,6 +159,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -202,6 +206,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -248,6 +253,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ diff --git a/pkg/v3commands/build/utils_test.go b/pkg/v3commands/build/utils_test.go index 89c310673..a78d294c2 100644 --- a/pkg/v3commands/build/utils_test.go +++ b/pkg/v3commands/build/utils_test.go @@ -10,6 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" +"github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -68,6 +69,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -103,6 +105,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -138,6 +141,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ diff --git a/pkg/v3commands/build/vulcan_test.go b/pkg/v3commands/build/vulcan_test.go index 3607bc89d..d7ffbe15f 100644 --- a/pkg/v3commands/build/vulcan_test.go +++ b/pkg/v3commands/build/vulcan_test.go @@ -12,6 +12,7 @@ import ( "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" vulcanPkg "github.com/aziontech/azion-cli/pkg/vulcan" +"github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -75,6 +76,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -134,6 +136,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -194,6 +197,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -254,6 +258,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ @@ -314,6 +319,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), +Config: viper.New(), }, }, args: args{ diff --git a/pkg/v3commands/init/init_test.go b/pkg/v3commands/init/init_test.go index a30ac8581..7b74fa08e 100644 --- a/pkg/v3commands/init/init_test.go +++ b/pkg/v3commands/init/init_test.go @@ -22,6 +22,7 @@ import ( "github.com/aziontech/azion-cli/pkg/testutils" "github.com/go-git/go-git/v5" "github.com/spf13/cobra" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -101,6 +102,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -176,6 +178,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -252,6 +255,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: true, name: "", @@ -328,6 +332,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -404,6 +409,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -480,6 +486,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -558,6 +565,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "project-piece", @@ -636,6 +644,7 @@ func Test_initCmd_Run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, globalFlagAll: false, name: "", @@ -815,6 +824,7 @@ func Test_initCmd_deps(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, commandRunInteractive: func(f *cmdutil.Factory, comm string) error { return nil @@ -846,6 +856,7 @@ func Test_initCmd_deps(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), + Config: viper.New(), }, commandRunInteractive: func(f *cmdutil.Factory, comm string) error { return errors.New("error depsInstall") From 2617f9712b6264266fd0cc30292fb4711f657fac Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:09:21 -0300 Subject: [PATCH 04/18] tests: update whoami tests --- pkg/cmd/whoami/whoami_test.go | 2 +- pkg/cmdutil/factory.go | 10 +++++++--- pkg/token/token.go | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/whoami/whoami_test.go b/pkg/cmd/whoami/whoami_test.go index c0653615a..67388f11b 100644 --- a/pkg/cmd/whoami/whoami_test.go +++ b/pkg/cmd/whoami/whoami_test.go @@ -31,7 +31,7 @@ func TestWhoami(t *testing.T) { ClientId: "abcd-1234", }, mockReadError: nil, - expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n Active Profile: \n", + expectedOutput: " Client ID: abcd-1234\n Email: test@example.com\n Active Profile: default\n", expectedError: nil, }, { diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index 3626a8a8b..dca867c53 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -27,16 +27,20 @@ type Flags struct { // GetActiveProfile returns the active profile name from config, defaulting to "default" if empty func (f *Factory) GetActiveProfile() string { + if f.Config == nil { + return "default" + } + activeProfile := f.Config.GetString("active_profile") - + if activeProfile == "" { return "default" } - + // If the value looks like a path, extract just the profile name (last component) if strings.Contains(activeProfile, "/") { return filepath.Base(activeProfile) } - + return activeProfile } diff --git a/pkg/token/token.go b/pkg/token/token.go index d1f033ef1..d6a826049 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -179,10 +179,12 @@ func WriteProfiles(profile Profile) error { } func ReadSettings(path string) (Settings, error) { - dir := config.Dir() - if path != "" { - dir.Dir = filepath.Join(dir.Dir, path) + if path == "" { + path = "default" } + + dir := config.Dir() + dir.Dir = filepath.Join(dir.Dir, path) filePath := filepath.Join(dir.Dir, dir.Settings) // Check if the file exists From a40f84bf0d8e7b3bbe2c25c18ba64c26593580cd Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:16:52 -0300 Subject: [PATCH 05/18] refactor: fix identation --- pkg/v3commands/build/build_test.go | 8 ++++---- pkg/v3commands/build/run_test.go | 12 ++++++------ pkg/v3commands/build/utils_test.go | 8 ++++---- pkg/v3commands/build/vulcan_test.go | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/v3commands/build/build_test.go b/pkg/v3commands/build/build_test.go index e7473de81..44afd614a 100644 --- a/pkg/v3commands/build/build_test.go +++ b/pkg/v3commands/build/build_test.go @@ -9,7 +9,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" -"github.com/spf13/viper" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -22,7 +22,7 @@ func TestNewCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), } NewCmd(f) } @@ -86,7 +86,7 @@ func TestBuildCmd_ExternalRun(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -130,7 +130,7 @@ func TestNewBuildCmd(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), } NewBuildCmd(f) } diff --git a/pkg/v3commands/build/run_test.go b/pkg/v3commands/build/run_test.go index 1ed6f4672..5f6d0a3aa 100644 --- a/pkg/v3commands/build/run_test.go +++ b/pkg/v3commands/build/run_test.go @@ -10,7 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" -"github.com/spf13/viper" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -72,7 +72,7 @@ func TestBuildCmd_run(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -113,7 +113,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -159,7 +159,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -206,7 +206,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -253,7 +253,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ diff --git a/pkg/v3commands/build/utils_test.go b/pkg/v3commands/build/utils_test.go index a78d294c2..82f648acc 100644 --- a/pkg/v3commands/build/utils_test.go +++ b/pkg/v3commands/build/utils_test.go @@ -10,7 +10,7 @@ import ( "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" -"github.com/spf13/viper" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -69,7 +69,7 @@ func TestBuildCmd_runCommand(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -105,7 +105,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -141,7 +141,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ diff --git a/pkg/v3commands/build/vulcan_test.go b/pkg/v3commands/build/vulcan_test.go index d7ffbe15f..68e5f34a0 100644 --- a/pkg/v3commands/build/vulcan_test.go +++ b/pkg/v3commands/build/vulcan_test.go @@ -12,7 +12,7 @@ import ( "github.com/aziontech/azion-cli/pkg/iostreams" "github.com/aziontech/azion-cli/pkg/logger" vulcanPkg "github.com/aziontech/azion-cli/pkg/vulcan" -"github.com/spf13/viper" + "github.com/spf13/viper" "go.uber.org/zap/zapcore" ) @@ -76,7 +76,7 @@ func TestBuildCmd_vulcan(t *testing.T) { NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -136,7 +136,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -197,7 +197,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -258,7 +258,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ @@ -319,7 +319,7 @@ Config: viper.New(), NoColor: false, }, IOStreams: iostreams.System(), -Config: viper.New(), + Config: viper.New(), }, }, args: args{ From 37b66fc41dfdb2fd345ad1c07342c3ac99e865e2 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:06:57 -0300 Subject: [PATCH 06/18] chore: add commands to v3 tree --- pkg/v3commands/create/create.go | 3 +++ pkg/v3commands/delete/delete.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pkg/v3commands/create/create.go b/pkg/v3commands/create/create.go index 6664dc4dc..6f6b45228 100644 --- a/pkg/v3commands/create/create.go +++ b/pkg/v3commands/create/create.go @@ -3,6 +3,7 @@ package create import ( "github.com/MakeNowJust/heredoc" msg "github.com/aziontech/azion-cli/messages/create" + profile "github.com/aziontech/azion-cli/pkg/cmd/create/profile" "github.com/aziontech/azion-cli/pkg/cmdutil" cacheSetting "github.com/aziontech/azion-cli/pkg/v3commands/create/cache_setting" domain "github.com/aziontech/azion-cli/pkg/v3commands/create/domain" @@ -31,6 +32,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { $ azion create edge-function -h $ azion create variables -h $ azion create edge-storage -h + $ azion create profile -h `), RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() @@ -46,6 +48,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(edgeFunction.NewCmd(f)) cmd.AddCommand(variables.NewCmd(f)) cmd.AddCommand(edgeStorage.NewCmd(f)) + cmd.AddCommand(profile.NewCmd(f)) cmd.Flags().BoolP("help", "h", false, msg.FlagHelp) return cmd diff --git a/pkg/v3commands/delete/delete.go b/pkg/v3commands/delete/delete.go index c9bc57919..9c2e27373 100644 --- a/pkg/v3commands/delete/delete.go +++ b/pkg/v3commands/delete/delete.go @@ -3,6 +3,7 @@ package delete import ( "github.com/MakeNowJust/heredoc" msg "github.com/aziontech/azion-cli/messages/delete" + profile "github.com/aziontech/azion-cli/pkg/cmd/delete/profile" "github.com/aziontech/azion-cli/pkg/cmdutil" domain "github.com/aziontech/azion-cli/pkg/v3commands/delete/domain" edgeApplication "github.com/aziontech/azion-cli/pkg/v3commands/delete/edge_application" @@ -21,6 +22,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { Short: msg.ShortDescription, Long: msg.LongDescription, Example: heredoc.Doc(` $ azion delete --help + $ azion delete profile -h `), RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() @@ -36,6 +38,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { // cmd.AddCommand(cache.NewCmd(f)) cmd.AddCommand(variables.NewCmd(f)) cmd.AddCommand(edgeStorage.NewCmd(f)) + cmd.AddCommand(profile.NewCmd(f)) cmd.Flags().BoolP("help", "h", false, msg.FlagHelp) return cmd From 743831d2af9c28da682b6dcf97edf88b38ea3f86 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:29:04 -0300 Subject: [PATCH 07/18] chore: switch to defult on deletion of active profile --- messages/profile/errors.go | 1 + messages/profile/messages.go | 3 +++ pkg/cmd/create/profile/profile.go | 16 +++++++++++++++- pkg/cmd/delete/profile/profile.go | 15 ++++++++++++--- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/messages/profile/errors.go b/messages/profile/errors.go index 6c0126978..2fb5c5c7b 100644 --- a/messages/profile/errors.go +++ b/messages/profile/errors.go @@ -13,4 +13,5 @@ var ( ErrorCannotDeleteDefault = errors.New("Cannot delete the 'default' profile") ErrorDeleteToken = errors.New("Failed to delete token: %w") ErrorDeleteCancelled = errors.New("Profile deletion cancelled") + ErrorSetDefault = errors.New("Failed to set profile as default: %w") ) diff --git a/messages/profile/messages.go b/messages/profile/messages.go index 5cb532e34..4db0806e9 100644 --- a/messages/profile/messages.go +++ b/messages/profile/messages.go @@ -14,7 +14,9 @@ var ( FieldToken = "Please inform a token for the new profile" FieldProfileName = "Please inform a name for the new profile" QuestionCollectMetrics = "To better understand user needs and enhance our application, we gather anonymous data. Do you agree to participate? (Y/n)" + QuestionMakeDefault = "Would you like to make '%s' your default profile? (Y/n)" CreateOutputSuccess = "Profile '%s' created successfully" + CreateOutputSuccessDefault = "Profile '%s' created successfully and set as default" UsageProfiles = "profiles" ProfilesShortDescription = "Manage profiles" @@ -27,6 +29,7 @@ var ( DeleteLongDescription = "Delete a profile and all its associated data" DeleteFlagHelp = "Displays more information about the delete profile subcommand" DeleteOutputSuccess = "Profile '%s' deleted successfully" + DeleteOutputSuccessWithSwitch = "Profile '%s' deleted successfully. Active profile switched to 'default'" QuestionDeleteProfile = "Choose a profile to delete:" ConfirmDeleteProfile = "Are you sure you want to delete profile '%s'? This action cannot be undone (Y/n)" WarningDeleteToken = "Warning: Failed to delete token from server: %v" diff --git a/pkg/cmd/create/profile/profile.go b/pkg/cmd/create/profile/profile.go index fd0888b7c..6e1755ac0 100644 --- a/pkg/cmd/create/profile/profile.go +++ b/pkg/cmd/create/profile/profile.go @@ -116,8 +116,22 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { return err } + // Ask if user wants to make this profile the default + var successMessage string + makeDefault := confirmFn(f.GlobalFlagAll, fmt.Sprintf(msg.QuestionMakeDefault, fields.Name), true) + if makeDefault { + profile := token.Profile{Name: fields.Name} + err := token.WriteProfiles(profile) + if err != nil { + return fmt.Errorf(msg.ErrorSetDefault.Error(), err) + } + successMessage = fmt.Sprintf(msg.CreateOutputSuccessDefault, fields.Name) + } else { + successMessage = fmt.Sprintf(msg.CreateOutputSuccess, fields.Name) + } + profileOut := output.GeneralOutput{ - Msg: fmt.Sprintf(msg.CreateOutputSuccess, fields.Name), + Msg: successMessage, Out: f.IOStreams.Out, } return output.Print(&profileOut) diff --git a/pkg/cmd/delete/profile/profile.go b/pkg/cmd/delete/profile/profile.go index deadfcb84..10847a8a7 100644 --- a/pkg/cmd/delete/profile/profile.go +++ b/pkg/cmd/delete/profile/profile.go @@ -84,6 +84,10 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { return msg.ErrorDeleteCancelled } + // Check if the profile to delete is the currently active one + currentProfile, _, err := token.ReadProfiles() + isActiveProfile := err == nil && currentProfile.Name == profileToDelete + dir := config.Dir() profilePath := filepath.Join(dir.Dir, profileToDelete) if _, err := os.Stat(profilePath); os.IsNotExist(err) { @@ -104,17 +108,22 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command { return fmt.Errorf(msg.ErrorDeleteProfile.Error(), err) } - currentProfile, _, err := token.ReadProfiles() - if err == nil && currentProfile.Name == profileToDelete { + var successMessage string + if isActiveProfile { defaultProfile := token.Profile{Name: "default"} err = token.WriteProfiles(defaultProfile) if err != nil { fmt.Fprintf(f.IOStreams.Out, msg.WarningSetActiveProfile+"\n", err) + successMessage = fmt.Sprintf(msg.DeleteOutputSuccess, profileToDelete) + } else { + successMessage = fmt.Sprintf(msg.DeleteOutputSuccessWithSwitch, profileToDelete) } + } else { + successMessage = fmt.Sprintf(msg.DeleteOutputSuccess, profileToDelete) } profileOut := output.GeneralOutput{ - Msg: fmt.Sprintf(msg.DeleteOutputSuccess, profileToDelete), + Msg: successMessage, Out: f.IOStreams.Out, } return output.Print(&profileOut) From dca505dba7006622d5e6ff1ee36c75486e633e38 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:19:14 -0300 Subject: [PATCH 08/18] chore: improve retry logic --- pkg/cmd/deploy_remote/worker.go | 27 +++++++++++++++----------- pkg/v3commands/deploy_remote/worker.go | 27 +++++++++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/pkg/cmd/deploy_remote/worker.go b/pkg/cmd/deploy_remote/worker.go index 8a3bba2e1..a21cf3f73 100644 --- a/pkg/cmd/deploy_remote/worker.go +++ b/pkg/cmd/deploy_remote/worker.go @@ -27,29 +27,34 @@ func Worker(jobs <-chan contracts.FileOps, results chan<- error, currentFile *in logger.Debug("\nSkipping upload of empty file: " + job.Path) results <- nil atomic.AddInt64(currentFile, 1) - return + continue } if err := clientUpload.Upload(context.Background(), &job, conf, bucket); err != nil { logger.Debug("Error while worker tried to upload file: <"+job.Path+"> to storage api", zap.Error(err)) - for Retries < 20 { + + fileRetries := 0 + maxRetries := 5 + + for fileRetries < maxRetries { + fileRetries++ atomic.AddInt64(&Retries, 1) - _, err := job.FileContent.Seek(0, 0) - if err != nil { - logger.Debug("An error occurred while seeking fileContent", zap.Error(err)) + + _, seekErr := job.FileContent.Seek(0, 0) + if seekErr != nil { + logger.Debug("An error occurred while seeking fileContent", zap.Error(seekErr)) break } - logger.Debug("Retrying to upload the following file: <"+job.Path+"> to storage api", zap.Error(err)) + logger.Debug("Retrying to upload file", zap.Int("attempt", fileRetries), zap.Int("maxRetries", maxRetries), zap.String("path", job.Path)) err = clientUpload.Upload(context.Background(), &job, conf, bucket) - if err != nil { - continue + if err == nil { + break } - break } - if Retries >= 20 { - logger.Debug("There have been 20 retries already, quitting upload") + if fileRetries >= maxRetries { + logger.Debug("Failed to upload file after retries", zap.Int("maxRetries", maxRetries), zap.String("path", job.Path)) results <- err return } diff --git a/pkg/v3commands/deploy_remote/worker.go b/pkg/v3commands/deploy_remote/worker.go index eb163c323..7fb55d912 100644 --- a/pkg/v3commands/deploy_remote/worker.go +++ b/pkg/v3commands/deploy_remote/worker.go @@ -26,29 +26,34 @@ func Worker(jobs <-chan contracts.FileOps, results chan<- error, currentFile *in logger.Debug("\nSkipping upload of empty file: " + job.Path) results <- nil atomic.AddInt64(currentFile, 1) - return + continue } if err := clientUpload.Upload(context.Background(), &job, conf, bucket); err != nil { logger.Debug("Error while worker tried to upload file: <"+job.Path+"> to storage api", zap.Error(err)) - for Retries < 20 { + + fileRetries := 0 + maxRetries := 5 + + for fileRetries < maxRetries { + fileRetries++ atomic.AddInt64(&Retries, 1) - _, err := job.FileContent.Seek(0, 0) - if err != nil { - logger.Debug("An error occurred while seeking fileContent", zap.Error(err)) + + _, seekErr := job.FileContent.Seek(0, 0) + if seekErr != nil { + logger.Debug("An error occurred while seeking fileContent", zap.Error(seekErr)) break } - logger.Debug("Retrying to upload the following file: <"+job.Path+"> to storage api", zap.Error(err)) + logger.Debug("Retrying to upload file", zap.Int("attempt", fileRetries), zap.Int("maxRetries", maxRetries), zap.String("path", job.Path)) err = clientUpload.Upload(context.Background(), &job, conf, bucket) - if err != nil { - continue + if err == nil { + break } - break } - if Retries >= 20 { - logger.Debug("There have been 20 retries already, quitting upload") + if fileRetries >= maxRetries { + logger.Debug("Failed to upload file after retries", zap.Int("maxRetries", maxRetries), zap.String("path", job.Path)) results <- err return } From a7f1d139a9a0d2e0e8dd48cc540483070598802b Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:23:21 -0300 Subject: [PATCH 09/18] chore: update go version --- .github/workflows/deploy_prod.yml | 2 +- .github/workflows/deploy_stage.yml | 2 +- .github/workflows/e2e_test.yml | 2 +- .github/workflows/generate_docs.yml | 2 +- .github/workflows/package-audit.yaml | 2 +- .github/workflows/test_and_build.yml | 2 +- Dockerfile | 2 +- Makefile | 2 +- go.mod | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy_prod.yml b/.github/workflows/deploy_prod.yml index a96038cab..ef453a67c 100644 --- a/.github/workflows/deploy_prod.yml +++ b/.github/workflows/deploy_prod.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest container: - image: golang:1.25.0 + image: golang:1.25.3 outputs: binver: ${{ steps.binversion.outputs.BIN_VERSION }} env: diff --git a/.github/workflows/deploy_stage.yml b/.github/workflows/deploy_stage.yml index 0d4a84c39..9e1dded0e 100644 --- a/.github/workflows/deploy_stage.yml +++ b/.github/workflows/deploy_stage.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest container: - image: golang:1.25.0 + image: golang:1.25.3 env: CGO_ENABLED: 0 # Statically linked diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml index 624f8120a..4f5c497d8 100644 --- a/.github/workflows/e2e_test.yml +++ b/.github/workflows/e2e_test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-e2e') }} container: - image: golang:1.25.0 + image: golang:1.25.3 env: CGO_ENABLED: 0 # Statically linked diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 72ce9ff4c..6badd2a43 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest container: - image: golang:1.25.0 + image: golang:1.25.3 env: CGO_ENABLED: 0 # Statically linked diff --git a/.github/workflows/package-audit.yaml b/.github/workflows/package-audit.yaml index 507d88f93..a18807b46 100644 --- a/.github/workflows/package-audit.yaml +++ b/.github/workflows/package-audit.yaml @@ -12,7 +12,7 @@ jobs: name: Package Auditor (GoVulnCheck) runs-on: ubuntu-latest container: - image: golang:1.25.0 + image: golang:1.25.3 steps: - name: Checkout Repository uses: actions/checkout@v3 diff --git a/.github/workflows/test_and_build.yml b/.github/workflows/test_and_build.yml index 9c303b602..c61f4ab5c 100644 --- a/.github/workflows/test_and_build.yml +++ b/.github/workflows/test_and_build.yml @@ -14,7 +14,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-unit-tests') }} runs-on: ubuntu-latest container: - image: golang:1.25.0 + image: golang:1.25.3 steps: - name: Setting GIT diff --git a/Dockerfile b/Dockerfile index c4921da3f..30c57d06c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.0 +FROM golang:1.25.3 WORKDIR /go/src/app diff --git a/Makefile b/Makefile index 67b742fa6..b36b3b114 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ SHELL := env PATH=$(PATH) /bin/bash NAME := azion ifeq (, $(GO)) -$(error "No go binary found in your system, please install go 1.25.0 before continuing") +$(error "No go binary found in your system, please install go 1.25.3 before continuing") endif GOPATH ?= $(shell $(GO) env GOPATH) diff --git a/go.mod b/go.mod index e91d859f5..7aac80896 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/aziontech/azion-cli -go 1.25.0 +go 1.25.3 require ( github.com/AlecAivazis/survey/v2 v2.3.7 From 7dd565fc9208b0a27c9e20298695089287241e46 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:12:34 -0300 Subject: [PATCH 10/18] refactor: update deps --- go.mod | 28 ++++++++++++++-------------- go.sum | 56 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 7aac80896..7c691c3d7 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.25.3 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/MakeNowJust/heredoc v1.0.0 - github.com/aws/aws-sdk-go-v2 v1.39.3 - github.com/aws/aws-sdk-go-v2/config v1.31.14 - github.com/aws/aws-sdk-go-v2/credentials v1.18.18 - github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6 + github.com/aws/aws-sdk-go-v2 v1.39.5 + github.com/aws/aws-sdk-go-v2/config v1.31.16 + github.com/aws/aws-sdk-go-v2/credentials v1.18.20 + github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1 github.com/aziontech/azionapi-go-sdk v0.143.0 github.com/aziontech/go-thoth v0.0.0-20240228144710-d061a88cc39f github.com/aziontech/tablecli v0.0.0-20241007135202-07712c07aa9e @@ -49,18 +49,18 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect github.com/aws/smithy-go v1.23.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect diff --git a/go.sum b/go.sum index 5867089a7..3b77cc72a 100644 --- a/go.sum +++ b/go.sum @@ -50,40 +50,40 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.39.3 h1:h7xSsanJ4EQJXG5iuW4UqgP7qBopLpj84mpkNx3wPjM= -github.com/aws/aws-sdk-go-v2 v1.39.3/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w= +github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= -github.com/aws/aws-sdk-go-v2/config v1.31.14 h1:kj/KpDqvt0UqcEL3WOvCykE9QUpBb6b23hQdnXe+elo= -github.com/aws/aws-sdk-go-v2/config v1.31.14/go.mod h1:X5PaY6QCzViihn/ru7VxnIamcJQrG9NSeTxuSKm2YtU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.18 h1:5AfxTvDN0AJoA7rg/yEc0sHhl6/B9fZ+NtiQuOjWGQM= -github.com/aws/aws-sdk-go-v2/credentials v1.18.18/go.mod h1:m9mE1mJ1s7zI6rrt7V3RQU2SCgUbNaphlfqEksLp+Fs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10 h1:UuGVOX48oP4vgQ36oiKmW9RuSeT8jlgQgBFQD+HUiHY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10/go.mod h1:vM/Ini41PzvudT4YkQyE/+WiQJiQ6jzeDyU8pQKwCac= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 h1:mj/bdWleWEh81DtpdHKkw41IrS+r3uw1J/VQtbwYYp8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10/go.mod h1:7+oEMxAZWP8gZCyjcm9VicI0M61Sx4DJtcGfKYv2yKQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 h1:wh+/mn57yhUrFtLIxyFPh2RgxgQz/u+Yrf7hiHGHqKY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10/go.mod h1:7zirD+ryp5gitJJ2m1BBux56ai8RIRDykXZrJSp540w= +github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc= +github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34= +github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q= +github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 h1:p/9flfXdoAnwJnuW9xHEAFY22R3A6skYkW19JFF9F+8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12/go.mod h1:ZTLHakoVCTtW8AaLGSwJ3LXqHD9uQKnOcv1TrpO6u2k= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 h1:2lTWFvRcnWFFLzHWmtddu5MTchc5Oj2OOey++99tPZ0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12/go.mod h1:hI92pK+ho8HVcWMHKHrK3Uml4pfG7wvL86FzO0LVtQQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10 h1:FHw90xCTsofzk6vjU808TSuDtDfOOKPNdz5Weyc3tUI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10/go.mod h1:n8jdIE/8F3UYkg8O4IGkQpn2qUmapg/1K1yl29/uf/c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12 h1:itu4KHu8JK/N6NcLIISlf3LL1LccMqruLUXZ9y7yBZw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12/go.mod h1:i+6vTU3xziikTY3vcox23X8pPGW5X3wVgd1VZ7ha+x8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1 h1:ne+eepnDB2Wh5lHKzELgEncIqeVlQ1rSF9fEa4r5I+A= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1/go.mod h1:u0Jkg0L+dcG1ozUq21uFElmpbmjBnhHR5DELHIme4wg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10 h1:DRND0dkCKtJzCj4Xl4OpVbXZgfttY5q712H9Zj7qc/0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10/go.mod h1:tGGNmJKOTernmR2+VJ0fCzQRurcPZj9ut60Zu5Fi6us= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10 h1:DA+Hl5adieRyFvE7pCvBWm3VOZTRexGVkXw33SUqNoY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10/go.mod h1:L+A89dH3/gr8L4ecrdzuXUYd1znoko6myzndVGZx/DA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6 h1:Hcb4yllr4GTOHC/BKjEklxWhciWMHIqzeCI9oYf1OIk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6/go.mod h1:N/iojY+8bW3MYol9NUMuKimpSbPEur75cuI1SmtonFM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.7 h1:fspVFg6qMx0svs40YgRmE7LZXh9VRZvTT35PfdQR6FM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.7/go.mod h1:BQTKL3uMECaLaUV3Zc2L4Qybv8C6BIXjuu1dOPyxTQs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2 h1:scVnW+NLXasGOhy7HhkdT9AGb6kjgW7fJ5xYkUaqHs0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2/go.mod h1:FRNCY3zTEWZXBKm2h5UBUPvCVDOecTad9KhynDyGBc0= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.8 h1:xSL4IV19pKDASL2fjWXRfTGmZddPiPPZNPpbv6uZQZY= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.8/go.mod h1:L1xxV3zAdB+qVrVW/pBIrIAnHFWHo6FBbFe4xOGsG/o= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3 h1:NEe7FaViguRQEm8zl8Ay/kC/QRsMtWUiCGZajQIsLdc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3/go.mod h1:JLuCKu5VfiLBBBl/5IzZILU7rxS0koQpHzMOCzycOJU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 h1:MM8imH7NZ0ovIVX7D2RxfMDv7Jt9OiUXkcQ+GqywA7M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12/go.mod h1:gf4OGwdNkbEsb7elw2Sy76odfhwNktWII3WgvQgQQ6w= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12 h1:R3uW0iKl8rgNEXNjVGliW/oMEh9fO/LlUEV8RvIFr1I= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12/go.mod h1:XEttbEr5yqsw8ebi7vlDoGJJjMXRez4/s9pibpJyL5s= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1 h1:Dq82AV+Qxpno/fG162eAhnD8d48t9S+GZCfz7yv1VeA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1/go.mod h1:MbKLznDKpf7PnSonNRUVYZzfP0CeLkRIUexeblgKcU4= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA= +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY= +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= From aef6d385d52b5540b4c6e97270ec3638d82854c2 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:55:57 -0300 Subject: [PATCH 11/18] chore: add option for sensitive info --- pkg/cmd/deploy_remote/upload.go | 2 + pkg/cmd/deploy_remote/utils.go | 92 --------------------------------- pkg/cmd/init/contracts.go | 5 +- pkg/cmd/init/init.go | 5 +- pkg/cmd/init/utils.go | 6 --- utils/helpers.go | 21 ++++++-- 6 files changed, 25 insertions(+), 106 deletions(-) diff --git a/pkg/cmd/deploy_remote/upload.go b/pkg/cmd/deploy_remote/upload.go index 196d8520c..50d69fe49 100644 --- a/pkg/cmd/deploy_remote/upload.go +++ b/pkg/cmd/deploy_remote/upload.go @@ -74,6 +74,8 @@ func (cmd *DeployCmd) uploadFiles( } if info.Mode()&os.ModeSymlink != 0 { + logger.Debug("Skipping symlink file", zap.Any("File name", pathDir)) + return nil } logger.Debug("Reading the following file", zap.Any("File name", pathDir)) if !info.IsDir() { diff --git a/pkg/cmd/deploy_remote/utils.go b/pkg/cmd/deploy_remote/utils.go index d60fa69be..ea9bd0f13 100644 --- a/pkg/cmd/deploy_remote/utils.go +++ b/pkg/cmd/deploy_remote/utils.go @@ -2,15 +2,12 @@ package deploy import ( "encoding/json" - "fmt" "os" "path" msg "github.com/aziontech/azion-cli/messages/deploy" "github.com/aziontech/azion-cli/pkg/contracts" "github.com/aziontech/azion-cli/pkg/logger" - vulcanPkg "github.com/aziontech/azion-cli/pkg/vulcan" - edgesdk "github.com/aziontech/azionapi-v4-go-sdk-dev/edge-api" "go.uber.org/zap" ) @@ -30,92 +27,3 @@ func WriteManifest(manifest *contracts.ManifestV4, pathMan string) error { return nil } - -func (cmd *DeployCmd) firstRunManifestToConfig(conf *contracts.AzionApplicationOptions) error { - - truePointer := true - appManifest := contracts.Applications{ - Name: conf.Name, - Modules: &edgesdk.ApplicationModulesRequest{ - Functions: &edgesdk.EdgeFunctionModuleRequest{ - Enabled: &truePointer, - }, - }, - Active: &truePointer, - } - - storageType := edgesdk.ConnectorStorageAttributesRequest{ - Bucket: conf.Bucket, - Prefix: &conf.Prefix, - } - storageConnector := edgesdk.ConnectorStorageRequest{ - Name: conf.Name, - Active: &truePointer, - Attributes: storageType, - } - connectorManifest := edgesdk.ConnectorPolymorphicRequest{ - ConnectorStorageRequest: &storageConnector, - } - - functionMan := contracts.Function{ - Name: conf.Name, - Argument: ".edge/worker.js", - Bindings: contracts.FunctionBindings{ - Storage: contracts.StorageBinding{ - Bucket: conf.Bucket, - Prefix: conf.Prefix, - }, - }, - } - - storageMan := contracts.StorageManifest{ - Name: conf.Bucket, - EdgeAccess: "read_only", - Dir: conf.Prefix, - } - - manifestToConfig := &contracts.ManifestV4{} - manifestToConfig.Connectors = append(manifestToConfig.Connectors, connectorManifest) - manifestToConfig.Applications = append(manifestToConfig.Applications, appManifest) - manifestToConfig.Functions = append(manifestToConfig.Functions, functionMan) - manifestToConfig.Storage = append(manifestToConfig.Storage, storageMan) - - err := cmd.WriteManifest(manifestToConfig, "") - if err != nil { - return err - } - defer os.Remove("manifesttoconvert.json") - - vul := vulcanPkg.NewVulcan() - command := vul.Command("", "manifest -o %s transform %s", cmd.F) - format, err := findAzionConfig() - if err != nil { - format = ".mjs" - } - fileName := fmt.Sprintf("azion.config%s", format) - err = cmd.commandRunInteractive(cmd.F, fmt.Sprintf(command, fileName, "manifesttoconvert.json")) - if err != nil { - return err - } - err = cmd.callBundlerInit(conf) - if err != nil { - return nil - } - - return nil -} - -func findAzionConfig() (string, error) { - extensions := []string{".cjs", ".mjs", ".js"} - baseName := "azion.config" - - for _, ext := range extensions { - filename := baseName + ext - if _, err := os.Stat(filename); err == nil { - return ext, nil - } else if !os.IsNotExist(err) { - return "", fmt.Errorf("error checking file %s: %w", filename, err) - } - } - return "", fmt.Errorf("no azion.config file found") -} diff --git a/pkg/cmd/init/contracts.go b/pkg/cmd/init/contracts.go index cda077f51..fc97da5f2 100644 --- a/pkg/cmd/init/contracts.go +++ b/pkg/cmd/init/contracts.go @@ -25,6 +25,7 @@ type Extras struct { // ExtraInput represents a single input for the extras block // For type "env", each input contains a key and a user-facing prompt text type ExtraInput struct { - Key string `json:"key"` - Text string `json:"text"` + Key string `json:"key"` + Text string `json:"text"` + IsSecret bool `json:"is_secret"` } diff --git a/pkg/cmd/init/init.go b/pkg/cmd/init/init.go index 3933a496d..cdbc149b1 100644 --- a/pkg/cmd/init/init.go +++ b/pkg/cmd/init/init.go @@ -37,7 +37,8 @@ import ( const ( SAMPLESURL = "https://github.com/aziontech/azion-samples.git" - APIURL = "https://api.azion.com/v4/utils/project_samples" + // APIURL = "https://api.azion.com/v4/utils/project_samples" + APIURL = "https://nf6r0s48g4.map.azionedge.net/api/templates" ) type initCmd struct { @@ -278,7 +279,7 @@ func (cmd *initCmd) Run(c *cobra.Command, _ []string) error { if selectedItem.Extras != nil && len(selectedItem.Extras.Inputs) > 0 { inputs := make([]utils.EnvInput, 0, len(selectedItem.Extras.Inputs)) for _, in := range selectedItem.Extras.Inputs { - inputs = append(inputs, utils.EnvInput{Key: in.Key, Text: in.Text}) + inputs = append(inputs, utils.EnvInput{Key: in.Key, Text: in.Text, IsSecret: in.IsSecret}) } switch strings.ToLower(selectedItem.Extras.Type) { case "env": diff --git a/pkg/cmd/init/utils.go b/pkg/cmd/init/utils.go index bf86e0052..33e91f3d5 100644 --- a/pkg/cmd/init/utils.go +++ b/pkg/cmd/init/utils.go @@ -47,12 +47,6 @@ func (cmd *initCmd) selectVulcanTemplates(vul *vulcanPkg.VulcanPkg) error { return err } - // cmdVulcanInit := "store init" - // if len(cmd.preset) > 0 { - // formatted := fmt.Sprintf(jsonTemplate, cmd.preset) - // cmdVulcanInit = fmt.Sprintf("%s --config '%s'", cmdVulcanInit, formatted) - // } - // TODO: use later cmdVulcanBuild := "build" if len(cmd.preset) > 0 { diff --git a/utils/helpers.go b/utils/helpers.go index fe25834b2..2425e4443 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -737,8 +737,9 @@ func ReplaceInvalidCharsBucket(str string) string { // EnvInput represents a key and a user-facing prompt text for collecting // environment variable values and writing them to a .env file. type EnvInput struct { - Key string - Text string + Key string + Text string + IsSecret bool } // CollectEnvInputsAndWriteFile prompts the user for each provided EnvInput and @@ -753,7 +754,13 @@ func CollectEnvInputsAndWriteFile(inputs []EnvInput, destDir string) error { // Collect inputs from user envLines := make([]string, 0, len(inputs)) for _, in := range inputs { - val, err := AskInputEmpty(in.Text) + var val string + var err error + if in.IsSecret { + val, err = AskPassword(in.Text) + } else { + val, err = AskInputEmpty(in.Text) + } if err != nil { return err } @@ -790,7 +797,13 @@ func CollectArgsInputsAndWriteFile(inputs []EnvInput, destDir string) error { data := make(map[string]string) for _, in := range inputs { - val, err := AskInputEmpty(in.Text) + var val string + var err error + if in.IsSecret { + val, err = AskPassword(in.Text) + } else { + val, err = AskInputEmpty(in.Text) + } if err != nil { return err } From 593622e40bd10015c30ff1868a2cc99aed8466f2 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:57:04 -0300 Subject: [PATCH 12/18] chore: update APIURL var --- pkg/cmd/init/init.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/cmd/init/init.go b/pkg/cmd/init/init.go index cdbc149b1..b9fb465c4 100644 --- a/pkg/cmd/init/init.go +++ b/pkg/cmd/init/init.go @@ -37,8 +37,7 @@ import ( const ( SAMPLESURL = "https://github.com/aziontech/azion-samples.git" - // APIURL = "https://api.azion.com/v4/utils/project_samples" - APIURL = "https://nf6r0s48g4.map.azionedge.net/api/templates" + APIURL = "https://api.azion.com/v4/utils/project_samples" ) type initCmd struct { From 5fefb358bcdb24f7714fb82753c3c8d7a0e7dfa8 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:54:41 -0300 Subject: [PATCH 13/18] chore: update generate docs code --- .github/workflows/generate_docs.yml | 4 ++-- cmd/gen_docs/main.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 6badd2a43..66301ec22 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -22,7 +22,7 @@ jobs: run: git config --global url."https://${{ secrets.GLOBAL_TOKEN }}:x-oauth-basic@github.com/aziontech".insteadOf "https://github.com/aziontech" - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -30,7 +30,7 @@ jobs: run: go run ./cmd/gen_docs/main.go --file-type "md" --doc-path "/tmp/docs" - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 repository: ${{github.repository}}.wiki diff --git a/cmd/gen_docs/main.go b/cmd/gen_docs/main.go index 146eaa960..eae5eda0b 100644 --- a/cmd/gen_docs/main.go +++ b/cmd/gen_docs/main.go @@ -11,6 +11,7 @@ import ( cmd "github.com/aziontech/azion-cli/pkg/cmd/root" "github.com/aziontech/azion-cli/pkg/cmdutil" "github.com/aziontech/azion-cli/pkg/iostreams" + "github.com/aziontech/azion-cli/pkg/logger" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "github.com/spf13/pflag" @@ -48,6 +49,9 @@ func run(args []string) error { return fmt.Errorf("error: --doc-path not set") } + // Initialize logger to prevent nil pointer dereference + logger.LogLevel(logger.Logger{}) + fact := cmd.NewFactoryRoot(&cmdutil.Factory{ IOStreams: iostreams.System(), Config: emptyConfig{}, From 4fdee66a9416343050e035af6475152fda8d4410 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:44:48 -0300 Subject: [PATCH 14/18] chore: keep azion.json order when synching --- pkg/cmd/sync/sync.go | 2 +- pkg/cmd/sync/tasks.go | 4 +- pkg/v3commands/sync/sync.go | 2 +- pkg/v3commands/sync/tasks.go | 8 +- utils/helpers.go | 209 +++++++++++++++++++++++++++++++++++ utils/helpers_test.go | 189 +++++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+), 8 deletions(-) diff --git a/pkg/cmd/sync/sync.go b/pkg/cmd/sync/sync.go index 2c97b34a1..28f6fcd33 100644 --- a/pkg/cmd/sync/sync.go +++ b/pkg/cmd/sync/sync.go @@ -38,7 +38,7 @@ func NewSyncCmd(f *cmdutil.Factory) *SyncCmd { F: f, Io: f.IOStreams, GetAzionJsonContent: utils.GetAzionJsonContent, - WriteAzionJsonContent: utils.WriteAzionJsonContent, + WriteAzionJsonContent: utils.WriteAzionJsonContentPreserveOrder, SyncResources: SyncLocalResources, ReadEnv: godotenv.Read, WriteManifest: WriteManifest, diff --git a/pkg/cmd/sync/tasks.go b/pkg/cmd/sync/tasks.go index 46a0974f9..a19264968 100644 --- a/pkg/cmd/sync/tasks.go +++ b/pkg/cmd/sync/tasks.go @@ -159,7 +159,7 @@ func (synch *SyncCmd) syncCache(info contracts.SyncOpts, f *cmdutil.Factory, man cacheAzion = append(cacheAzion, newCache) info.Conf.CacheSettings = cacheAzion } - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentPreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return remoteCacheIds, err @@ -254,7 +254,7 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man // Update the configuration with all rules info.Conf.RulesEngine.Rules = rulesAzion - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentPreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return err diff --git a/pkg/v3commands/sync/sync.go b/pkg/v3commands/sync/sync.go index 74bf4ab05..34ab8e231 100644 --- a/pkg/v3commands/sync/sync.go +++ b/pkg/v3commands/sync/sync.go @@ -38,7 +38,7 @@ func NewSyncCmd(f *cmdutil.Factory) *SyncCmd { F: f, Io: f.IOStreams, GetAzionJsonContent: utils.GetAzionJsonContentV3, - WriteAzionJsonContent: utils.WriteAzionJsonContentV3, + WriteAzionJsonContent: utils.WriteAzionJsonContentV3PreserveOrder, SyncResources: SyncLocalResources, ReadEnv: godotenv.Read, WriteManifest: WriteManifest, diff --git a/pkg/v3commands/sync/tasks.go b/pkg/v3commands/sync/tasks.go index b7b11a7dd..eb1bb33ee 100644 --- a/pkg/v3commands/sync/tasks.go +++ b/pkg/v3commands/sync/tasks.go @@ -128,7 +128,7 @@ func (synch *SyncCmd) syncOrigin(info contracts.SyncOptsV3, f *cmdutil.Factory, originsAzion = append(originsAzion, newOrigin) info.Conf.Origin = originsAzion } - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentV3PreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return remoteOriginIds, err @@ -187,7 +187,7 @@ func (synch *SyncCmd) syncCache(info contracts.SyncOptsV3, f *cmdutil.Factory, m cacheAzion = append(cacheAzion, newCache) info.Conf.CacheSettings = cacheAzion } - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentV3PreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return remoteCacheIds, err @@ -240,7 +240,7 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOptsV3, f *cmdutil.Factory, m rulesAzion = append(rulesAzion, newRule) info.Conf.RulesEngine.Rules = rulesAzion } - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentV3PreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return err @@ -286,7 +286,7 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOptsV3, f *cmdutil.Factory, m info.Conf.RulesEngine.Rules = rulesAzion } - err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) + err = utils.WriteAzionJsonContentV3PreserveOrder(info.Conf, ProjectConf) if err != nil { logger.Debug("Error while writing azion.json file", zap.Error(err)) return err diff --git a/utils/helpers.go b/utils/helpers.go index 2425e4443..1761213be 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -230,6 +230,215 @@ func WriteAzionJsonContentV3(conf *contracts.AzionApplicationOptionsV3, confPath return nil } +// WriteAzionJsonContentPreserveOrder writes azion.json while preserving the order of existing resources +func WriteAzionJsonContentPreserveOrder(conf *contracts.AzionApplicationOptions, confPath string) error { + wd, err := GetWorkingDir() + if err != nil { + return err + } + + dirPath := path.Join(wd, confPath) + err = os.MkdirAll(dirPath, 0755) + if err != nil { + logger.Debug("Error creating directory", zap.Error(err)) + return err + } + + existingConf, err := GetAzionJsonContent(confPath) + if err != nil { + return WriteAzionJsonContent(conf, confPath) + } + + mergedConf := mergeResourcesPreservingOrder(existingConf, conf) + + data, err := json.MarshalIndent(mergedConf, "", " ") + if err != nil { + logger.Debug("Error marshalling response", zap.Error(err)) + return ErrorMarshalAzionJsonFile + } + + err = os.WriteFile(path.Join(wd, confPath, "azion.json"), data, 0644) + if err != nil { + logger.Debug("Error writing file", zap.Error(err)) + return ErrorWritingAzionJsonFile + } + + return nil +} + +// WriteAzionJsonContentV3PreserveOrder writes azion.json while preserving the order of existing resources for V3 +func WriteAzionJsonContentV3PreserveOrder(conf *contracts.AzionApplicationOptionsV3, confPath string) error { + wd, err := GetWorkingDir() + if err != nil { + return err + } + + dirPath := path.Join(wd, confPath) + err = os.MkdirAll(dirPath, 0755) + if err != nil { + logger.Debug("Error creating directory", zap.Error(err)) + return err + } + + existingConf, err := GetAzionJsonContentV3(confPath) + if err != nil { + return WriteAzionJsonContentV3(conf, confPath) + } + + mergedConf := mergeResourcesPreservingOrderV3(existingConf, conf) + + data, err := json.MarshalIndent(mergedConf, "", " ") + if err != nil { + logger.Debug("Error marshalling response", zap.Error(err)) + return ErrorMarshalAzionJsonFile + } + + err = os.WriteFile(path.Join(wd, confPath, "azion.json"), data, 0644) + if err != nil { + logger.Debug("Error writing file", zap.Error(err)) + return ErrorWritingAzionJsonFile + } + + return nil +} + +// mergeResourcesPreservingOrder merges new resources with existing ones while preserving order +func mergeResourcesPreservingOrder(existing, new *contracts.AzionApplicationOptions) *contracts.AzionApplicationOptions { + result := *new + + newCacheMap := make(map[int64]contracts.AzionJsonDataCacheSettings) + for _, cache := range new.CacheSettings { + newCacheMap[cache.Id] = cache + } + + newOriginMap := make(map[int64]contracts.AzionJsonDataOrigin) + for _, origin := range new.Origin { + newOriginMap[origin.OriginId] = origin + } + + newRulesMap := make(map[int64]contracts.AzionJsonDataRules) + for _, rule := range new.RulesEngine.Rules { + newRulesMap[rule.Id] = rule + } + + var mergedCaches []contracts.AzionJsonDataCacheSettings + var mergedOrigins []contracts.AzionJsonDataOrigin + var mergedRules []contracts.AzionJsonDataRules + + for _, existingCache := range existing.CacheSettings { + if newCache, exists := newCacheMap[existingCache.Id]; exists { + mergedCaches = append(mergedCaches, newCache) + delete(newCacheMap, existingCache.Id) + } else { + mergedCaches = append(mergedCaches, existingCache) + } + } + for _, newCache := range newCacheMap { + mergedCaches = append(mergedCaches, newCache) + } + + for _, existingOrigin := range existing.Origin { + if newOrigin, exists := newOriginMap[existingOrigin.OriginId]; exists { + mergedOrigins = append(mergedOrigins, newOrigin) + delete(newOriginMap, existingOrigin.OriginId) + } else { + mergedOrigins = append(mergedOrigins, existingOrigin) + } + } + + for _, newOrigin := range newOriginMap { + mergedOrigins = append(mergedOrigins, newOrigin) + } + + for _, existingRule := range existing.RulesEngine.Rules { + if newRule, exists := newRulesMap[existingRule.Id]; exists { + mergedRules = append(mergedRules, newRule) + delete(newRulesMap, existingRule.Id) + } else { + mergedRules = append(mergedRules, existingRule) + } + } + + for _, newRule := range newRulesMap { + mergedRules = append(mergedRules, newRule) + } + + result.CacheSettings = mergedCaches + result.Origin = mergedOrigins + result.RulesEngine.Rules = mergedRules + + return &result +} + +// mergeResourcesPreservingOrderV3 merges new resources with existing ones while preserving order for V3 +func mergeResourcesPreservingOrderV3(existing, new *contracts.AzionApplicationOptionsV3) *contracts.AzionApplicationOptionsV3 { + result := *new + + newCacheMap := make(map[int64]contracts.AzionJsonDataCacheSettings) + for _, cache := range new.CacheSettings { + newCacheMap[cache.Id] = cache + } + + newOriginMap := make(map[int64]contracts.AzionJsonDataOrigin) + for _, origin := range new.Origin { + newOriginMap[origin.OriginId] = origin + } + + newRulesMap := make(map[int64]contracts.AzionJsonDataRules) + for _, rule := range new.RulesEngine.Rules { + newRulesMap[rule.Id] = rule + } + + var mergedCaches []contracts.AzionJsonDataCacheSettings + var mergedOrigins []contracts.AzionJsonDataOrigin + var mergedRules []contracts.AzionJsonDataRules + + for _, existingCache := range existing.CacheSettings { + if newCache, exists := newCacheMap[existingCache.Id]; exists { + mergedCaches = append(mergedCaches, newCache) + delete(newCacheMap, existingCache.Id) + } else { + mergedCaches = append(mergedCaches, existingCache) + } + } + + for _, newCache := range newCacheMap { + mergedCaches = append(mergedCaches, newCache) + } + + for _, existingOrigin := range existing.Origin { + if newOrigin, exists := newOriginMap[existingOrigin.OriginId]; exists { + mergedOrigins = append(mergedOrigins, newOrigin) + delete(newOriginMap, existingOrigin.OriginId) + } else { + mergedOrigins = append(mergedOrigins, existingOrigin) + } + } + + for _, newOrigin := range newOriginMap { + mergedOrigins = append(mergedOrigins, newOrigin) + } + + for _, existingRule := range existing.RulesEngine.Rules { + if newRule, exists := newRulesMap[existingRule.Id]; exists { + mergedRules = append(mergedRules, newRule) + delete(newRulesMap, existingRule.Id) + } else { + mergedRules = append(mergedRules, existingRule) + } + } + + for _, newRule := range newRulesMap { + mergedRules = append(mergedRules, newRule) + } + + result.CacheSettings = mergedCaches + result.Origin = mergedOrigins + result.RulesEngine.Rules = mergedRules + + return &result +} + func WriteAzionConfig(conf *contracts.AzionConfig) error { logger.Debug("Writing your azion.config file", zap.Any("File name", conf.FileName)) // Get the current working directory diff --git a/utils/helpers_test.go b/utils/helpers_test.go index f52db01ed..d33dfb9db 100644 --- a/utils/helpers_test.go +++ b/utils/helpers_test.go @@ -1544,3 +1544,192 @@ func TestFileExists(t *testing.T) { }) } } + +func TestOrderPreservation(t *testing.T) { + // Test the merge function directly without file I/O + t.Run("Merge Resources Preserving Order V4", func(t *testing.T) { + // Create existing configuration with specific order + existing := &contracts.AzionApplicationOptions{ + Name: "test-app", + CacheSettings: []contracts.AzionJsonDataCacheSettings{ + {Id: 3, Name: "cache-c"}, + {Id: 1, Name: "cache-a"}, + {Id: 2, Name: "cache-b"}, + }, + Origin: []contracts.AzionJsonDataOrigin{ + {OriginId: 30, Name: "origin-z"}, + {OriginId: 10, Name: "origin-x"}, + {OriginId: 20, Name: "origin-y"}, + }, + RulesEngine: contracts.AzionJsonDataRulesEngine{ + Rules: []contracts.AzionJsonDataRules{ + {Id: 300, Name: "rule-gamma", Phase: "request"}, + {Id: 100, Name: "rule-alpha", Phase: "request"}, + {Id: 200, Name: "rule-beta", Phase: "response"}, + }, + }, + } + + // Create new configuration with updates and new resources + new := &contracts.AzionApplicationOptions{ + Name: "test-app", + CacheSettings: []contracts.AzionJsonDataCacheSettings{ + {Id: 1, Name: "cache-a-updated"}, // Updated existing + {Id: 2, Name: "cache-b-updated"}, // Updated existing + {Id: 3, Name: "cache-c-updated"}, // Updated existing + {Id: 4, Name: "cache-d"}, // New resource + }, + Origin: []contracts.AzionJsonDataOrigin{ + {OriginId: 10, Name: "origin-x-updated"}, // Updated existing + {OriginId: 20, Name: "origin-y-updated"}, // Updated existing + {OriginId: 30, Name: "origin-z-updated"}, // Updated existing + {OriginId: 40, Name: "origin-w"}, // New resource + }, + RulesEngine: contracts.AzionJsonDataRulesEngine{ + Rules: []contracts.AzionJsonDataRules{ + {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing + {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing + {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing + {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource + }, + }, + } + + // Test the merge function + result := mergeResourcesPreservingOrder(existing, new) + + // Verify cache settings order: original order preserved, new ones at end + expectedCacheOrder := []string{"cache-c-updated", "cache-a-updated", "cache-b-updated", "cache-d"} + actualCacheOrder := make([]string, len(result.CacheSettings)) + for i, cache := range result.CacheSettings { + actualCacheOrder[i] = cache.Name + } + assert.Equal(t, expectedCacheOrder, actualCacheOrder, "Cache settings order should be preserved") + + // Verify origins order: original order preserved, new ones at end + expectedOriginOrder := []string{"origin-z-updated", "origin-x-updated", "origin-y-updated", "origin-w"} + actualOriginOrder := make([]string, len(result.Origin)) + for i, origin := range result.Origin { + actualOriginOrder[i] = origin.Name + } + assert.Equal(t, expectedOriginOrder, actualOriginOrder, "Origins order should be preserved") + + // Verify rules order: original order preserved, new ones at end + expectedRulesOrder := []string{"rule-gamma-updated", "rule-alpha-updated", "rule-beta-updated", "rule-delta"} + actualRulesOrder := make([]string, len(result.RulesEngine.Rules)) + for i, rule := range result.RulesEngine.Rules { + actualRulesOrder[i] = rule.Name + } + assert.Equal(t, expectedRulesOrder, actualRulesOrder, "Rules order should be preserved") + }) + + t.Run("Merge Resources Preserving Order V3", func(t *testing.T) { + // Create existing configuration with specific order + existing := &contracts.AzionApplicationOptionsV3{ + Name: "test-app-v3", + CacheSettings: []contracts.AzionJsonDataCacheSettings{ + {Id: 3, Name: "cache-c"}, + {Id: 1, Name: "cache-a"}, + {Id: 2, Name: "cache-b"}, + }, + Origin: []contracts.AzionJsonDataOrigin{ + {OriginId: 30, Name: "origin-z"}, + {OriginId: 10, Name: "origin-x"}, + {OriginId: 20, Name: "origin-y"}, + }, + RulesEngine: contracts.AzionJsonDataRulesEngine{ + Rules: []contracts.AzionJsonDataRules{ + {Id: 300, Name: "rule-gamma", Phase: "request"}, + {Id: 100, Name: "rule-alpha", Phase: "request"}, + {Id: 200, Name: "rule-beta", Phase: "response"}, + }, + }, + } + + // Create new configuration with updates and new resources + new := &contracts.AzionApplicationOptionsV3{ + Name: "test-app-v3", + CacheSettings: []contracts.AzionJsonDataCacheSettings{ + {Id: 1, Name: "cache-a-updated"}, // Updated existing + {Id: 2, Name: "cache-b-updated"}, // Updated existing + {Id: 3, Name: "cache-c-updated"}, // Updated existing + {Id: 4, Name: "cache-d"}, // New resource + }, + Origin: []contracts.AzionJsonDataOrigin{ + {OriginId: 10, Name: "origin-x-updated"}, // Updated existing + {OriginId: 20, Name: "origin-y-updated"}, // Updated existing + {OriginId: 30, Name: "origin-z-updated"}, // Updated existing + {OriginId: 40, Name: "origin-w"}, // New resource + }, + RulesEngine: contracts.AzionJsonDataRulesEngine{ + Rules: []contracts.AzionJsonDataRules{ + {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing + {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing + {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing + {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource + }, + }, + } + + // Test the merge function + result := mergeResourcesPreservingOrderV3(existing, new) + + // Verify cache settings order: original order preserved, new ones at end + expectedCacheOrder := []string{"cache-c-updated", "cache-a-updated", "cache-b-updated", "cache-d"} + actualCacheOrder := make([]string, len(result.CacheSettings)) + for i, cache := range result.CacheSettings { + actualCacheOrder[i] = cache.Name + } + assert.Equal(t, expectedCacheOrder, actualCacheOrder, "Cache settings order should be preserved") + + // Verify origins order: original order preserved, new ones at end + expectedOriginOrder := []string{"origin-z-updated", "origin-x-updated", "origin-y-updated", "origin-w"} + actualOriginOrder := make([]string, len(result.Origin)) + for i, origin := range result.Origin { + actualOriginOrder[i] = origin.Name + } + assert.Equal(t, expectedOriginOrder, actualOriginOrder, "Origins order should be preserved") + + // Verify rules order: original order preserved, new ones at end + expectedRulesOrder := []string{"rule-gamma-updated", "rule-alpha-updated", "rule-beta-updated", "rule-delta"} + actualRulesOrder := make([]string, len(result.RulesEngine.Rules)) + for i, rule := range result.RulesEngine.Rules { + actualRulesOrder[i] = rule.Name + } + assert.Equal(t, expectedRulesOrder, actualRulesOrder, "Rules order should be preserved") + }) + + t.Run("JSON Structure Consistency", func(t *testing.T) { + // Test that the JSON structure is consistent + conf := &contracts.AzionApplicationOptions{ + Name: "consistency-test", + CacheSettings: []contracts.AzionJsonDataCacheSettings{ + {Id: 1, Name: "cache-1"}, + {Id: 2, Name: "cache-2"}, + }, + Origin: []contracts.AzionJsonDataOrigin{ + {OriginId: 10, Name: "origin-1"}, + {OriginId: 20, Name: "origin-2"}, + }, + } + + // Test merge with same data (no changes) + merged := mergeResourcesPreservingOrder(conf, conf) + + // Verify all fields are preserved + assert.Equal(t, conf.Name, merged.Name) + assert.Equal(t, len(conf.CacheSettings), len(merged.CacheSettings)) + assert.Equal(t, len(conf.Origin), len(merged.Origin)) + + // Verify order is maintained + for i, cache := range conf.CacheSettings { + assert.Equal(t, cache.Id, merged.CacheSettings[i].Id) + assert.Equal(t, cache.Name, merged.CacheSettings[i].Name) + } + + for i, origin := range conf.Origin { + assert.Equal(t, origin.OriginId, merged.Origin[i].OriginId) + assert.Equal(t, origin.Name, merged.Origin[i].Name) + } + }) +} From 8950aabde1ed9d92838718698361126b7d16bbde Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:40:54 -0300 Subject: [PATCH 15/18] refactor: small code refactor --- pkg/cmd/sync/tasks.go | 63 +++---------------------------------------- 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/pkg/cmd/sync/tasks.go b/pkg/cmd/sync/tasks.go index a19264968..4c0117600 100644 --- a/pkg/cmd/sync/tasks.go +++ b/pkg/cmd/sync/tasks.go @@ -39,17 +39,12 @@ func SyncLocalResources(f *cmdutil.Factory, info contracts.SyncOpts, synch *Sync var err error manifest := &contracts.ManifestV4{} - remoteCaches, err := synch.syncCache(info, f, manifest) + _, err = synch.syncCache(info, f) if err != nil { return fmt.Errorf(msg.ERRORSYNC, err.Error()) } - // remoteConnectors, err := synch.syncConnector(info, f, manifest) - // if err != nil { - // return fmt.Errorf(msg.ERRORSYNC, err.Error()) - // } - - err = synch.syncRules(info, f, manifest, remoteCaches) + err = synch.syncRules(info, f, manifest) if err != nil { return fmt.Errorf(msg.ERRORSYNC, err.Error()) } @@ -82,50 +77,7 @@ func SyncLocalResources(f *cmdutil.Factory, info contracts.SyncOpts, synch *Sync return nil } -// func (synch *SyncCmd) syncConnector(info contracts.SyncOpts, f *cmdutil.Factory, manifest *contracts.ManifestV4) (map[string]contracts.AzionJsonDataConnectors, error) { -// remoteConnectorIds := make(map[string]contracts.AzionJsonDataConnectors) -// client := connectorApi.NewClient(f.HttpClient, f.Config.GetString("api_v4_url"), f.Config.GetString("token")) -// resp, err := client.List(ctx, opts) -// if err != nil { -// return remoteConnectorIds, err -// } - -// ConnectorsAzion := []contracts.AzionJsonDataConnectors{} -// info.Conf.Connectors = ConnectorsAzion - -// for _, connector := range resp.Results { -// remoteConnectorIds[strconv.FormatInt(*&connector.Id, 10)] = contracts.AzionJsonDataConnectors{ -// Id: connector.Id, -// Name: connector.Name, -// Address: connector.Addresses, -// } -// jsonBytes, err := json.Marshal(connector) -// if err != nil { -// return remoteConnectorIds, err -// } -// oEntry := edgesdk.EdgeConnectorPolymorphicRequest{} -// err = json.Unmarshal(jsonBytes, &oEntry) -// if err != nil { -// return remoteConnectorIds, err -// } -// manifest.EdgeConnectors = append(manifest.EdgeConnectors, oEntry) -// newConnector := contracts.AzionJsonDataConnectors{ -// Id: connector.Id, -// Name: connector.Name, -// Address: connector.Addresses, -// } -// ConnectorsAzion = append(ConnectorsAzion, newConnector) -// info.Conf.Connectors = ConnectorsAzion -// } -// err = synch.WriteAzionJsonContent(info.Conf, ProjectConf) -// if err != nil { -// logger.Debug("Error while writing azion.json file", zap.Error(err)) -// return remoteConnectorIds, err -// } -// return remoteConnectorIds, nil -// } - -func (synch *SyncCmd) syncCache(info contracts.SyncOpts, f *cmdutil.Factory, manifest *contracts.ManifestV4) (map[string]contracts.AzionJsonDataCacheSettings, error) { +func (synch *SyncCmd) syncCache(info contracts.SyncOpts, f *cmdutil.Factory) (map[string]contracts.AzionJsonDataCacheSettings, error) { remoteCacheIds := make(map[string]contracts.AzionJsonDataCacheSettings) client := edgeApp.NewClient(f.HttpClient, f.Config.GetString("api_v4_url"), f.Config.GetString("token")) str := strconv.FormatInt(info.Conf.Application.ID, 10) @@ -150,7 +102,6 @@ func (synch *SyncCmd) syncCache(info contracts.SyncOpts, f *cmdutil.Factory, man if err != nil { return remoteCacheIds, err } - // manifest.EdgeApplications[0].Cache = append(manifest.EdgeApplications[0].Cache, cEntry) newCache := contracts.AzionJsonDataCacheSettings{ Id: cache.GetId(), @@ -167,8 +118,7 @@ func (synch *SyncCmd) syncCache(info contracts.SyncOpts, f *cmdutil.Factory, man return remoteCacheIds, nil } -func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, manifest *contracts.ManifestV4, - remoteCacheIds map[string]contracts.AzionJsonDataCacheSettings) error { +func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, manifest *contracts.ManifestV4) error { client := edgeApp.NewClient(f.HttpClient, f.Config.GetString("api_v4_url"), f.Config.GetString("token")) str := strconv.FormatInt(info.Conf.Application.ID, 10) rulesAzion := []contracts.AzionJsonDataRules{} @@ -195,7 +145,6 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man continue } - // Create a ManifestRulesEngine for the request phase rule manifestRule := contracts.ManifestRulesEngine{ Phase: "request", Rule: contracts.ManifestRule{ @@ -206,7 +155,6 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man }, } - // Add rule to manifest manifest.Applications[0].Rules = append(manifest.Applications[0].Rules, manifestRule) newRule := contracts.AzionJsonDataRules{ @@ -217,7 +165,6 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man rulesAzion = append(rulesAzion, newRule) } - // Get response phase rules respResp, err := client.ListRulesEngineResponse(context.Background(), opts, str) if err != nil { logger.Debug("Error while listing response phase rules", zap.Error(err)) @@ -230,7 +177,6 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man continue } - // Create a ManifestRulesEngine for the response phase rule manifestRule := contracts.ManifestRulesEngine{ Phase: "response", Rule: contracts.ManifestRule{ @@ -241,7 +187,6 @@ func (synch *SyncCmd) syncRules(info contracts.SyncOpts, f *cmdutil.Factory, man }, } - // Add rule to manifest manifest.Applications[0].Rules = append(manifest.Applications[0].Rules, manifestRule) newRule := contracts.AzionJsonDataRules{ From 1252f729a5d8a6d6aa2dc8cfdf749cb04ee33f5b Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:50:42 -0300 Subject: [PATCH 16/18] tests: update unit tests --- utils/helpers_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/utils/helpers_test.go b/utils/helpers_test.go index d33dfb9db..33d009281 100644 --- a/utils/helpers_test.go +++ b/utils/helpers_test.go @@ -1587,10 +1587,10 @@ func TestOrderPreservation(t *testing.T) { }, RulesEngine: contracts.AzionJsonDataRulesEngine{ Rules: []contracts.AzionJsonDataRules{ - {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing - {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing - {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing - {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource + {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing + {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing + {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing + {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource }, }, } @@ -1663,10 +1663,10 @@ func TestOrderPreservation(t *testing.T) { }, RulesEngine: contracts.AzionJsonDataRulesEngine{ Rules: []contracts.AzionJsonDataRules{ - {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing - {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing - {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing - {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource + {Id: 100, Name: "rule-alpha-updated", Phase: "request"}, // Updated existing + {Id: 200, Name: "rule-beta-updated", Phase: "response"}, // Updated existing + {Id: 300, Name: "rule-gamma-updated", Phase: "request"}, // Updated existing + {Id: 400, Name: "rule-delta", Phase: "request"}, // New resource }, }, } From 6a335398541626b0adb9541866c5ea488a42cd09 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:14:21 -0300 Subject: [PATCH 17/18] chore: order by id in cache-settings --- pkg/api/applications/cache_setting.go | 2 +- pkg/v3api/edge_applications/cache_setting.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/applications/cache_setting.go b/pkg/api/applications/cache_setting.go index 88fe0bc6d..c9e9dc465 100644 --- a/pkg/api/applications/cache_setting.go +++ b/pkg/api/applications/cache_setting.go @@ -63,7 +63,7 @@ func (c *Client) ListCacheEdgeApp( ) ([]sdk.ResponseListCacheSetting, error) { logger.Debug("List Cache - Application") resp, httpResp, err := c.apiClient.ApplicationsCacheSettingsAPI. - ListCacheSettings(ctx, edgeApplicationID).Execute() + ListCacheSettings(ctx, edgeApplicationID).Ordering("id").Execute() if err != nil { errBody := "" if httpResp != nil { diff --git a/pkg/v3api/edge_applications/cache_setting.go b/pkg/v3api/edge_applications/cache_setting.go index 7627dfe71..f47a4c596 100644 --- a/pkg/v3api/edge_applications/cache_setting.go +++ b/pkg/v3api/edge_applications/cache_setting.go @@ -56,7 +56,7 @@ func (c *Client) ListCacheEdgeApp( ) ([]sdk.ApplicationCacheResults, error) { logger.Debug("List Cache Edge Application") resp, httpResp, err := c.apiClient.EdgeApplicationsCacheSettingsAPI. - EdgeApplicationsEdgeApplicationIdCacheSettingsGet(ctx, edgeApplicationID).Execute() + EdgeApplicationsEdgeApplicationIdCacheSettingsGet(ctx, edgeApplicationID).OrderBy("id").Execute() if err != nil { if httpResp != nil { logger.Debug("Error while listing a cache setting", zap.Error(err)) From c3f853845d159e911d8597dfeddd210603f4ca01 Mon Sep 17 00:00:00 2001 From: PatrickMenoti <82882574+PatrickMenoti@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:20:37 -0300 Subject: [PATCH 18/18] chore: update SDK to use new mtls schema --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7c691c3d7..df2f4fba2 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/aziontech/azionapi-v4-go-sdk-dev v0.80.0 +require github.com/aziontech/azionapi-v4-go-sdk-dev v0.84.0 require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 3b77cc72a..1d610d045 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,8 @@ github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2 github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= github.com/aziontech/azionapi-v4-go-sdk-dev v0.80.0 h1:q4Ma0fp/zcN8zzHtiOjOx+EaB/xK/vmlIMMXSAP4V8Y= github.com/aziontech/azionapi-v4-go-sdk-dev v0.80.0/go.mod h1:kJOyMICpeA+2xeo7/trGKGzMLdBMRKXM9A270vknSbI= +github.com/aziontech/azionapi-v4-go-sdk-dev v0.84.0 h1:WMBXtpqR+4LFzn1B05V4RmMIC/eF9OVbVp3OU/c45Wc= +github.com/aziontech/azionapi-v4-go-sdk-dev v0.84.0/go.mod h1:kJOyMICpeA+2xeo7/trGKGzMLdBMRKXM9A270vknSbI= github.com/aziontech/go-thoth v0.0.0-20240228144710-d061a88cc39f h1:b0IX6tpiiG+QzCVOBqwYEHP5gPeeESq57A5ZXiYyDS4= github.com/aziontech/go-thoth v0.0.0-20240228144710-d061a88cc39f/go.mod h1:v4AMg2JrM68ckr2nt7c7PRuOELek/JbVRa62+inlprQ= github.com/aziontech/tablecli v0.0.0-20241007135202-07712c07aa9e h1:4jnF5abLeCo+SqWNvegJjFuK1FUpIuuy09Fsfj2Ys+0=