Moved extensions and sdk into azure-dev

This commit is contained in:
Wallace Breza 2024-10-17 15:55:43 -07:00
Родитель b252a17b2a
Коммит 8fa9a3f57a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A992B043D2187E1A
117 изменённых файлов: 10944 добавлений и 426 удалений

1
cli/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
*.log

Просмотреть файл

@ -1,11 +1,11 @@
module github.com/azure/azure-dev
module github.com/azure/azure-dev/cli/azd
go 1.23
require (
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.0.0-beta.1
@ -13,7 +13,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.6.0
@ -21,12 +21,12 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/machinelearning/armmachinelearning/v3 v3.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.7.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armdeploymentstacks v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql/v2 v2.0.0-beta.4
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.2.2
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.3.1
github.com/Azure/azure-storage-file-go v0.8.0
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2
github.com/MakeNowJust/heredoc/v2 v2.0.1
@ -38,59 +38,59 @@ require (
github.com/buger/goterm v1.0.4
github.com/cli/browser v1.1.0
github.com/drone/envsubst v1.0.3
github.com/fatih/color v1.13.0
github.com/fatih/color v1.17.0
github.com/gofrs/flock v0.8.1
github.com/golobby/container/v3 v3.3.1
github.com/golobby/container/v3 v3.3.2
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/joho/godotenv v1.4.0
github.com/magefile/mage v1.12.1
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/joho/godotenv v1.5.1
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
github.com/microsoft/ApplicationInsights-Go v0.4.4
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0
github.com/microsoft/go-deviceid v1.0.0
github.com/moby/patternmatcher v0.6.0
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d
github.com/otiai10/copy v1.9.0
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e
github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db
github.com/sethvargo/go-retry v0.2.3
github.com/spf13/cobra v1.3.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/theckman/yacspin v0.13.12
go.lsp.dev/jsonrpc2 v0.10.0
go.opentelemetry.io/otel v1.8.0
go.opentelemetry.io/otel v1.30.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.8.0
go.opentelemetry.io/otel/sdk v1.8.0
go.opentelemetry.io/otel/trace v1.8.0
go.uber.org/atomic v1.9.0
go.opentelemetry.io/otel/sdk v1.30.0
go.opentelemetry.io/otel/trace v1.30.0
go.uber.org/atomic v1.11.0
go.uber.org/multierr v1.8.0
golang.org/x/sys v0.21.0
golang.org/x/sys v0.26.0
gopkg.in/dnaeon/go-vcr.v3 v3.1.2
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Azure/azure-pipeline-go v0.2.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
@ -98,11 +98,12 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/proto/otlp v0.18.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect

Просмотреть файл

@ -13,20 +13,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -35,7 +21,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -51,12 +36,10 @@ github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.0.0 h1:Ai3+BE11JvwQ2PxLGNKAfMNSceYXjeijReLJiCouO6o=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.0.0/go.mod h1:gr6fiHmIii3Zw3riWMSr+P0tWTz4hfqTVcFttdi2JBo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.0.0 h1:5reBX+9pzc5xp9VrjSUoPrE8Wl/3y7wjfHzGjXzJbNk=
@ -71,8 +54,7 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthoriza
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0/go.mod h1:lPneRe3TwsoDRKY4O6YDLXHhEWrD+TIRa8XrV/3/fqw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1 h1:6A4M8smF+y8nM/DYsLNQz9n7n2ZGaEVqfz8ZWQirQkI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1/go.mod h1:WqyxV5S0VtXD2+2d6oPqOvyhGubCvzLCKSAKgQ004Uk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1 h1:ynIxbR7wH5nBEJzprbeBFVBtoYTYcQbN39vM7eNS3Xc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1/go.mod h1:nUhnLNlOtAVpn/PRwJKIf3ulXLvdMiWlGk8nufEUaKc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0 h1:TiYjDq0LCNgtee1teMayYT5FjHmlunWUpthVANUXYPM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0 h1:Z5/bDxQL2Zc9t6ZDwdRU60bpLHZvoKOeuaM7XVbf2z0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0/go.mod h1:0FPu3oDRGPvuX1H8TtHJ5XGA0KrXLunomcixR+PQGGA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.2.0 h1:3L+gX5ssCABAToH0VQ64/oNz7rr+ShW+2sB+sonzIlY=
@ -93,29 +75,24 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.7.1/go.mod h1:21rlzm+SuYrS9ARS92XEGxcHQeLVDcaY2YV30rHjSd4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armdeploymentstacks v1.0.0 h1:lbA8Oh+TM2s7d68yz1hbtysTNpcYuBDsgGdCp03fgNw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armdeploymentstacks v1.0.0/go.mod h1:40Esvrf2NihfFyyoM0lDUCJfEbI6ZxkR7iCwY1IzRAU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 h1:xXmHA6JxGDHOY2anNQhpgIibZOiEaOvPLZOiAs07/4k=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0/go.mod h1:qkZjuhvy20x2Ckq4BzopZ8UjZLhib6nRJbRQiC6EFXY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 h1:wxQx2Bt4xzPIKvW59WQf1tJNx/ZZKPfN+EhPX3Z6CYY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql/v2 v2.0.0-beta.4 h1:REtDJygCJQ4IkwgSOAY3CUpI1+AWKuOVP3jIGPYYiPk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql/v2 v2.0.0-beta.4/go.mod h1:mXe/tbvI454sWulmm+3N5fkEs+yDvbQsA+8Xqr1kEQo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0 h1:XY0plaTx8oeipK+XogAck2Qzv39KdnJNBwrxC4A0GL4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0/go.mod h1:tj2JhpZY+NjcQcZ207YHkfwYuivmTrcj5ZNpQxpT3Qk=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1 h1:fXPMAmuh0gDuRDey0atC8cXBuKIlqCzCkL8sm1n9Ov0=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1/go.mod h1:SUZc9YRRHfx2+FAQKNDGrssXehqLpxmwRv2mC/5ntj4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.2.2 h1:PmDhkIT8S5U4nkY/s78Xmf7CXT8qCliNEBhbrkBp3Q0=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.2.2/go.mod h1:Kj2pCkQ47klX1aAlDnlN/BUvwBiARqIJkc9iw1Up7q8=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.3.1 h1:a1U6j4GPI18JQCqgz7/DcqXA1vzvGBugm14AXZfU0gs=
github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI=
github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
@ -123,22 +100,9 @@ github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b h1:g9SuFmxM/WucQFKTMSP+irxyf5m0RiUJreBDhGI6jSA=
github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b/go.mod h1:XjvqMUpGd3Xn9Jtzk/4GEBCSoBX0eB2RyriXgne0IdM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
@ -150,73 +114,49 @@ github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQt
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY=
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -226,7 +166,6 @@ github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -234,8 +173,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -251,13 +188,10 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golobby/container/v3 v3.3.1 h1:Y+QpwChmkz86tAKimvqc0qls8A4eYm/PhMqSEt/HTj4=
github.com/golobby/container/v3 v3.3.1/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0=
github.com/golobby/container/v3 v3.3.2 h1:7u+RgNnsdVlhGoS8gY4EXAG601vpMMzLZlYqSp77Quw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -267,18 +201,13 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -286,13 +215,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -300,69 +222,26 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -374,29 +253,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magefile/mage v1.12.1 h1:oGdAbhIUd6iKamKlDGVtU6XGdy5SgNuCWn7gCTgHDtU=
github.com/magefile/mage v1.12.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY=
@ -405,23 +269,8 @@ github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63Qvybx
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194=
github.com/microsoft/go-deviceid v1.0.0 h1:i5AQ654Xk9kfvwJeKQm3w2+eT1+ImBDVEpAR0AjpP40=
github.com/microsoft/go-deviceid v1.0.0/go.mod h1:KY13FeVdHkzD8gy+6T8+kVmD/7RMpTaWW75K+T4uZWg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d h1:NqRhLdNVlozULwM1B3VaHhcXYSgrOAv8V5BE65om+1Q=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d/go.mod h1:cxIIfNMTwff8f/ZvRouvWYF6wOoO7nj99neWSx2q/Es=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -434,33 +283,14 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4=
github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e h1:51xcRlSMBU5rhM9KahnJGfEsBPVPz3182TgFRowA8yY=
github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db h1:uwKfJcTGnZ4ziFqr0bjzNc/P1fydrAg450b9Dz8Fj8M=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@ -468,27 +298,16 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=
github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
github.com/sethvargo/go-retry v0.2.3 h1:oYlgvIvsju3jNbottWABtbnoLC+GDtLdBHxKWxQm/iU=
github.com/sethvargo/go-retry v0.2.3/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -496,7 +315,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -507,19 +325,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=
github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=
github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -527,10 +338,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 h1:ao8CJIShCaIbaMsGxy+jp2YHSudketpDgDRcbirov78=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 h1:LrHL1A3KqIgAgi6mK7Q0aczmzU414AONAGT5xtnp+uo=
@ -539,33 +347,23 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0 h1:SMO1Ho
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0/go.mod h1:tsw+QO2+pGo7xOrPXrS27HxW8uqGQkw5AzJwdsoyvgw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.8.0 h1:FVy7BZCjoA2Nk+fHqIdoTmm554J9wTX+YcrDp+mc368=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.8.0/go.mod h1:ztncjvKpotSUQq7rlgPibGt8kZfSI3/jI8EO7JjuY2c=
go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk=
go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c=
go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80=
go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -588,8 +386,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -598,15 +394,9 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -614,11 +404,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -633,35 +421,13 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -671,37 +437,23 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -713,57 +465,26 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -780,7 +501,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -805,22 +525,9 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -841,29 +548,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -894,39 +584,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -941,24 +599,12 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -974,25 +620,18 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dnaeon/go-vcr.v3 v3.1.2 h1:F1smfXBqQqwpVifDfUBQG6zzaGjzT+EnVZakrOdr5wA=
gopkg.in/dnaeon/go-vcr.v3 v3.1.2/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

25
cli/extensions/ai/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,25 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env

Просмотреть файл

@ -0,0 +1,3 @@
# azd ai extension
An AZD AI extension

Просмотреть файл

@ -0,0 +1,51 @@
#!/bin/bash
# Get the directory of the script
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Change to the script directory
cd "$SCRIPT_DIR" || exit
# Define application name
APP_NAME="azd-ext-ai"
# Define output directory
OUTPUT_DIR="$SCRIPT_DIR/bin"
# Define target directory in the user's home
TARGET_DIR="$HOME/.azd/bin"
# Create output and target directories if they don't exist
mkdir -p "$OUTPUT_DIR"
mkdir -p "$TARGET_DIR"
# List of OS and architecture combinations
PLATFORMS=("windows/amd64" "darwin/amd64" "linux/amd64")
# Loop through platforms and build
for PLATFORM in "${PLATFORMS[@]}"; do
OS=$(echo $PLATFORM | cut -d'/' -f1)
ARCH=$(echo $PLATFORM | cut -d'/' -f2)
OUTPUT_NAME="$OUTPUT_DIR/$APP_NAME-$OS-$ARCH"
TARGET_NAME="$TARGET_DIR/$APP_NAME-$OS-$ARCH"
if [ "$OS" = "windows" ]; then
OUTPUT_NAME+='.exe'
TARGET_NAME+='.exe'
fi
echo "Building for $OS/$ARCH..."
GOOS=$OS GOARCH=$ARCH go build -o "$OUTPUT_NAME"
if [ $? -ne 0 ]; then
echo "An error occurred while building for $OS/$ARCH"
exit 1
fi
# Copy the build to the target directory
cp "$OUTPUT_NAME" "$TARGET_NAME"
echo "Copied $OUTPUT_NAME to $TARGET_NAME"
done
echo "Build completed. Binaries are located in the $OUTPUT_DIR directory and copied to $TARGET_DIR."

23
cli/extensions/ai/go.mod Normal file
Просмотреть файл

@ -0,0 +1,23 @@
module github.com/azure/azure-dev/cli/extensions/ai
go 1.23.0
require (
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.7.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0
github.com/fatih/color v1.17.0
github.com/spf13/cobra v1.8.1
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
)

44
cli/extensions/ai/go.sum Normal file
Просмотреть файл

@ -0,0 +1,44 @@
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.7.0 h1:rangxMLGrRjqoVMypfkfwGg4ecLtLPlcjEeHnvu1298=
github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.7.0/go.mod h1:W+7E7pJtvdzscy/I4tqL5C0/weLsa32wyTbHbPdkkv0=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0 h1:TiYjDq0LCNgtee1teMayYT5FjHmlunWUpthVANUXYPM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0/go.mod h1:yErdzWZBzjNJCnbC1DcUcSVhjTgllT4PyOenFSeXSJI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Просмотреть файл

@ -0,0 +1,257 @@
package cmd
import (
"context"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/azure/azure-dev/cli/extensions/ai/internal/service"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext/output"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
type chatUsageFlags struct {
message string
systemMessage string
subscriptionId string
resourceGroup string
serviceName string
modelName string
temperature float32
maxTokens int32
}
var (
defaultSystemMessage = "You are an AI assistant that helps people find information."
defaultTemperature = float32(0.7)
defaultMaxTokens = int32(800)
)
func newChatCommand() *cobra.Command {
chatFlags := &chatUsageFlags{}
chatCmd := &cobra.Command{
Use: "chat",
Short: "Commands for managing chat",
RunE: func(cmd *cobra.Command, args []string) error {
header := output.CommandHeader{
Title: "Chat with AI Model (azd ai chat)",
Description: "Start a chat with an AI model from your Azure AI service model deployment.",
}
header.Print()
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
credential, err := azdContext.Credential()
if err != nil {
return err
}
var aiConfig *service.AiConfig
if chatFlags.subscriptionId != "" && chatFlags.resourceGroup != "" && chatFlags.serviceName != "" {
aiConfig = &service.AiConfig{
Subscription: chatFlags.subscriptionId,
ResourceGroup: chatFlags.resourceGroup,
Service: chatFlags.serviceName,
}
} else {
aiConfig, err = service.LoadOrPrompt(ctx, azdContext)
if err != nil {
return err
}
}
if chatFlags.modelName != "" {
aiConfig.Model = chatFlags.modelName
}
if aiConfig.Model == "" {
selectedDeployment, err := service.PromptModelDeployment(ctx, azdContext)
if err != nil {
if errors.Is(err, service.ErrNoModelDeployments) {
return &ext.ErrorWithSuggestion{
Err: err,
Suggestion: fmt.Sprintf(
"Run %s to create a model deployment",
color.CyanString("azd ai model deployment create"),
),
}
}
return err
}
aiConfig.Model = *selectedDeployment.Name
if err := service.Save(ctx, azdContext, aiConfig); err != nil {
return err
}
fmt.Println()
}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: "Starting chat...",
ClearOnStop: true,
})
loadingSpinner.Start(ctx)
accountClient, err := armcognitiveservices.NewAccountsClient(aiConfig.Subscription, credential, nil)
if err != nil {
return err
}
account, err := accountClient.Get(ctx, aiConfig.ResourceGroup, aiConfig.Service, nil)
if err != nil {
return err
}
keysResponse, err := accountClient.ListKeys(ctx, aiConfig.ResourceGroup, aiConfig.Service, nil)
if err != nil {
return err
}
keyCredential := azcore.NewKeyCredential(*keysResponse.Key1)
endpointName := "OpenAI Language Model Instance API"
endpoint := *account.Properties.Endpoints[endpointName]
chatClient, err := azopenai.NewClientWithKeyCredential(endpoint, keyCredential, nil)
if err != nil {
return err
}
deploymentsClient, err := armcognitiveservices.NewDeploymentsClient(aiConfig.Subscription, credential, nil)
if err != nil {
return err
}
deployment, err := deploymentsClient.Get(ctx, aiConfig.ResourceGroup, aiConfig.Service, aiConfig.Model, nil)
if err != nil {
return err
}
loadingSpinner.Stop(ctx)
fmt.Printf(
"AI Service: %s %s\n",
color.CyanString(aiConfig.Service),
color.HiBlackString("(%s)", aiConfig.ResourceGroup),
)
fmt.Printf(
"Model: %s %s\n",
color.CyanString(aiConfig.Model),
color.HiBlackString(
"(Model: %s, Version: %s)",
*deployment.Properties.Model.Name,
*deployment.Properties.Model.Version,
),
)
fmt.Printf("System Message: %s\n", color.CyanString(chatFlags.systemMessage))
fmt.Printf(
"Temperature: %s %s\n",
color.CyanString(fmt.Sprint(chatFlags.temperature)),
color.HiBlackString("(Controls randomness)"),
)
fmt.Printf(
"Max Tokens: %s %s\n",
color.CyanString(fmt.Sprint(chatFlags.maxTokens)),
color.HiBlackString("(Maximum number of tokens to generate)"),
)
fmt.Println()
messages := []azopenai.ChatRequestMessageClassification{}
messages = append(messages, &azopenai.ChatRequestSystemMessage{
Content: azopenai.NewChatRequestSystemMessageContent(chatFlags.systemMessage),
})
thinkingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: "Thinking...",
})
userMessage := chatFlags.message
for {
var err error
if userMessage == "" {
chatPrompt := ux.NewPrompt(&ux.PromptConfig{
Message: "User",
PlaceHolder: "Press `Ctrl+X` to cancel",
Required: true,
RequiredMessage: "Please enter a message",
ClearOnCompletion: true,
IgnoreHintKeys: true,
})
userMessage, err = chatPrompt.Ask()
if err != nil {
if errors.Is(err, ux.ErrCancelled) {
break
}
return err
}
}
fmt.Printf("%s: %s\n", color.GreenString("User"), color.HiBlackString(userMessage))
fmt.Println()
messages = append(messages, &azopenai.ChatRequestUserMessage{
Content: azopenai.NewChatRequestUserMessageContent(userMessage),
})
var chatResponse *azopenai.ChatCompletions
err = thinkingSpinner.Run(ctx, func(ctx context.Context) error {
response, err := chatClient.GetChatCompletions(ctx, azopenai.ChatCompletionsOptions{
Messages: messages,
DeploymentName: &aiConfig.Model,
Temperature: &chatFlags.temperature,
ResponseFormat: &azopenai.ChatCompletionsTextResponseFormat{},
MaxTokens: &chatFlags.maxTokens,
}, nil)
if err != nil {
return err
}
chatResponse = &response.ChatCompletions
return nil
})
if err != nil {
return err
}
for _, choice := range chatResponse.Choices {
fmt.Printf("%s: %s\n", color.CyanString("AI"), *choice.Message.Content)
}
fmt.Println()
userMessage = ""
}
return nil
},
}
chatCmd.Flags().
StringVar(&chatFlags.systemMessage, "system-message", defaultSystemMessage, "System message to send to the AI model")
chatCmd.Flags().Float32Var(&chatFlags.temperature, "temperature", defaultTemperature, "Temperature for sampling")
chatCmd.Flags().Int32Var(&chatFlags.maxTokens, "max-tokens", defaultMaxTokens, "Maximum number of tokens to generate")
chatCmd.Flags().StringVarP(&chatFlags.message, "message", "m", "", "Message to send to the AI model")
chatCmd.Flags().StringVarP(&chatFlags.modelName, "model deployment name", "d", "", "Name of the model to use")
chatCmd.Flags().StringVarP(&chatFlags.resourceGroup, "resource-group", "g", "", "Azure resource group")
chatCmd.Flags().StringVarP(&chatFlags.serviceName, "name", "n", "", "Azure AI service name")
chatCmd.Flags().StringVarP(&chatFlags.subscriptionId, "subscription", "s", "", "Azure subscription ID")
return chatCmd
}

Просмотреть файл

@ -0,0 +1,471 @@
package cmd
import (
"context"
"errors"
"fmt"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/azure/azure-dev/cli/extensions/ai/internal/service"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
func newDeploymentCommand() *cobra.Command {
deploymentCmd := &cobra.Command{
Use: "deployment",
Short: "Commands for managing Azure AI model deployments",
}
deploymentListCmd := &cobra.Command{
Use: "list",
Short: "List all deployments",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
serviceConfig, err := service.LoadOrPrompt(ctx, azdContext)
if err != nil {
return err
}
credential, err := azdContext.Credential()
if err != nil {
return err
}
deployments := []*armcognitiveservices.Deployment{}
deploymentsClient, err := armcognitiveservices.NewDeploymentsClient(serviceConfig.Subscription, credential, nil)
if err != nil {
return err
}
deploymentsPager := deploymentsClient.NewListPager(serviceConfig.ResourceGroup, serviceConfig.Service, nil)
for deploymentsPager.More() {
pageResponse, err := deploymentsPager.NextPage(ctx)
if err != nil {
return err
}
deployments = append(deployments, pageResponse.Value...)
}
for _, deployment := range deployments {
fmt.Printf("Name: %s\n", *deployment.Name)
fmt.Printf("SKU: %s\n", *deployment.SKU.Name)
fmt.Printf("Model: %s\n", *deployment.Properties.Model.Name)
fmt.Printf("Version: %s\n", *deployment.Properties.Model.Version)
fmt.Println()
}
return nil
},
}
deploymentCreateCmd := &cobra.Command{
Use: "create",
Short: "Create a new model deployment",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
serviceConfig, err := service.LoadOrPrompt(ctx, azdContext)
if err != nil {
return err
}
credential, err := azdContext.Credential()
if err != nil {
return err
}
clientFactory, err := armcognitiveservices.NewClientFactory(serviceConfig.Subscription, credential, nil)
if err != nil {
return err
}
accountsClient := clientFactory.NewAccountsClient()
modelsClient := clientFactory.NewModelsClient()
deploymentsClient := clientFactory.NewDeploymentsClient()
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: "Loading AI models",
})
models := []*armcognitiveservices.Model{}
loadingSpinner.Run(ctx, func(ctx context.Context) error {
aiService, err := accountsClient.Get(ctx, serviceConfig.ResourceGroup, serviceConfig.Service, nil)
if err != nil {
return err
}
modelPager := modelsClient.NewListPager(*aiService.Location, nil)
for modelPager.More() {
pageResponse, err := modelPager.NextPage(ctx)
if err != nil {
return err
}
for _, model := range pageResponse.Value {
if *model.Kind == *aiService.Kind {
models = append(models, model)
}
}
}
return nil
})
modelChoices := make([]string, len(models))
for i, model := range models {
modelChoices[i] = fmt.Sprintf("%s (Version: %s)", *model.Model.Name, *model.Model.Version)
}
modelSelect := ux.NewSelect(&ux.SelectConfig{
Message: "Select a model",
Allowed: modelChoices,
DisplayCount: 10,
DisplayNumbers: to.Ptr(true),
})
selectedModelIndex, err := modelSelect.Ask()
if err != nil {
return err
}
selectedModel := models[*selectedModelIndex]
skuChoices := make([]string, len(selectedModel.Model.SKUs))
for i, sku := range selectedModel.Model.SKUs {
skuChoices[i] = *sku.Name
}
skuPrompt := ux.NewSelect(&ux.SelectConfig{
Message: "Select a Deployment Type",
Allowed: skuChoices,
})
selectedSkuIndex, err := skuPrompt.Ask()
if err != nil {
return err
}
selectedSku := selectedModel.Model.SKUs[*selectedSkuIndex]
var deploymentName string
namePrompt := ux.NewPrompt(&ux.PromptConfig{
Message: "Enter the name for the deployment",
DefaultValue: *selectedModel.Model.Name,
})
deploymentName, err = namePrompt.Ask()
if err != nil {
return err
}
deployment := armcognitiveservices.Deployment{
Name: &deploymentName,
SKU: &armcognitiveservices.SKU{
Name: selectedSku.Name,
Capacity: selectedSku.Capacity.Default,
},
Properties: &armcognitiveservices.DeploymentProperties{
Model: &armcognitiveservices.DeploymentModel{
Format: selectedModel.Model.Format,
Name: selectedModel.Model.Name,
Version: selectedModel.Model.Version,
},
RaiPolicyName: to.Ptr("Microsoft.DefaultV2"),
VersionUpgradeOption: to.Ptr(
armcognitiveservices.DeploymentModelVersionUpgradeOptionOnceNewDefaultVersionAvailable,
),
},
}
fmt.Println()
taskList := ux.NewTaskList(&ux.DefaultTaskListConfig)
if err := taskList.Run(); err != nil {
return err
}
taskList.AddTask(fmt.Sprintf("Creating deployment %s", deploymentName), func() (ux.TaskState, error) {
existingDeployment, err := deploymentsClient.Get(
ctx,
serviceConfig.ResourceGroup,
serviceConfig.Service,
deploymentName,
nil,
)
if err == nil && *existingDeployment.Name == deploymentName {
return ux.Error, errors.New("deployment with the same name already exists")
}
poller, err := deploymentsClient.BeginCreateOrUpdate(
ctx,
serviceConfig.ResourceGroup,
serviceConfig.Service,
deploymentName,
deployment,
nil,
)
if err != nil {
return ux.Error, err
}
if _, err := poller.PollUntilDone(ctx, nil); err != nil {
return ux.Error, err
}
return ux.Success, nil
})
for {
if taskList.Completed() {
if err := taskList.Update(); err != nil {
return err
}
fmt.Println()
color.Green("Deployment '%s' created successfully", deploymentName)
break
}
}
return nil
},
}
type deploymentDeleteFlags struct {
name string
force bool
}
deleteFlags := &deploymentDeleteFlags{}
deploymentDeleteCmd := &cobra.Command{
Use: "delete <deployment-name>",
Short: "Delete a model deployment",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
serviceConfig, err := service.LoadOrPrompt(ctx, azdContext)
if err != nil {
return err
}
credential, err := azdContext.Credential()
if err != nil {
return err
}
clientFactory, err := armcognitiveservices.NewClientFactory(serviceConfig.Subscription, credential, nil)
if err != nil {
return err
}
deploymentsClient := clientFactory.NewDeploymentsClient()
if deleteFlags.name == "" {
deployments := []*armcognitiveservices.Deployment{}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: "Loading AI deployments",
})
err := loadingSpinner.Run(ctx, func(ctx context.Context) error {
deploymentsPager := deploymentsClient.NewListPager(
serviceConfig.ResourceGroup,
serviceConfig.Service,
nil,
)
for deploymentsPager.More() {
pageResponse, err := deploymentsPager.NextPage(ctx)
if err != nil {
return err
}
deployments = append(deployments, pageResponse.Value...)
}
return nil
})
if err != nil {
return err
}
if len(deployments) == 0 {
return fmt.Errorf("no deployments found")
}
deploymentChoices := make([]string, len(deployments))
for i, deployment := range deployments {
deploymentChoices[i] = fmt.Sprintf(
"%s (Model: %s, Version: %s)",
*deployment.Name,
*deployment.Properties.Model.Name,
*deployment.Properties.Model.Version,
)
}
deploymentSelect := ux.NewSelect(&ux.SelectConfig{
Message: "Select a deployment to delete",
Allowed: deploymentChoices,
})
selectedDeploymentIndex, err := deploymentSelect.Ask()
if err != nil {
return err
}
deleteFlags.name = *deployments[*selectedDeploymentIndex].Name
}
_, err = deploymentsClient.Get(ctx, serviceConfig.ResourceGroup, serviceConfig.Service, deleteFlags.name, nil)
if err != nil {
return fmt.Errorf("deployment '%s' not found", deleteFlags.name)
}
confirmed := to.Ptr(false)
if !deleteFlags.force {
confirmPrompt := ux.NewConfirm(&ux.ConfirmConfig{
Message: fmt.Sprintf("Are you sure you want to delete the deployment '%s'?", deleteFlags.name),
DefaultValue: to.Ptr(false),
})
confirmed, err = confirmPrompt.Ask()
if err != nil {
return err
}
}
fmt.Println()
taskList := ux.NewTaskList(&ux.DefaultTaskListConfig)
if err := taskList.Run(); err != nil {
return err
}
taskList.AddTask(fmt.Sprintf("Deleting deployment %s", deleteFlags.name), func() (ux.TaskState, error) {
if !*confirmed {
return ux.Skipped, ux.ErrCancelled
}
poller, err := deploymentsClient.BeginDelete(
ctx,
serviceConfig.ResourceGroup,
serviceConfig.Service,
deleteFlags.name,
nil,
)
if err != nil {
return ux.Error, err
}
if _, err := poller.PollUntilDone(ctx, nil); err != nil {
return ux.Error, err
}
return ux.Success, nil
})
for {
if taskList.Completed() {
if err := taskList.Update(); err != nil {
return err
}
fmt.Println()
color.Green("Deployment '%s' deleted successfully", deleteFlags.name)
break
}
time.Sleep(1 * time.Second)
if err := taskList.Update(); err != nil {
return err
}
}
return nil
},
}
deploymentDeleteCmd.Flags().StringVarP(&deleteFlags.name, "name", "n", "", "Name of the deployment to delete")
deploymentDeleteCmd.Flags().BoolVarP(&deleteFlags.force, "force", "f", false, "Force deletion without confirmation")
type deploymentSelectFlags struct {
deploymentName string
}
selectFlags := &deploymentSelectFlags{}
deploymentSelectCmd := &cobra.Command{
Use: "select",
Short: "Select a model",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
// Load AI config
aiConfig, err := service.LoadOrPrompt(ctx, azdContext)
if err != nil {
return err
}
// Select model deployment
if selectFlags.deploymentName == "" {
selectedDeployment, err := service.PromptModelDeployment(ctx, azdContext)
if err != nil {
return err
}
aiConfig.Model = *selectedDeployment.Name
} else {
aiConfig.Model = selectFlags.deploymentName
}
// Update AI Config
if err := service.Save(ctx, azdContext, aiConfig); err != nil {
return err
}
return nil
},
}
deploymentSelectCmd.Flags().StringVarP(&selectFlags.deploymentName, "name", "n", "", "Model name")
deploymentCmd.AddCommand(deploymentListCmd)
deploymentCmd.AddCommand(deploymentCreateCmd)
deploymentCmd.AddCommand(deploymentDeleteCmd)
deploymentCmd.AddCommand(deploymentSelectCmd)
return deploymentCmd
}

Просмотреть файл

@ -0,0 +1,25 @@
package cmd
import (
"github.com/spf13/cobra"
)
func newModelCommand() *cobra.Command {
modelCmd := &cobra.Command{
Use: "model",
Short: "Commands for managing Azure AI models",
}
modelListCmd := &cobra.Command{
Use: "list",
Short: "List all models",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
modelCmd.AddCommand(modelListCmd)
modelCmd.AddCommand(newDeploymentCommand())
return modelCmd
}

Просмотреть файл

@ -0,0 +1,26 @@
package cmd
import (
"github.com/azure/azure-dev/cli/sdk/azdcore/ext/debug"
"github.com/spf13/cobra"
)
func NewRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "azd ai <group> [options]",
Short: "A CLI for managing AI models and services",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
debug.WaitForDebugger()
},
SilenceUsage: true,
SilenceErrors: true,
}
rootCmd.AddCommand(newModelCommand())
rootCmd.AddCommand(newServiceCommand())
rootCmd.AddCommand(newChatCommand())
rootCmd.PersistentFlags().Bool("debug", false, "Enable debug mode")
return rootCmd
}

Просмотреть файл

@ -0,0 +1,104 @@
package cmd
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/azure/azure-dev/cli/extensions/ai/internal/service"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/spf13/cobra"
)
type serviceSetFlags struct {
subscription string
resourceGroup string
serviceName string
modelName string
}
func newServiceCommand() *cobra.Command {
serviceCmd := &cobra.Command{
Use: "service",
Short: "Commands for managing Azure AI services",
}
setFlags := &serviceSetFlags{}
serviceSetCmd := &cobra.Command{
Use: "set",
Short: "Set the default Azure AI service",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
var aiConfig *service.AiConfig
if setFlags.subscription == "" || setFlags.resourceGroup == "" || setFlags.serviceName == "" {
selectedAccount, err := service.PromptAccount(ctx, azdContext)
if err != nil {
return err
}
parsedResource, err := arm.ParseResourceID(*selectedAccount.ID)
if err != nil {
return err
}
aiConfig = &service.AiConfig{
Subscription: parsedResource.SubscriptionID,
ResourceGroup: parsedResource.ResourceGroupName,
Service: parsedResource.Name,
}
} else {
aiConfig = &service.AiConfig{
Subscription: setFlags.subscription,
ResourceGroup: setFlags.resourceGroup,
Service: setFlags.serviceName,
}
}
if err := service.Save(ctx, azdContext, aiConfig); err != nil {
return err
}
return nil
},
}
serviceSetCmd.Flags().StringVarP(&setFlags.subscription, "subscription", "s", "", "Azure subscription ID")
serviceSetCmd.Flags().StringVarP(&setFlags.resourceGroup, "resource-group", "g", "", "Azure resource group")
serviceSetCmd.Flags().StringVarP(&setFlags.serviceName, "name", "n", "", "Azure AI service name")
serviceShowCmd := &cobra.Command{
Use: "show",
Short: "Show the currently selected Azure AI service",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return err
}
serviceConfig, err := service.Load(ctx, azdContext)
if err != nil {
return err
}
fmt.Printf("Service: %s\n", serviceConfig.Service)
fmt.Printf("Resource Group: %s\n", serviceConfig.ResourceGroup)
fmt.Printf("Subscription ID: %s\n", serviceConfig.Subscription)
return nil
},
}
serviceCmd.AddCommand(serviceSetCmd)
serviceCmd.AddCommand(serviceShowCmd)
return serviceCmd
}

Просмотреть файл

@ -0,0 +1,257 @@
package service
import (
"context"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/azure/azure-dev/cli/sdk/azdcore/azure"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext/prompt"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
)
type AiConfig struct {
Subscription string `json:"subscription"`
ResourceGroup string `json:"resourceGroup"`
Service string `json:"service"`
Model string `json:"model"`
}
var (
ErrNotFound = errors.New("service config not found")
ErrNoModelDeployments = errors.New("no model deployments found")
ErrNoAiServices = errors.New("no Azure AI services found")
)
func LoadOrPrompt(ctx context.Context, azdContext *ext.Context) (*AiConfig, error) {
config, err := Load(ctx, azdContext)
if err != nil && errors.Is(err, ErrNotFound) {
account, err := PromptAccount(ctx, azdContext)
if err != nil {
return nil, err
}
parsedResource, err := arm.ParseResourceID(*account.ID)
if err != nil {
return nil, err
}
config = &AiConfig{
Subscription: parsedResource.SubscriptionID,
ResourceGroup: parsedResource.ResourceGroupName,
Service: parsedResource.Name,
}
if err := Save(ctx, azdContext, config); err != nil {
return nil, err
}
}
return config, nil
}
func Load(ctx context.Context, azdContext *ext.Context) (*AiConfig, error) {
var azdConfig config.Config
env, err := azdContext.Environment(ctx)
if err == nil {
azdConfig = env.Config
} else {
userConfig, err := azdContext.UserConfig(ctx)
if err == nil {
azdConfig = userConfig
}
}
if azdConfig == nil {
return nil, errors.New("azd configuration is not available")
}
var config AiConfig
has, err := azdConfig.GetSection("ai.config", &config)
if err != nil {
return nil, err
}
if has {
return &config, nil
}
return nil, ErrNotFound
}
func Save(ctx context.Context, azdContext *ext.Context, config *AiConfig) error {
if azdContext == nil {
return errors.New("azdContext is required")
}
if config == nil {
return errors.New("config is required")
}
credential, err := azdContext.Credential()
if err != nil {
return err
}
clientFactory, err := armcognitiveservices.NewClientFactory(config.Subscription, credential, nil)
if err != nil {
return err
}
accountClient := clientFactory.NewAccountsClient()
// Validate account name
_, err = accountClient.Get(ctx, config.ResourceGroup, config.Service, nil)
if err != nil {
return fmt.Errorf("the specified service configuration is invalid: %w", err)
}
// Validate model deployment name
if config.Model != "" {
deploymentClient := clientFactory.NewDeploymentsClient()
_, err = deploymentClient.Get(ctx, config.ResourceGroup, config.Service, config.Model, nil)
if err != nil {
return fmt.Errorf("the specified model deployment is invalid: %w", err)
}
}
env, err := azdContext.Environment(ctx)
if err == nil && env != nil {
if err := env.Config.Set("ai.config", config); err != nil {
return err
}
if err := azdContext.SaveEnvironment(ctx, env); err != nil {
return err
}
return nil
}
userConfig, err := azdContext.UserConfig(ctx)
if err == nil && userConfig != nil {
if err := userConfig.Set("ai.config", config); err != nil {
return err
}
if err := azdContext.SaveUserConfig(ctx, userConfig); err != nil {
return err
}
return nil
}
return errors.New("unable to save service configuration")
}
func PromptAccount(ctx context.Context, azdContext *ext.Context) (*armcognitiveservices.Account, error) {
subscription, err := prompt.PromptSubscription(ctx, nil)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
selectedAccount, err := prompt.PromptSubscriptionResource(ctx, subscription, prompt.PromptResourceOptions{
ResourceType: to.Ptr(azure.ResourceTypeCognitiveServiceAccount),
ResourceTypeDisplayName: "Azure AI service",
})
if err != nil {
return nil, err
}
parsedService, err := arm.ParseResourceID(selectedAccount.Id)
if err != nil {
return nil, err
}
accountClient, err := armcognitiveservices.NewAccountsClient(subscription.Id, credential, nil)
if err != nil {
return nil, err
}
accountResponse, err := accountClient.Get(ctx, parsedService.ResourceGroupName, parsedService.Name, nil)
if err != nil {
return nil, err
}
return &accountResponse.Account, nil
}
func PromptModelDeployment(ctx context.Context, azdContext *ext.Context) (*armcognitiveservices.Deployment, error) {
aiConfig, err := LoadOrPrompt(ctx, azdContext)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
deploymentsClient, err := armcognitiveservices.NewDeploymentsClient(aiConfig.Subscription, credential, nil)
if err != nil {
return nil, err
}
deploymentList := []*armcognitiveservices.Deployment{}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: "Loading model deployments...",
})
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
modelPager := deploymentsClient.NewListPager(aiConfig.ResourceGroup, aiConfig.Service, nil)
for modelPager.More() {
models, err := modelPager.NextPage(ctx)
if err != nil {
return err
}
deploymentList = append(deploymentList, models.Value...)
}
return nil
})
if err != nil {
return nil, err
}
choices := make([]string, len(deploymentList))
for i, deployment := range deploymentList {
choices[i] = fmt.Sprintf(
"%s (Model: %s, Version: %s)",
*deployment.Name,
*deployment.Properties.Model.Name,
*deployment.Properties.Model.Version,
)
}
if len(choices) == 0 {
return nil, ErrNoModelDeployments
}
modelSelector := ux.NewSelect(&ux.SelectConfig{
Message: "Select a model",
Allowed: choices,
DisplayCount: 10,
DisplayNumbers: ux.Ptr(true),
})
selectedModelIndex, err := modelSelector.Ask()
if err != nil {
return nil, err
}
return deploymentList[*selectedModelIndex], nil
}

31
cli/extensions/ai/main.go Normal file
Просмотреть файл

@ -0,0 +1,31 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/azure/azure-dev/cli/extensions/ai/internal/cmd"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/fatih/color"
)
func main() {
// Execute the root command
ctx := context.Background()
rootCmd := cmd.NewRootCommand()
if err := rootCmd.ExecuteContext(ctx); err != nil {
var errWithSuggestion *ext.ErrorWithSuggestion
if ok := errors.As(err, &errWithSuggestion); ok {
color.Red("Error: %v", errWithSuggestion.Err)
fmt.Printf("%s: %s\n", color.YellowString("Suggestion:"), errWithSuggestion.Suggestion)
} else {
color.Red("Error: %v", err)
}
os.Exit(1)
}
}

25
cli/extensions/test/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,25 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env

Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Wallace Breza
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Просмотреть файл

@ -0,0 +1,3 @@
# azd test extension
A test AZD extension

Просмотреть файл

@ -0,0 +1,40 @@
#!/bin/bash
# Get the directory of the script
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Change to the script directory
cd "$SCRIPT_DIR" || exit
# Define application name
APP_NAME="azd-ext-test"
# Define output directory
OUTPUT_DIR="$SCRIPT_DIR/bin"
# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_DIR"
# List of OS and architecture combinations
PLATFORMS=("windows/amd64" "darwin/amd64" "linux/amd64")
# Loop through platforms and build
for PLATFORM in "${PLATFORMS[@]}"; do
OS=$(echo $PLATFORM | cut -d'/' -f1)
ARCH=$(echo $PLATFORM | cut -d'/' -f2)
OUTPUT_NAME="$OUTPUT_DIR/$APP_NAME-$OS-$ARCH"
if [ "$OS" = "windows" ]; then
OUTPUT_NAME+='.exe'
fi
echo "Building for $OS/$ARCH..."
GOOS=$OS GOARCH=$ARCH go build -o "$OUTPUT_NAME"
if [ $? -ne 0 ]; then
echo "An error occurred while building for $OS/$ARCH"
exit 1
fi
done
echo "Build completed. Binaries are located in the $OUTPUT_DIR directory."

Просмотреть файл

@ -0,0 +1,16 @@
module github.com/azure/azure-dev/cli/extensions/test
go 1.23.0
require (
github.com/fatih/color v1.17.0
github.com/spf13/cobra v1.8.1
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.26.0 // indirect
)

Просмотреть файл

@ -0,0 +1,20 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Просмотреть файл

@ -0,0 +1,48 @@
package main
import (
"context"
"fmt"
"os"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
type rootFlags struct {
unit bool
}
func main() {
flags := rootFlags{}
rootCmd := &cobra.Command{
Use: "azd test [options]",
Short: "A tool to help test azd projects and services.",
Example: "azd test <service> --unit",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Print("Running tests for all services")
} else {
fmt.Printf("Running tests for %s service", args[0])
}
if flags.unit {
fmt.Print(" (including unit tests)")
}
fmt.Print("\n")
return nil
},
}
rootCmd.Flags().BoolVar(&flags.unit, "unit", false, "Runs unit tests")
ctx := context.Background()
if err := rootCmd.ExecuteContext(ctx); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
}

8
cli/go.work Normal file
Просмотреть файл

@ -0,0 +1,8 @@
go 1.23.0
use (
./azd
./extensions/ai
./extensions/test
./sdk/azdcore
)

58
cli/go.work.sum Normal file
Просмотреть файл

@ -0,0 +1,58 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0/go.mod h1:WCPBHsOXfBVnivScjs2ypRfimjEW0qPVLGgJkZlrIOA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=

Просмотреть файл

@ -0,0 +1,115 @@
package azure
import (
"encoding/base64"
"encoding/json"
"errors"
"regexp"
)
// cspell: disable
// jwtClaimsRegex is a regular expression for JWT. A JWT is a string with three base64 encoded
// components (using the "url safe" base64 alphabet) separated by dots. For example:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
//
//nolint:lll
var jwtClaimsRegex = regexp.MustCompile(`^[a-zA-Z0-9-_]*\.([a-zA-Z0-9-_]*)\.[a-zA-Z0-9-_]*$`)
// cspell: enable
// TokenClaims contains claims about a user from an access token.
// https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference.
type TokenClaims struct {
PreferredUsername string `json:"preferred_username,omitempty"`
UniqueName string `json:"unique_name,omitempty"`
GivenName string `json:"given_name,omitempty"`
FamilyName string `json:"family_name,omitempty"`
MiddleName string `json:"middle_name,omitempty"`
Name string `json:"name,omitempty"`
Oid string `json:"oid,omitempty"`
TenantId string `json:"tid,omitempty"`
Subject string `json:"sub,omitempty"`
Upn string `json:"upn,omitempty"`
Email string `json:"email,omitempty"`
AlternativeId string `json:"alternative_id,omitempty"`
Issuer string `json:"iss,omitempty"`
Audience string `json:"aud,omitempty"`
ExpirationTime int64 `json:"exp,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
}
// Returns an ID associated with the account.
// This ID is suitable for local use, and not for any server authorization use.
func (tc *TokenClaims) LocalAccountId() string {
if tc.Oid != "" {
return tc.Oid
}
// Fall back to sub if oid is not present.
// This happens, for example, for personal accounts in their home tenant.
return tc.Subject
}
// Returns a display name for the account.
func (tc *TokenClaims) DisplayUsername() string {
// For v2.0 token, use preferred_username
if tc.PreferredUsername != "" {
return tc.PreferredUsername
}
// Fallback to unique_name for v1.0 token
return tc.UniqueName
}
func GetTenantIdFromToken(token string) (string, error) {
claims, err := GetClaimsFromAccessToken(token)
if err != nil {
return "", err
}
if claims.TenantId == "" {
return "", errors.New("no tid claim")
}
return claims.TenantId, nil
}
// GetOidFromAccessToken extracts a string claim with the name "oid" from an access token.
// Access Tokens are JWT and the middle component is a base64 encoded string of a JSON object
// with claims.
func GetOidFromAccessToken(token string) (string, error) {
claims, err := GetClaimsFromAccessToken(token)
if err != nil {
return "", err
}
if claims.Oid == "" {
return "", errors.New("no oid claim")
}
return claims.Oid, nil
}
// GetClaimsFromAccessToken extracts claims from an access token.
// Access Tokens are JWT and the middle component is a base64 encoded string of a JSON object
// with claims.
func GetClaimsFromAccessToken(token string) (TokenClaims, error) {
matches := jwtClaimsRegex.FindStringSubmatch(token)
if len(matches) != 2 {
return TokenClaims{}, errors.New("malformed access token")
}
bytes, err := base64.RawURLEncoding.DecodeString(matches[1])
if err != nil {
return TokenClaims{}, err
}
var claims TokenClaims
if err := json.Unmarshal(bytes, &claims); err != nil {
return TokenClaims{}, err
}
return claims, nil
}

Просмотреть файл

@ -0,0 +1,100 @@
package azure
import (
"encoding/json"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
)
const (
ConfigPath = "cloud"
AzurePublicName = "AzureCloud"
AzureChinaCloudName = "AzureChinaCloud"
AzureUSGovernmentName = "AzureUSGovernment"
)
type Cloud struct {
Configuration cloud.Configuration
// The base URL for the cloud's portal (e.g. https://portal.azure.com for
// Azure public cloud).
PortalUrlBase string
// The suffix for the cloud's storage endpoints (e.g. core.windows.net for
// Azure public cloud). These are well known values and can be found at:
// https://<management-endpoint>/metadata/endpoints?api-version=2023-12-01
StorageEndpointSuffix string
// The suffix for the cloud's container registry endpoints. These are well
// known values and can be found at:
// https://<management-endpoint>/metadata/endpoints?api-version=2023-12-01
ContainerRegistryEndpointSuffix string
}
type CloudConfig struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
func NewCloud(config *CloudConfig) (*Cloud, error) {
if cloud, err := parseCloudName(config.Name); err != nil {
return nil, err
} else {
return cloud, nil
}
}
func ParseCloudConfig(partialConfig any) (*CloudConfig, error) {
var config *CloudConfig
jsonBytes, err := json.Marshal(partialConfig)
if err != nil {
return nil, fmt.Errorf("failed to marshal cloud configuration: %w", err)
}
if err := json.Unmarshal(jsonBytes, &config); err != nil {
return nil, fmt.Errorf("failed to unmarshal cloud configuration: %w", err)
}
return config, nil
}
func AzurePublic() *Cloud {
return &Cloud{
Configuration: cloud.AzurePublic,
PortalUrlBase: "https://portal.azure.com",
StorageEndpointSuffix: "core.windows.net",
ContainerRegistryEndpointSuffix: "azurecr.io",
}
}
func AzureGovernment() *Cloud {
return &Cloud{
Configuration: cloud.AzureGovernment,
PortalUrlBase: "https://portal.azure.us",
StorageEndpointSuffix: "core.usgovcloudapi.net",
ContainerRegistryEndpointSuffix: "azurecr.us",
}
}
func AzureChina() *Cloud {
return &Cloud{
Configuration: cloud.AzureChina,
PortalUrlBase: "https://portal.azure.cn",
StorageEndpointSuffix: "core.chinacloudapi.cn",
ContainerRegistryEndpointSuffix: "azurecr.cn",
}
}
func parseCloudName(name string) (*Cloud, error) {
if name == AzurePublicName || name == "" {
return AzurePublic(), nil
} else if name == AzureChinaCloudName {
return AzureChina(), nil
} else if name == AzureUSGovernmentName {
return AzureGovernment(), nil
}
return &Cloud{}, fmt.Errorf("Cloud name '%s' not found.", name)
}

Просмотреть файл

@ -0,0 +1,83 @@
package azure
type ResourceType string
const (
ResourceTypeApim ResourceType = "Microsoft.ApiManagement/service"
ResourceTypeAppConfig ResourceType = "Microsoft.AppConfiguration/configurationStores"
ResourceTypeAppInsightComponent ResourceType = "Microsoft.Insights/components"
ResourceTypeCacheForRedis ResourceType = "Microsoft.Cache/redis"
ResourceTypeCDNProfile ResourceType = "Microsoft.Cdn/profiles"
ResourceTypeCosmosDb ResourceType = "Microsoft.DocumentDB/databaseAccounts"
ResourceTypeContainerApp ResourceType = "Microsoft.App/containerApps"
ResourceTypeSpringApp ResourceType = "Microsoft.AppPlatform/Spring"
ResourceTypeContainerAppEnvironment ResourceType = "Microsoft.App/managedEnvironments"
ResourceTypeDeployment ResourceType = "Microsoft.Resources/deployments"
ResourceTypeKeyVault ResourceType = "Microsoft.KeyVault/vaults"
ResourceTypeManagedHSM ResourceType = "Microsoft.KeyVault/managedHSMs"
ResourceTypeLoadTest ResourceType = "Microsoft.LoadTestService/loadTests"
ResourceTypeLogAnalyticsWorkspace ResourceType = "Microsoft.OperationalInsights/workspaces"
ResourceTypePortalDashboard ResourceType = "Microsoft.Portal/dashboards"
ResourceTypePostgreSqlServer ResourceType = "Microsoft.DBforPostgreSQL/flexibleServers"
ResourceTypeMySqlServer ResourceType = "Microsoft.DBforMySQL/flexibleServers"
ResourceTypeResourceGroup ResourceType = "Microsoft.Resources/resourceGroups"
ResourceTypeStorageAccount ResourceType = "Microsoft.Storage/storageAccounts"
ResourceTypeStaticWebSite ResourceType = "Microsoft.Web/staticSites"
ResourceTypeServiceBusNamespace ResourceType = "Microsoft.ServiceBus/namespaces"
ResourceTypeServicePlan ResourceType = "Microsoft.Web/serverfarms"
ResourceTypeSqlServer ResourceType = "Microsoft.Sql/servers"
ResourceTypeVirtualNetwork ResourceType = "Microsoft.Network/virtualNetworks"
ResourceTypeWebSite ResourceType = "Microsoft.Web/sites"
ResourceTypeContainerRegistry ResourceType = "Microsoft.ContainerRegistry/registries"
ResourceTypeManagedCluster ResourceType = "Microsoft.ContainerService/managedClusters"
ResourceTypeAgentPool ResourceType = "Microsoft.ContainerService/managedClusters/agentPools"
ResourceTypeCognitiveServiceAccount ResourceType = "Microsoft.CognitiveServices/accounts"
ResourceTypeSearchService ResourceType = "Microsoft.Search/searchServices"
ResourceTypeVideoIndexer ResourceType = "Microsoft.VideoIndexer/accounts"
ResourceTypePrivateEndpoint ResourceType = "Microsoft.Network/privateEndpoints"
ResourceTypeDevCenter ResourceType = "Microsoft.DevCenter/devcenters"
ResourceTypeDevCenterProject ResourceType = "Microsoft.DevCenter/projects"
ResourceTypeMachineLearningWorkspace ResourceType = "Microsoft.MachineLearningServices/workspaces"
ResourceTypeMachineLearningConnection ResourceType = "Microsoft.MachineLearningServices/workspaces/connections"
//nolint:lll
ResourceTypeMachineLearningEndpoint ResourceType = "Microsoft.MachineLearningServices/workspaces/onlineEndpoints"
ResourceTypeCognitiveServiceAccountDeployment ResourceType = "Microsoft.CognitiveServices/accounts/deployments"
)
type Subscription struct {
Id string `json:"id"`
Name string `json:"name"`
TenantId string `json:"tenantId"`
// The tenant under which the user has access to the subscription.
UserAccessTenantId string `json:"userAccessTenantId"`
IsDefault bool `json:"isDefault,omitempty"`
}
type Location struct {
// The name of the location (e.g. "westus2")
Name string `json:"name"`
// The human friendly name of the location (e.g. "West US 2")
DisplayName string `json:"displayName"`
// The human friendly name of the location, prefixed with a
// region name (e.g "(US) West US 2")
RegionalDisplayName string `json:"regionalDisplayName"`
}
type ResourceGroup struct {
Id string `json:"id"`
Name string `json:"name"`
Location string `json:"location"`
}
type Resource struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Location string `json:"location"`
}
type ResourceExtended struct {
Resource
Kind string `json:"kind"`
}

Просмотреть файл

@ -0,0 +1,251 @@
package azure
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
)
// Optional parameters for resource group listing.
type ListResourceGroupOptions struct {
// An optional tag filter
TagFilter *Filter
// An optional filter expression to filter the resource group results
// https://learn.microsoft.com/en-us/rest/api/resources/resource-groups/list
Filter *string
}
type Filter struct {
Key string
Value string
}
// Optional parameters for resource group resources listing.
type ListResourceGroupResourcesOptions struct {
// An optional filter expression to filter the resource list result
// https://learn.microsoft.com/en-us/rest/api/resources/resources/list-by-resource-group#uri-parameters
Filter *string
}
type ResourceService struct {
credential azcore.TokenCredential
armClientOptions *arm.ClientOptions
}
func NewResourceService(
credential azcore.TokenCredential,
armClientOptions *arm.ClientOptions,
) *ResourceService {
return &ResourceService{
credential: credential,
armClientOptions: armClientOptions,
}
}
func (rs *ResourceService) GetResource(
ctx context.Context, subscriptionId string, resourceId string, apiVersion string) (ResourceExtended, error) {
client, err := rs.createResourcesClient(subscriptionId)
if err != nil {
return ResourceExtended{}, err
}
res, err := client.GetByID(ctx, resourceId, apiVersion, nil)
if err != nil {
return ResourceExtended{}, fmt.Errorf("getting resource by id: %w", err)
}
return ResourceExtended{
Resource: Resource{
Id: *res.ID,
Name: *res.Name,
Type: *res.Type,
Location: *res.Location,
},
Kind: *res.Kind,
}, nil
}
func (rs *ResourceService) ListResourceGroupResources(
ctx context.Context,
subscriptionId string,
resourceGroupName string,
listOptions *ListResourceGroupResourcesOptions,
) ([]*Resource, error) {
client, err := rs.createResourcesClient(subscriptionId)
if err != nil {
return nil, err
}
// Filter expression on the underlying REST API are different from --query param in az cli.
// https://learn.microsoft.com/en-us/rest/api/resources/resources/list-by-resource-group#uri-parameters
options := armresources.ClientListByResourceGroupOptions{}
if listOptions != nil && *listOptions.Filter != "" {
options.Filter = listOptions.Filter
}
resources := []*Resource{}
pager := client.NewListByResourceGroupPager(resourceGroupName, &options)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, err
}
for _, resource := range page.ResourceListResult.Value {
resources = append(resources, &Resource{
Id: *resource.ID,
Name: *resource.Name,
Type: *resource.Type,
Location: *resource.Location,
})
}
}
return resources, nil
}
func (rs *ResourceService) ListResourceGroup(
ctx context.Context,
subscriptionId string,
listOptions *ListResourceGroupOptions,
) ([]*Resource, error) {
client, err := rs.createResourceGroupClient(subscriptionId)
if err != nil {
return nil, err
}
// Filter values differ from those support in the --query param of az cli.
// https://learn.microsoft.com/en-us/rest/api/resources/resource-groups/list
options := armresources.ResourceGroupsClientListOptions{}
if listOptions != nil {
if listOptions.TagFilter != nil {
tagFilter := fmt.Sprintf(
"tagName eq '%s' and tagValue eq '%s'",
listOptions.TagFilter.Key,
listOptions.TagFilter.Value,
)
options.Filter = &tagFilter
} else if listOptions.Filter != nil {
options.Filter = listOptions.Filter
}
}
groups := []*Resource{}
pager := client.NewListPager(&options)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, err
}
for _, group := range page.ResourceGroupListResult.Value {
groups = append(groups, &Resource{
Id: *group.ID,
Name: *group.Name,
Type: *group.Type,
Location: *group.Location,
})
}
}
return groups, nil
}
func (rs *ResourceService) ListSubscriptionResources(
ctx context.Context,
subscriptionId string,
listOptions *armresources.ClientListOptions,
) ([]*Resource, error) {
client, err := rs.createResourcesClient(subscriptionId)
if err != nil {
return nil, err
}
// Filter expression on the underlying REST API are different from --query param in az cli.
// https://learn.microsoft.com/en-us/rest/api/resources/resources/list-by-resource-group#uri-parameters
options := armresources.ClientListOptions{}
if listOptions != nil && *listOptions.Filter != "" {
options.Filter = listOptions.Filter
}
resources := []*Resource{}
pager := client.NewListPager(&options)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, err
}
for _, resource := range page.ResourceListResult.Value {
resources = append(resources, &Resource{
Id: *resource.ID,
Name: *resource.Name,
Type: *resource.Type,
Location: *resource.Location,
})
}
}
return resources, nil
}
func (rs *ResourceService) CreateOrUpdateResourceGroup(
ctx context.Context,
subscriptionId string,
resourceGroupName string,
location string,
tags map[string]*string,
) error {
client, err := rs.createResourceGroupClient(subscriptionId)
if err != nil {
return err
}
_, err = client.CreateOrUpdate(ctx, resourceGroupName, armresources.ResourceGroup{
Location: &location,
Tags: tags,
}, nil)
return err
}
func (rs *ResourceService) DeleteResourceGroup(ctx context.Context, subscriptionId string, resourceGroupName string) error {
client, err := rs.createResourceGroupClient(subscriptionId)
if err != nil {
return err
}
poller, err := client.BeginDelete(ctx, resourceGroupName, nil)
if err != nil {
return fmt.Errorf("beginning resource group deletion: %w", err)
}
_, err = poller.PollUntilDone(ctx, nil)
if err != nil {
return fmt.Errorf("deleting resource group: %w", err)
}
return nil
}
func (rs *ResourceService) createResourcesClient(subscriptionId string) (*armresources.Client, error) {
client, err := armresources.NewClient(subscriptionId, rs.credential, rs.armClientOptions)
if err != nil {
return nil, fmt.Errorf("creating Resource client: %w", err)
}
return client, nil
}
func (rs *ResourceService) createResourceGroupClient(subscriptionId string) (*armresources.ResourceGroupsClient, error) {
client, err := armresources.NewResourceGroupsClient(subscriptionId, rs.credential, rs.armClientOptions)
if err != nil {
return nil, fmt.Errorf("creating ResourceGroup client: %w", err)
}
return client, nil
}

Просмотреть файл

@ -0,0 +1,187 @@
package storage
import (
"context"
"errors"
"fmt"
"io"
"path/filepath"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/azure/azure-dev/cli/sdk/azdcore/azure"
)
// AccountConfig contains the configuration for connecting to a storage account
type AccountConfig struct {
AccountName string
ContainerName string
Endpoint string
}
var (
ErrContainerNotFound = errors.New("container not found")
)
type BlobClient interface {
// Download downloads a blob from the configured storage account container.
Download(ctx context.Context, blobPath string) (io.ReadCloser, error)
// Upload uploads a blob to the configured storage account container.
Upload(ctx context.Context, blobPath string, reader io.Reader) error
// Delete deletes a blob from the configured storage account container.
Delete(ctx context.Context, blobPath string) error
// Items returns a list of blobs in the configured storage account container.
Items(ctx context.Context) ([]*Blob, error)
}
// NewBlobClient creates a new BlobClient instance to manage blobs within a container.
func NewBlobClient(
config *AccountConfig,
client *azblob.Client,
) BlobClient {
return &blobClient{
config: config,
client: client,
}
}
type blobClient struct {
config *AccountConfig
client *azblob.Client
}
// Blob represents a blob within a storage account container.
type Blob struct {
Name string
Path string
CreationTime time.Time
LastModified time.Time
}
// Items returns a list of blobs in the configured storage account container.
func (bc *blobClient) Items(ctx context.Context) ([]*Blob, error) {
if err := bc.ensureContainerExists(ctx); err != nil {
return nil, err
}
blobs := []*Blob{}
pager := bc.client.NewListBlobsFlatPager(bc.config.ContainerName, nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get next page of blobs, %w", err)
}
for _, blob := range page.Segment.BlobItems {
blobs = append(blobs, &Blob{
Name: filepath.Base(*blob.Name),
Path: *blob.Name,
CreationTime: *blob.Properties.CreationTime,
LastModified: *blob.Properties.LastModified,
})
}
}
return blobs, nil
}
// Download downloads a blob from the configured storage account container.
func (bc *blobClient) Download(ctx context.Context, blobPath string) (io.ReadCloser, error) {
if err := bc.ensureContainerExists(ctx); err != nil {
return nil, err
}
resp, err := bc.client.DownloadStream(ctx, bc.config.ContainerName, blobPath, nil)
if err != nil {
return nil, fmt.Errorf("failed to download blob '%s', %w", blobPath, err)
}
return resp.Body, nil
}
// Upload uploads a blob to the configured storage account container.
func (bc *blobClient) Upload(ctx context.Context, blobPath string, reader io.Reader) error {
if err := bc.ensureContainerExists(ctx); err != nil {
return err
}
_, err := bc.client.UploadStream(ctx, bc.config.ContainerName, blobPath, reader, nil)
if err != nil {
return fmt.Errorf("failed to upload blob '%s', %w", blobPath, err)
}
return nil
}
// Delete deletes a blob from the configured storage account container.
func (bc *blobClient) Delete(ctx context.Context, blobPath string) error {
if err := bc.ensureContainerExists(ctx); err != nil {
return err
}
_, err := bc.client.DeleteBlob(ctx, bc.config.ContainerName, blobPath, nil)
if err != nil {
return fmt.Errorf("failed to delete blob '%s', %w", blobPath, err)
}
return nil
}
// Check if the specified container exists
// If it doesn't already exist then create it
func (bc *blobClient) ensureContainerExists(ctx context.Context) error {
exists := false
pager := bc.client.NewListContainersPager(nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return fmt.Errorf("failed getting next page of containers: %w", err)
}
for _, container := range page.ContainerItems {
if *container.Name == bc.config.ContainerName {
exists = true
break
}
}
}
if !exists {
_, err := bc.client.CreateContainer(ctx, bc.config.ContainerName, nil)
if err != nil {
return fmt.Errorf("failed to create container '%s', %w", bc.config.ContainerName, err)
}
}
return nil
}
// createClient creates a new blob client and caches it for future use
func NewBlobSdkClient(
credential azcore.TokenCredential,
accountConfig *AccountConfig,
coreClientOptions *azcore.ClientOptions,
cloud *azure.Cloud,
) (*azblob.Client, error) {
blobOptions := &azblob.ClientOptions{
ClientOptions: *coreClientOptions,
}
if accountConfig.Endpoint == "" {
accountConfig.Endpoint = cloud.StorageEndpointSuffix
}
serviceUrl := fmt.Sprintf("https://%s.blob.%s", accountConfig.AccountName, accountConfig.Endpoint)
client, err := azblob.NewClient(serviceUrl, credential, blobOptions)
if err != nil {
return nil, fmt.Errorf("failed to create blob client, %w", err)
}
return client, nil
}

Просмотреть файл

@ -0,0 +1,98 @@
package storage
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/service"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/share"
)
type FileShareService interface {
// Upload files from source path to a file share
UploadPath(ctx context.Context, subId, shareUrl, source string) error
}
func NewFileShareService(
credential azcore.TokenCredential,
options *arm.ClientOptions,
) FileShareService {
return &fileShareClient{
credential: credential,
options: options,
}
}
type fileShareClient struct {
credential azcore.TokenCredential
options *arm.ClientOptions
}
func (f *fileShareClient) UploadPath(ctx context.Context, subId, shareUrl, source string) error {
return filepath.WalkDir(source, func(path string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
destination := strings.TrimPrefix(path, source+string(filepath.Separator))
if err := f.uploadFile(ctx, shareUrl, path, destination, f.credential); err != nil {
return fmt.Errorf("error uploading file to file share: %w", err)
}
}
return nil
})
}
// uploadFile implements FileShareService.
func (f *fileShareClient) uploadFile(
ctx context.Context, fileShareUrl, source, dest string, credential azcore.TokenCredential) error {
client, err := share.NewClient(fileShareUrl, credential, &share.ClientOptions{
ClientOptions: f.options.ClientOptions,
FileRequestIntent: to.Ptr(service.ShareTokenIntentBackup),
})
if err != nil {
return err
}
dirClient := client.NewRootDirectoryClient()
dirPaths := strings.Split(dest, string(os.PathSeparator))
incrementPath := ""
for _, dirPath := range dirPaths[:len(dirPaths)-1] {
incrementPath = filepath.Join(incrementPath, dirPath)
dirClient = client.NewDirectoryClient(incrementPath)
if _, err := dirClient.Create(ctx, nil); err != nil {
if !strings.Contains(err.Error(), "ResourceAlreadyExists") {
return err
}
}
}
file, err := os.OpenFile(source, os.O_RDONLY, 0)
if err != nil {
return err
}
defer file.Close()
fInfo, err := file.Stat()
if err != nil {
return err
}
fSize := fInfo.Size()
fileName := filepath.Base(dest)
fClient := dirClient.NewFileClient(fileName)
if _, err := fClient.Create(ctx, fSize, nil); err != nil {
return err
}
return fClient.UploadFile(ctx, file, nil)
}

Просмотреть файл

@ -0,0 +1,165 @@
package azure
import (
"context"
"fmt"
"sort"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/compare"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/convert"
)
// SubscriptionsService allows querying of subscriptions and tenants.
type SubscriptionsService struct {
credential azcore.TokenCredential
armClientOptions *arm.ClientOptions
}
func NewSubscriptionsService(
credential azcore.TokenCredential,
armClientOptions *arm.ClientOptions,
) *SubscriptionsService {
return &SubscriptionsService{
credential: credential,
armClientOptions: armClientOptions,
}
}
func (ss *SubscriptionsService) createSubscriptionsClient(
ctx context.Context,
tenantId string,
) (*armsubscriptions.Client, error) {
client, err := armsubscriptions.NewClient(ss.credential, ss.armClientOptions)
if err != nil {
return nil, fmt.Errorf("creating subscriptions client: %w", err)
}
return client, nil
}
func (ss *SubscriptionsService) createTenantsClient(ctx context.Context) (*armsubscriptions.TenantsClient, error) {
// Use default home tenant, since tenants itself can be listed across tenants
client, err := armsubscriptions.NewTenantsClient(ss.credential, ss.armClientOptions)
if err != nil {
return nil, fmt.Errorf("creating tenants client: %w", err)
}
return client, nil
}
func (s *SubscriptionsService) ListSubscriptions(
ctx context.Context,
tenantId string,
) ([]*armsubscriptions.Subscription, error) {
client, err := s.createSubscriptionsClient(ctx, tenantId)
if err != nil {
return nil, err
}
subscriptions := []*armsubscriptions.Subscription{}
pager := client.NewListPager(nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting next page of subscriptions: %w", err)
}
subscriptions = append(subscriptions, page.SubscriptionListResult.Value...)
}
sort.Slice(subscriptions, func(i, j int) bool {
return *subscriptions[i].DisplayName < *subscriptions[j].DisplayName
})
return subscriptions, nil
}
func (s *SubscriptionsService) GetSubscription(
ctx context.Context, subscriptionId string, tenantId string) (*armsubscriptions.Subscription, error) {
client, err := s.createSubscriptionsClient(ctx, tenantId)
if err != nil {
return nil, err
}
subscription, err := client.Get(ctx, subscriptionId, nil)
if err != nil {
return nil, fmt.Errorf("failed getting subscription for '%s'", subscriptionId)
}
return &subscription.Subscription, nil
}
// ListSubscriptionLocations lists physical locations in Azure for the given subscription.
func (s *SubscriptionsService) ListSubscriptionLocations(
ctx context.Context, subscriptionId string, tenantId string) ([]Location, error) {
client, err := s.createSubscriptionsClient(ctx, tenantId)
if err != nil {
return nil, err
}
locations := []Location{}
pager := client.NewListLocationsPager(subscriptionId, nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting next page of locations: %w", err)
}
for _, location := range page.LocationListResult.Value {
// Only include physical locations
if *location.Metadata.RegionType == "Physical" &&
!compare.PtrValueEquals(location.Metadata.PhysicalLocation, "") {
displayName := convert.ToValueWithDefault(location.DisplayName, *location.Name)
regionalDisplayName := convert.ToValueWithDefault(location.RegionalDisplayName, displayName)
locations = append(locations, Location{
Name: *location.Name,
DisplayName: displayName,
RegionalDisplayName: regionalDisplayName,
})
}
}
}
sort.Slice(locations, func(i, j int) bool {
return locations[i].RegionalDisplayName < locations[j].RegionalDisplayName
})
return locations, nil
}
func (s *SubscriptionsService) ListTenants(ctx context.Context) ([]armsubscriptions.TenantIDDescription, error) {
client, err := s.createTenantsClient(ctx)
if err != nil {
return nil, err
}
tenants := []armsubscriptions.TenantIDDescription{}
pager := client.NewListPager(nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting next page of tenants: %w", err)
}
for _, tenant := range page.TenantListResult.Value {
if tenant != nil && tenant.TenantID != nil {
tenants = append(tenants, *tenant)
}
}
}
sort.Slice(tenants, func(i, j int) bool {
return convert.ToValueWithDefault(tenants[i].DisplayName, "") <
convert.ToValueWithDefault(tenants[j].DisplayName, "")
})
return tenants, nil
}

Просмотреть файл

@ -0,0 +1,13 @@
package compare
import "strings"
// IsStringNilOrEmpty returns true if the string pointer is nil or the trimmed string is empty.
func IsStringNilOrEmpty(value *string) bool {
return value == nil || strings.TrimSpace(*value) == ""
}
// PtrValueEquals returns true if the pointer is not nil and the value is equal to the expected value.
func PtrValueEquals[T comparable](actual *T, expected T) bool {
return actual != nil && *actual == expected
}

Просмотреть файл

@ -0,0 +1,15 @@
package convert
// Converts a pointer to a value type
// If the ptr is nil returns default value, otherwise the value of value of the pointer
func ToValueWithDefault[T any](ptr *T, defaultValue T) T {
if ptr == nil {
return defaultValue
}
if str, ok := any(ptr).(*string); ok && *str == "" {
return defaultValue
}
return *ptr
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package common
import (
"fmt"
"github.com/drone/envsubst"
)
func NewExpandableString(template string) ExpandableString {
return ExpandableString{
template: template,
}
}
// ExpandableString is a string that has ${foo} style references inside which can be evaluated.
type ExpandableString struct {
template string
}
// Empty returns true if the template is empty.
func (e ExpandableString) Empty() bool {
return e.template == ""
}
// Envsubst evaluates the template, substituting values as [envsubst.Eval] would.
func (e ExpandableString) Envsubst(mapping func(string) string) (string, error) {
return envsubst.Eval(e.template, mapping)
}
// MustEnvsubst evaluates the template, substituting values as [envsubst.Eval] would and panics if there
// is an error (for example, the string is malformed).
func (e ExpandableString) MustEnvsubst(mapping func(string) string) string {
if v, err := envsubst.Eval(e.template, mapping); err != nil {
panic(fmt.Sprintf("MustEnvsubst: %v", err))
} else {
return v
}
}
func (e ExpandableString) MarshalYAML() (interface{}, error) {
return e.template, nil
}
func (e *ExpandableString) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
e.template = s
return nil
}

Просмотреть файл

@ -0,0 +1,7 @@
package ioc
// binding represents the metadata used for an IoC registration consisting of a optional name and resolver.
type binding struct {
name string
resolver any
}

Просмотреть файл

@ -0,0 +1,306 @@
// This package wraps the golobby/container package to provide support for the following:
// 1. Easier usage of lazy type resolvers and ability to register specific type instances
// 2. Support for hierarchical/nested containers to resolve types from parent containers
// 3. Helper methods for easier/streamlined usage of of the IoC container
package ioc
import (
"errors"
"fmt"
"reflect"
"regexp"
"github.com/golobby/container/v3"
)
var (
// The golobby project does not support types errors,
// but all the error messages are prefixed with `container:`
containerErrorRegex *regexp.Regexp = regexp.MustCompile("container:")
ErrResolveInstance error = errors.New("failed resolving instance from container")
)
// NestedContainer is an IoC container that support nested containers
// Used for more complex registration scenarios such as scop based registration/resolution.
type NestedContainer struct {
inner container.Container
scopedBindings []*binding
}
// Creates a new nested container from the specified parent container
func NewNestedContainer(parent *NestedContainer) *NestedContainer {
current := container.New()
if parent != nil {
// Copy the bindings to the new container
// The bindings hold the concrete instance of singleton registrations
for key, value := range parent.inner {
current[key] = value
}
}
instance := &NestedContainer{
inner: current,
}
RegisterInstance[ServiceLocator](instance, instance)
return instance
}
// Creates a new container with only registrations from the given container.
func NewRegistrationsOnly(from *NestedContainer) *NestedContainer {
current := container.New()
if from != nil {
// Reset all concrete instances by copying 'resolver' and 'isSingleton' fields
// Reflection is necessary since *container.binding is unexported
for key, value := range from.inner {
var valueType = reflect.TypeOf(value)
newValue := reflect.MakeMapWithSize(valueType, len(value))
for name, binding := range value {
bindingVal := reflect.ValueOf(binding).Elem()
newBinding := reflect.New(reflect.TypeOf(binding).Elem())
setUnexportedField(
newBinding.Elem().FieldByName("resolver"),
getUnexportedField(bindingVal.FieldByName("resolver")))
setUnexportedField(
newBinding.Elem().FieldByName("isSingleton"),
getUnexportedField(bindingVal.FieldByName("isSingleton")))
n := name
newValue.SetMapIndex(reflect.ValueOf(n), newBinding)
}
reflect.ValueOf(current).SetMapIndex(reflect.ValueOf(key), newValue)
}
}
instance := &NestedContainer{
inner: current,
}
RegisterInstance[ServiceLocator](instance, instance)
return instance
}
func getUnexportedField(field reflect.Value) interface{} {
return reflect.NewAt(field.Type(), field.Addr().UnsafePointer()).Elem().Interface()
}
func setUnexportedField(field reflect.Value, value interface{}) {
reflect.NewAt(field.Type(), field.Addr().UnsafePointer()).
Elem().
Set(reflect.ValueOf(value))
}
// Fill takes a structure and resolves fields with the tag `container:"type" or `container:"name"`.
func (c *NestedContainer) Fill(structure any) error {
return c.inner.Fill(structure)
}
// Registers a resolver with a singleton lifetime
// Returns an error if the resolver is not valid
func (c *NestedContainer) RegisterSingleton(resolveFn any) error {
return c.inner.SingletonLazy(resolveFn)
}
// Registers a resolver with a singleton lifetime
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterSingleton(resolveFn any) {
container.MustSingletonLazy(c.inner, resolveFn)
}
// Registers a resolver with a singleton lifetime and instantiates the instance
// Instance is stored in container cache is used for future resolutions
// Returns an error if the resolver cannot instantiate the type
func (c *NestedContainer) RegisterSingletonAndInvoke(resolveFn any) error {
return c.inner.Singleton(resolveFn)
}
// Registers a named resolver with a singleton lifetime
// Returns an error if the resolver is not valid
func (c *NestedContainer) RegisterNamedSingleton(name string, resolveFn any) error {
return c.inner.NamedSingletonLazy(name, resolveFn)
}
// Registers a named resolver with a singleton lifetime
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterNamedSingleton(name string, resolveFn any) {
container.MustNamedSingletonLazy(c.inner, name, resolveFn)
}
// Registers a resolver with a transient lifetime (instance per resolution)
// Returns an error if the resolver is not valid
func (c *NestedContainer) RegisterTransient(resolveFn any) error {
return c.inner.TransientLazy(resolveFn)
}
// Registers a named resolver with a singleton lifetime and instantiates the instance
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterTransient(resolveFn any) {
container.MustTransientLazy(c.inner, resolveFn)
}
// Registers a named resolver with a transient lifetime (instance per resolution)
// Returns an error if the resolver is not valid
func (c *NestedContainer) RegisterNamedTransient(name string, resolveFn any) error {
return c.inner.NamedTransientLazy(name, resolveFn)
}
// Registers a named resolver with a transient lifetime (instance per resolution)
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterNamedTransient(name string, resolveFn any) {
container.MustNamedTransientLazy(c.inner, name, resolveFn)
}
// Registers a resolver with a scoped lifetime (instance per scope)
// Ex: Each new cobra command will create a new scope
// Scoped registrations are added as singletons in the current container then are reset in any new child containers
// Returns an error if the resolver is not valid
func (c *NestedContainer) RegisterScoped(resolveFn any) error {
if err := c.inner.SingletonLazy(resolveFn); err != nil {
return err
}
c.scopedBindings = append(c.scopedBindings, &binding{
resolver: resolveFn,
})
return nil
}
// Registers a resolver with a scoped lifetime (instance per scope)
// Ex: Each new cobra command will create a new scope
// Scoped registrations are added as singletons in the current container then are reset in any new child containers
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterScoped(resolveFn any) {
if err := c.RegisterScoped(resolveFn); err != nil {
panic(err)
}
}
// Registers a named resolver with a scoped lifetime (instance per scope)
// Ex: Each new cobra command will create a new scope
// Scoped registrations are added as singletons in the current container then are reset in any new child containers
func (c *NestedContainer) RegisterNamedScoped(name string, resolveFn any) error {
if err := c.inner.NamedSingletonLazy(name, resolveFn); err != nil {
return err
}
c.scopedBindings = append(c.scopedBindings, &binding{
name: name,
resolver: resolveFn,
})
return nil
}
// Registers a named resolver with a scoped lifetime (instance per scope)
// Ex: Each new cobra command will create a new scope
// Scoped registrations are added as singletons in the current container then are reset in any new child containers
// Panics if the resolver is not valid
func (c *NestedContainer) MustRegisterNamedScoped(name string, resolveFn any) {
if err := c.RegisterNamedScoped(name, resolveFn); err != nil {
panic(err)
}
}
// Resolves an instance for the specified type
// Returns an error if the resolution fails
func (c *NestedContainer) Resolve(instance any) error {
if err := c.inner.Resolve(instance); err != nil {
return inspectResolveError(err)
}
return nil
}
// Resolves a named instance for the specified type
// Returns an error if the resolution fails
func (c *NestedContainer) ResolveNamed(name string, instance any) error {
if err := c.inner.NamedResolve(instance, name); err != nil {
return inspectResolveError(err)
}
return nil
}
// Invokes the specified function and resolves any arguments specified
// from the container resolver registrations
func (c *NestedContainer) Invoke(resolver any) error {
return c.inner.Call(resolver)
}
// Registers a constructed instance of the specified type
// Panics if the registration fails
func RegisterInstance[F any](c *NestedContainer, instance F) {
container.MustSingletonLazy(c.inner, func() F {
return instance
})
}
// Registers a named constructed instance of the specified type
// Panics if the registration fails
func RegisterNamedInstance[F any](c *NestedContainer, name string, instance F) {
container.MustNamedSingletonLazy(c.inner, name, func() F {
return instance
})
}
// NewScope creates a new nested container with a relationship back to the parent container
// Scoped registrations are converted to singleton registrations within the new nested container.
func (c *NestedContainer) NewScope() (*NestedContainer, error) {
childContainer := NewNestedContainer(c)
for _, binding := range c.scopedBindings {
if binding.name == "" {
if err := childContainer.RegisterSingleton(binding.resolver); err != nil {
return nil, err
}
} else {
if err := childContainer.RegisterNamedSingleton(binding.name, binding.resolver); err != nil {
return nil, err
}
}
childContainer.scopedBindings = append(childContainer.scopedBindings, binding)
}
return childContainer, nil
}
// NewScopeRegistrationsOnly creates a new container with bindings deep copied from the container.
// Scoped registrations are then activated as singletons within the new nested container.
func (c *NestedContainer) NewScopeRegistrationsOnly() (*NestedContainer, error) {
childContainer := NewRegistrationsOnly(c)
for _, binding := range c.scopedBindings {
if binding.name == "" {
if err := childContainer.RegisterSingleton(binding.resolver); err != nil {
return nil, err
}
} else {
if err := childContainer.RegisterNamedSingleton(binding.name, binding.resolver); err != nil {
return nil, err
}
}
childContainer.scopedBindings = append(childContainer.scopedBindings, binding)
}
return childContainer, nil
}
// Inspects the specified error to determine whether the error is a
// developer container registration error or an error that was
// returned while instantiating a dependency.
func inspectResolveError(err error) error {
if containerErrorRegex.Match([]byte(err.Error())) {
return fmt.Errorf("%w: %w", ErrResolveInstance, err)
}
return err
}

Просмотреть файл

@ -0,0 +1,7 @@
package ioc
type ServiceLocator interface {
Resolve(instance any) error
ResolveNamed(name string, instance any) error
Invoke(resolver any) error
}

Просмотреть файл

@ -0,0 +1,14 @@
package permissions
import "os"
const (
PermissionDirectory os.FileMode = 0755
PermissionExecutableFile os.FileMode = 0755
PermissionFile os.FileMode = 0644
PermissionDirectoryOwnerOnly os.FileMode = 0700
PermissionFileOwnerOnly os.FileMode = 0600
PermissionMaskDirectoryExecute os.FileMode = 0100
)

Просмотреть файл

@ -0,0 +1,66 @@
package common
import (
"bytes"
"encoding/json"
)
// WithOrder is like map, but also retains information about the order the keys of the object where in
// when it was unmarshalled from JSON.
type WithOrder[T any] struct {
innerMap map[string]*T
keys []string
}
// Keys returns the keys of the map in the order they were unmarshalled from JSON.
func (b *WithOrder[T]) OrderedKeys() []string {
return b.keys
}
// OrderedValues returns the values of the map in the order they were unmarshalled from JSON.
func (b *WithOrder[T]) OrderedValues() []*T {
values := make([]*T, len(b.keys))
for i, key := range b.keys {
values[i] = b.innerMap[key]
}
return values
}
// Get returns the value associated with the given key, and a boolean indicating whether the key was present.
func (b *WithOrder[T]) Get(key string) (*T, bool) {
v, ok := b.innerMap[key]
return v, ok
}
func (b *WithOrder[T]) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &b.innerMap); err != nil {
return err
}
dec := json.NewDecoder(bytes.NewReader(data))
// read the start of the object
_, err := dec.Token()
if err != nil {
return err
}
for {
// read key or end
tok, err := dec.Token()
if err != nil {
return err
}
if tok == json.Delim('}') {
return nil
} else {
b.keys = append(b.keys, tok.(string))
}
// read binding value (and discard it, we already unmarshalled it into b.bindings)
var b T
if err := dec.Decode(&b); err != nil {
return err
}
}
}

Просмотреть файл

@ -0,0 +1,335 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Package config provides functionality related to storing application-wide configuration data.
//
// Configuration data stored should not be specific to a given repository/project.
package config
import (
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/google/uuid"
)
//
//nolint:lll
var vaultPattern = regexp.MustCompile(
`^vault://[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$`,
)
// Azd configuration for the current user
// Configuration data is stored in user's home directory @ ~/.azd/config.json
type Config interface {
Raw() map[string]any
// similar to Raw() but it will resolve any vault references
ResolvedRaw() map[string]any
// Get retrieves the value stored at the specified path
Get(path string) (any, bool)
// GetString retrieves the value stored at the specified path as a string
GetString(path string) (string, bool)
// GetSection retrieves the value stored at the specified path and unmarshals it into the provided section
GetSection(path string, section any) (bool, error)
// GetMap retrieves the map stored at the specified path
GetMap(path string) (map[string]any, bool)
// GetSlice retrieves the slice stored at the specified path
GetSlice(path string) ([]any, bool)
// Set stores the value at the specified path
Set(path string, value any) error
// SetSecret stores the secrets at the specified path within a local user vault
SetSecret(path string, value string) error
// Unset removes the value stored at the specified path
Unset(path string) error
// IsEmpty returns a value indicating whether the configuration is empty
IsEmpty() bool
}
// NewEmptyConfig creates a empty configuration object.
func NewEmptyConfig() Config {
return NewConfig(nil)
}
// NewConfig creates a configuration object, populated with an initial set of keys and values. If [data] is nil or an
// empty map, and empty configuration object is returned, but [NewEmptyConfig] might better express your intention.
func NewConfig(data map[string]any) Config {
if data == nil {
data = map[string]any{}
}
return &config{
data: data,
}
}
// Top level AZD configuration
type config struct {
vaultId string
vault Config
data map[string]any
}
// Returns a value indicating whether the configuration is empty
func (c *config) IsEmpty() bool {
return len(c.data) == 0
}
// Gets the raw values stored in the configuration as a Go map
func (c *config) Raw() map[string]any {
return c.data
}
const vaultKeyName = "vault"
// Gets the raw values stored in the configuration and resolve any vault references
func (c *config) ResolvedRaw() map[string]any {
resolvedRaw := &config{
data: map[string]any{},
}
paths := paths(c.data)
for _, path := range paths {
if path == vaultKeyName {
// a resolved raw should not include a reference a vault, as all secrets should be resolved
// when a config file contains a vault reference and the vault is not found, azd returns os.ErrNotExist
// and to the eyes of components using a Config, that means the config does not exists.
continue
}
// get will always return true (no need to check) because the path was gotten from the raw config
value, _ := c.Get(path)
if err := resolvedRaw.Set(path, value); err != nil {
panic(fmt.Errorf("failed setting resolved raw value: %w", err))
}
}
return resolvedRaw.data
}
// paths recursively traverses a map and returns a list of all the paths to the leaf nodes.
// The start parameter is the initial map to start traversing from.
// It returns a slice of strings representing the paths to the leaf nodes.
func paths(start map[string]any) []string {
var all []string
for path, value := range start {
if node, isNode := value.(map[string]any); isNode {
for _, child := range paths(node) {
all = append(all, fmt.Sprintf("%s.%s", path, child))
}
} else {
all = append(all, path)
}
}
return all
}
// SetSecret stores the secrets at the specified path within a local user vault
func (c *config) SetSecret(path string, value string) error {
if c.vaultId == "" {
c.vault = NewConfig(nil)
c.vaultId = uuid.New().String()
if err := c.Set(vaultKeyName, c.vaultId); err != nil {
return fmt.Errorf("failed setting vault id: %w", err)
}
}
pathId := uuid.New().String()
vaultRef := fmt.Sprintf("vault://%s/%s", c.vaultId, pathId)
if err := c.vault.Set(pathId, base64.StdEncoding.EncodeToString([]byte(value))); err != nil {
return fmt.Errorf("failed setting secret value: %w", err)
}
return c.Set(path, vaultRef)
}
// Sets a value at the specified location
func (c *config) Set(path string, value any) error {
depth := 1
currentNode := c.data
parts := strings.Split(path, ".")
for _, part := range parts {
if depth == len(parts) {
currentNode[part] = value
return nil
}
var node map[string]any
value, ok := currentNode[part]
if !ok || value == nil {
node = map[string]any{}
}
if value != nil {
node, ok = value.(map[string]any)
if !ok {
return fmt.Errorf("failed converting node at path '%s' to map", part)
}
}
currentNode[part] = node
currentNode = node
depth++
}
return nil
}
// Removes any values stored at the specified path
// When the path location is an object will remove the whole node
// When the path does not exist, will return a `nil` value
func (c *config) Unset(path string) error {
depth := 1
currentNode := c.data
parts := strings.Split(path, ".")
for _, part := range parts {
if depth == len(parts) {
delete(currentNode, part)
return nil
}
var node map[string]any
value, ok := currentNode[part]
// Path already doesn't exist, NOOP
if !ok || value == nil {
return nil
}
node, ok = value.(map[string]any)
if !ok {
return fmt.Errorf("failed converting node at path '%s' to map", part)
}
currentNode[part] = node
currentNode = node
depth++
}
return nil
}
// Gets the value stored at the specified location
// Returns the value if exists, otherwise returns nil & a value indicating if the value existing
func (c *config) Get(path string) (any, bool) {
depth := 1
currentNode := c.data
parts := strings.Split(path, ".")
for _, part := range parts {
// When the depth is equal to the number of parts, we have reached the desired node path
// At this point we can perform any final processing on the node and return the result
if depth == len(parts) {
value, ok := currentNode[part]
if !ok {
return value, ok
}
return c.interpolateNodeValue(value)
}
value, ok := currentNode[part]
if !ok {
return value, ok
}
node, ok := value.(map[string]any)
if !ok {
return nil, false
}
currentNode = node
depth++
}
return nil, false
}
// GetMap retrieves the map stored at the specified path
func (c *config) GetMap(path string) (map[string]any, bool) {
value, ok := c.Get(path)
if !ok {
return nil, false
}
node, ok := value.(map[string]any)
return node, ok
}
// GetSlice retrieves the slice stored at the specified path
func (c *config) GetSlice(path string) ([]any, bool) {
value, ok := c.Get(path)
if !ok {
return nil, false
}
node, ok := value.([]any)
return node, ok
}
// Gets the value stored at the specified location as a string
func (c *config) GetString(path string) (string, bool) {
value, ok := c.Get(path)
if !ok {
return "", false
}
str, ok := value.(string)
return str, ok
}
func (c *config) GetSection(path string, section any) (bool, error) {
sectionConfig, ok := c.Get(path)
if !ok {
return false, nil
}
jsonBytes, err := json.Marshal(sectionConfig)
if err != nil {
return true, fmt.Errorf("marshalling section config: %w", err)
}
if err := json.Unmarshal(jsonBytes, section); err != nil {
return true, fmt.Errorf("unmarshalling section config: %w", err)
}
return true, nil
}
// getSecret retrieves the secret stored at the specified path from a local user vault
func (c *config) getSecret(vaultRef string) (string, bool) {
encodedValue, ok := c.vault.GetString(filepath.Base(vaultRef))
if !ok {
return "", false
}
bytes, err := base64.StdEncoding.DecodeString(encodedValue)
if err != nil {
return "", false
}
return string(bytes), true
}
// interpolateNodeValue processes the node, iterates on any nested nodes and interpolates any vault references
func (c *config) interpolateNodeValue(value any) (any, bool) {
// Check if the value is a vault reference
// If it is, retrieve the secret from the vault
if vaultRef, isString := value.(string); isString && vaultPattern.MatchString(vaultRef) {
return c.getSecret(vaultRef)
}
// If the value is a map, recursively iterate over the map and interpolate the values
if node, isMap := value.(map[string]any); isMap {
// We want to ensure we return a cloned map so that we don't modify the original data
// stored within the config map data structure
cloneMap := map[string]any{}
for key, val := range node {
if nodeValue, ok := c.interpolateNodeValue(val); ok {
cloneMap[key] = nodeValue
}
}
return cloneMap, true
}
// Finally, if the value is not handled above we can return the value as is
return value, true
}

Просмотреть файл

@ -0,0 +1,110 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/permissions"
)
// FileConfigManager provides the ability to load, parse and save azd configuration files
type FileConfigManager interface {
// Saves the azd configuration to the specified file path
// Path is automatically created if it does not exist
Save(config Config, filePath string) error
// Loads azd configuration from the specified file path
Load(filePath string) (Config, error)
}
// NewFileConfigManager creates a new FileConfigManager instance
func NewFileConfigManager(configManager Manager) FileConfigManager {
return &fileConfigManager{
manager: configManager,
}
}
type fileConfigManager struct {
manager Manager
}
func (m *fileConfigManager) Load(filePath string) (Config, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("failed opening azd configuration file: %w", err)
}
defer file.Close()
azdConfig, err := m.manager.Load(file)
if err != nil {
return nil, err
}
// If the configuration contains a vault, then also load the vault configuration
vaultId, ok := azdConfig.GetString(vaultKeyName)
if ok {
configPath, err := GetUserConfigDir()
if err != nil {
return nil, fmt.Errorf("failed getting user config directory: %w", err)
}
vaultPath := filepath.Join(configPath, "vaults", fmt.Sprintf("%s.json", vaultId))
vaultConfig, err := m.Load(vaultPath)
if err != nil {
return nil, fmt.Errorf("failed loading vault configuration from '%s': %w", vaultPath, err)
}
baseConfig, ok := azdConfig.(*config)
if !ok {
return nil, fmt.Errorf("failed casting azd configuration to config")
}
baseConfig.vaultId = vaultId
baseConfig.vault = vaultConfig
}
return azdConfig, nil
}
func (m *fileConfigManager) Save(c Config, filePath string) error {
folderPath := filepath.Dir(filePath)
if err := os.MkdirAll(folderPath, permissions.PermissionDirectory); err != nil {
return fmt.Errorf("failed creating config directory: %w", err)
}
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, permissions.PermissionFile)
if err != nil {
return fmt.Errorf("failed creating config directory: %w", err)
}
defer file.Close()
err = m.manager.Save(c, file)
if err != nil {
return err
}
baseConfig, ok := c.(*config)
if !ok {
return fmt.Errorf("failed casting azd configuration to config")
}
// If the configuration contains a vault, then also save the vault configuration
// Vault configuration always gets saved in a separate file in the users HOME directory.
if baseConfig.vaultId != "" {
configPath, err := GetUserConfigDir()
if err != nil {
return fmt.Errorf("failed getting user config directory: %w", err)
}
vaultPath := filepath.Join(configPath, "vaults", fmt.Sprintf("%s.json", baseConfig.vaultId))
if err = os.MkdirAll(filepath.Dir(vaultPath), permissions.PermissionDirectory); err != nil {
return fmt.Errorf("failed creating vaults directory: %w", err)
}
return m.Save(baseConfig.vault, vaultPath)
}
return nil
}

Просмотреть файл

@ -0,0 +1,101 @@
package config
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/permissions"
)
// Config Manager provides the ability to load, parse and save azd configuration files
type manager struct {
}
type Manager interface {
Save(config Config, writer io.Writer) error
Load(io.Reader) (Config, error)
}
// Creates a new Configuration Manager
func NewManager() Manager {
return &manager{}
}
// Saves the azd configuration to the specified file path
func (c *manager) Save(config Config, writer io.Writer) error {
configJson, err := json.MarshalIndent(config.Raw(), "", " ")
if err != nil {
return fmt.Errorf("failed marshalling config JSON: %w", err)
}
_, err = writer.Write(configJson)
if err != nil {
return fmt.Errorf("failed writing configuration data: %w", err)
}
return nil
}
// Loads azd configuration from the specified file path
func (c *manager) Load(reader io.Reader) (Config, error) {
jsonBytes, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("failed reading azd configuration file")
}
return Parse(jsonBytes)
}
// Parses azd configuration JSON and returns a Config instance
func Parse(configJson []byte) (Config, error) {
var data map[string]any
err := json.Unmarshal(configJson, &data)
if err != nil {
return nil, fmt.Errorf("failed unmarshalling configuration JSON: %w", err)
}
return NewConfig(data), nil
}
// GetUserConfigDir returns the config directory for storing user wide configuration data.
//
// The config directory is guaranteed to exist, otherwise an error is returned.
func GetUserConfigDir() (string, error) {
configDirPath := os.Getenv("AZD_CONFIG_DIR")
if configDirPath == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("could not determine current home directory: %w", err)
}
configDirPath = filepath.Join(homeDir, ".azd")
}
err := os.MkdirAll(configDirPath, permissions.PermissionDirectoryOwnerOnly)
if err != nil {
return configDirPath, err
}
// Ensure that the "x" permission is set on the folder for the current
// user. In cases where the config directory is ~/.azd, OS upgrades and
// other processes can remove the "x" permission
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
info, err := os.Stat(configDirPath)
if err != nil {
return configDirPath, err
}
filePermissions := info.Mode().Perm()
if filePermissions&permissions.PermissionMaskDirectoryExecute == 0 {
// Ensure user execute permissions
err := os.Chmod(configDirPath, filePermissions|permissions.PermissionMaskDirectoryExecute)
return configDirPath, err
}
}
return configDirPath, err
}

Просмотреть файл

@ -0,0 +1,73 @@
package config
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
)
type UserConfig Config
type UserConfigManager interface {
Save(UserConfig) error
Load() (UserConfig, error)
}
type userConfigManager struct {
manager FileConfigManager
}
func NewUserConfigManager(configManager FileConfigManager) UserConfigManager {
return &userConfigManager{
manager: configManager,
}
}
func (m *userConfigManager) Load() (UserConfig, error) {
var azdConfig Config
configFilePath, err := GetUserConfigFilePath()
if err != nil {
return nil, err
}
azdConfig, err = m.manager.Load(configFilePath)
if err != nil {
// Ignore missing file errors
// File will automatically be created on first `set` operation
if errors.Is(err, os.ErrNotExist) {
log.Printf("creating empty config since '%s' did not exist.", configFilePath)
return NewConfig(nil), nil
}
return nil, fmt.Errorf("failed loading azd user config from '%s'. %w", configFilePath, err)
}
return azdConfig, nil
}
func (m *userConfigManager) Save(c UserConfig) error {
userConfigFilePath, err := GetUserConfigFilePath()
if err != nil {
return fmt.Errorf("failed getting user config file path. %w", err)
}
err = m.manager.Save(c, userConfigFilePath)
if err != nil {
return fmt.Errorf("failed saving configuration. %w", err)
}
return nil
}
// Gets the local file system path to the Azd configuration file
func GetUserConfigFilePath() (string, error) {
configPath, err := GetUserConfigDir()
if err != nil {
return "", fmt.Errorf("failed getting user config file path '%s'. %w", configPath, err)
}
return filepath.Join(configPath, "config.json"), nil
}

170
cli/sdk/azdcore/context.go Normal file
Просмотреть файл

@ -0,0 +1,170 @@
package azdcore
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/permissions"
)
const ProjectFileName = "azure.yaml"
const EnvironmentDirectoryName = ".azure"
const DotEnvFileName = ".env"
const ConfigFileName = "config.json"
const ConfigFileVersion = 1
type Context struct {
projectDirectory string
}
func (c *Context) ProjectDirectory() string {
return c.projectDirectory
}
func (c *Context) SetProjectDirectory(dir string) {
c.projectDirectory = dir
}
func (c *Context) ProjectPath() string {
return filepath.Join(c.ProjectDirectory(), ProjectFileName)
}
func (c *Context) EnvironmentDirectory() string {
return filepath.Join(c.ProjectDirectory(), EnvironmentDirectoryName)
}
func (c *Context) GetDefaultProjectName() string {
return filepath.Base(c.ProjectDirectory())
}
func (c *Context) EnvironmentRoot(name string) string {
return filepath.Join(c.EnvironmentDirectory(), name)
}
func (c *Context) GetEnvironmentWorkDirectory(name string) string {
return filepath.Join(c.EnvironmentRoot(name), "wd")
}
// GetDefaultEnvironmentName returns the name of the default environment. Returns
// an empty string if a default environment has not been set.
func (c *Context) GetDefaultEnvironmentName() (string, error) {
path := filepath.Join(c.EnvironmentDirectory(), ConfigFileName)
file, err := os.ReadFile(path)
switch {
case errors.Is(err, os.ErrNotExist):
return "", nil
case err != nil:
return "", fmt.Errorf("reading config file: %w", err)
}
var config configFile
if err := json.Unmarshal(file, &config); err != nil {
return "", fmt.Errorf("deserializing config file: %w", err)
}
return config.DefaultEnvironment, nil
}
// ProjectState represents the state of the project.
type ProjectState struct {
DefaultEnvironment string
}
// SetProjectState persists the state of the project to the file system, like the default environment.
func (c *Context) SetProjectState(state ProjectState) error {
path := filepath.Join(c.EnvironmentDirectory(), ConfigFileName)
config := configFile{
Version: ConfigFileVersion,
DefaultEnvironment: state.DefaultEnvironment,
}
if err := writeConfig(path, config); err != nil {
return err
}
// make sure to ignore the environment directory
path = filepath.Join(c.EnvironmentDirectory(), ".gitignore")
return os.WriteFile(path, []byte("# .azure is not intended to be committed\n*"), permissions.PermissionFile)
}
// Creates context with project directory set to the desired directory.
func NewContextWithDirectory(projectDirectory string) *Context {
return &Context{
projectDirectory: projectDirectory,
}
}
var (
ErrNoProject = errors.New("no project exists; to create a new project, run `azd init`")
)
// Creates context with project directory set to the nearest project file found by calling NewAzdContextFromWd
// on the current working directory.
func NewContext() (*Context, error) {
wd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get the current directory: %w", err)
}
return NewContextFromWd(wd)
}
// Creates context with project directory set to the nearest project file found.
//
// The project file is first searched for in the working directory, if not found, the parent directory is searched
// recursively up to root. If no project file is found, errNoProject is returned.
func NewContextFromWd(wd string) (*Context, error) {
// Walk up from the wd to the root, looking for a project file. If we find one, that's
// the root project directory.
searchDir, err := filepath.Abs(wd)
if err != nil {
return nil, fmt.Errorf("resolving path: %w", err)
}
for {
projectFilePath := filepath.Join(searchDir, ProjectFileName)
stat, err := os.Stat(projectFilePath)
if os.IsNotExist(err) || (err == nil && stat.IsDir()) {
parent := filepath.Dir(searchDir)
if parent == searchDir {
return nil, ErrNoProject
}
searchDir = parent
} else if err == nil {
// We found our azure.yaml file, and searchDir is the directory
// that contains it.
break
} else {
return nil, fmt.Errorf("searching for project file: %w", err)
}
}
return &Context{
projectDirectory: searchDir,
}, nil
}
type configFile struct {
Version int `json:"version"`
DefaultEnvironment string `json:"defaultEnvironment,omitempty"`
}
func writeConfig(path string, config configFile) error {
bytes, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("serializing config file: %w", err)
}
if err := os.MkdirAll(filepath.Dir(path), permissions.PermissionDirectory); err != nil {
return fmt.Errorf("creating environment root: %w", err)
}
if err := os.WriteFile(path, bytes, permissions.PermissionFile); err != nil {
return fmt.Errorf("writing config file: %w", err)
}
return nil
}

Просмотреть файл

@ -0,0 +1,34 @@
package contracts
type AksOptions struct {
// The namespace used for deploying k8s resources. Defaults to the project name
Namespace string `yaml:"namespace"`
// The relative folder path from the service that contains the k8s deployment manifests. Defaults to 'manifests'
DeploymentPath string `yaml:"deploymentPath"`
// The services ingress configuration options
Ingress AksIngressOptions `yaml:"ingress"`
// The services deployment configuration options
Deployment AksDeploymentOptions `yaml:"deployment"`
// The services service configuration options
Service AksServiceOptions `yaml:"service"`
// The helm configuration options
Helm *HelmConfig `yaml:"helm"`
// The kustomize configuration options
Kustomize *KustomizeConfig `yaml:"kustomize"`
}
// The AKS ingress options
type AksIngressOptions struct {
Name string `yaml:"name"`
RelativePath string `yaml:"relativePath"`
}
// The AKS deployment options
type AksDeploymentOptions struct {
Name string `yaml:"name"`
}
// The AKS service configuration options
type AksServiceOptions struct {
Name string `yaml:"name"`
}

Просмотреть файл

@ -0,0 +1,19 @@
package contracts
import "github.com/azure/azure-dev/cli/sdk/azdcore/common"
type DockerProjectOptions struct {
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Context string `yaml:"context,omitempty" json:"context,omitempty"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Registry common.ExpandableString `yaml:"registry,omitempty" json:"registry,omitempty"`
Image common.ExpandableString `yaml:"image,omitempty" json:"image,omitempty"`
Tag common.ExpandableString `yaml:"tag,omitempty" json:"tag,omitempty"`
RemoteBuild bool `yaml:"remoteBuild,omitempty" json:"remoteBuild,omitempty"`
BuildArgs []common.ExpandableString `yaml:"buildArgs,omitempty" json:"buildArgs,omitempty"`
// not supported from azure.yaml directly yet. Adding it for Aspire to use it, initially.
// Aspire would pass the secret keys, which are env vars that azd will set just to run docker build.
BuildSecrets []string `yaml:"-" json:"-"`
BuildEnv []string `yaml:"-" json:"-"`
}

Просмотреть файл

@ -0,0 +1,19 @@
package contracts
type HelmConfig struct {
Repositories []*HelmRepository `yaml:"repositories"`
Releases []*HelmRelease `yaml:"releases"`
}
type HelmRepository struct {
Name string `yaml:"name"`
Url string `yaml:"url"`
}
type HelmRelease struct {
Name string `yaml:"name"`
Chart string `yaml:"chart"`
Version string `yaml:"version"`
Namespace string `yaml:"namespace"`
Values string `yaml:"values"`
}

Просмотреть файл

@ -0,0 +1,102 @@
package contracts
import "fmt"
// The type of hooks. Supported values are 'pre' and 'post'
type HookType string
type HookPlatformType string
type ShellType string
type ScriptLocation string
const (
ShellTypeBash ShellType = "sh"
ShellTypePowershell ShellType = "pwsh"
ScriptTypeUnknown ShellType = ""
ScriptLocationInline ScriptLocation = "inline"
ScriptLocationPath ScriptLocation = "path"
ScriptLocationUnknown ScriptLocation = ""
// Executes pre hooks
HookTypePre HookType = "pre"
// Execute post hooks
HookTypePost HookType = "post"
HookTypeNone HookType = ""
HookPlatformWindows HookPlatformType = "windows"
HookPlatformPosix HookPlatformType = "posix"
)
type HookConfig struct {
// The location of the script hook (file path or inline)
location ScriptLocation
// When location is `path` a file path must be specified relative to the project or service
path string
// Stores a value whether or not this hook config has been previously validated
validated bool
// Stores the working directory set for this hook config
cwd string
// When location is `inline` a script must be defined inline
script string
// Internal name of the hook running for a given command
Name string `yaml:",omitempty"`
// The type of script hook (bash or powershell)
Shell ShellType `yaml:"shell,omitempty"`
// The inline script to execute or path to existing file
Run string `yaml:"run,omitempty"`
// When set to true will not halt command execution even when a script error occurs.
ContinueOnError bool `yaml:"continueOnError,omitempty"`
// When set to true will bind the stdin, stdout & stderr to the running console
Interactive bool `yaml:"interactive,omitempty"`
// When running on windows use this override config
Windows *HookConfig `yaml:"windows,omitempty"`
// When running on linux/macos use this override config
Posix *HookConfig `yaml:"posix,omitempty"`
}
// HooksConfig is an alias for map of hook names to slice of hook configurations
// This custom alias type is used to help support YAML unmarshalling of legacy single hook configurations
// and new multiple hook configurations
type HooksConfig map[string][]*HookConfig
// UnmarshalYAML converts the hooks configuration from YAML supporting both legacy single hook configurations
// and new multiple hook configurations
func (ch *HooksConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
var legacyConfig map[string]*HookConfig
// Attempt to unmarshal the legacy single hook configuration
if err := unmarshal(&legacyConfig); err == nil {
newConfig := HooksConfig{}
for key, value := range legacyConfig {
newConfig[key] = []*HookConfig{value}
}
*ch = newConfig
} else { // Unmarshal the new multiple hook configuration
var newConfig map[string][]*HookConfig
if err := unmarshal(&newConfig); err != nil {
return fmt.Errorf("failed to unmarshal hooks configuration: %w", err)
}
*ch = newConfig
}
return nil
}
// MarshalYAML marshals the hooks configuration to YAML supporting both legacy single hook configurations
func (ch HooksConfig) MarshalYAML() (interface{}, error) {
if len(ch) == 0 {
return nil, nil
}
result := map[string]any{}
for key, hooks := range ch {
if len(hooks) == 1 {
result[key] = hooks[0]
} else {
result[key] = hooks
}
}
return result, nil
}

Просмотреть файл

@ -0,0 +1,9 @@
package contracts
import "github.com/azure/azure-dev/cli/sdk/azdcore/common"
type KustomizeConfig struct {
Directory common.ExpandableString `yaml:"dir"`
Edits []common.ExpandableString `yaml:"edits"`
Env map[string]common.ExpandableString `yaml:"env"`
}

Просмотреть файл

@ -0,0 +1,8 @@
package contracts
// options supported in azure.yaml
type PipelineOptions struct {
Provider string `yaml:"provider"`
Variables []string `yaml:"variables"`
Secrets []string `yaml:"secrets"`
}

Просмотреть файл

@ -0,0 +1,8 @@
package contracts
type PlatformKind string
type PlatformConfig struct {
Type PlatformKind `yaml:"type"`
Config map[string]any `yaml:"config"`
}

Просмотреть файл

@ -0,0 +1,36 @@
package contracts
import "fmt"
type ProvisioningProviderKind string
const (
NotSpecified ProvisioningProviderKind = ""
Bicep ProvisioningProviderKind = "bicep"
Arm ProvisioningProviderKind = "arm"
Terraform ProvisioningProviderKind = "terraform"
Pulumi ProvisioningProviderKind = "pulumi"
Test ProvisioningProviderKind = "test"
)
type ProvisioningOptions struct {
Provider ProvisioningProviderKind `yaml:"provider,omitempty"`
Path string `yaml:"path,omitempty"`
Module string `yaml:"module,omitempty"`
DeploymentStacks map[string]any `yaml:"deploymentStacks,omitempty"`
// Not expected to be defined at azure.yaml
IgnoreDeploymentState bool `yaml:"-"`
}
// Parses the specified IaC Provider to ensure whether it is valid or not
// Defaults to `Bicep` if no provider is specified
func ParseProvisioningProvider(kind ProvisioningProviderKind) (ProvisioningProviderKind, error) {
switch kind {
// For the time being we need to include `Test` here for the unit tests to work as expected
// App builds will pass this test but fail resolving the provider since `Test` won't be registered in the container
case NotSpecified, Bicep, Terraform, Test:
return kind, nil
}
return ProvisioningProviderKind(""), fmt.Errorf("unsupported IaC provider '%s'", kind)
}

Просмотреть файл

@ -0,0 +1,7 @@
package contracts
// The Azure Spring Apps configuration options
type SpringOptions struct {
// The deployment name of ASA app
DeploymentName string `yaml:"deploymentName"`
}

Просмотреть файл

@ -0,0 +1,11 @@
package contracts
type StateConfig struct {
Remote *RemoteConfig `json:"remote" yaml:"remote"`
}
// RemoteConfig is the state configuration for a remote backend
type RemoteConfig struct {
Backend string `json:"backend" yaml:"backend"`
Config map[string]any `json:"config" yaml:"config"`
}

Просмотреть файл

@ -0,0 +1,168 @@
package contracts
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
// Stores a map of workflows configured for an azd project
type WorkflowMap map[string]*Workflow
// UnmarshalYAML will unmarshal the WorkflowMap from YAML.
// The unmarshalling will marshall the YAML like a standard Go map
// but will also persist the key as the workflow name within the Workflow struct.
func (wm *WorkflowMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
var m map[string]*Workflow
if err := unmarshal(&m); err != nil {
return err
}
for key, workflow := range m {
workflow.Name = key
}
*wm = m
return nil
}
// Workflow stores a list of steps to execute
type Workflow struct {
Name string `yaml:"-"`
Steps []*Step `yaml:"steps,omitempty"`
}
// UnmarshalYAML will unmarshal the Workflow from YAML.
// The workflow YAML can be specified as either a simple array of steps or a more verbose map/struct style
func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
parsed := false
// Map
var m map[string]interface{}
if err := unmarshal(&m); err == nil {
rawName, has := m["name"]
if has {
w.Name = rawName.(string)
}
rawSteps, has := m["steps"]
if has {
stepsArray, ok := rawSteps.([]interface{})
if ok {
w.Steps, err = w.unmarshalSteps(stepsArray)
if err != nil {
return err
}
}
}
parsed = true
}
// Array
var steps []interface{}
if err := unmarshal(&steps); err == nil {
w.Steps, err = w.unmarshalSteps(steps)
if err != nil {
return err
}
parsed = true
}
if !parsed || len(w.Steps) == 0 {
return fmt.Errorf("workflow configuration must be a map or an array of steps")
}
return nil
}
// unmarshalSteps will unmarshal the steps from YAML.
func (w *Workflow) unmarshalSteps(rawSteps any) ([]*Step, error) {
stepsArray, ok := rawSteps.([]interface{})
if !ok {
return nil, fmt.Errorf("steps must be an array")
}
steps := []*Step{}
for _, rawStep := range stepsArray {
stepYaml, err := yaml.Marshal(rawStep)
if err != nil {
return nil, err
}
var step Step
if err := yaml.Unmarshal(stepYaml, &step); err != nil {
return nil, err
}
steps = append(steps, &step)
}
return steps, nil
}
// Step stores a single step to execute within a workflow
// This struct can be expanded over time to support other types of steps/commands
type Step struct {
AzdCommand Command `yaml:"azd,omitempty"`
}
// NewAzdCommandStep creates a new step that executes an azd command with the specified name and args
func NewAzdCommandStep(args ...string) *Step {
return &Step{
AzdCommand: Command{
Args: args,
},
}
}
// Command stores a single command to execute
type Command struct {
Args []string `yaml:"args,omitempty"`
}
// UnmarshalYAML will unmarshal the Command from YAML.
// In command YAML the command can be specified as a simple string or a more verbose map/struct style
func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
parsed := false
// Map
var m map[string]interface{}
if err := unmarshal(&m); err == nil {
rawArgs, has := m["args"]
if has {
argsArray, ok := rawArgs.([]interface{})
if ok {
for _, arg := range argsArray {
argValue, ok := arg.(string)
if ok {
c.Args = append(c.Args, argValue)
}
}
}
}
parsed = true
}
// String
var s string
if err := unmarshal(&s); err == nil {
parts := strings.Split(s, " ")
if len(parts) > 0 {
c.Args = parts
}
parsed = true
}
if !parsed {
return fmt.Errorf("command must be a string or a map")
}
return nil
}

Просмотреть файл

@ -0,0 +1,49 @@
package environment
import (
"context"
)
// DataStore is the interface for the interacting with the persistent storage of environments.
type RemoteKind string
const (
RemoteKindAzureBlobStorage RemoteKind = "AzureBlobStorage"
)
var ValidRemoteKinds = []string{
string(RemoteKindAzureBlobStorage),
}
// SaveOptions provide additional metadata for the save operation
type SaveOptions struct {
// Whether or not the environment is new
IsNew bool
}
type DataStore interface {
// Gets the path to the environment .env file
EnvPath(env *Environment) string
// Gets the path to the environment JSON config file
ConfigPath(env *Environment) string
// Gets a list of all environments within the stat store
List(ctx context.Context) ([]*EnvironmentListItem, error)
// Gets the environment instance for the specified environment name
Get(ctx context.Context, name string) (*Environment, error)
// Reloads the environment from the persistent data store
Reload(ctx context.Context, env *Environment) error
// Saves the environment to the persistent data store
Save(ctx context.Context, env *Environment, options *SaveOptions) error
// Deletes the environment from the persistent data store
Delete(ctx context.Context, name string) error
}
type LocalDataStore DataStore
type RemoteDataStore DataStore

Просмотреть файл

@ -0,0 +1,290 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package environment
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"regexp"
"strings"
"maps"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/joho/godotenv"
)
// EnvNameEnvVarName is the name of the key used to store the envname property in the environment.
const EnvNameEnvVarName = "AZURE_ENV_NAME"
// LocationEnvVarName is the name of the key used to store the location property in the environment.
const LocationEnvVarName = "AZURE_LOCATION"
// SubscriptionIdEnvVarName is the name of they key used to store the subscription id property in the environment.
const SubscriptionIdEnvVarName = "AZURE_SUBSCRIPTION_ID"
// PrincipalIdEnvVarName is the name of they key used to store the id of a principal in the environment.
const PrincipalIdEnvVarName = "AZURE_PRINCIPAL_ID"
// TenantIdEnvVarName is the tenant that owns the subscription
const TenantIdEnvVarName = "AZURE_TENANT_ID"
// ContainerRegistryEndpointEnvVarName is the name of they key used to store the endpoint of the container registry to push
// to.
const ContainerRegistryEndpointEnvVarName = "AZURE_CONTAINER_REGISTRY_ENDPOINT"
// AksClusterEnvVarName is the name of they key used to store the endpoint of the AKS cluster to push to.
const AksClusterEnvVarName = "AZURE_AKS_CLUSTER_NAME"
// ResourceGroupEnvVarName is the name of the azure resource group that should be used for deployments
const ResourceGroupEnvVarName = "AZURE_RESOURCE_GROUP"
// PlatformTypeEnvVarName is the name of the key used to store the current azd platform type
const PlatformTypeEnvVarName = "AZD_PLATFORM_TYPE"
// The zero value of an Environment is not valid. Use [New] to create one. When writing tests,
// [Ephemeral] and [EphemeralWithValues] are useful to create environments which are not persisted to disk.
type Environment struct {
name string
// dotenv is a map of keys to values, persisted to the `.env` file stored in this environment's [Root].
dotenv map[string]string
// deletedKeys keeps track of deleted keys from the `.env` to be reapplied before a merge operation
// happens in Save
deletedKeys map[string]struct{}
// Config is environment specific config
Config config.Config
}
const AzdInitialEnvironmentConfigName = "AZD_INITIAL_ENVIRONMENT_CONFIG"
// New returns a new environment with the specified name.
func New(name string) *Environment {
env := &Environment{
name: name,
dotenv: make(map[string]string),
deletedKeys: make(map[string]struct{}),
Config: getInitialConfig(),
}
env.DotenvSet(EnvNameEnvVarName, name)
return env
}
func getInitialConfig() config.Config {
initialConfig := os.Getenv(AzdInitialEnvironmentConfigName)
if initialConfig == "" {
return config.NewEmptyConfig()
}
var initConfig map[string]any
if err := json.Unmarshal([]byte(initialConfig), &initConfig); err != nil {
log.Println("Failed to unmarshal initial config", err, "Using empty config.")
return config.NewEmptyConfig()
}
return config.NewConfig(initConfig)
}
// NewWithValues returns an ephemeral environment (i.e. not backed by a data store) with a set
// of values. Useful for testing. The name parameter is added to the environment with the
// AZURE_ENV_NAME key, replacing an existing value in the provided values map. A nil values is
// treated the same way as an empty map.
func NewWithValues(name string, values map[string]string) *Environment {
env := New(name)
if values != nil {
env.dotenv = values
}
return env
}
type EnvironmentResolver func(ctx context.Context) (*Environment, error)
// Same restrictions as a deployment name (ref:
// https://docs.microsoft.com/azure/azure-resource-manager/management/resource-name-rules#microsoftresources)
var EnvironmentNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9-\(\)_\.]{1,64}$`)
// The maximum length of an environment name.
var EnvironmentNameMaxLength = 64
func IsValidEnvironmentName(name string) bool {
return EnvironmentNameRegexp.MatchString(name)
}
// CleanName returns a version of [name] where all characters not allowed in an environment name have been replaced
// with hyphens
func CleanName(name string) string {
result := strings.Builder{}
for _, c := range name {
if (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '(' ||
c == ')' ||
c == '_' ||
c == '.' {
result.WriteRune(c)
} else {
result.WriteRune('-')
}
}
return result.String()
}
// Getenv behaves like os.Getenv, except that any keys in the `.env` file associated with this environment are considered
// first.
func (e *Environment) Getenv(key string) string {
if v, has := e.dotenv[key]; has {
return v
}
return os.Getenv(key)
}
// LookupEnv behaves like os.LookupEnv, except that any keys in the `.env` file associated with this environment are
// considered first.
func (e *Environment) LookupEnv(key string) (string, bool) {
if v, has := e.dotenv[key]; has {
return v, true
}
return os.LookupEnv(key)
}
// DotenvDelete removes the given key from the .env file in the environment, it is a no-op if the key
// does not exist. [Save] should be called to ensure this change is persisted.
func (e *Environment) DotenvDelete(key string) {
delete(e.dotenv, key)
e.deletedKeys[key] = struct{}{}
}
// Dotenv returns a copy of the key value pairs from the .env file in the environment.
func (e *Environment) Dotenv() map[string]string {
return maps.Clone(e.dotenv)
}
// DotenvSet sets the value of [key] to [value] in the .env file associated with the environment. [Save] should be
// called to ensure this change is persisted.
func (e *Environment) DotenvSet(key string, value string) {
e.dotenv[key] = value
delete(e.deletedKeys, key)
}
// Name gets the name of the environment
// If empty will fallback to the value of the AZURE_ENV_NAME environment variable
func (e *Environment) Name() string {
if e.name == "" {
e.name = e.Getenv(EnvNameEnvVarName)
}
return e.name
}
// GetSubscriptionId is shorthand for Getenv(SubscriptionIdEnvVarName)
func (e *Environment) GetSubscriptionId() string {
return e.Getenv(SubscriptionIdEnvVarName)
}
// GetTenantId is shorthand for Getenv(TenantIdEnvVarName)
func (e *Environment) GetTenantId() string {
return e.Getenv(TenantIdEnvVarName)
}
// SetLocation is shorthand for DotenvSet(SubscriptionIdEnvVarName, location)
func (e *Environment) SetSubscriptionId(id string) {
e.DotenvSet(SubscriptionIdEnvVarName, id)
}
// GetLocation is shorthand for Getenv(LocationEnvVarName)
func (e *Environment) GetLocation() string {
return e.Getenv(LocationEnvVarName)
}
// SetLocation is shorthand for DotenvSet(LocationEnvVarName, location)
func (e *Environment) SetLocation(location string) {
e.DotenvSet(LocationEnvVarName, location)
}
func normalize(key string) string {
return strings.ReplaceAll(strings.ToUpper(key), "-", "_")
}
// GetServiceProperty is shorthand for Getenv(SERVICE_$SERVICE_NAME_$PROPERTY_NAME)
func (e *Environment) GetServiceProperty(serviceName string, propertyName string) string {
return e.Getenv(fmt.Sprintf("SERVICE_%s_%s", normalize(serviceName), propertyName))
}
// Sets the value of a service-namespaced property in the environment.
func (e *Environment) SetServiceProperty(serviceName string, propertyName string, value string) {
e.DotenvSet(fmt.Sprintf("SERVICE_%s_%s", normalize(serviceName), propertyName), value)
}
// Creates a slice of key value pairs, based on the entries in the `.env` file like `KEY=VALUE` that
// can be used to pass into command runner or similar constructs.
func (e *Environment) Environ() []string {
envVars := []string{}
for k, v := range e.dotenv {
envVars = append(envVars, fmt.Sprintf("%s=%s", k, v))
}
return envVars
}
// fixupUnquotedDotenv is a workaround for behavior in how godotenv.Marshal handles numeric like values. Marshaling
// a map[string]string to a dotenv file, if a value can be successfully parsed with strconv.Atoi, it will be written in
// the dotenv file without quotes and the value written will be the value returned by strconv.Atoi. This can lead to dropping
// leading zeros from the value that we persist.
//
// For example, given a map with the key value pair ("FOO", "01"), the value returned by godotenv.Marshal will have a line
// that looks like FOO=1 instead of FOO=01 or FOO="01".
//
// This function takes the value returned by godotenv.Marshal and for any unquoted value replaces it with the value from
// the values map if they differ. This means that a key value pair ("FOO", "1") remains as FOO=1.
//
// When replacing a key in this manner, we ensure the value is wrapped in quotes, on the assumption that the leading zero
// is of significance to the value and wrapping it quotes means it is more likely to be treated as a string instead of a
// number by any downstream systems. We do not need to worry about escaping quotes in the value, because we know that
// godotenv.Marshal only did this translation for numeric values and so we know the original value did not contain quotes.
func fixupUnquotedDotenv(values map[string]string, dotenv string) string {
entries := strings.Split(dotenv, "\n")
for idx, line := range entries {
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
envKey := parts[0]
envValue := parts[1]
if len(envValue) > 0 && envValue[0] != '"' {
if values[envKey] != envValue {
entries[idx] = fmt.Sprintf("%s=\"%s\"", envKey, values[envKey])
}
}
}
return strings.Join(entries, "\n")
}
// Prepare dotenv for saving and returns a marshalled string that can be save to the underlying data store
// Instead of calling `godotenv.Write` directly, we need to save the file ourselves, so we can fixup any numeric values
// that were incorrectly unquoted.
func marshallDotEnv(env *Environment) (string, error) {
marshalled, err := godotenv.Marshal(env.dotenv)
if err != nil {
return "", fmt.Errorf("marshalling .env: %w", err)
}
return fixupUnquotedDotenv(env.dotenv, marshalled), nil
}

Просмотреть файл

@ -0,0 +1,15 @@
package environment
import "fmt"
type EnvironmentInitError struct {
Name string
}
func NewEnvironmentInitError(envName string) *EnvironmentInitError {
return &EnvironmentInitError{Name: envName}
}
func (err *EnvironmentInitError) Error() string {
return fmt.Sprintf("environment already initialized to %s", err.Name)
}

Просмотреть файл

@ -0,0 +1,182 @@
package environment
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/azure/azure-dev/cli/sdk/azdcore"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/joho/godotenv"
)
// LocalFileDataStore is a DataStore implementation that stores environment data in the local file system.
type LocalFileDataStore struct {
azdContext *azdcore.Context
configManager config.FileConfigManager
}
// NewLocalFileDataStore creates a new LocalFileDataStore instance
func NewLocalFileDataStore(azdContext *azdcore.Context, configManager config.FileConfigManager) LocalDataStore {
return &LocalFileDataStore{
azdContext: azdContext,
configManager: configManager,
}
}
// Path returns the path to the .env file for the given environment
func (fs *LocalFileDataStore) EnvPath(env *Environment) string {
return filepath.Join(fs.azdContext.EnvironmentRoot(env.name), DotEnvFileName)
}
// ConfigPath returns the path to the config.json file for the given environment
func (fs *LocalFileDataStore) ConfigPath(env *Environment) string {
return filepath.Join(fs.azdContext.EnvironmentRoot(env.name), ConfigFileName)
}
// List returns a list of all environments within the data store
func (fs *LocalFileDataStore) List(ctx context.Context) ([]*EnvironmentListItem, error) {
defaultEnv, err := fs.azdContext.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}
environments, err := os.ReadDir(fs.azdContext.EnvironmentDirectory())
if errors.Is(err, os.ErrNotExist) {
return []*EnvironmentListItem{}, nil
}
if err != nil {
return nil, fmt.Errorf("listing entries: %w", err)
}
// prefer empty array over `nil` since this is a contracted return value,
// where empty array is preferred for "NotFound" semantics.
envs := []*EnvironmentListItem{}
for _, ent := range environments {
if ent.IsDir() {
ev := &EnvironmentListItem{
Name: ent.Name(),
IsDefault: ent.Name() == defaultEnv,
DotEnvPath: filepath.Join(fs.azdContext.EnvironmentRoot(ent.Name()), DotEnvFileName),
ConfigPath: filepath.Join(fs.azdContext.EnvironmentRoot(ent.Name()), ConfigFileName),
}
envs = append(envs, ev)
}
}
slices.SortFunc(envs, func(a, b *EnvironmentListItem) int {
return strings.Compare(a.Name, b.Name)
})
return envs, nil
}
// Get returns the environment instance for the specified environment name
func (fs *LocalFileDataStore) Get(ctx context.Context, name string) (*Environment, error) {
root := fs.azdContext.EnvironmentRoot(name)
_, err := os.Stat(root)
if errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("'%s': %w", name, ErrNotFound)
} else if err != nil {
return nil, fmt.Errorf("listing env root: %w", err)
}
env := New(name)
if err := fs.Reload(ctx, env); err != nil {
return nil, err
}
return env, nil
}
// Reload reloads the environment from the persistent data store
func (fs *LocalFileDataStore) Reload(ctx context.Context, env *Environment) error {
// Reload env values
if envMap, err := godotenv.Read(fs.EnvPath(env)); errors.Is(err, os.ErrNotExist) {
env.dotenv = make(map[string]string)
env.deletedKeys = make(map[string]struct{})
} else if err != nil {
return fmt.Errorf("loading .env: %w", err)
} else {
env.dotenv = envMap
env.deletedKeys = make(map[string]struct{})
}
// Reload env config
if cfg, err := fs.configManager.Load(fs.ConfigPath(env)); errors.Is(err, os.ErrNotExist) {
env.Config = config.NewEmptyConfig()
} else if err != nil {
return fmt.Errorf("loading config: %w", err)
} else {
env.Config = cfg
}
return nil
}
// Save saves the environment to the persistent data store
func (fs *LocalFileDataStore) Save(ctx context.Context, env *Environment, options *SaveOptions) error {
// Update configuration
if err := fs.configManager.Save(env.Config, fs.ConfigPath(env)); err != nil {
return fmt.Errorf("saving config: %w", err)
}
// Cache current values & reload to get any new env vars
currentValues := env.dotenv
deletedValues := env.deletedKeys
if err := fs.Reload(ctx, env); err != nil {
return fmt.Errorf("failed reloading env vars, %w", err)
}
// Overlay current values before saving
for key, value := range currentValues {
env.dotenv[key] = value
}
// Replay deletion
for key := range deletedValues {
delete(env.dotenv, key)
}
marshalled, err := marshallDotEnv(env)
if err != nil {
return fmt.Errorf("marshalling .env: %w", err)
}
envFile, err := os.Create(fs.EnvPath(env))
if err != nil {
return fmt.Errorf("saving .env: %w", err)
}
defer envFile.Close()
// Write the contents (with a trailing newline), and sync the file, as godotenv.Write would have.
if _, err := envFile.WriteString(marshalled + "\n"); err != nil {
return fmt.Errorf("saving .env: %w", err)
}
if err := envFile.Sync(); err != nil {
return fmt.Errorf("saving .env: %w", err)
}
return nil
}
func (fs *LocalFileDataStore) Delete(ctx context.Context, name string) error {
envRoot := fs.azdContext.EnvironmentRoot(name)
_, err := os.Stat(envRoot)
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("'%s': %w", name, ErrNotFound)
} else if err != nil {
return fmt.Errorf("listing env root: %w", err)
}
if err := os.RemoveAll(envRoot); err != nil {
return fmt.Errorf("removing env root: %w", err)
}
return nil
}

Просмотреть файл

@ -0,0 +1,331 @@
package environment
import (
"context"
"errors"
"fmt"
"slices"
"strings"
"github.com/azure/azure-dev/cli/sdk/azdcore"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/ioc"
"github.com/azure/azure-dev/cli/sdk/azdcore/contracts"
)
// Description is a metadata description of an environment returned for the `azd env list` command
type Description struct {
// The name of the environment
Name string
// The path to the local .env file for the environment. Useful for IDEs like VS / VSCode
DotEnvPath string
// Specifies when the environment exists locally
HasLocal bool
// Specifies when the environment exists remotely
HasRemote bool
// Specifies when the environment is the default environment
IsDefault bool
}
// Spec is the specification for creating a new environment
type Spec struct {
Name string
Subscription string
Location string
// suggest is the name that is offered as a suggestion if we need to prompt the user for an environment name.
Examples []string
}
const DotEnvFileName = ".env"
const ConfigFileName = "config.json"
var (
// Error returned when an environment with the specified name already exists
ErrExists = errors.New("environment already exists")
// Error returned when an environment with a specified name cannot be found
ErrNotFound = errors.New("environment not found")
// Error returned when an environment name is not specified
ErrNameNotSpecified = errors.New("environment not specified")
)
// Manager is the interface used for managing instances of environments
type Manager interface {
Create(ctx context.Context, spec Spec) (*Environment, error)
List(ctx context.Context) ([]*Description, error)
// Get returns the existing environment with the given name.
// If the environment specified by the given name does not exist, ErrNotFound is returned.
Get(ctx context.Context, name string) (*Environment, error)
Save(ctx context.Context, env *Environment) error
SaveWithOptions(ctx context.Context, env *Environment, options *SaveOptions) error
Reload(ctx context.Context, env *Environment) error
// Delete deletes the environment from local storage.
Delete(ctx context.Context, name string) error
EnvPath(env *Environment) string
ConfigPath(env *Environment) string
}
type manager struct {
local DataStore
remote DataStore
azdContext *azdcore.Context
}
// NewManager creates a new Manager instance
func NewManager(
serviceLocator ioc.ServiceLocator,
azdContext *azdcore.Context,
local LocalDataStore,
remoteConfig *contracts.RemoteConfig,
) (Manager, error) {
var remote RemoteDataStore
// Ideally we would have liked to inject the remote data store directly into the manager,
// via the container but we can't do that because the remote data store is optional and the IoC
// container doesn't support optional interface based dependencies.
if remoteConfig != nil {
err := serviceLocator.ResolveNamed(remoteConfig.Backend, &remote)
if err != nil {
if errors.Is(err, ioc.ErrResolveInstance) {
return nil, fmt.Errorf(
"remote state configuration is invalid. The specified backend '%s' is not valid. Valid values are '%s'.",
remoteConfig.Backend,
strings.Join(ValidRemoteKinds, ","),
)
}
return nil, fmt.Errorf("resolving remote state data store: %w", err)
}
}
return &manager{
azdContext: azdContext,
local: local,
remote: remote,
}, nil
}
func (m *manager) Create(ctx context.Context, spec Spec) (*Environment, error) {
if spec.Name != "" && !IsValidEnvironmentName(spec.Name) {
errMsg := invalidEnvironmentNameMsg(spec.Name)
return nil, errors.New(errMsg)
}
if err := m.ensureValidEnvironmentName(ctx, &spec); err != nil {
return nil, err
}
// Ensure the environment does not already exist:
_, err := m.Get(ctx, spec.Name)
switch {
case errors.Is(err, ErrNotFound):
case err != nil:
return nil, fmt.Errorf("checking for existing environment: %w", err)
default:
return nil, fmt.Errorf("environment '%s' already exists", spec.Name)
}
env := New(spec.Name)
if spec.Subscription != "" {
env.SetSubscriptionId(spec.Subscription)
}
if spec.Location != "" {
env.SetLocation(spec.Location)
}
if err := m.SaveWithOptions(ctx, env, &SaveOptions{IsNew: true}); err != nil {
return nil, err
}
return env, nil
}
// ConfigPath returns the path to the environment config file
func (m *manager) ConfigPath(env *Environment) string {
return m.local.ConfigPath(env)
}
// EnvPath returns the path to the environment .env file
func (m *manager) EnvPath(env *Environment) string {
return m.local.EnvPath(env)
}
// List returns a list of all environments within the data store
func (m *manager) List(ctx context.Context) ([]*Description, error) {
envMap := map[string]*Description{}
defaultEnvName, err := m.azdContext.GetDefaultEnvironmentName()
if err != nil {
defaultEnvName = ""
}
localEnvs, err := m.local.List(ctx)
if err != nil {
return nil, fmt.Errorf("retrieving local environments, %w", err)
}
for _, env := range localEnvs {
envMap[env.Name] = &Description{
Name: env.Name,
HasLocal: true,
DotEnvPath: env.DotEnvPath,
}
}
if m.remote != nil {
remoteEnvs, err := m.remote.List(ctx)
if err != nil {
return nil, fmt.Errorf("retrieving remote environments, %w", err)
}
for _, env := range remoteEnvs {
existing, has := envMap[env.Name]
if !has {
existing = &Description{
Name: env.Name,
HasRemote: true,
}
} else {
existing.HasRemote = true
}
envMap[env.Name] = existing
}
}
allEnvs := []*Description{}
for _, env := range envMap {
env.IsDefault = env.Name == defaultEnvName
allEnvs = append(allEnvs, env)
}
slices.SortFunc(allEnvs, func(a, b *Description) int {
return strings.Compare(a.Name, b.Name)
})
return allEnvs, nil
}
// Get returns the environment instance for the specified environment name
func (m *manager) Get(ctx context.Context, name string) (*Environment, error) {
if name == "" {
return nil, ErrNameNotSpecified
}
localEnv, err := m.local.Get(ctx, name)
if err != nil {
if m.remote == nil {
return nil, err
}
remoteEnv, err := m.remote.Get(ctx, name)
if err != nil {
return nil, err
}
if err := m.local.Save(ctx, remoteEnv, nil); err != nil {
return nil, err
}
localEnv = remoteEnv
}
// Ensures local environment variable name is synced with the environment name
envName, ok := localEnv.LookupEnv(EnvNameEnvVarName)
if !ok || envName != name {
localEnv.DotenvSet(EnvNameEnvVarName, name)
if err := m.Save(ctx, localEnv); err != nil {
return nil, err
}
}
return localEnv, nil
}
// Save saves the environment to the persistent data store
func (m *manager) Save(ctx context.Context, env *Environment) error {
return m.SaveWithOptions(ctx, env, nil)
}
// Save saves the environment to the persistent data store with the specified options
func (m *manager) SaveWithOptions(ctx context.Context, env *Environment, options *SaveOptions) error {
if options == nil {
options = &SaveOptions{}
}
if err := m.local.Save(ctx, env, options); err != nil {
return fmt.Errorf("saving local environment, %w", err)
}
if m.remote == nil {
return nil
}
if err := m.remote.Save(ctx, env, options); err != nil {
return fmt.Errorf("saving remote environment, %w", err)
}
return nil
}
// Reload reloads the environment from the persistent data store
func (m *manager) Reload(ctx context.Context, env *Environment) error {
return m.local.Reload(ctx, env)
}
func (m *manager) Delete(ctx context.Context, name string) error {
if name == "" {
return ErrNameNotSpecified
}
err := m.local.Delete(ctx, name)
if err != nil {
return err
}
defaultEnvName, err := m.azdContext.GetDefaultEnvironmentName()
if err != nil {
return fmt.Errorf("getting default environment: %w", err)
}
if defaultEnvName == name {
err = m.azdContext.SetProjectState(azdcore.ProjectState{DefaultEnvironment: ""})
if err != nil {
return fmt.Errorf("clearing default environment: %w", err)
}
}
return nil
}
// ensureValidEnvironmentName ensures the environment name is valid, if it is not, an error is printed
// and the user is prompted for a new name.
func (m *manager) ensureValidEnvironmentName(ctx context.Context, spec *Spec) error {
exampleText := ""
if len(spec.Examples) > 0 {
exampleText = "\n\nExamples:"
}
for _, example := range spec.Examples {
exampleText += fmt.Sprintf("\n %s", example)
}
for !IsValidEnvironmentName(spec.Name) {
return errors.New(invalidEnvironmentNameMsg(spec.Name))
}
return nil
}
func invalidEnvironmentNameMsg(environmentName string) string {
return fmt.Sprintf(
"environment name '%s' is invalid (it should contain only alphanumeric characters and hyphens)\n",
environmentName,
)
}

Просмотреть файл

@ -0,0 +1,8 @@
package environment
type EnvironmentListItem struct {
Name string `json:"Name"`
IsDefault bool `json:"IsDefault"`
DotEnvPath string `json:"DotEnvPath"`
ConfigPath string `json:"ConfigPath"`
}

Просмотреть файл

@ -0,0 +1,241 @@
package environment
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/azure/azure-dev/cli/sdk/azdcore/azure/storage"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
var (
ErrAccessDenied = errors.New("access denied connecting Azure Blob Storage container.")
ErrInvalidContainer = errors.New("storage container name is invalid.")
)
type StorageBlobDataStore struct {
configManager config.Manager
blobClient storage.BlobClient
}
func NewStorageBlobDataStore(configManager config.Manager, blobClient storage.BlobClient) RemoteDataStore {
return &StorageBlobDataStore{
configManager: configManager,
blobClient: blobClient,
}
}
// EnvPath returns the path to the .env file for the given environment
func (fs *StorageBlobDataStore) EnvPath(env *Environment) string {
return fmt.Sprintf("%s/%s", env.name, DotEnvFileName)
}
// ConfigPath returns the path to the config.json file for the given environment
func (fs *StorageBlobDataStore) ConfigPath(env *Environment) string {
return fmt.Sprintf("%s/%s", env.name, ConfigFileName)
}
func (sbd *StorageBlobDataStore) List(ctx context.Context) ([]*EnvironmentListItem, error) {
blobs, err := sbd.blobClient.Items(ctx)
if err != nil {
normalizedErr := describeError(err)
if errors.Is(normalizedErr, storage.ErrContainerNotFound) {
return []*EnvironmentListItem{}, nil
}
return nil, fmt.Errorf("listing blobs: %w", normalizedErr)
}
envMap := map[string]*EnvironmentListItem{}
for _, blob := range blobs {
envName := filepath.Base(filepath.Dir(blob.Path))
env, has := envMap[envName]
if !has {
env = &EnvironmentListItem{
Name: envName,
}
envMap[envName] = env
}
switch blob.Name {
case ConfigFileName:
env.ConfigPath = blob.Path
case DotEnvFileName:
env.DotEnvPath = blob.Path
}
}
envs := []*EnvironmentListItem{}
for _, env := range envMap {
envs = append(envs, env)
}
slices.SortFunc(envs, func(a, b *EnvironmentListItem) int {
return strings.Compare(a.Name, b.Name)
})
return envs, nil
}
func (sbd *StorageBlobDataStore) Get(ctx context.Context, name string) (*Environment, error) {
envs, err := sbd.List(ctx)
if err != nil {
return nil, err
}
matchingIndex := slices.IndexFunc(envs, func(env *EnvironmentListItem) bool {
return env.Name == name
})
if matchingIndex < 0 {
return nil, fmt.Errorf("'%s': %w", name, ErrNotFound)
}
matchingEnv := envs[matchingIndex]
env := &Environment{
name: matchingEnv.Name,
}
if err := sbd.Reload(ctx, env); err != nil {
return nil, err
}
return env, nil
}
func (sbd *StorageBlobDataStore) Save(ctx context.Context, env *Environment, options *SaveOptions) error {
// Update configuration
cfgWriter := new(bytes.Buffer)
if err := sbd.configManager.Save(env.Config, cfgWriter); err != nil {
return fmt.Errorf("saving config: %w", err)
}
if err := sbd.blobClient.Upload(ctx, sbd.ConfigPath(env), cfgWriter); err != nil {
return fmt.Errorf("uploading config: %w", describeError(err))
}
marshalled, err := marshallDotEnv(env)
if err != nil {
return fmt.Errorf("marshalling .env: %w", err)
}
buffer := bytes.NewBuffer([]byte(marshalled))
if err := sbd.blobClient.Upload(ctx, sbd.EnvPath(env), buffer); err != nil {
return fmt.Errorf("uploading .env: %w", describeError(err))
}
tracing.SetUsageAttributes(fields.StringHashed(fields.EnvNameKey, env.Name()))
return nil
}
func (sbd *StorageBlobDataStore) Reload(ctx context.Context, env *Environment) error {
// Reload .env file
dotEnvBuffer, err := sbd.blobClient.Download(ctx, sbd.EnvPath(env))
if err != nil {
return describeError(err)
}
defer dotEnvBuffer.Close()
envMap, err := godotenv.Parse(dotEnvBuffer)
if err != nil {
env.dotenv = make(map[string]string)
env.deletedKeys = make(map[string]struct{})
} else {
env.dotenv = envMap
env.deletedKeys = make(map[string]struct{})
}
// Reload config file
configBuffer, err := sbd.blobClient.Download(ctx, sbd.ConfigPath(env))
if err != nil {
return describeError(err)
}
defer configBuffer.Close()
if cfg, err := sbd.configManager.Load(configBuffer); errors.Is(err, os.ErrNotExist) {
env.Config = config.NewEmptyConfig()
} else if err != nil {
return fmt.Errorf("loading config: %w", err)
} else {
env.Config = cfg
}
if env.Name() != "" {
tracing.SetUsageAttributes(fields.StringHashed(fields.EnvNameKey, env.Name()))
}
if _, err := uuid.Parse(env.GetSubscriptionId()); err == nil {
tracing.SetGlobalAttributes(fields.SubscriptionIdKey.String(env.GetSubscriptionId()))
} else {
tracing.SetGlobalAttributes(fields.StringHashed(fields.SubscriptionIdKey, env.GetSubscriptionId()))
}
return nil
}
func (sbd *StorageBlobDataStore) Delete(ctx context.Context, name string) error {
envs, err := sbd.List(ctx)
if err != nil {
return describeError(err)
}
matchingIndex := slices.IndexFunc(envs, func(env *EnvironmentListItem) bool {
return env.Name == name
})
if matchingIndex < 0 {
return fmt.Errorf("'%s': %w", name, ErrNotFound)
}
env := envs[matchingIndex]
if env.ConfigPath != "" {
err := sbd.blobClient.Delete(ctx, env.ConfigPath)
if err != nil {
return fmt.Errorf("deleting remote config: %w", describeError(err))
}
}
if env.DotEnvPath != "" {
err := sbd.blobClient.Delete(ctx, env.DotEnvPath)
if err != nil {
return fmt.Errorf("deleting remote .env: %w", describeError(err))
}
}
return nil
}
func describeError(err error) error {
var responseErr *azcore.ResponseError
if errors.As(err, &responseErr) {
switch responseErr.ErrorCode {
case "AuthorizationPermissionMismatch":
errorMsg := "Ensure your Azure account has `Storage Blob Contributor` role on the storage account or container."
return fmt.Errorf("%w %s %w", ErrAccessDenied, errorMsg, err)
case "InvalidResourceName":
//nolint:lll
errorMsg := "It must be between 3 and 63 characters in length, and must contain only lowercase letters, numbers, and dashes."
return fmt.Errorf("%w %s %w", ErrInvalidContainer, errorMsg, err)
}
}
return err
}

Просмотреть файл

@ -0,0 +1,7 @@
package account
import (
"github.com/azure/azure-dev/cli/sdk/azdcore/azure"
)
type Principal azure.TokenClaims

Просмотреть файл

@ -0,0 +1,216 @@
package ext
import (
"context"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/azure/azure-dev/cli/sdk/azdcore"
"github.com/azure/azure-dev/cli/sdk/azdcore/azure"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/ioc"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/azure/azure-dev/cli/sdk/azdcore/contracts"
"github.com/azure/azure-dev/cli/sdk/azdcore/environment"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext/account"
"github.com/azure/azure-dev/cli/sdk/azdcore/project"
)
var (
ErrProjectNotFound = errors.New("azd project not found in current path")
ErrEnvironmentNotFound = errors.New("azd environment not found")
ErrUserConfigNotFound = errors.New("azd user config not found")
ErrPrincipalNotFound = errors.New("azd principal not found")
ErrNotLoggedIn = errors.New("azd credential not available")
)
var current *Context
type Context struct {
container *ioc.NestedContainer
project *project.ProjectConfig
environment *environment.Environment
userConfig config.UserConfig
credential azcore.TokenCredential
principal *account.Principal
}
func CurrentContext(ctx context.Context) (*Context, error) {
if current == nil {
container := ioc.NewNestedContainer(nil)
registerComponents(ctx, container)
current = &Context{
container: container,
}
}
return current, nil
}
func (c *Context) Project(ctx context.Context) (*project.ProjectConfig, error) {
if c.project == nil {
var project *project.ProjectConfig
if err := c.container.Resolve(&project); err != nil {
return nil, fmt.Errorf("%w, Details: %w", ErrProjectNotFound, err)
}
c.project = project
}
return c.project, nil
}
func (c *Context) Environment(ctx context.Context) (*environment.Environment, error) {
if c.environment == nil {
var env *environment.Environment
if err := c.container.Resolve(&env); err != nil {
return nil, fmt.Errorf("%w, Details: %w", ErrEnvironmentNotFound, err)
}
c.environment = env
}
return c.environment, nil
}
func (c *Context) UserConfig(ctx context.Context) (config.UserConfig, error) {
if c.userConfig == nil {
var userConfig config.UserConfig
if err := c.container.Resolve(&userConfig); err != nil {
return nil, fmt.Errorf("%w, Details: %w", ErrUserConfigNotFound, err)
}
c.userConfig = userConfig
}
return c.userConfig, nil
}
func (c *Context) Credential() (azcore.TokenCredential, error) {
if c.credential == nil {
azdCredential, err := azidentity.NewAzureDeveloperCLICredential(nil)
if err != nil {
return nil, fmt.Errorf("%w, Details: %w", ErrNotLoggedIn, err)
}
c.credential = azdCredential
}
return c.credential, nil
}
func (c *Context) Principal(ctx context.Context) (*account.Principal, error) {
if c.principal == nil {
credential, err := c.Credential()
if err != nil {
return nil, fmt.Errorf("%w, Details: %w", ErrPrincipalNotFound, err)
}
accessToken, err := credential.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{"https://management.azure.com/.default"},
})
if err != nil {
return nil, err
}
claims, err := azure.GetClaimsFromAccessToken(accessToken.Token)
if err != nil {
return nil, err
}
principal := account.Principal(claims)
c.principal = &principal
}
return c.principal, nil
}
func (c *Context) SaveEnvironment(ctx context.Context, env *environment.Environment) error {
err := c.container.Invoke(func(envManager environment.Manager) error {
return envManager.Save(ctx, env)
})
return err
}
func (c *Context) SaveUserConfig(ctx context.Context, userConfig config.UserConfig) error {
err := c.container.Invoke(func(userConfigManager config.UserConfigManager) error {
return userConfigManager.Save(userConfig)
})
return err
}
func registerComponents(ctx context.Context, container *ioc.NestedContainer) error {
container.MustRegisterSingleton(func() ioc.ServiceLocator {
return container
})
container.MustRegisterSingleton(azdcore.NewContext)
container.MustRegisterSingleton(environment.NewManager)
container.MustRegisterSingleton(environment.NewLocalFileDataStore)
container.MustRegisterSingleton(config.NewFileConfigManager)
container.MustRegisterSingleton(config.NewManager)
container.MustRegisterSingleton(config.NewUserConfigManager)
container.MustRegisterSingleton(func(azdContext *azdcore.Context) (*project.ProjectConfig, error) {
if azdContext == nil {
return nil, azdcore.ErrNoProject
}
return project.Load(ctx, azdContext.ProjectPath())
})
container.MustRegisterSingleton(
func(azdContext *azdcore.Context, envManager environment.Manager) (*environment.Environment, error) {
if azdContext == nil {
return nil, azdcore.ErrNoProject
}
envName, err := azdContext.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}
environment, err := envManager.Get(ctx, envName)
if err != nil {
return nil, err
}
return environment, nil
},
)
container.MustRegisterSingleton(func(userConfigManager config.UserConfigManager) (config.UserConfig, error) {
return userConfigManager.Load()
})
container.MustRegisterSingleton(
func(projectConfig *project.ProjectConfig, userConfigManager config.UserConfigManager) (*contracts.RemoteConfig, error) {
var remoteStateConfig *contracts.RemoteConfig
userConfig, err := userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("loading user config: %w", err)
}
// Lookup remote state config in the following precedence:
// 1. Project azure.yaml
// 2. User configuration
if projectConfig != nil && projectConfig.State != nil && projectConfig.State.Remote != nil {
remoteStateConfig = projectConfig.State.Remote
} else {
if _, err := userConfig.GetSection("state.remote", &remoteStateConfig); err != nil {
return nil, fmt.Errorf("getting remote state config: %w", err)
}
}
return remoteStateConfig, nil
},
)
return nil
}

Просмотреть файл

@ -0,0 +1,24 @@
package debug
import (
"fmt"
"os"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
)
func WaitForDebugger() {
if _, has := os.LookupEnv("AZD_DEBUG"); has {
for {
debugConfirm := ux.NewConfirm(&ux.ConfirmConfig{
Message: fmt.Sprintf("Debugger Ready? (pid: %d)", os.Getpid()),
DefaultValue: ux.Ptr(true),
})
ready, err := debugConfirm.Ask()
if err == nil && *ready {
return
}
}
}
}

Просмотреть файл

@ -0,0 +1,98 @@
package ext
import (
"log"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
azcorelog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
"github.com/spf13/pflag"
)
var logFile *os.File
func init() {
if isDebugEnabled() {
azcorelog.SetListener(func(event azcorelog.Event, msg string) {
log.Printf("%s: %s\n", event, msg)
})
} else {
var err error
exePath, err := os.Executable()
if err != nil {
log.Fatalf("Failed to get executable name: %v", err)
}
exeNameWithExt := filepath.Base(exePath) // Get the base name of the executable
exeName := strings.TrimSuffix(exeNameWithExt, filepath.Ext(exeNameWithExt)) // Remove the extension
currentDate := time.Now().Format("20060102") // Format the current date as YYYYMMDD
logFileName := exeName + "-" + currentDate + ".log" // Log file name based on the date
logFile, err = os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
}
// Set log output to the file
log.SetOutput(logFile)
// Optional: Adds timestamp and file information to each log entry
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Register the signal handler to ensure log file is closed gracefully
setupSignalHandler()
}
}
// setupSignalHandler listens for system signals (e.g., SIGINT, SIGTERM) and ensures cleanup
func setupSignalHandler() {
signals := make(chan os.Signal, 1)
// Notify channel on SIGINT (Ctrl+C) or SIGTERM (termination)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
// Goroutine to handle the signals
go func() {
// Block until we receive a signal
sig := <-signals
log.Printf("Received signal: %v, shutting down gracefully...", sig)
// Perform cleanup
flush()
}()
}
// flush ensures the log file is closed
func flush() {
if logFile != nil {
logFile.Sync()
}
}
// isDebugEnabled checks to see if `--debug` was passed with a truthy
// value.
func isDebugEnabled() bool {
debug := false
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
// Since we are running this parse logic on the full command line, there may be additional flags
// which we have not defined in our flag set (but would be defined by whatever command we end up
// running). Setting UnknownFlags instructs `flags.Parse` to continue parsing the command line
// even if a flag is not in the flag set (instead of just returning an error saying the flag was not
// found).
flags.ParseErrorsWhitelist.UnknownFlags = true
flags.BoolVar(&debug, "debug", false, "")
// if flag `-h` of `--help` is within the command, the usage is automatically shown.
// Setting `Usage` to a no-op will hide this extra unwanted output.
flags.Usage = func() {}
_ = flags.Parse(os.Args[1:])
return debug
}

Просмотреть файл

@ -0,0 +1,14 @@
package ext
type ErrorWithSuggestion struct {
Err error
Suggestion string
}
func (e *ErrorWithSuggestion) Error() string {
return e.Err.Error()
}
func (e *ErrorWithSuggestion) Unwrap() error {
return e.Err
}

Просмотреть файл

@ -0,0 +1,21 @@
package output
import (
"fmt"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
"github.com/fatih/color"
)
type CommandHeader struct {
Title string
Description string
}
func (ch CommandHeader) Print() {
color.White(ux.BoldString(ch.Title))
if ch.Description != "" {
color.HiBlack(ch.Description)
}
fmt.Println()
}

Просмотреть файл

@ -0,0 +1,530 @@
package prompt
import (
"context"
"errors"
"fmt"
"log"
"dario.cat/mergo"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions"
"github.com/azure/azure-dev/cli/sdk/azdcore/azure"
"github.com/azure/azure-dev/cli/sdk/azdcore/ext"
"github.com/azure/azure-dev/cli/sdk/azdcore/ux"
)
var (
ErrNoResourcesFound = fmt.Errorf("no resources found")
)
type PromptResourceOptions struct {
ResourceType *azure.ResourceType
ResourceTypeDisplayName string
AllowNewResource bool
SelectorOptions *PromptSelectOptions
}
type PromptResourceGroupOptions struct {
AllowNewResource bool
SelectorOptions *PromptSelectOptions
}
type PromptSelectOptions struct {
Message string
HelpMessage string
LoadingMessage string
DisplayNumbers *bool
DisplayCount int
}
func PromptSubscription(ctx context.Context, selectorOptions *PromptSelectOptions) (*azure.Subscription, error) {
if selectorOptions == nil {
selectorOptions = &PromptSelectOptions{}
}
mergo.Merge(selectorOptions, &PromptSelectOptions{
Message: "Select subscription",
LoadingMessage: "Loading subscriptions...",
HelpMessage: "Choose an Azure subscription for your project.",
DisplayNumbers: ux.Ptr(true),
DisplayCount: 10,
})
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return nil, err
}
principal, err := azdContext.Principal(ctx)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
userConfig, err := azdContext.UserConfig(ctx)
if err != nil {
log.Println("User config not found")
}
subscriptionService := azure.NewSubscriptionsService(credential, nil)
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: selectorOptions.LoadingMessage,
ClearOnStop: true,
})
var subscriptions []*armsubscriptions.Subscription
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
subscriptionList, err := subscriptionService.ListSubscriptions(ctx, principal.TenantId)
if err != nil {
return err
}
subscriptions = subscriptionList
return nil
})
if err != nil {
return nil, err
}
var defaultIndex *int
var defaultSubscriptionId = ""
if userConfig != nil {
subscriptionId, has := userConfig.GetString("defaults.subscription")
if has {
defaultSubscriptionId = subscriptionId
}
}
for i, subscription := range subscriptions {
if *subscription.SubscriptionID == defaultSubscriptionId {
defaultIndex = &i
break
}
}
choices := make([]string, len(subscriptions))
for i, subscription := range subscriptions {
choices[i] = fmt.Sprintf("%s (%s)", *subscription.DisplayName, *subscription.SubscriptionID)
}
subscriptionSelector := ux.NewSelect(&ux.SelectConfig{
Message: selectorOptions.Message,
HelpMessage: selectorOptions.HelpMessage,
DisplayCount: selectorOptions.DisplayCount,
DisplayNumbers: selectorOptions.DisplayNumbers,
Allowed: choices,
DefaultIndex: defaultIndex,
})
selectedIndex, err := subscriptionSelector.Ask()
if err != nil {
return nil, err
}
if selectedIndex == nil {
return nil, nil
}
selectedSubscription := subscriptions[*selectedIndex]
return &azure.Subscription{
Id: *selectedSubscription.SubscriptionID,
Name: *selectedSubscription.DisplayName,
TenantId: *selectedSubscription.TenantID,
UserAccessTenantId: principal.TenantId,
}, nil
}
func PromptLocation(
ctx context.Context,
subscription *azure.Subscription,
selectorOptions *PromptSelectOptions,
) (*azure.Location, error) {
if selectorOptions == nil {
selectorOptions = &PromptSelectOptions{}
}
mergo.Merge(selectorOptions, &PromptSelectOptions{
Message: "Select location",
LoadingMessage: "Loading locations...",
HelpMessage: "Choose an Azure location for your project.",
DisplayNumbers: ux.Ptr(true),
DisplayCount: 10,
})
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
userConfig, err := azdContext.UserConfig(ctx)
if errors.Is(err, ext.ErrUserConfigNotFound) {
log.Println("User config not found")
}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: selectorOptions.LoadingMessage,
ClearOnStop: true,
})
var locations []azure.Location
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
subscriptionService := azure.NewSubscriptionsService(credential, nil)
locationList, err := subscriptionService.ListSubscriptionLocations(ctx, subscription.Id, subscription.TenantId)
if err != nil {
return err
}
locations = locationList
return nil
})
if err != nil {
return nil, err
}
var defaultIndex *int
var defaultLocation = "eastus2"
if userConfig != nil {
location, has := userConfig.GetString("defaults.location")
if has {
defaultLocation = location
}
}
for i, location := range locations {
if location.Name == defaultLocation {
defaultIndex = &i
break
}
}
choices := make([]string, len(locations))
for i, location := range locations {
choices[i] = fmt.Sprintf("%s (%s)", location.RegionalDisplayName, location.Name)
}
locationSelector := ux.NewSelect(&ux.SelectConfig{
Message: selectorOptions.Message,
HelpMessage: selectorOptions.HelpMessage,
DisplayCount: selectorOptions.DisplayCount,
DisplayNumbers: selectorOptions.DisplayNumbers,
Allowed: choices,
DefaultIndex: defaultIndex,
})
selectedIndex, err := locationSelector.Ask()
if err != nil {
return nil, err
}
if selectedIndex == nil {
return nil, nil
}
selectedLocation := locations[*selectedIndex]
return &azure.Location{
Name: selectedLocation.Name,
DisplayName: selectedLocation.DisplayName,
RegionalDisplayName: selectedLocation.RegionalDisplayName,
}, nil
}
func PromptResourceGroup(
ctx context.Context,
subscription *azure.Subscription,
options *PromptResourceGroupOptions,
) (*azure.ResourceGroup, error) {
if options == nil {
options = &PromptResourceGroupOptions{}
}
if options.SelectorOptions == nil {
options.SelectorOptions = &PromptSelectOptions{}
}
mergo.Merge(options.SelectorOptions, &PromptSelectOptions{
Message: "Select resource group",
LoadingMessage: "Loading resource groups...",
HelpMessage: "Choose an Azure resource group for your project.",
DisplayNumbers: ux.Ptr(true),
DisplayCount: 10,
})
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: options.SelectorOptions.LoadingMessage,
})
var resourceGroups []*azure.Resource
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
resourceService := azure.NewResourceService(credential, nil)
resourceGroupList, err := resourceService.ListResourceGroup(ctx, subscription.Id, nil)
if err != nil {
return err
}
resourceGroups = resourceGroupList
return nil
})
if err != nil {
return nil, err
}
choices := make([]string, len(resourceGroups))
for i, resourceGroup := range resourceGroups {
choices[i] = resourceGroup.Name
}
resourceGroupSelector := ux.NewSelect(&ux.SelectConfig{
Message: options.SelectorOptions.Message,
HelpMessage: options.SelectorOptions.HelpMessage,
DisplayCount: options.SelectorOptions.DisplayCount,
DisplayNumbers: options.SelectorOptions.DisplayNumbers,
Allowed: choices,
})
selectedIndex, err := resourceGroupSelector.Ask()
if err != nil {
return nil, err
}
if selectedIndex == nil {
return nil, nil
}
selectedResourceGroup := resourceGroups[*selectedIndex]
return &azure.ResourceGroup{
Id: selectedResourceGroup.Id,
Name: selectedResourceGroup.Name,
Location: selectedResourceGroup.Location,
}, nil
}
func PromptSubscriptionResource(
ctx context.Context,
subscription *azure.Subscription,
options PromptResourceOptions,
) (*azure.Resource, error) {
if options.SelectorOptions == nil {
resourceName := options.ResourceTypeDisplayName
if resourceName == "" && options.ResourceType != nil {
resourceName = string(*options.ResourceType)
}
if resourceName == "" {
resourceName = "resource"
}
options.SelectorOptions = &PromptSelectOptions{
Message: fmt.Sprintf("Select %s", resourceName),
LoadingMessage: fmt.Sprintf("Loading %s resources...", resourceName),
HelpMessage: fmt.Sprintf("Choose an Azure %s for your project.", resourceName),
DisplayNumbers: ux.Ptr(true),
DisplayCount: 10,
}
}
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
var resourceListOptions *armresources.ClientListOptions
if options.ResourceType != nil {
resourceListOptions = &armresources.ClientListOptions{
Filter: to.Ptr(fmt.Sprintf("resourceType eq '%s'", string(*options.ResourceType))),
}
}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: options.SelectorOptions.LoadingMessage,
})
var resources []*azure.Resource
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
resourceService := azure.NewResourceService(credential, nil)
resourceList, err := resourceService.ListSubscriptionResources(ctx, subscription.Id, resourceListOptions)
if err != nil {
return err
}
resources = resourceList
return nil
})
if err != nil {
return nil, err
}
if len(resources) == 0 {
return nil, fmt.Errorf("no resources found with type '%v'", options.ResourceType)
}
choices := make([]string, len(resources))
for i, resource := range resources {
parsedResource, err := arm.ParseResourceID(resource.Id)
if err != nil {
return nil, fmt.Errorf("parsing resource id: %w", err)
}
choices[i] = fmt.Sprintf("%s (%s)", parsedResource.Name, parsedResource.ResourceGroupName)
}
resourceSelector := ux.NewSelect(&ux.SelectConfig{
Message: options.SelectorOptions.Message,
HelpMessage: options.SelectorOptions.HelpMessage,
DisplayCount: options.SelectorOptions.DisplayCount,
DisplayNumbers: options.SelectorOptions.DisplayNumbers,
Allowed: choices,
})
selectedIndex, err := resourceSelector.Ask()
if err != nil {
return nil, err
}
if selectedIndex == nil {
return nil, nil
}
return resources[*selectedIndex], nil
}
func PromptResourceGroupResource(
ctx context.Context,
resourceGroup *azure.ResourceGroup,
options PromptResourceOptions,
) (*azure.Resource, error) {
if options.SelectorOptions == nil {
resourceName := options.ResourceTypeDisplayName
if resourceName == "" && options.ResourceType != nil {
resourceName = string(*options.ResourceType)
}
if resourceName == "" {
resourceName = "resource"
}
options.SelectorOptions = &PromptSelectOptions{
Message: fmt.Sprintf("Select %s", resourceName),
LoadingMessage: fmt.Sprintf("Loading %s resources...", resourceName),
HelpMessage: fmt.Sprintf("Choose an Azure %s for your project.", resourceName),
DisplayNumbers: ux.Ptr(true),
DisplayCount: 10,
}
}
azdContext, err := ext.CurrentContext(ctx)
if err != nil {
return nil, err
}
credential, err := azdContext.Credential()
if err != nil {
return nil, err
}
parsedResourceGroup, err := arm.ParseResourceID(resourceGroup.Id)
if err != nil {
return nil, fmt.Errorf("parsing resource group id: %w", err)
}
var resourceListOptions *azure.ListResourceGroupResourcesOptions
if options.ResourceType != nil {
resourceListOptions = &azure.ListResourceGroupResourcesOptions{
Filter: to.Ptr(fmt.Sprintf("resourceType eq '%s'", *options.ResourceType)),
}
}
loadingSpinner := ux.NewSpinner(&ux.SpinnerConfig{
Text: options.SelectorOptions.LoadingMessage,
})
var resources []*azure.Resource
err = loadingSpinner.Run(ctx, func(ctx context.Context) error {
resourceService := azure.NewResourceService(credential, nil)
resourceList, err := resourceService.ListResourceGroupResources(
ctx,
parsedResourceGroup.SubscriptionID,
resourceGroup.Name,
resourceListOptions,
)
if err != nil {
return err
}
resources = resourceList
return nil
})
if err != nil {
return nil, err
}
if len(resources) == 0 {
return nil, fmt.Errorf("no resources found with type '%v'", options.ResourceType)
}
choices := make([]string, len(resources))
for i, resource := range resources {
choices[i] = resource.Name
}
resourceSelector := ux.NewSelect(&ux.SelectConfig{
Message: options.SelectorOptions.Message,
HelpMessage: options.SelectorOptions.HelpMessage,
DisplayCount: options.SelectorOptions.DisplayCount,
DisplayNumbers: options.SelectorOptions.DisplayNumbers,
Allowed: choices,
})
selectedIndex, err := resourceSelector.Ask()
if err != nil {
return nil, err
}
if selectedIndex == nil {
return nil, nil
}
return resources[*selectedIndex], nil
}

49
cli/sdk/azdcore/go.mod Normal file
Просмотреть файл

@ -0,0 +1,49 @@
module github.com/azure/azure-dev/cli/sdk/azdcore
go 1.23.0
require (
dario.cat/mergo v1.0.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.3.1
github.com/blang/semver/v4 v4.0.0
github.com/drone/envsubst v1.0.3
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
github.com/fatih/color v1.17.0
github.com/golobby/container/v3 v3.3.2
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/microsoft/ApplicationInsights-Go v0.4.4
github.com/microsoft/go-deviceid v1.0.0
github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel v1.30.0
go.opentelemetry.io/otel/sdk v1.30.0
go.opentelemetry.io/otel/trace v1.30.0
go.uber.org/atomic v1.11.0
golang.org/x/sys v0.26.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/text v0.19.0 // indirect
)

143
cli/sdk/azdcore/go.sum Normal file
Просмотреть файл

@ -0,0 +1,143 @@
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 h1:wxQx2Bt4xzPIKvW59WQf1tJNx/ZZKPfN+EhPX3Z6CYY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0/go.mod h1:TpiwjwnW/khS0LKs4vW5UmmT9OWcxaveS8U7+tlknzo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.3.1 h1:a1U6j4GPI18JQCqgz7/DcqXA1vzvGBugm14AXZfU0gs=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.3.1/go.mod h1:tZyRNcHi2/yo+ugYHTUuOrHiboKilaizLnRL5aZTe6A=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golobby/container/v3 v3.3.2 h1:7u+RgNnsdVlhGoS8gY4EXAG601vpMMzLZlYqSp77Quw=
github.com/golobby/container/v3 v3.3.2/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY=
github.com/microsoft/ApplicationInsights-Go v0.4.4/go.mod h1:fKRUseBqkw6bDiXTs3ESTiU/4YTIHsQS4W3fP2ieF4U=
github.com/microsoft/go-deviceid v1.0.0 h1:i5AQ654Xk9kfvwJeKQm3w2+eT1+ImBDVEpAR0AjpP40=
github.com/microsoft/go-deviceid v1.0.0/go.mod h1:KY13FeVdHkzD8gy+6T8+kVmD/7RMpTaWW75K+T4uZWg=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db h1:uwKfJcTGnZ4ziFqr0bjzNc/P1fydrAg450b9Dz8Fj8M=
github.com/psanford/memfs v0.0.0-20240922203233-02fb08c0f8db/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Просмотреть файл

@ -0,0 +1,70 @@
package installer
import (
"os"
"path/filepath"
"strings"
)
type InstallType string
const InstallTypeUnknown InstallType = ""
const InstallTypePs InstallType = "install-azd.ps1"
const InstallTypeSh InstallType = "install-azd.sh"
const InstallTypeBrew InstallType = "brew"
const InstallTypeChoco InstallType = "choco"
const InstallTypeWinget InstallType = "winget"
const InstallTypeDeb InstallType = "deb"
const InstallTypeRpm InstallType = "rpm"
// InstalledBy Returns the type of installer that installed the CLI, based on
// the contents of the `.installed-by.txt` file in the same directory as the
// executing instance of azd.
func InstalledBy() InstallType {
raw := RawInstalledBy()
switch raw {
case string(InstallTypePs):
return InstallTypePs
case string(InstallTypeSh):
return InstallTypeSh
case string(InstallTypeBrew):
return InstallTypeBrew
case string(InstallTypeChoco):
return InstallTypeChoco
case string(InstallTypeWinget):
return InstallTypeWinget
case string(InstallTypeDeb):
return InstallTypeDeb
case string(InstallTypeRpm):
return InstallTypeRpm
default:
return InstallTypeUnknown
}
}
// RawInstalledBy returns the raw value of the `.installed-by.txt` file in the
// same directory as the executing instance of azd after removing leading and
// trailing whitespace.
func RawInstalledBy() string {
exePath, err := os.Executable()
if err != nil {
return ""
}
resolvedPath, err := filepath.EvalSymlinks(exePath)
if err != nil {
return ""
}
exeDir := filepath.Dir(resolvedPath)
installedByFile := filepath.Join(exeDir, ".installed-by.txt")
bytes, err := os.ReadFile(installedByFile)
if err != nil {
return ""
}
return strings.TrimSpace(string(bytes))
}

Просмотреть файл

@ -0,0 +1,9 @@
//go:build !windows && !linux && !darwin
package osversion
import "errors"
func GetVersion() (string, error) {
return "", errors.New("unsupported OS")
}

Просмотреть файл

@ -0,0 +1,43 @@
package osversion
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import <Foundation/Foundation.h>
#import <Foundation/NSProcessInfo.h>
NSOperatingSystemVersion c_getVersion() {
NSProcessInfo *pInfo = [NSProcessInfo processInfo];
// check availability of the property operatingSystemVersion (10.10+) at runtime
if ([pInfo respondsToSelector:@selector(operatingSystemVersion)])
{
return [pInfo operatingSystemVersion];
}
else
{
NSOperatingSystemVersion version;
version.majorVersion = 10;
version.minorVersion = 9;
version.patchVersion = 0;
return version;
}
}
*/
import "C"
import (
"fmt"
)
func verToStr(ver C.NSOperatingSystemVersion) string {
res := fmt.Sprintf("%d.%d.%d", int(ver.majorVersion), int(ver.minorVersion), int(ver.patchVersion))
return res
}
func doGetVersion() string {
return verToStr(C.c_getVersion())
}
func GetVersion() (string, error) {
return doGetVersion(), nil
}

Просмотреть файл

@ -0,0 +1,28 @@
package osversion
import (
"fmt"
"golang.org/x/sys/unix"
)
func GetVersion() (string, error) {
uname := unix.Utsname{}
err := unix.Uname(&uname)
if err != nil {
return "", fmt.Errorf("unix.Uname returned error: %w", err)
}
return string(uname.Release[:cStringLen(uname.Release[:])]), nil
}
func cStringLen(s []byte) int {
for i := range s {
if s[i] == 0 {
return i
}
}
return len(s)
}

Просмотреть файл

@ -0,0 +1,14 @@
package osversion
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetVersion(t *testing.T) {
ver, err := GetVersion()
assert.NoError(t, err)
assert.NotEmpty(t, ver)
}

Просмотреть файл

@ -0,0 +1,17 @@
package osversion
import (
"errors"
"fmt"
"golang.org/x/sys/windows"
)
func GetVersion() (string, error) {
version := windows.RtlGetVersion()
if version != nil {
return fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.BuildNumber), nil
}
return "", errors.New("windows.RtlGetVersion returned nil")
}

Просмотреть файл

@ -0,0 +1,24 @@
package runcontext
import (
"log"
"os"
"strconv"
)
// AzdInCloudShellEnvVar is the environment variable that is set when running in Cloud Shell. It is set to a value recognized
// by strconv.ParseBool.
//
// Use [IsRunningInCloudShell] to check if the current process is running in Cloud Shell.
const AzdInCloudShellEnvVar = "AZD_IN_CLOUDSHELL"
func IsRunningInCloudShell() bool {
if azdInCloudShell, has := os.LookupEnv(AzdInCloudShellEnvVar); has {
if use, err := strconv.ParseBool(azdInCloudShell); err == nil && use {
log.Printf("running in Cloud Shell")
return true
}
}
return false
}

Просмотреть файл

@ -0,0 +1,193 @@
package tracing
import (
"context"
"slices"
"sync"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/baggage"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/atomic"
)
// SetBaggageInContext sets the given attributes as baggage.
// Baggage attributes are set for the current running span, and for any child spans created.
func SetBaggageInContext(ctx context.Context, attributes ...attribute.KeyValue) context.Context {
SetAttributesInContext(ctx, attributes...)
return baggage.ContextWithAttributes(ctx, attributes)
}
// SetAttributesInContext sets the given attributes for the current running span.
func SetAttributesInContext(ctx context.Context, attributes ...attribute.KeyValue) {
runningSpan := trace.SpanFromContext(ctx)
runningSpan.SetAttributes(attributes...)
}
// Attributes that are global and set on all events
var globalVal = valSynced{}
// Attributes that are only set on command-level usage events
var usageVal = valSynced{}
// Atomic value with mutex for multiple writers
type valSynced struct {
val atomic.Value
mu sync.Mutex
}
func init() {
globalVal.val.Store(baggage.NewBaggage())
usageVal.val.Store(baggage.NewBaggage())
}
func set(v *valSynced, attributes []attribute.KeyValue) {
v.mu.Lock()
defer v.mu.Unlock()
baggage := v.val.Load().(baggage.Baggage)
newBaggage := baggage.Set(attributes...)
v.val.Store(newBaggage)
}
func get(v *valSynced) []attribute.KeyValue {
baggage := v.val.Load().(baggage.Baggage)
return baggage.Attributes()
}
func appendTo(v *valSynced, attr attribute.KeyValue) {
v.mu.Lock()
defer v.mu.Unlock()
baggage := v.val.Load().(baggage.Baggage)
val, ok := baggage.Lookup(attr.Key)
if ok && val.Type() == attr.Value.Type() {
switch attr.Value.Type() {
case attribute.BOOLSLICE:
attr = attr.Key.BoolSlice(append(val.AsBoolSlice(), attr.Value.AsBoolSlice()...))
case attribute.INT64SLICE:
attr = attr.Key.Int64Slice(append(val.AsInt64Slice(), attr.Value.AsInt64Slice()...))
case attribute.FLOAT64SLICE:
attr = attr.Key.Float64Slice(append(val.AsFloat64Slice(), attr.Value.AsFloat64Slice()...))
case attribute.STRINGSLICE:
attr = attr.Key.StringSlice(append(val.AsStringSlice(), attr.Value.AsStringSlice()...))
}
}
newBaggage := baggage.Set(attr)
v.val.Store(newBaggage)
}
func appendToUnique(v *valSynced, attr attribute.KeyValue) {
v.mu.Lock()
defer v.mu.Unlock()
baggage := v.val.Load().(baggage.Baggage)
val, ok := baggage.Lookup(attr.Key)
if ok && val.Type() == attribute.STRINGSLICE {
switch attr.Value.Type() {
case attribute.STRING:
attrValue := attr.Value.AsString()
for _, v := range val.AsStringSlice() {
if v == attrValue { // already exists
return
}
}
attr = attr.Key.StringSlice(append(val.AsStringSlice(), attrValue))
case attribute.STRINGSLICE:
attrValues := attr.Value.AsStringSlice()
adds := make([]string, 0, len(attrValues))
for _, a := range attrValues {
if !slices.Contains(val.AsStringSlice(), a) {
adds = append(adds, a)
}
}
if len(adds) == 0 {
return
}
attr = attr.Key.StringSlice(append(val.AsStringSlice(), adds...))
}
} else if attr.Value.Type() == attribute.STRING {
attr = attr.Key.StringSlice([]string{attr.Value.AsString()})
}
newBaggage := baggage.Set(attr)
v.val.Store(newBaggage)
}
func increment(v *valSynced, attr attribute.KeyValue) {
v.mu.Lock()
defer v.mu.Unlock()
baggage := v.val.Load().(baggage.Baggage)
val, ok := baggage.Lookup(attr.Key)
if ok && val.Type() == attr.Value.Type() {
switch attr.Value.Type() {
case attribute.INT64:
attr = attr.Key.Int64(val.AsInt64() + attr.Value.AsInt64())
case attribute.FLOAT64:
attr = attr.Key.Float64(val.AsFloat64() + attr.Value.AsFloat64())
case attribute.STRING:
attr = attr.Key.String(val.AsString() + attr.Value.AsString())
}
}
newBaggage := baggage.Set(attr)
v.val.Store(newBaggage)
}
// Sets global attributes that are included with all telemetry events emitted.
// If the attribute already exists, the value is replaced.
func SetGlobalAttributes(attributes ...attribute.KeyValue) {
set(&globalVal, attributes)
}
// Returns all global attributes set.
func GetGlobalAttributes() []attribute.KeyValue {
return get(&globalVal)
}
// Sets usage attributes that are included with usage events emitted.
// If the attribute already exists, the value is replaced.
func SetUsageAttributes(attributes ...attribute.KeyValue) {
set(&usageVal, attributes)
}
// Returns all usage attributes set.
func GetUsageAttributes() []attribute.KeyValue {
return get(&usageVal)
}
// Sets or appends a value to a slice-type usage attribute that possibly exists.
// The attribute is expected to be a slice-type value, and matches the existing type.
// Otherwise, a strict replacement is performed.
func AppendUsageAttribute(attr attribute.KeyValue) {
appendTo(&usageVal, attr)
}
// Sets or appends a value to a string slice-type usage attribute that possibly exists,
// merging on unique elements.
//
// The attribute is expected to be a slice-type value, and matches the existing type.
// For convenience, a string-type value is also treated as a string-slice with a single element.
//
// If the types do not match, a strict replacement is performed.
func AppendUsageAttributeUnique(attr attribute.KeyValue) {
appendToUnique(&usageVal, attr)
}
// Sets or increments a possibly stored usage attribute.
// The attribute is expected to be a numeric-type or string-type value, and matches the existing type
// of a previously stored usage attribute.
// Otherwise, a strict replacement is performed.
func IncrementUsageAttribute(attr attribute.KeyValue) {
increment(&usageVal, attr)
}
// InteractTimeMs is the time spent waiting on user interaction in milliseconds.
var InteractTimeMs = atomic.NewInt64(0)

Просмотреть файл

@ -0,0 +1,200 @@
package tracing
import (
"testing"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/baggage"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
)
func TestSetAndGetAttributes(t *testing.T) {
val := &valSynced{}
val.val.Store(baggage.NewBaggage())
setAttributes := []attribute.KeyValue{
attribute.String("str", "val1"),
attribute.Bool("bool", true),
attribute.Int64("int64", 10),
attribute.Float64("float64", 10.0),
attribute.StringSlice("stringslice", []string{"v1", "v2"}),
attribute.BoolSlice("boolslice", []bool{false, true}),
attribute.Int64Slice("int64slice", []int64{1, 2}),
attribute.Float64Slice("float64slice", []float64{1.0, 2.0}),
}
set(val, setAttributes)
actual := get(val)
assert.ElementsMatch(t, actual, setAttributes)
}
func TestAppendAttribute(t *testing.T) {
tests := []struct {
name string
existing []attribute.KeyValue
set attribute.KeyValue
expected []attribute.KeyValue
}{
{"Set", []attribute.KeyValue{}, attribute.String("k", "v"), []attribute.KeyValue{attribute.String("k", "v")}},
{"SetSlice",
[]attribute.KeyValue{},
attribute.StringSlice("k", []string{"v"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v"})}},
{"ReplaceWhenUnmatched",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1"})},
attribute.String("k", "v"),
[]attribute.KeyValue{attribute.String("k", "v")}},
{"AppendStringSlice",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1"})},
attribute.StringSlice("k", []string{"v2"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})}},
{"AppendBoolSlice",
[]attribute.KeyValue{attribute.BoolSlice("k", []bool{true})},
attribute.BoolSlice("k", []bool{false}),
[]attribute.KeyValue{attribute.BoolSlice("k", []bool{true, false})}},
{"AppendInt64Slice",
[]attribute.KeyValue{attribute.Int64Slice("k", []int64{1})},
attribute.Int64Slice("k", []int64{2}),
[]attribute.KeyValue{attribute.Int64Slice("k", []int64{1, 2})}},
{"AppendFloat64Slice",
[]attribute.KeyValue{attribute.Float64Slice("k", []float64{1.0})},
attribute.Float64Slice("k", []float64{2.0}),
[]attribute.KeyValue{attribute.Float64Slice("k", []float64{1.0, 2.0})}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val := &valSynced{}
val.val.Store(baggage.NewBaggage())
set(val, tt.existing)
appendTo(val, tt.set)
attributes := get(val)
assert.ElementsMatch(t, attributes, tt.expected)
})
}
}
func TestAppendAttributeUnique(t *testing.T) {
tests := []struct {
name string
existing []attribute.KeyValue
set attribute.KeyValue
expected []attribute.KeyValue
}{
{"SetString",
[]attribute.KeyValue{},
attribute.String("k", "v"),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v"})}},
{"SetSlice",
[]attribute.KeyValue{},
attribute.StringSlice("k", []string{"v"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v"})}},
{"ReplaceStringWhenUnmatched",
[]attribute.KeyValue{attribute.BoolSlice("k", []bool{true})},
attribute.String("k", "v"),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v"})}},
{"ReplaceStringSliceWhenUnmatched",
[]attribute.KeyValue{attribute.BoolSlice("k", []bool{true})},
attribute.StringSlice("k", []string{"v"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v"})}},
{"AppendStringSlice",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1"})},
attribute.StringSlice("k", []string{"v2"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})}},
{"MergeStringSlice",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})},
attribute.StringSlice("k", []string{"v2", "v3"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2", "v3"})}},
{"ExistingStringSlice",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})},
attribute.StringSlice("k", []string{"v2"}),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})}},
{"AppendString",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1"})},
attribute.String("k", "v2"),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})}},
{"MergeString",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})},
attribute.String("k", "v3"),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2", "v3"})}},
{"ExistingString",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})},
attribute.String("k", "v2"),
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1", "v2"})}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val := &valSynced{}
val.val.Store(baggage.NewBaggage())
set(val, tt.existing)
appendToUnique(val, tt.set)
attributes := get(val)
assert.ElementsMatch(t, attributes, tt.expected)
})
}
}
func TestIncrementAttribute(t *testing.T) {
tests := []struct {
name string
existing []attribute.KeyValue
set attribute.KeyValue
expected []attribute.KeyValue
}{
{"SetUnknown", []attribute.KeyValue{}, attribute.String("k", "v"), []attribute.KeyValue{attribute.String("k", "v")}},
{"Set",
[]attribute.KeyValue{},
attribute.Float64("k", 5.0),
[]attribute.KeyValue{attribute.Float64("k", 5.0)}},
{"ReplaceWhenUnmatched",
[]attribute.KeyValue{attribute.StringSlice("k", []string{"v1"})},
attribute.Float64("k", 5.0),
[]attribute.KeyValue{attribute.Float64("k", 5.0)}},
{"IncrementFloat64",
[]attribute.KeyValue{attribute.Float64("k", 5.0)},
attribute.Float64("k", 5.0),
[]attribute.KeyValue{attribute.Float64("k", 10.0)}},
{"IncrementInt64",
[]attribute.KeyValue{attribute.Int64("k", 5)},
attribute.Int64("k", 5),
[]attribute.KeyValue{attribute.Int64("k", 10)}},
{"ConcatenateString",
[]attribute.KeyValue{attribute.String("k", "v")},
attribute.String("k", "v"),
[]attribute.KeyValue{attribute.String("k", "vv")}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val := &valSynced{}
val.val.Store(baggage.NewBaggage())
set(val, tt.existing)
increment(val, tt.set)
attributes := get(val)
assert.ElementsMatch(t, attributes, tt.expected)
})
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package baggage
import (
"maps"
"slices"
"go.opentelemetry.io/otel/attribute"
)
// An immutable object safe for concurrent use.
type Baggage struct {
// Use a map to avoid duplicates as OpenTelemetry does not allow for duplicate attributes
m map[attribute.Key]attribute.Value
}
// NewBaggage creates a properly-constructed Baggage.
func NewBaggage() Baggage {
return Baggage{m: map[attribute.Key]attribute.Value{}}
}
func (mb Baggage) copy() Baggage {
copied := Baggage{m: make(map[attribute.Key]attribute.Value, len(mb.m))}
for k, v := range mb.m {
copied.m[k] = v
}
return copied
}
// Set sets the provided key value pairs, returning a new copy of Baggage.
// For any existing keys, the value is overridden.
func (mb Baggage) Set(keyValue ...attribute.KeyValue) Baggage {
if len(keyValue) == 0 {
return mb
}
copied := mb.copy()
for _, kv := range keyValue {
copied.m[kv.Key] = kv.Value
}
return copied
}
// Delete removes the provided key, returning a new copy of Baggage.
// The new Baggage copy is returned that should be used.
func (mb Baggage) Delete(key attribute.Key) Baggage {
copied := Baggage{m: make(map[attribute.Key]attribute.Value, len(mb.m))}
for k, v := range mb.m {
if k != key {
copied.m[k] = v
}
}
return copied
}
// Lookup returns the value of the given key.
// If the key does not exist, the boolean value returned is false.
func (mb Baggage) Lookup(key attribute.Key) (val attribute.Value, ok bool) {
val, ok = mb.m[key]
return
}
// Get returns the value of the given key.
// If the key does not exist, the default value is returned.
func (mb Baggage) Get(key attribute.Key) attribute.Value {
return mb.m[key]
}
// Keys returns a copy of the keys contained.
func (mb Baggage) Keys() []attribute.Key {
return slices.Collect(maps.Keys(mb.m))
}
// Attributes returns a copy of the key-value attributes contained.
func (mb Baggage) Attributes() []attribute.KeyValue {
res := make([]attribute.KeyValue, mb.Len())
i := 0
for k, v := range mb.m {
res[i] = attribute.KeyValue{Key: k, Value: v}
i++
}
return res
}
// Len returns the number of attributes contained.
func (mb Baggage) Len() int {
return len(mb.m)
}

Просмотреть файл

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package baggage
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
)
func TestBaggage(t *testing.T) {
baggage := NewBaggage()
attr := attribute.String("key1", "val1")
newBaggage := baggage.Set(attr)
assert.Equal(t, newBaggage.Get("key1").AsString(), "val1")
attributes := []attribute.KeyValue{attribute.String("key2", "val2"), attribute.Int("key3", 3)}
newBaggage = newBaggage.Set(attributes...)
assert.Equal(t, newBaggage.Len(), 3)
assert.ElementsMatch(t, newBaggage.Keys(), []attribute.Key{"key1", "key2", "key3"})
assert.ElementsMatch(t, newBaggage.Attributes(), append(attributes, attr))
assert.Equal(t, newBaggage.Get("key1").AsString(), "val1")
assert.Equal(t, newBaggage.Get("key2").AsString(), "val2")
assert.Equal(t, newBaggage.Get("key3").AsInt64(), int64(3))
newBaggage = newBaggage.Delete("key2")
assertNotFound(t, newBaggage, "key2")
assert.Equal(t, newBaggage.Len(), 2)
assert.ElementsMatch(t, newBaggage.Keys(), []attribute.Key{"key1", "key3"})
newBaggage = newBaggage.Delete("key3")
assertNotFound(t, newBaggage, "key3")
assert.Equal(t, newBaggage.Len(), 1)
assert.ElementsMatch(t, newBaggage.Keys(), []attribute.Key{"key1"})
newBaggage = newBaggage.Delete("key1")
assertNotFound(t, newBaggage, "key1")
assert.Equal(t, newBaggage.Len(), 0)
assert.ElementsMatch(t, newBaggage.Keys(), []attribute.Key{})
}
func TestBaggageMutateCreatesCopy(t *testing.T) {
empty := NewBaggage()
withKey := empty.Set(attribute.String("key1", "val1"))
assertEmptyUnchanged := func() {
assertNotFound(t, empty, "key1")
assert.Equal(t, 1, withKey.Len())
}
assertWithKeyUnchanged := func() {
assert.Equal(t, "val1", withKey.Get("key1").AsString())
assert.Equal(t, 1, withKey.Len())
}
assertWithKeyUnchanged()
assertEmptyUnchanged()
withoutKey := withKey.Delete("key1")
assertWithKeyUnchanged()
assertEmptyUnchanged()
assertNotFound(t, withoutKey, "key1")
withKeyModified := withKey.Set(attribute.String("key1", "updated1"), attribute.String("key2", "val2"))
assertWithKeyUnchanged()
assertEmptyUnchanged()
assert.Equal(t, "updated1", withKeyModified.Get("key1").AsString())
assert.Equal(t, "val2", withKeyModified.Get("key2").AsString())
assert.Equal(t, 2, withKeyModified.Len())
}
func TestBaggageWhenEmpty(t *testing.T) {
baggage := NewBaggage()
newBaggage := baggage.Delete("notFound")
assert.Equal(t, newBaggage.Len(), 0)
newBaggage = baggage.Set()
assert.Equal(t, newBaggage.Len(), 0)
}
func assertNotFound(t *testing.T, baggage Baggage, key attribute.Key) {
val, ok := baggage.Lookup(key)
assert.False(t, ok)
assert.Equal(t, attribute.Value{}, val)
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Package baggage provides an implementation of storing trace-level context data, i.e. baggage.
// Unlike OpenTelemetry's implementation of baggage, this baggage is only propagated to child spans of the current local
// process.
// Information is not propagated with any external calls.
package baggage
import (
"context"
"go.opentelemetry.io/otel/attribute"
)
type contextKey string
const baggageKey contextKey = "baggageState"
// ContextWithAttributes returns a copy of ctx with attributes set.
func ContextWithAttributes(ctx context.Context, attributes []attribute.KeyValue) context.Context {
bg := BaggageFromContext(ctx)
bg = bg.Set(attributes...)
return ContextWithBaggage(ctx, bg)
}
// ContextWithBaggage returns a copy of ctx with baggage.
func ContextWithBaggage(ctx context.Context, tv Baggage) context.Context {
return context.WithValue(ctx, baggageKey, tv.m)
}
// ContextWithoutBaggage returns a copy of ctx with no baggage.
func ContextWithoutBaggage(ctx context.Context) context.Context {
return context.WithValue(ctx, baggageKey, NewBaggage())
}
// FromContext returns the baggage contained in ctx.
func BaggageFromContext(ctx context.Context) Baggage {
state, ok := ctx.Value(baggageKey).(map[attribute.Key]attribute.Value)
if !ok || state == nil {
return NewBaggage()
}
return Baggage{m: state}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package baggage
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
)
func TestContext(t *testing.T) {
ctx := context.Background()
assert.Equal(t, NewBaggage(), BaggageFromContext(ctx))
b := NewBaggage()
b.Set(attribute.String("key1", "val1"))
ctx = ContextWithBaggage(ctx, b)
assert.Equal(t, b, BaggageFromContext(ctx))
ctx = ContextWithoutBaggage(ctx)
assert.Equal(t, NewBaggage(), BaggageFromContext(ctx))
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Package events provides definitions and functions related to the definition of telemetry events.
package events
// Command event names follow the convention cmd.<command invocation path with spaces replaced by .>.
//
// Examples:
// - cmd.auth.login
// - cmd.init
// - cmd.up
const CommandEventPrefix = "cmd."
// Prefix for vsrpc events.
const VsRpcEventPrefix = "vsrpc."
// PackBuildEvent is the name of the event which tracks the overall pack build operation.
const PackBuildEvent = "tools.pack.build"

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package events
import "strings"
// GetCommandEventName returns the event name for CLI commands.
func GetCommandEventName(cmdPath string) string {
return CommandEventPrefix + formatCommandPath(cmdPath)
}
// formatCommandPath reformats the command path suitable for telemetry emission.
//
// It removes "azd" from command path and replaces spaces with dot.
//
// Example: "azd env list" -> "env.list"
func formatCommandPath(cmdPath string) string {
cmdPath = strings.TrimPrefix(cmdPath, "azd ")
return strings.ReplaceAll(cmdPath, " ", ".")
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package events
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetCommandEventName(t *testing.T) {
type args struct {
cmdPath string
}
tests := []struct {
name string
args args
want string
}{
{"Single", args{"azd provision"}, "cmd.provision"},
{"Multiple", args{"azd env list"}, "cmd.env.list"},
{"SpecialChar", args{"azd env get-values"}, "cmd.env.get-values"},
// These cases should not happen in the actual application.
// However, we should be lenient in formatting these and not error.
{"LenientSingle", args{"provision"}, "cmd.provision"},
{"LenientMultiple", args{"env list"}, "cmd.env.list"},
{"LenientSpecialChar", args{"env get-values"}, "cmd.env.get-values"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
eventName := GetCommandEventName(tt.args.cmdPath)
assert.Equal(t, eventName, tt.want)
})
}
}

Просмотреть файл

@ -0,0 +1,44 @@
package fields
type Domain struct {
// The domain name.
Name string
// The name of the service that is responsible for the domain name.
Service string
}
// Well-known domains. Domains can also be subdomains, thus should be evaluated as such.
//
// Taken from https://learn.microsoft.com/azure/security/fundamentals/azure-domains.
var Domains = []Domain{
// Order here matters, as it likely determines evaluation precedence due to short-circuiting.
{"dev.azure.com", "azdo"},
{"management.azure.com", "arm"},
{"management.core.windows.net", "arm"},
{"graph.microsoft.com", "graph"},
{"graph.windows.net", "graph"},
{"azmk8s.io", "aks"},
{"azure-api.net", "apim"},
{"azure-mobile.net", "mobile"},
{"azurecontainerapps.io", "aca"},
{"azurecr.io", "acr"},
{"azureedge.net", "edge"},
{"azurefd.net", "frontdoor"},
{"scm.azurewebsites.net", "kudu"},
{"azurewebsites.net", "websites"},
{"blob.core.windows.net", "blob"},
{"cloudapp.azure.com", "vm"},
{"cloudapp.net", "vm"},
{"cosmos.azure.com", "cosmos"},
{"database.windows.net", "sql"},
{"documents.azure.com", "cosmos"},
{"file.core.windows.net", "files"},
{"origin.mediaservices.windows.net", "media"},
{"queue.core.windows.net", "queue"},
{"servicebus.windows.net", "servicebus"},
{"table.core.windows.net", "table"},
{"trafficmanager.net", "trafficmanager"},
{"vault.azure.net", "keyvault"},
{"visualstudio.com", "vs"},
{"vo.msecnd.net", "cdn"},
}

Просмотреть файл

@ -0,0 +1,273 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Package fields provides definitions and functions related to the definition of telemetry fields.
package fields
import (
"github.com/microsoft/ApplicationInsights-Go/appinsights/contracts"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)
// Application-level fields. Guaranteed to be set and available for all events.
const (
// Application name. Value is always "azd".
ServiceNameKey = semconv.ServiceNameKey // service.name
// Application version.
ServiceVersionKey = semconv.ServiceVersionKey // service.version
// The operating system type.
OSTypeKey = semconv.OSTypeKey // os.type
// The operating system version.
//
// Examples:
// - On Windows systems: The Windows version 10.x.x
// - On Unix-based systems: The release portion of uname. https://en.wikipedia.org/wiki/Uname#Examples
// - On MacOS: The MacOS version. For example: 12.5.1 for macOS Monterey
OSVersionKey = semconv.OSVersionKey // os.version
// The CPU architecture the host system is running on.
HostArchKey = semconv.HostArchKey // host.arch
// The version of the runtime of this process, as returned by the runtime without
// modification.
ProcessRuntimeVersionKey = semconv.ProcessRuntimeVersionKey // process.runtime.version
// A unique ID associated to the machine the application is installed on.
//
// This shares implementation with VSCode's machineId and can match exactly on a given device, although there are no
// guarantees.
MachineIdKey = attribute.Key("machine.id")
// The unique DevDeviceId associated with the device.
DevDeviceIdKey = attribute.Key("machine.devdeviceid")
// An enumeration of possible environments that the application is running on.
//
// Example: Desktop, Azure Pipelines, Visual Studio.
//
// See EnvDesktop for complete set of values.
ExecutionEnvironmentKey = attribute.Key("execution.environment")
// Installer used to install the application. Set in .installed-by.txt file
// located in the same folder as the executable.
//
// Example: "msi", "brew", "choco", "rpm", "deb"
InstalledByKey = attribute.Key("service.installer")
)
// Fields related to the experimentation platform
const (
// The assignment context as returned by the experimentation platform.
ExpAssignmentContextKey = attribute.Key("exp.assignmentContext")
)
// Context level fields. Availability depends on the command running.
const (
// Object ID of the principal.
ObjectIdKey = attribute.Key(contracts.UserAuthUserId) // user_AuthenticatedId
// Tenant ID of the principal.
TenantIdKey = attribute.Key("ad.tenant.id")
// The type of account. See AccountTypeUser for all possible options.
AccountTypeKey = attribute.Key("ad.account.type")
// Currently selected Subscription ID.
SubscriptionIdKey = attribute.Key("ad.subscription.id")
)
// Project (azure.yaml) related attributes
const (
// Hashed template ID metadata
ProjectTemplateIdKey = attribute.Key("project.template.id")
// Hashed template.version metadata
ProjectTemplateVersionKey = attribute.Key("project.template.version")
// Hashed project name. Could be used as an indicator for number of different azd projects.
ProjectNameKey = attribute.Key("project.name")
// The collection of hashed service hosts in the project.
ProjectServiceHostsKey = attribute.Key("project.service.hosts")
// The collection of service targets (resolved service hosts) in the project.
ProjectServiceTargetsKey = attribute.Key("project.service.targets")
// The collection of hashed service languages in the project.
ProjectServiceLanguagesKey = attribute.Key("project.service.languages")
// The service language being executed.
ProjectServiceLanguageKey = attribute.Key("project.service.language")
)
// Platform related attributes for integrations like devcenter / ADE
const (
PlatformTypeKey = attribute.Key("platform.type")
)
// Machine-level configuration related attribute.
const (
// Tracks what alpha features are enabled on each command
AlphaFeaturesKey = attribute.Key("config.features")
)
// Environment related attributes
const (
// Hashed environment name
EnvNameKey = attribute.Key("env.name")
)
// Command entry-point attributes
const (
// Flags set by the user. Only parsed flag names are available. Values are not recorded.
CmdFlags = attribute.Key("cmd.flags")
// Number of positional arguments set.
CmdArgsCount = attribute.Key("cmd.args.count")
// The command invocation entrypoint.
//
// The command invocation is formatted using [events.GetCommandEventName]. This makes it consistent with how
// commands are represented in telemetry.
CmdEntry = attribute.Key("cmd.entry")
)
// All possible enumerations of ExecutionEnvironmentKey
//
// Environments are mutually exclusive. Modifiers can be set additionally to signal different types of usages.
// An execution environment is formatted as follows:
// `<environment>[;<modifier1>;<modifier2>...]`
const (
// A desktop environment. The user is directly interacting with azd via a terminal.
EnvDesktop = "Desktop"
// Environments that are wrapped by an intermediate calling program, and are significant enough to warrant
// being an environment and not an environment modifier.
EnvVisualStudio = "Visual Studio"
EnvVisualStudioCode = "Visual Studio Code"
EnvCloudShell = "Azure CloudShell"
// Continuous Integration environments
EnvUnknownCI = "UnknownCI"
EnvAzurePipelines = "Azure Pipelines"
EnvGitHubActions = "GitHub Actions"
EnvAppVeyor = "AppVeyor"
EnvBamboo = "Bamboo"
EnvBitBucketPipelines = "BitBucket Pipelines"
EnvTravisCI = "Travis CI"
EnvCircleCI = "Circle CI"
EnvGitLabCI = "GitLab CI"
EnvJenkins = "Jenkins"
EnvAwsCodeBuild = "AWS CodeBuild"
EnvGoogleCloudBuild = "Google Cloud Build"
EnvTeamCity = "TeamCity"
EnvJetBrainsSpace = "JetBrains Space"
EnvCodespaces = "GitHub Codespaces"
// Environment modifiers. These are not environments themselves, but rather modifiers to the environment
// that signal specific types of usages.
EnvModifierAzureSpace = "Azure App Spaces Portal"
)
// All possible enumerations of AccountTypeKey
const (
// A user.
AccountTypeUser = "User"
// A service principal, typically an application.
AccountTypeServicePrincipal = "Service Principal"
)
// The value used for ServiceNameKey
const ServiceNameAzd = "azd"
// Error related fields
const (
// Error code that describes an error.
ErrCode = attribute.Key("error.code")
// Inner error.
ErrInner = attribute.Key("error.inner")
// The frame of the error.
ErrFrame = attribute.Key("error.frame")
)
// Service related fields.
const (
// Hostname of the service.
// The list of allowed values can be found in [Domains].
ServiceHost = attribute.Key("service.host")
// Name of the service.
ServiceName = attribute.Key("service.name")
// Status code of a response returned by the service.
// For HTTP, this corresponds to the HTTP status code.
ServiceStatusCode = attribute.Key("service.statusCode")
// Method of a request to the service.
// For HTTP, this corresponds to the HTTP method of the request made.
ServiceMethod = attribute.Key("service.method")
// An error code returned by the service in a response.
// For HTTP, the error code can be found in the response header or body.
ServiceErrorCode = attribute.Key("service.errorCode")
// Correlation ID for a request to the service.
ServiceCorrelationId = attribute.Key("service.correlationId")
)
// Tool related fields
const (
// The name of the tool.
ToolName = attribute.Key("tool.name")
// The exit code of the tool after invocation.
ToolExitCode = attribute.Key("tool.exitCode")
)
// Performance related fields
const (
// The time spent waiting on user interaction in milliseconds.
PerfInteractTime = attribute.Key("perf.interact_time")
)
// Pack related fields
const (
// The builder image used. Hashed when a user-defined image is used.
PackBuilderImage = attribute.Key("pack.builder.image")
// The tag of the builder image used. Hashed when a user-defined image is used.
PackBuilderTag = attribute.Key("pack.builder.tag")
)
// Initialization from app related fields
const (
InitMethod = attribute.Key("init.method")
AppInitDetectedDatabase = attribute.Key("appinit.detected.databases")
AppInitDetectedServices = attribute.Key("appinit.detected.services")
AppInitConfirmedDatabases = attribute.Key("appinit.confirmed.databases")
AppInitConfirmedServices = attribute.Key("appinit.confirmed.services")
AppInitModifyAddCount = attribute.Key("appinit.modify_add.count")
AppInitModifyRemoveCount = attribute.Key("appinit.modify_remove.count")
// The last step recorded during the app init process.
AppInitLastStep = attribute.Key("appinit.lastStep")
)
// Remote docker build related fields
const (
RemoteBuildCount = attribute.Key("container.remoteBuild.count")
)
// JSON-RPC related fields
const (
// Logical name of the method from the RPC interface
// perspective, which can be different from the name of any implementing
// method/function. See semconv.RPCMethodKey.
RpcMethod = semconv.RPCMethodKey
// `id` property of JSON-RPC request or response.
JsonRpcId = semconv.RPCJsonrpcRequestIDKey
// `error_code` property of JSON-RPC request or response. Type: int.
JsonRpcErrorCode = semconv.RPCJsonrpcErrorCodeKey
)

Просмотреть файл

@ -0,0 +1,48 @@
package fields
import (
"crypto/sha256"
"encoding/hex"
"strings"
"go.opentelemetry.io/otel/attribute"
)
func StringHashed(k attribute.Key, v string) attribute.KeyValue {
return attribute.KeyValue{
Key: attribute.Key(k),
Value: attribute.StringValue(CaseInsensitiveHash(v)),
}
}
func StringSliceHashed(k attribute.Key, v []string) attribute.KeyValue {
val := CaseInsensitiveSliceHash(v)
return attribute.KeyValue{
Key: attribute.Key(k),
Value: attribute.StringSliceValue(val),
}
}
func CaseInsensitiveSliceHash(value []string) []string {
hashed := make([]string, len(value))
for i := range value {
hashed[i] = CaseInsensitiveHash(value[i])
}
return hashed
}
func CaseInsensitiveHash(value string) string {
return Sha256Hash(strings.ToLower(value))
}
// Sha256Hash returns the hex-encoded Sha256 hash of the given string.
func Sha256Hash(val string) string {
sha := sha256.Sum256([]byte(val))
hash := hex.EncodeToString(sha[:])
return hash
}
// ErrorKey returns a new Key with "error." prefix appended.
func ErrorKey(k attribute.Key) attribute.Key {
return attribute.Key("error." + string(k))
}

Просмотреть файл

@ -0,0 +1,84 @@
package resource
import (
"os"
"strings"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
)
// Rules that apply when the specified environment variable is set to "true" (case-insensitive)
var ciVarBoolRules = []struct {
envVar string
environment string
}{
// Azure Pipelines -
// https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables#system-variables-devops-servicesQ
{"TF_BUILD", fields.EnvAzurePipelines},
// GitHub Actions,
// https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
{"GITHUB_ACTIONS", fields.EnvGitHubActions},
// AppVeyor - https://www.appveyor.com/docs/environment-variables/
{"APPVEYOR", fields.EnvAppVeyor},
// Travis CI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
{"TRAVIS", fields.EnvTravisCI},
// Circle CI - https://circleci.com/docs/env-vars#built-in-environment-variables
{"CIRCLECI", fields.EnvCircleCI},
// GitLab CI
{"GITLAB_CI", fields.EnvGitLabCI},
}
// Rules that apply when the specified environment variable is set to any value
var ciVarSetRules = []struct {
envVar string
environment string
}{
// AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
{"CODEBUILD_BUILD_ID", fields.EnvAwsCodeBuild},
//nolint:lll
// Jenkins -
// https://github.com/jenkinsci/jenkins/blob/master/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.groovy
{"JENKINS_URL", fields.EnvJenkins},
//nolint:lll
// TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
{"TEAMCITY_VERSION", fields.EnvTeamCity},
//nolint:lll
// JetBrains Space -
// https://www.jetbrains.com/help/space/automation-environment-variables.html#when-does-automation-resolve-its-environment-variables
{"JB_SPACE_API_URL", fields.EnvJetBrainsSpace},
// Bamboo -
// https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html#Bamboovariables-Build-specificvariables
{"bamboo.buildKey", fields.EnvBamboo},
// BitBucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/
{"BITBUCKET_BUILD_NUMBER", fields.EnvBitBucketPipelines},
// Unknown CI cases
{"CI", fields.EnvUnknownCI},
{"BUILD_ID", fields.EnvUnknownCI},
}
// getExecutionEnvironmentForHosted detects the execution environment for CI/CD providers and returns the corresponding
// named environment.
//
// Returns an empty string if no CI/CD provider is detected.
func execEnvForCi() string {
for _, rule := range ciVarBoolRules {
// Some CI providers specify 'True' on Windows vs 'true' on Linux, while others use `True` always
// Thus, it's better to err on the side of being generous and be case-insensitive
if strings.ToLower(os.Getenv(rule.envVar)) == "true" {
return rule.environment
}
}
for _, rule := range ciVarSetRules {
if _, ok := os.LookupEnv(rule.envVar); ok {
return rule.environment
}
}
return ""
}
// IsRunningOnCI returns true if the current process is running on a CI/CD provider.
func IsRunningOnCI() bool {
return execEnvForCi() != ""
}

Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package resource
import (
"os"
"strings"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/runcontext"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
)
func getExecutionEnvironment() string {
// calling programs receive the highest priority, since they end up wrapping the CLI and are the most
// inner layers.
env := execEnvFromCaller()
if env == "" {
// machine-level execution environments
env = execEnvForHosts()
}
if env == "" {
// machine-level CI execution environments
env = execEnvForCi()
}
// no special execution environment found, default to plain desktop
if env == "" {
env = fields.EnvDesktop
}
// global modifiers that are applicable to all environments
modifiers := execEnvModifiers()
return strings.Join(append([]string{env}, modifiers...), ";")
}
func execEnvFromCaller() string {
userAgent := os.Getenv(internal.AzdUserAgentEnvVar)
if strings.Contains(userAgent, internal.VsCodeAgentPrefix) {
return fields.EnvVisualStudioCode
}
if strings.Contains(userAgent, internal.VsAgentPrefix) {
return fields.EnvVisualStudio
}
return ""
}
func execEnvForHosts() string {
if _, ok := os.LookupEnv(runcontext.AzdInCloudShellEnvVar); ok {
return fields.EnvCloudShell
}
// GitHub Codespaces
// https://docs.github.com/en/codespaces/developing-in-codespaces/default-environment-variables-for-your-codespacei
if _, ok := os.LookupEnv("CODESPACES"); ok {
return fields.EnvCodespaces
}
return ""
}
func execEnvModifiers() []string {
modifiers := []string{}
userAgent := os.Getenv(internal.AzdUserAgentEnvVar)
if strings.Contains(userAgent, "azure_app_space_portal") {
modifiers = append(modifiers, fields.EnvModifierAzureSpace)
}
return modifiers
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package resource
import (
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/installer"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
)
// Returns a hash of the content of `.installed-by.txt` file in the same directory as
// the executable. If the file does not exist, returns empty string.
func getInstalledBy() string {
return fields.Sha256Hash(installer.RawInstalledBy())
}

Просмотреть файл

@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package resource
import (
"log"
"net"
"os"
"path/filepath"
deviceid "github.com/microsoft/go-deviceid"
"github.com/azure/azure-dev/cli/sdk/azdcore/common/permissions"
"github.com/azure/azure-dev/cli/sdk/azdcore/config"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
"github.com/google/uuid"
)
const machineIdCacheFileName = "machine-id.cache"
var invalidMacAddresses = map[string]struct{}{
"00:00:00:00:00:00": {},
"ff:ff:ff:ff:ff:ff": {},
"ac:de:48:00:11:22": {},
}
// DevDeviceId returns the unique device ID for the machine.
func DevDeviceId() string {
deviceId, err := deviceid.Get()
if err != nil {
log.Println("could not get device id, returning empty: ", err)
return ""
}
return deviceId
}
// MachineId returns a unique ID for the machine.
func MachineId() string {
// We store the machine ID on the filesystem not due to performance,
// but to increase the stability of the ID to be constant across factors like changing mac addresses, NICs.
return loadOrCalculate(calculateMachineId, machineIdCacheFileName)
}
func calculateMachineId() string {
mac, ok := getMacAddress()
if ok {
return fields.Sha256Hash(mac)
} else {
// No valid mac address, return a GUID instead.
return uuid.NewString()
}
}
func loadOrCalculate(calc func() string, cacheFileName string) string {
configDir, err := config.GetUserConfigDir()
if err != nil {
log.Printf("could not load machineId from cache. returning calculated value: %s", err)
return calc()
}
cacheFile := filepath.Join(configDir, cacheFileName)
bytes, err := os.ReadFile(configDir)
if err == nil {
return string(bytes)
}
err = os.WriteFile(cacheFile, []byte(calc()), permissions.PermissionFile)
if err != nil {
log.Printf("could not write machineId to cache. returning calculated value: %s", err)
}
return calc()
}
func getMacAddress() (string, bool) {
interfaces, _ := net.Interfaces()
for _, ift := range interfaces {
if len(ift.HardwareAddr) > 0 && ift.Flags&net.FlagLoopback == 0 {
hwAddr, err := net.ParseMAC(ift.HardwareAddr.String())
if err != nil {
continue
}
ipAddr, _ := ift.Addrs()
if len(ipAddr) == 0 || ift.Flags&net.FlagUp == 0 {
continue
}
mac := hwAddr.String()
if isValidMacAddress(mac) {
return mac, true
}
}
}
return "", false
}
func isValidMacAddress(addr string) bool {
_, invalidAddr := invalidMacAddresses[addr]
return !invalidAddr
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Package resource provides application-level resource attributes for telemetry purposes.
package resource
import (
"fmt"
"runtime"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/osversion"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)
// New creates a resource with all application-level fields populated.
func New() *resource.Resource {
r, err := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
fields.ServiceNameKey.String(fields.ServiceNameAzd),
fields.ServiceVersionKey.String(internal.VersionInfo().Version.String()),
fields.OSTypeKey.String(runtime.GOOS),
fields.OSVersionKey.String(getOsVersion()),
fields.HostArchKey.String(runtime.GOARCH),
fields.ProcessRuntimeVersionKey.String(runtime.Version()),
fields.ExecutionEnvironmentKey.String(getExecutionEnvironment()),
fields.MachineIdKey.String(MachineId()),
fields.InstalledByKey.String(getInstalledBy()),
fields.DevDeviceIdKey.String(DevDeviceId()),
),
)
// One possible reason this might fail is if there's a mismatch between the semconv.SchemaURL and the schema used
// by resource.Default(). This can happen if we upgrade our open telemetry package version but don't update the import
// path of `semconv` above to point to the correct version. Instead of returning an empty resource without any attributes
// just fail eagerly.
if err != nil {
panic(fmt.Sprintf("failed to create resource: %v", err))
}
return r
}
func getOsVersion() string {
ver, err := osversion.GetVersion()
if err != nil {
return "Unknown"
}
return ver
}

Просмотреть файл

@ -0,0 +1,151 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package tracing
import (
"context"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/baggage"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// It is often valuable to extend functionality of 3rd-party library types.
// Therefore, we provide extension points for OpenTelemetry tracers here.
// Tracer is the creator of Spans.
//
// This is simply trace.Tracer except returning our Span instead of trace.Span.
type Tracer interface {
Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span)
}
// Wrapper around trace.Tracer.
type wrapperTracer struct {
tracer trace.Tracer
}
func (w *wrapperTracer) Start(
ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span) {
ctx, span := w.tracer.Start(ctx, spanName, opts...)
// Propagate any baggage in the current context
baggage := baggage.BaggageFromContext(ctx)
span.SetAttributes(baggage.Attributes()...)
return ctx, &wrapperSpan{span}
}
// redefinedSpan is a slightly modified version of trace.Span.
//
// The only change made is to remove functionality around emitting events.
// Events are nested telemetry events that can be fired from a Span.
// We currently do not support this yet (no use case), but this is likely to be changed in the future.
//
// Exact modifications to trace.Span:
// - Removed AddEvent
// - Removed RecordError. This creates an error type event
type redefinedSpan interface {
// End completes the Span. The Span is considered complete and ready to be
// delivered through the rest of the telemetry pipeline after this method
// is called. Therefore, updates to the Span are not allowed after this
// method has been called.
End(options ...trace.SpanEndOption)
// IsRecording returns the recording state of the Span. It will return
// true if the Span is active and events can be recorded.
IsRecording() bool
// SpanContext returns the SpanContext of the Span. The returned SpanContext
// is usable even after the End method has been called for the Span.
SpanContext() trace.SpanContext
// SetStatus sets the status of the Span in the form of a code and a
// description, overriding previous values set. The description is only
// included in a status when the code is for an error.
SetStatus(code codes.Code, description string)
// SetName sets the Span name.
SetName(name string)
// SetAttributes sets kv as attributes of the Span. If a key from kv
// already exists for an attribute of the Span it will be overwritten with
// the value contained in kv.
SetAttributes(kv ...attribute.KeyValue)
// TracerProvider returns a TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
TracerProvider() trace.TracerProvider
}
// Span is the individual component of a trace. It represents a single named
// and timed operation of a workflow that is traced. A Tracer is used to
// create a Span and it is then up to the operation the Span represents to
// properly end the Span when the operation itself ends.
type Span interface {
redefinedSpan
// EndWithStatus calls End, but also sets Status based on the value of err (nil mean success).
EndWithStatus(err error, options ...trace.SpanEndOption)
}
type wrapperSpan struct {
span trace.Span
}
// End completes the Span. The Span is considered complete and ready to be
// delivered through the rest of the telemetry pipeline after this method
// is called. Therefore, updates to the Span are not allowed after this
// method has been called.
func (s *wrapperSpan) End(options ...trace.SpanEndOption) {
s.span.SetAttributes(GetGlobalAttributes()...)
s.span.End(options...)
}
func (s *wrapperSpan) EndWithStatus(err error, options ...trace.SpanEndOption) {
if err != nil {
s.span.SetStatus(codes.Error, "UnknownError")
} else {
s.span.SetStatus(codes.Ok, "")
}
s.End(options...)
}
// IsRecording returns the recording state of the Span. It will return
// true if the Span is active and events can be recorded.
func (s *wrapperSpan) IsRecording() bool {
return s.span.IsRecording()
}
// SpanContext returns the SpanContext of the Span. The returned SpanContext
// is usable even after the End method has been called for the Span.
func (s *wrapperSpan) SpanContext() trace.SpanContext {
return s.span.SpanContext()
}
// SetStatus sets the status of the Span in the form of a code and a
// description, overriding previous values set. The description is only
// included in a status when the code is for an error.
func (s *wrapperSpan) SetStatus(code codes.Code, description string) {
s.span.SetStatus(code, description)
}
// SetName sets the Span name.
func (s *wrapperSpan) SetName(name string) {
s.span.SetName(name)
}
// SetAttributes sets kv as attributes of the Span. If a key from kv
// already exists for an attribute of the Span it will be overwritten with
// the value contained in kv.
func (s *wrapperSpan) SetAttributes(kv ...attribute.KeyValue) {
s.span.SetAttributes(kv...)
}
// TracerProvider returns a TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
func (s *wrapperSpan) TracerProvider() trace.TracerProvider {
return s.span.TracerProvider()
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package tracing
import (
"context"
"github.com/azure/azure-dev/cli/sdk/azdcore/internal/tracing/fields"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
var tracer = &wrapperTracer{otel.Tracer(fields.ServiceNameAzd)}
// Start creates a span and a context.Context containing the newly-created span.
//
// If the context.Context provided in `ctx` contains a Span then the newly-created
// Span will be a child of that span, otherwise it will be a root span. This behavior
// can be overridden by providing `WithNewRoot()` as a SpanOption, causing the
// newly-created Span to be a root span even if `ctx` contains a Span.
//
// When creating a Span it is recommended to provide all known span attributes using
// the `WithAttributes()` SpanOption as samplers will only have access to the
// attributes provided when a Span is created.
func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, Span) {
return tracer.Start(ctx, name, opts...)
}

Просмотреть файл

@ -0,0 +1,47 @@
package internal
import (
"fmt"
"os"
"runtime"
"strings"
)
// Environment variable that identifies a user agent calling into azd.
// Any caller of azd can set this variable to identify themselves.
const AzdUserAgentEnvVar = "AZURE_DEV_USER_AGENT"
// Well-known user agents prefixes.
const (
VsCodeAgentPrefix = "vscode:/extensions/ms-azuretools.azure-dev"
// cspell: disable-next-line
VsAgentPrefix = "vside:/webtools/azdev.publish"
)
// UserAgent() creates the user agent string for azd.
//
// Examples:
// - azdev/1.0.0 (Go 1.18; windows/amd64)
// - azdev/1.0.0 (Go 1.18; windows/amd64) azd-caller/1.0.0
func UserAgent() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("azdev/%s", VersionInfo().Version.String()))
sb.WriteString(" ")
sb.WriteString(runtimeInfo())
callerAgent := os.Getenv(AzdUserAgentEnvVar)
if callerAgent != "" {
sb.WriteString(" ")
sb.WriteString(callerAgent)
}
if strings.ToLower(os.Getenv("GITHUB_ACTIONS")) == "true" {
sb.WriteString(" ")
sb.WriteString("GhActions")
}
return sb.String()
}
func runtimeInfo() string {
return fmt.Sprintf("(Go %s; %s/%s)", runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

Просмотреть файл

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package internal
import (
"regexp"
"strings"
"github.com/blang/semver/v4"
)
// devVersionString is the default version that is used when [Version] is not overridden at build time, i.e.
// a developer building locally using `go install`.
const devVersionString = "0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)"
// The version string, as printed by `azd version`.
//
// This MUST be of the form "<semver> (commit <full commit hash>)"
//
// The default value here is used for a version built directly by a developer when running either
// `go install` or `go build` without overriding the value at link time (the default behavior when
// build or install are run without arguments).
//
// Official builds set this value based on the version and commit we are building, using `-ldflags`
//
// Example:
//
// -ldflags="-X 'github.com/azure/azure-dev/cli/sdk/azdcore/internal.Version=0.0.1-alpha.1 (commit 8a49ae5ae9ab13beeade35f91ad4b4611c2f5574)'"
//
// This value is exported and not const so it can be mutated by certain tests. Instead of accessing this member
// directly, use [VersionInfo] which returns a structured version of this value.
//
// nolint: lll
var Version = devVersionString
func init() {
// VersionInfo panics if the version string is malformed, run the code at package startup to
// ensure everything is okay. This allows the rest of the system to call VersionInfo() to get
// parsed version information without having to worry about error handling.
_ = VersionInfo()
}
type AzdVersionInfo struct {
Version semver.Version
Commit string
}
func IsDevVersion() bool {
return Version == devVersionString
}
func IsNonProdVersion() bool {
if IsDevVersion() {
return true
}
// This currently relies on checking for specific internal release tags.
// This can be improved to instead check for any presence of prerelease versioning
// once the product is GA.
return strings.Contains(VersionInfo().Version.String(), "pr")
}
var versionStringRegexp = regexp.MustCompile(`^(\S+) \(commit ([0-9a-f]{40})\)$`)
func VersionInfo() AzdVersionInfo {
matches := versionStringRegexp.FindStringSubmatch(Version)
if len(matches) != 3 {
panic("azd version is malformed, ensure github.com/azure/azure-dev/cli/sdk/azdcore/internal.Version is correct")
}
return AzdVersionInfo{
Version: semver.MustParse(matches[1]),
Commit: matches[2],
}
}

Просмотреть файл

@ -0,0 +1,175 @@
package aspire
import (
"github.com/azure/azure-dev/cli/sdk/azdcore/common"
"github.com/psanford/memfs"
)
type Manifest struct {
Schema string `json:"$schema"`
Resources map[string]*Resource `json:"resources"`
// BicepFiles holds any bicep files generated by Aspire next to the manifest file.
BicepFiles *memfs.FS `json:"-"`
}
type Resource struct {
// Type is present on all resource types
Type string `json:"type"`
// Path is present on a project.v0 resource and is the path to the project file, and on a dockerfile.v0
// resource and is the path to the Dockerfile (including the "Dockerfile" filename).
// For a bicep.v0 resource, it is the path to the bicep file.
Path *string `json:"path,omitempty"`
// Context is present on a dockerfile.v0 resource and is the path to the context directory.
Context *string `json:"context,omitempty"`
// BuildArgs is present on a dockerfile.v0 resource and is the --build-arg for building the docker image.
BuildArgs map[string]string `json:"buildArgs,omitempty"`
// Args is optionally present on project.v0 and dockerfile.v0 resources and are the arguments to pass to the container.
Args []string `json:"args,omitempty"`
// Parent is present on a resource which is a child of another. It is the name of the parent resource. For example, a
// postgres.database.v0 is a child of a postgres.server.v0, and so it would have a parent of which is the name of
// the server resource.
Parent *string `json:"parent,omitempty"`
// Image is present on a container.v0 resource and is the image to use for the container.
Image *string `json:"image,omitempty"`
// Bindings is present on container.v0, project.v0 and dockerfile.v0 resources, and is a map of binding names to
// binding details.
Bindings common.WithOrder[Binding] `json:"bindings,omitempty"`
// Env is present on project.v0, container.v0 and dockerfile.v0 resources, and is a map of environment variable
// names to value expressions. The value expressions are simple expressions like "{redis.connectionString}" or
// "{postgres.port}" to allow referencing properties of other resources. The set of properties supported in these
// expressions depends on the type of resource you are referencing.
Env map[string]string `json:"env,omitempty"`
// Queues is optionally present on a azure.servicebus.v0 resource, and is a list of queue names to create.
Queues *[]string `json:"queues,omitempty"`
// Topics is optionally present on a azure.servicebus.v0 resource, and is a list of topic names to create.
Topics *[]string `json:"topics,omitempty"`
// Some resources just represent connections to existing resources that need not be provisioned. These resources have
// a "connectionString" property which is the connection string that should be used during binding.
ConnectionString *string `json:"connectionString,omitempty"`
// Dapr is present on dapr.v0 resources.
Dapr *DaprResourceMetadata `json:"dapr,omitempty"`
// DaprComponent is present on dapr.component.v0 resources.
DaprComponent *DaprComponentResourceMetadata `json:"daprComponent,omitempty"`
// Inputs is present on resources that need inputs from during the provisioning process (e.g asking for an API key, or
// a password for a database).
Inputs map[string]Input `json:"inputs,omitempty"`
// For a bicep.v0 resource, defines the input parameters for the bicep file.
Params map[string]any `json:"params,omitempty"`
// parameter.v0 uses value field to define the value of the parameter.
Value string
// container.v0 uses volumes field to define the volumes of the container.
Volumes []*Volume `json:"volumes,omitempty"`
// The entrypoint to use for the container image when executed.
Entrypoint string `json:"entrypoint,omitempty"`
// An object that captures properties that control the building of a container image.
Build *ContainerV1Build `json:"build,omitempty"`
// container.v0 uses bind mounts field to define the volumes with initial data of the container.
BindMounts []*BindMount `json:"bindMounts,omitempty"`
}
type ContainerV1Build struct {
// The path to the context directory for the container build.
// Can be relative of absolute. If relative it is relative to the location of the manifest file.
Context string `json:"context"`
// The path to the Dockerfile. Can be relative or absolute. If relative it is relative to the manifest file.
Dockerfile string `json:"dockerfile"`
// Args is optionally present on project.v0 and dockerfile.v0 resources and are the arguments to pass to the container.
Args map[string]string `json:"args,omitempty"`
// A list of build arguments which are used during container build."
Secrets map[string]ContainerV1BuildSecrets `json:"secrets,omitempty"`
}
type ContainerV1BuildSecrets struct {
// "env" (will come with value) or "file" (will come with source).
Type string `json:"type"`
// If provided use as the value for the environment variable when docker build is run.
Value *string `json:"value,omitempty"`
// Path to secret file. If relative, the path is relative to the manifest file.
Source *string `json:"source,omitempty"`
}
type DaprResourceMetadata struct {
AppId *string `json:"appId,omitempty"`
Application *string `json:"application,omitempty"`
AppPort *int `json:"appPort,omitempty"`
AppProtocol *string `json:"appProtocol,omitempty"`
DaprHttpMaxRequestSize *int `json:"daprHttpMaxRequestSize,omitempty"`
DaprHttpReadBufferSize *int `json:"daprHttpReadBufferSize,omitempty"`
EnableApiLogging *bool `json:"enableApiLogging,omitempty"`
LogLevel *string `json:"logLevel,omitempty"`
}
type DaprComponentResourceMetadata struct {
Type *string `json:"type"`
}
type Reference struct {
Bindings []string `json:"bindings,omitempty"`
}
type Binding struct {
TargetPort *int `json:"targetPort,omitempty"`
Port *int `json:"port,omitempty"`
Scheme string `json:"scheme"`
Protocol string `json:"protocol"`
Transport string `json:"transport"`
External bool `json:"external"`
}
type Volume struct {
Name string `json:"name,omitempty"`
Target string `json:"target"`
ReadOnly bool `json:"readOnly"`
}
type BindMount struct {
Name string `json:"-"`
Source string `json:"source,omitempty"`
Target string `json:"target"`
ReadOnly bool `json:"readOnly"`
}
type Input struct {
Type string `json:"type"`
Secret bool `json:"secret"`
Default *InputDefault `json:"default,omitempty"`
}
type InputDefaultGenerate struct {
MinLength *uint `json:"minLength,omitempty"`
Lower *bool `json:"lower,omitempty"`
Upper *bool `json:"upper,omitempty"`
Numeric *bool `json:"numeric,omitempty"`
Special *bool `json:"special,omitempty"`
MinLower *uint `json:"minLower,omitempty"`
MinUpper *uint `json:"minUpper,omitempty"`
MinNumeric *uint `json:"minNumeric,omitempty"`
MinSpecial *uint `json:"minSpecial,omitempty"`
}
type InputDefault struct {
Generate *InputDefaultGenerate `json:"generate,omitempty"`
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше