diff --git a/Makefile b/Makefile index d1994aa..7d6093c 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ terraform-mr-up: run: # AZURE_TENANT_ID=$${TENANT_ID} AZURE_CLIENT_ID=$${CLIENT_ID} AZURE_CLIENT_SECRET=$${CLIENT_SECRET} \ go run ./src \ + reconcile \ --debug \ --resource-group-name $${RG_NAME} \ --own-resource-group-name $${OWN_RG_NAME} \ @@ -66,13 +67,12 @@ run: --managed-environment-id $${ME_ID} \ --key-vault-name $${KV_NAME} \ --location westeurope \ - --dapr-topic-name $${DAPR_TOPIC} \ - --reconcile-interval "10s" \ --git-url $${GIT_URL_AND_CREDS} \ --git-branch "main" \ --git-yaml-path "yaml/" \ --notifications-enabled \ - --environment $${ENV} + --environment $${ENV} \ + --cosmosdb-account $${CDB_ACCOUNT} .PHONY: docker-build docker-build: @@ -81,6 +81,7 @@ docker-build: .PHONY: docker-run docker-run: docker-build docker run -it --rm -e AZURE_TENANT_ID=$${TENANT_ID} -e AZURE_CLIENT_ID=$${CLIENT_ID} -e AZURE_CLIENT_SECRET=$${CLIENT_SECRET} $(IMG) \ + reconcile \ --debug \ --resource-group-name $${RG_NAME} \ --own-resource-group-name $${OWN_RG_NAME} \ @@ -88,13 +89,13 @@ docker-run: docker-build --managed-environment-id $${ME_ID} \ --key-vault-name $${KV_NAME} \ --location westeurope \ - --dapr-topic-name $${DAPR_TOPIC} \ --reconcile-interval "10s" \ --git-url $${GIT_URL_AND_CREDS} \ --git-branch "main" \ --git-yaml-path "yaml/" \ --notifications-enabled \ - --environment $${ENV} + --environment $${ENV} \ + --cosmosdb-account $${CDB_ACCOUNT} .PHONY: k6-http-get k6-http-get: diff --git a/README.md b/README.md index b545d39..857da7e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,17 @@ It's [GitOps](https://opengitops.dev/#principles) for [Azure Container Apps](htt Below, large (and eventually breaking) will be documented: +### v0.0.19 + +Refactored `azcagit` to run as an [Azure Container App Job](https://github.com/XenitAB/azcagit/pull/57) on a schedule. Lots of breaking changes. + +**BREAKING CHANGES** + +- trigger-client cli parameter: `--namespace` instead of `--fully-qualified-namespace` (note: don't use the full name anymore) +- trigger-client cli parameter: `--queue` instead of `--topic` +- CosmosDB is used for cache +- Service Bus is now basic + ### v0.0.18 Support for `AzureContainerJob` was added. @@ -220,12 +231,12 @@ The easiest way to test it is using the terraform code which you can find in `te ### Manually trigger reconcile -If you have used the example terraform, there will be a service bus created with a topic and subscription. `azcagit` has subscribed to in through Darp and when it receives a message on it, it will trigger a reconcile. +If you have used the example terraform, there will be a service bus created with a queue. `azcagit-trigger` will start and then trigger `azcagit-reconcile` when a message is received on the queue. You can use `azcagit-trigger-client` to trigger it: ```go -go run ./trigger-client -n example.servicebus.windows.net -t azcagit_trigger +go run ./trigger-client -n namespace -q queue ``` Please note that this requires you to be authenticated with either the Azure CLI and have access to publish to this topic with your current user, or use environment varaibles with a service principal that has access. diff --git a/go.mod b/go.mod index a575bc9..306666f 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,20 @@ module github.com/xenitab/azcagit go 1.20 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2 v2.0.0-beta.3 github.com/alexflint/go-arg v1.4.3 - github.com/dapr/go-sdk v1.8.0 github.com/fluxcd/pkg/git v0.12.3 github.com/fluxcd/pkg/git/gogit v0.12.1 github.com/fluxcd/pkg/gittestserver v0.8.4 github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.4 github.com/google/go-github/v41 v41.0.0 + github.com/hairyhenderson/go-fsimpl v0.0.0-20230722184334-4c242a8cf7b7 github.com/hashicorp/go-multierror v1.1.1 github.com/invopop/jsonschema v0.7.0 github.com/invopop/yaml v0.2.0 @@ -24,62 +25,101 @@ require ( github.com/whilp/git-urls v1.0.0 go.uber.org/zap v1.24.0 golang.org/x/oauth2 v0.10.0 - golang.org/x/sync v0.3.0 sigs.k8s.io/yaml v1.3.0 ) require ( + cloud.google.com/go v0.110.6 // indirect + cloud.google.com/go/compute v1.22.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/storage v1.31.0 // indirect + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect github.com/Azure/go-amqp v1.0.1 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/alexflint/go-scalar v1.2.0 // indirect + github.com/aws/aws-sdk-go v1.44.306 // indirect + github.com/aws/aws-sdk-go-v2 v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect + github.com/aws/smithy-go v1.13.5 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fluxcd/gitkit v0.6.0 // indirect github.com/fluxcd/pkg/ssh v0.8.0 // indirect github.com/fluxcd/pkg/version v0.2.2 // indirect - github.com/go-chi/chi/v5 v5.0.8 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-git/go-git/v5 v5.7.0 // indirect + github.com/go-git/go-git/v5 v5.8.0 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/google/wire v0.5.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.15.14 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/skeema/knownhosts v1.1.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect + gocloud.dev v0.32.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/tools v0.11.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.132.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect google.golang.org/grpc v1.56.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 3040a16..849c06c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,24 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= +cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y= +cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/pubsub v1.32.0 h1:JOEkgEYBuUTHSyHS4TcqOFuWr+vD6qO/imsFqShUCp4= +cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= +cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5 h1:qS0Bp4do0cIvnuQgSGeO6ZCu/q/HlRKl4NPfv1eJ2p0= +github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.5/go.mod h1:Beh5cHIXJ0oWEDWk9lNFtuklCojLLQ5hl+LqSNTTs0I= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw= @@ -12,10 +29,18 @@ github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.4.0 h1:MxbPJrYY8 github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.4.0/go.mod h1:pXDkeh10bAqElvd+S5Ppncj+DCKvJGXNa8rRT2R7rIw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2 v2.0.0-beta.3 h1:+7Bi8HE+y00rK1fA9u8LuNkTr+21mkdrPn9HRzcC4+U= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2 v2.0.0-beta.3/go.mod h1:BfVverXAuvcu/qsWG1lwUn9iz6Kz+7QC748YRXZjOXQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= github.com/Azure/go-amqp v1.0.1 h1:Jf8OQCKzRDMZ3pCiH4onM7yrhl5curkRSGkRLTyP35o= github.com/Azure/go-amqp v1.0.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -31,25 +56,81 @@ github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oy github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/aws/aws-sdk-go v1.44.306 h1:H487V/1N09BDxeGR7oR+LloC2uUpmf4atmqJaBgQOIs= +github.com/aws/aws-sdk-go v1.44.306/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k= +github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= +github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw= +github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72 h1:m0MmP89v1B0t3b8W8rtATU76KNsodak69QtiokHyEvo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72/go.mod h1:ylOTxIuoTL+XjH46Omv2iPjHdeGUk3SQ4hxYho4EHMA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 h1:cZG7psLfqpkB6H+fIrgUDWmlzM474St1LP0jcz272yI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27/go.mod h1:ZdjYvJpDlefgh8/hWelJhqgqJeodxu4SmbVsSdBlL7E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 h1:Bje8Xkh2OWpjBdNfXLrnn8eZg569dUQmhgtydxAYyP0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30/go.mod h1:qQtIBl5OVMfmeQkz8HaVyh5DzFmmFXyvK27UgIgOr4c= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 h1:hx4WksB0NRQ9utR+2c3gEGzl6uKj3eM6PMQ6tN3lgXs= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4/go.mod h1:JniVpqvw90sVjNqanGLufrVapWySL28fhBlYgl96Q/w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 h1:PalLOEGZ/4XfQxpGZFTLaoJSmPoybnqJYotaIZEf/Rg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0/go.mod h1:PwyKKVL0cNkC37QwLcrhyeCrAk+5bY8O2ou7USyAS2A= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U= +github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY= +github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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-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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/dapr/go-sdk v1.8.0 h1:OEleeL3zUTqXxIZ7Vkk3PClAeCh1g8sZ1yR2JFZKfXM= -github.com/dapr/go-sdk v1.8.0/go.mod h1:MBcTKXg8PmBc8A968tVWQg1Xt+DZtmeVR6zVVVGcmeA= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/pkg/git v0.12.3 h1:1KmRYTdcBKDUutg6NIT4x0BCCMT72PjjXs3AnHjybHY= @@ -63,16 +144,16 @@ github.com/fluxcd/pkg/ssh v0.8.0/go.mod h1:bo6HgWqIIuXU6r5HCxRFa7Uo7b4Nnzsz6Mvdt github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI= github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fsouza/fake-gcs-server v1.45.2 h1:m4hkghNBY7lmPtpgE41nZa1mOo2PedAZnw3Hd2RfsGk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= -github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ= +github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= @@ -81,25 +162,66 @@ github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZg github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +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= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.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.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.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= +github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 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= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hairyhenderson/go-fsimpl v0.0.0-20230722184334-4c242a8cf7b7 h1:skN/QKKLASUxs6TusuZkOmErIbieTWVkz8gzp2Upn2c= +github.com/hairyhenderson/go-fsimpl v0.0.0-20230722184334-4c242a8cf7b7/go.mod h1:bm7zNi/r8vuhJ0kuhXTcaJPRiHHGcKKhVAX0VXLaQG0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -115,6 +237,11 @@ github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/johannesboyne/gofakes3 v0.0.0-20230310080033-c0edf658332b h1:dRMf9/2xfp4tky4wnvFxsMQz78n92VeqDIxR27uass4= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -142,22 +269,32 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 h1:WnNuhiq+FOY3jNj6JXFT+eLN3CQ/oPIsDPRanvwsmbI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= 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/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.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= @@ -166,6 +303,9 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -177,46 +317,67 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +gocloud.dev v0.32.0 h1:jHf8WSkByuAuXcvFt04OiiQH+N0zaRtxI6iEph8Bq8Y= +gocloud.dev v0.32.0/go.mod h1:m/x/N9cRjDF5MD0i5TLFbKbqkGffl/qayXA9FcMT5Oc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +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= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -227,6 +388,7 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -235,6 +397,7 @@ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= @@ -244,13 +407,18 @@ 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.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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -261,12 +429,43 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= +google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= +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.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753 h1:+VoAg+OKmWaommL56xmZSE2sUK8A7m6SUO7X89F2tbw= +google.golang.org/genproto v0.0.0-20230717213848-3f92550aa753/go.mod h1:iqkVr8IRpZ53gx1dEnWlCUIEwDWqWARWrbzpasaTNYM= +google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 h1:lCbbUxUDD+DiXx9Q6F/ttL0aAu7N2pz8XnmMm8ZW4NE= +google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 h1:XUODHrpzJEUeWmVo/jfNTLj0YyVveOo28oE6vkFbkO4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +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= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= @@ -279,12 +478,16 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/src/azure/cosmosdb.go b/src/azure/cosmosdb.go new file mode 100644 index 0000000..0ba5541 --- /dev/null +++ b/src/azure/cosmosdb.go @@ -0,0 +1,100 @@ +package azure + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" +) + +type CosmosDBClient struct { + containerClient *azcosmos.ContainerClient +} + +func NewCosmosDBClient(account string, db string, container string, cred azcore.TokenCredential) (*CosmosDBClient, error) { + endpoint := fmt.Sprintf("https://%s.documents.azure.com:443/", account) + client, err := azcosmos.NewClient(endpoint, cred, nil) + if err != nil { + return nil, err + } + + containerClient, err := client.NewContainer(db, container) + if err != nil { + return nil, err + } + + return &CosmosDBClient{ + containerClient, + }, nil +} + +type CosmosDBContainerClient[T any] struct { + client *azcosmos.ContainerClient + partitionKey string + ttl *int +} + +type cosmosDBEntry[T any] struct { + Id string `json:"id"` + ParitionKey string `json:"partition_key"` + TTL *int `json:"ttl"` + Value T `json:"value"` +} + +func NewCosmosDBContainerClient[T any](cosmosDBClient *CosmosDBClient, partitionKey string, ttl *int) (*CosmosDBContainerClient[T], error) { + return &CosmosDBContainerClient[T]{ + client: cosmosDBClient.containerClient, + partitionKey: partitionKey, + ttl: ttl, + }, nil +} + +func (client *CosmosDBContainerClient[T]) getId(key string) string { + return fmt.Sprintf("%s-%s", client.partitionKey, key) +} + +func (client *CosmosDBContainerClient[T]) Get(ctx context.Context, key string) (*T, error) { + item, err := client.client.ReadItem(ctx, azcosmos.NewPartitionKeyString(client.partitionKey), client.getId(key), &azcosmos.ItemOptions{}) + isNotFound := err != nil && strings.Contains(err.Error(), "404 Not Found") + if err != nil && !isNotFound { + return new(T), err + } + + if isNotFound { + return nil, nil + } + + if len(item.Value) == 0 { + return nil, nil + } + + entry := &cosmosDBEntry[T]{} + err = json.Unmarshal(item.Value, entry) + if err != nil { + return new(T), err + } + + return &entry.Value, nil +} + +func (client *CosmosDBContainerClient[T]) Set(ctx context.Context, key string, value T) error { + b, err := json.Marshal(cosmosDBEntry[T]{ + Id: client.getId(key), + ParitionKey: client.partitionKey, + TTL: client.ttl, + Value: value, + }) + if err != nil { + return err + } + + _, err = client.client.UpsertItem(ctx, azcosmos.NewPartitionKeyString(client.partitionKey), b, &azcosmos.ItemOptions{}) + if err != nil { + return err + } + + return nil +} diff --git a/src/cache/app.go b/src/cache/app.go deleted file mode 100644 index d40ffe5..0000000 --- a/src/cache/app.go +++ /dev/null @@ -1,86 +0,0 @@ -package cache - -import ( - "crypto/md5" - "fmt" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" -) - -type AppCacheEntry struct { - modified time.Time - sourceAppHash string -} - -type AppCache map[string]AppCacheEntry - -func NewAppCache() *AppCache { - c := make(AppCache) - return &c -} - -func (c *AppCache) Set(name string, remoteApp, sourceApp *armappcontainers.ContainerApp) { - if remoteApp == nil { - return - } - if remoteApp.SystemData == nil { - return - } - - timestamp := remoteApp.SystemData.LastModifiedAt - if timestamp == nil { - if remoteApp.SystemData.CreatedAt == nil { - return - } - timestamp = remoteApp.SystemData.CreatedAt - } - - b, err := sourceApp.MarshalJSON() - if err != nil { - return - } - hash := fmt.Sprintf("%x", md5.Sum(b)) - - (*c)[name] = AppCacheEntry{ - modified: *timestamp, - sourceAppHash: hash, - } -} - -func (c *AppCache) NeedsUpdate(name string, remoteApp, sourceApp *armappcontainers.ContainerApp) (bool, string) { - entry, ok := (*c)[name] - if !ok { - return true, "not in AppCache" - } - - if remoteApp == nil { - return true, "remoteApp nil" - } - if remoteApp.SystemData == nil { - return true, "remoteApp SystemData nil" - } - - if remoteApp.SystemData.LastModifiedAt != nil { - if entry.modified != *remoteApp.SystemData.LastModifiedAt { - return true, "changed LastModifiedAt" - } - } else if remoteApp.SystemData.CreatedAt != nil { - if entry.modified != *remoteApp.SystemData.CreatedAt { - return true, "changed CreatedAt" - } - } - - b, err := sourceApp.MarshalJSON() - if err != nil { - return true, "sourceApp MarshalJSON() failed" - } - - hash := fmt.Sprintf("%x", md5.Sum(b)) - - if entry.sourceAppHash != hash { - return true, "changed sourceApp hash" - } - - return false, "no changes" -} diff --git a/src/cache/cache.go b/src/cache/cache.go new file mode 100644 index 0000000..cdd5511 --- /dev/null +++ b/src/cache/cache.go @@ -0,0 +1,49 @@ +package cache + +import ( + "context" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" + "github.com/xenitab/azcagit/src/notification" +) + +type CacheEntry struct { + Name string `json:"name"` + Modified time.Time `json:"modified"` + Hash string `json:"hash"` +} + +func newCacheEntry(name string, modified time.Time, hash string) CacheEntry { + return CacheEntry{ + Name: name, + Modified: modified.Round(time.Millisecond), + Hash: hash, + } +} + +type AppCache interface { + Set(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) error + NeedsUpdate(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) (bool, string, error) +} + +type JobCache interface { + Set(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) error + NeedsUpdate(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) (bool, string, error) +} + +type SecretCacheEntry struct { + name string + value string + modified time.Time +} + +type NotificationCache interface { + Set(ctx context.Context, event notification.NotificationEvent) error + Get(ctx context.Context) (notification.NotificationEvent, bool, error) +} + +type RevisionCache interface { + Set(ctx context.Context, revision string) error + Get(ctx context.Context) (string, error) +} diff --git a/src/cache/cosmosdb_app.go b/src/cache/cosmosdb_app.go new file mode 100644 index 0000000..1a03ba8 --- /dev/null +++ b/src/cache/cosmosdb_app.go @@ -0,0 +1,97 @@ +package cache + +import ( + "context" + "crypto/md5" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" + "github.com/xenitab/azcagit/src/azure" + "github.com/xenitab/azcagit/src/config" +) + +type CosmosDBAppCache struct { + client *azure.CosmosDBContainerClient[CacheEntry] +} + +var _ AppCache = (*CosmosDBAppCache)(nil) + +func NewCosmosDBAppCache(cfg config.ReconcileConfig, cosmosDBClient *azure.CosmosDBClient) (*CosmosDBAppCache, error) { + ttl := 3600 + client, err := azure.NewCosmosDBContainerClient[CacheEntry](cosmosDBClient, "app-cache", &ttl) + if err != nil { + return nil, err + } + + return &CosmosDBAppCache{ + client, + }, nil +} + +func (c *CosmosDBAppCache) Set(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) error { + if remoteApp == nil { + return nil + } + if remoteApp.SystemData == nil { + return nil + } + + timestamp := remoteApp.SystemData.LastModifiedAt + if timestamp == nil { + if remoteApp.SystemData.CreatedAt == nil { + return nil + } + timestamp = remoteApp.SystemData.CreatedAt + } + + b, err := sourceApp.MarshalJSON() + if err != nil { + return nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + cacheEntry := newCacheEntry(name, *timestamp, hash) + return c.client.Set(ctx, name, cacheEntry) +} + +func (c *CosmosDBAppCache) NeedsUpdate(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) (bool, string, error) { + entry, err := c.client.Get(ctx, name) + if err != nil { + return false, "CosmosDB client returned an error", err + } + + if entry == nil { + return true, "not in AppCache", nil + } + + if remoteApp == nil { + return true, "remoteApp nil", nil + } + if remoteApp.SystemData == nil { + return true, "remoteApp SystemData nil", nil + } + + if remoteApp.SystemData.LastModifiedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteApp.SystemData.LastModifiedAt).Round(time.Millisecond) { + return true, "changed LastModifiedAt", nil + } + } else if remoteApp.SystemData.CreatedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteApp.SystemData.CreatedAt).Round(time.Millisecond) { + return true, "changed CreatedAt", nil + } + } + + b, err := sourceApp.MarshalJSON() + if err != nil { + return true, "sourceApp MarshalJSON() failed", nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + + if entry.Hash != hash { + return true, "changed sourceApp hash", nil + } + + return false, "no changes", nil +} diff --git a/src/cache/cosmosdb_job.go b/src/cache/cosmosdb_job.go new file mode 100644 index 0000000..49d6471 --- /dev/null +++ b/src/cache/cosmosdb_job.go @@ -0,0 +1,97 @@ +package cache + +import ( + "context" + "crypto/md5" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" + "github.com/xenitab/azcagit/src/azure" + "github.com/xenitab/azcagit/src/config" +) + +type CosmosDBJobCache struct { + client *azure.CosmosDBContainerClient[CacheEntry] +} + +var _ JobCache = (*CosmosDBJobCache)(nil) + +func NewCosmosDBJobCache(cfg config.ReconcileConfig, cosmosDBClient *azure.CosmosDBClient) (*CosmosDBJobCache, error) { + ttl := 3600 + client, err := azure.NewCosmosDBContainerClient[CacheEntry](cosmosDBClient, "job-cache", &ttl) + if err != nil { + return nil, err + } + + return &CosmosDBJobCache{ + client, + }, nil +} + +func (c *CosmosDBJobCache) Set(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) error { + if remoteJob == nil { + return nil + } + if remoteJob.SystemData == nil { + return nil + } + + timestamp := remoteJob.SystemData.LastModifiedAt + if timestamp == nil { + if remoteJob.SystemData.CreatedAt == nil { + return nil + } + timestamp = remoteJob.SystemData.CreatedAt + } + + b, err := sourceJob.MarshalJSON() + if err != nil { + return nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + cacheEntry := newCacheEntry(name, *timestamp, hash) + return c.client.Set(ctx, name, cacheEntry) +} + +func (c *CosmosDBJobCache) NeedsUpdate(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) (bool, string, error) { + entry, err := c.client.Get(ctx, name) + if err != nil { + return false, "CosmosDB client returned an error", err + } + + if entry == nil { + return true, "not in JobCache", nil + } + + if remoteJob == nil { + return true, "remoteJob nil", nil + } + if remoteJob.SystemData == nil { + return true, "remoteJob SystemData nil", nil + } + + if remoteJob.SystemData.LastModifiedAt != nil { + if (entry.Modified).Round(time.Millisecond) != (*remoteJob.SystemData.LastModifiedAt).Round(time.Millisecond) { + return true, "changed LastModifiedAt", nil + } + } else if remoteJob.SystemData.CreatedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteJob.SystemData.CreatedAt).Round(time.Millisecond) { + return true, "changed CreatedAt", nil + } + } + + b, err := sourceJob.MarshalJSON() + if err != nil { + return true, "remoteJob MarshalJSON() failed", nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + + if entry.Hash != hash { + return true, "changed remoteJob hash", nil + } + + return false, "no changes", nil +} diff --git a/src/cache/cosmosdb_notification.go b/src/cache/cosmosdb_notification.go new file mode 100644 index 0000000..e564139 --- /dev/null +++ b/src/cache/cosmosdb_notification.go @@ -0,0 +1,46 @@ +package cache + +import ( + "context" + + "github.com/xenitab/azcagit/src/azure" + "github.com/xenitab/azcagit/src/config" + "github.com/xenitab/azcagit/src/notification" +) + +type CosmosDBNotificationCache struct { + client *azure.CosmosDBContainerClient[notification.NotificationEvent] +} + +var _ NotificationCache = (*CosmosDBNotificationCache)(nil) + +const notificationCacheKey = "previous_notification" + +func NewCosmosDBNotificationCache(cfg config.ReconcileConfig, cosmosDBClient *azure.CosmosDBClient) (*CosmosDBNotificationCache, error) { + ttl := -1 // -1 disables time to live + client, err := azure.NewCosmosDBContainerClient[notification.NotificationEvent](cosmosDBClient, "notification-cache", &ttl) + if err != nil { + return nil, err + } + + return &CosmosDBNotificationCache{ + client, + }, nil +} + +func (c *CosmosDBNotificationCache) Set(ctx context.Context, event notification.NotificationEvent) error { + return c.client.Set(ctx, notificationCacheKey, event) +} + +func (c *CosmosDBNotificationCache) Get(ctx context.Context) (notification.NotificationEvent, bool, error) { + value, err := c.client.Get(ctx, notificationCacheKey) + if err != nil { + return notification.NotificationEvent{}, false, err + } + + if value == nil { + return notification.NotificationEvent{}, false, nil + } + + return *value, true, nil +} diff --git a/src/cache/cosmosdb_revision.go b/src/cache/cosmosdb_revision.go new file mode 100644 index 0000000..feefad6 --- /dev/null +++ b/src/cache/cosmosdb_revision.go @@ -0,0 +1,45 @@ +package cache + +import ( + "context" + + "github.com/xenitab/azcagit/src/azure" + "github.com/xenitab/azcagit/src/config" +) + +type CosmosDBRevisionCache struct { + client *azure.CosmosDBContainerClient[string] +} + +var _ RevisionCache = (*CosmosDBRevisionCache)(nil) + +const revisionCacheKey = "revision" + +func NewCosmosDBRevisionCache(cfg config.ReconcileConfig, cosmosDBClient *azure.CosmosDBClient) (*CosmosDBRevisionCache, error) { + ttl := -1 // -1 disables time to live + client, err := azure.NewCosmosDBContainerClient[string](cosmosDBClient, "revision-cache", &ttl) + if err != nil { + return nil, err + } + + return &CosmosDBRevisionCache{ + client, + }, nil +} + +func (c *CosmosDBRevisionCache) Set(ctx context.Context, revision string) error { + return c.client.Set(ctx, revisionCacheKey, revision) +} + +func (c *CosmosDBRevisionCache) Get(ctx context.Context) (string, error) { + value, err := c.client.Get(ctx, revisionCacheKey) + if err != nil { + return "", err + } + + if value == nil { + return "", nil + } + + return *value, nil +} diff --git a/src/cache/inmem_app.go b/src/cache/inmem_app.go new file mode 100644 index 0000000..3ec50c6 --- /dev/null +++ b/src/cache/inmem_app.go @@ -0,0 +1,83 @@ +package cache + +import ( + "context" + "crypto/md5" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" +) + +type InMemAppCache map[string]CacheEntry + +var _ AppCache = (*InMemAppCache)(nil) + +func NewInMemAppCache() *InMemAppCache { + c := make(InMemAppCache) + return &c +} + +func (c *InMemAppCache) Set(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) error { + if remoteApp == nil { + return nil + } + if remoteApp.SystemData == nil { + return nil + } + + timestamp := remoteApp.SystemData.LastModifiedAt + if timestamp == nil { + if remoteApp.SystemData.CreatedAt == nil { + return nil + } + timestamp = remoteApp.SystemData.CreatedAt + } + + b, err := sourceApp.MarshalJSON() + if err != nil { + return nil + } + hash := fmt.Sprintf("%x", md5.Sum(b)) + + (*c)[name] = newCacheEntry(name, *timestamp, hash) + + return nil +} + +func (c *InMemAppCache) NeedsUpdate(ctx context.Context, name string, remoteApp, sourceApp *armappcontainers.ContainerApp) (bool, string, error) { + entry, ok := (*c)[name] + if !ok { + return true, "not in AppCache", nil + } + + if remoteApp == nil { + return true, "remoteApp nil", nil + } + if remoteApp.SystemData == nil { + return true, "remoteApp SystemData nil", nil + } + + if remoteApp.SystemData.LastModifiedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteApp.SystemData.LastModifiedAt).Round(time.Millisecond) { + return true, "changed LastModifiedAt", nil + } + } else if remoteApp.SystemData.CreatedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteApp.SystemData.CreatedAt).Round(time.Millisecond) { + return true, "changed CreatedAt", nil + } + } + + b, err := sourceApp.MarshalJSON() + if err != nil { + return true, "sourceApp MarshalJSON() failed", nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + + if entry.Hash != hash { + return true, "changed sourceApp hash", nil + } + + return false, "no changes", nil +} diff --git a/src/cache/inmem_job.go b/src/cache/inmem_job.go new file mode 100644 index 0000000..cc45f80 --- /dev/null +++ b/src/cache/inmem_job.go @@ -0,0 +1,83 @@ +package cache + +import ( + "context" + "crypto/md5" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" +) + +type InMemJobCache map[string]CacheEntry + +var _ JobCache = (*InMemJobCache)(nil) + +func NewInMemJobCache() *InMemJobCache { + c := make(InMemJobCache) + return &c +} + +func (c *InMemJobCache) Set(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) error { + if remoteJob == nil { + return nil + } + if remoteJob.SystemData == nil { + return nil + } + + timestamp := remoteJob.SystemData.LastModifiedAt + if timestamp == nil { + if remoteJob.SystemData.CreatedAt == nil { + return nil + } + timestamp = remoteJob.SystemData.CreatedAt + } + + b, err := sourceJob.MarshalJSON() + if err != nil { + return nil + } + hash := fmt.Sprintf("%x", md5.Sum(b)) + + (*c)[name] = newCacheEntry(name, *timestamp, hash) + + return nil +} + +func (c *InMemJobCache) NeedsUpdate(ctx context.Context, name string, remoteJob, sourceJob *armappcontainers.Job) (bool, string, error) { + entry, ok := (*c)[name] + if !ok { + return true, "not in JobCache", nil + } + + if remoteJob == nil { + return true, "remoteJob nil", nil + } + if remoteJob.SystemData == nil { + return true, "remoteJob SystemData nil", nil + } + + if remoteJob.SystemData.LastModifiedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteJob.SystemData.LastModifiedAt).Round(time.Millisecond) { + return true, "changed LastModifiedAt", nil + } + } else if remoteJob.SystemData.CreatedAt != nil { + if entry.Modified.Round(time.Millisecond) != (*remoteJob.SystemData.CreatedAt).Round(time.Millisecond) { + return true, "changed CreatedAt", nil + } + } + + b, err := sourceJob.MarshalJSON() + if err != nil { + return true, "sourceJob MarshalJSON() failed", nil + } + + hash := fmt.Sprintf("%x", md5.Sum(b)) + + if entry.Hash != hash { + return true, "changed sourceJob hash", nil + } + + return false, "no changes", nil +} diff --git a/src/cache/inmem_notification.go b/src/cache/inmem_notification.go new file mode 100644 index 0000000..02c241e --- /dev/null +++ b/src/cache/inmem_notification.go @@ -0,0 +1,34 @@ +package cache + +import ( + "context" + + "github.com/xenitab/azcagit/src/notification" +) + +type InMemNotificationCache struct { + event *notification.NotificationEvent +} + +var _ NotificationCache = (*InMemNotificationCache)(nil) + +func NewInMemNotificationCache() *InMemNotificationCache { + return &InMemNotificationCache{} +} + +func (c *InMemNotificationCache) Set(ctx context.Context, event notification.NotificationEvent) error { + c.event = &event + return nil +} + +func (c *InMemNotificationCache) Get(ctx context.Context) (notification.NotificationEvent, bool, error) { + if c.event == nil { + return notification.NotificationEvent{}, false, nil + } + + return *c.event, true, nil +} + +func (c *InMemNotificationCache) Reset() { + c.event = nil +} diff --git a/src/cache/inmem_revision.go b/src/cache/inmem_revision.go new file mode 100644 index 0000000..3be1a79 --- /dev/null +++ b/src/cache/inmem_revision.go @@ -0,0 +1,32 @@ +package cache + +import ( + "context" +) + +type InMemRevisionCache struct { + revision *string +} + +var _ RevisionCache = (*InMemRevisionCache)(nil) + +func NewInMemRevisionCache() *InMemRevisionCache { + return &InMemRevisionCache{} +} + +func (c *InMemRevisionCache) Set(ctx context.Context, revision string) error { + c.revision = &revision + return nil +} + +func (c *InMemRevisionCache) Get(ctx context.Context) (string, error) { + if c.revision == nil { + return "", nil + } + + return *c.revision, nil +} + +func (c *InMemRevisionCache) Reset() { + c.revision = nil +} diff --git a/src/cache/inmem_secret.go b/src/cache/inmem_secret.go new file mode 100644 index 0000000..1ff65c2 --- /dev/null +++ b/src/cache/inmem_secret.go @@ -0,0 +1,23 @@ +package cache + +import "time" + +type InMemSecretCache map[string]SecretCacheEntry + +func NewInMemSecretCache() *InMemSecretCache { + c := make(InMemSecretCache) + return &c +} + +func (c *InMemSecretCache) Set(name string, value string, modified time.Time) { + (*c)[name] = SecretCacheEntry{ + name, + value, + modified, + } +} + +func (c *InMemSecretCache) Get(name string) (string, bool) { + entry, ok := (*c)[name] + return entry.value, ok +} diff --git a/src/cache/job.go b/src/cache/job.go deleted file mode 100644 index 0b8706f..0000000 --- a/src/cache/job.go +++ /dev/null @@ -1,86 +0,0 @@ -package cache - -import ( - "crypto/md5" - "fmt" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" -) - -type JobCacheEntry struct { - modified time.Time - sourceJobHash string -} - -type JobCache map[string]JobCacheEntry - -func NewJobCache() *JobCache { - c := make(JobCache) - return &c -} - -func (c *JobCache) Set(name string, remoteJob, sourceJob *armappcontainers.Job) { - if remoteJob == nil { - return - } - if remoteJob.SystemData == nil { - return - } - - timestamp := remoteJob.SystemData.LastModifiedAt - if timestamp == nil { - if remoteJob.SystemData.CreatedAt == nil { - return - } - timestamp = remoteJob.SystemData.CreatedAt - } - - b, err := sourceJob.MarshalJSON() - if err != nil { - return - } - hash := fmt.Sprintf("%x", md5.Sum(b)) - - (*c)[name] = JobCacheEntry{ - modified: *timestamp, - sourceJobHash: hash, - } -} - -func (c *JobCache) NeedsUpdate(name string, remoteJob, sourceJob *armappcontainers.Job) (bool, string) { - entry, ok := (*c)[name] - if !ok { - return true, "not in JobCache" - } - - if remoteJob == nil { - return true, "remoteJob nil" - } - if remoteJob.SystemData == nil { - return true, "remoteJob SystemData nil" - } - - if remoteJob.SystemData.LastModifiedAt != nil { - if entry.modified != *remoteJob.SystemData.LastModifiedAt { - return true, "changed LastModifiedAt" - } - } else if remoteJob.SystemData.CreatedAt != nil { - if entry.modified != *remoteJob.SystemData.CreatedAt { - return true, "changed CreatedAt" - } - } - - b, err := sourceJob.MarshalJSON() - if err != nil { - return true, "sourceJob MarshalJSON() failed" - } - - hash := fmt.Sprintf("%x", md5.Sum(b)) - - if entry.sourceJobHash != hash { - return true, "changed sourceJob hash" - } - - return false, "no changes" -} diff --git a/src/cache/secret.go b/src/cache/secret.go deleted file mode 100644 index bd41784..0000000 --- a/src/cache/secret.go +++ /dev/null @@ -1,41 +0,0 @@ -package cache - -import "time" - -type SecretCacheEntry struct { - name string - value string - modified time.Time -} -type SecretCache map[string]SecretCacheEntry - -func NewSecretCache() *SecretCache { - c := make(SecretCache) - return &c -} - -func (c *SecretCache) Set(name string, value string, modified time.Time) { - (*c)[name] = SecretCacheEntry{ - name, - value, - modified, - } -} - -func (c *SecretCache) Get(name string) (string, bool) { - entry, ok := (*c)[name] - return entry.value, ok -} - -func (c *SecretCache) NeedsUpdate(name string, modified time.Time) bool { - entry, ok := (*c)[name] - if !ok { - return true - } - - if modified != entry.modified { - return true - } - - return false -} diff --git a/src/config/config.go b/src/config/config.go index 70a87ac..9e60663 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,39 +1,40 @@ package config import ( + "fmt" "net/url" + "os" "github.com/alexflint/go-arg" ) -type Config struct { +type ReconcileConfig struct { ResourceGroupName string `json:"resource_group_name" arg:"-g,--resource-group-name,env:RESOURCE_GROUP_NAME,required" help:"Azure Resource Group Name"` Environment string `json:"environment" arg:"--environment,env:ENVIRONMENT,required" help:"The current environment that azcagit is running in"` SubscriptionID string `json:"subscription_id" arg:"-s,--subscription-id,env:AZURE_SUBSCRIPTION_ID,required" help:"Azure Subscription ID"` ManagedEnvironmentID string `json:"managed_environment_id" arg:"-m,--managed-environment-id,env:MANAGED_ENVIRONMENT_ID,required" help:"Azure Container Apps Managed Environment ID"` KeyVaultName string `json:"key_vault_name" arg:"-k,--key-vault-name,env:KEY_VAULT_NAME,required" help:"Azure KeyVault name to extract secrets from"` - OwnContainerAppName string `json:"own_container_app_name" arg:"--own-container-app-name,env:OWN_CONTAINER_APP_NAME" default:"azcagit" help:"The name of the Container App that is running azcagit"` + OwnContainerJobName string `json:"own_container_job_name" arg:"--own-container-job-name,env:OWN_CONTAINER_JOB_NAME" default:"azcagit-reconcile" help:"The name of the Container App job that is running azcagit"` OwnResourceGroupName string `json:"own_resource_group" arg:"--own-resource-group-name,env:OWN_RESOURCE_GROUP_NAME,required" help:"The name of the resource group that the azcagit Container App is located in"` ContainerRegistryServer string `json:"container_registry_server" arg:"--container-registry-server,env:CONTAINER_REGISTRY_SERVER" help:"The container registry server"` ContainerRegistryUsername string `json:"container_registry_username" arg:"--container-registry-username,env:CONTAINER_REGISTRY_USERNAME" help:"The container registry username"` ContainerRegistryPassword string `json:"container_registry_password" arg:"--container-registry-password,env:CONTAINER_REGISTRY_PASSWORD" help:"The container registry password"` Location string `json:"location" arg:"-l,--location,env:LOCATION,required" help:"Azure Region (location)"` - ReconcileInterval string `json:"reconcile_interval" arg:"-i,--reconcile-interval,env:RECONCILE_INTERVAL" default:"5m" help:"The interval between reconciles"` CheckoutPath string `json:"checkout_path" arg:"-c,--checkout-path,env:CHECKOUT_PATH" default:"/tmp" help:"The local path where the git repository should be checked out"` GitUrl string `json:"git_url" arg:"-u,--git-url,env:GIT_URL,required" help:"The git url to checkout"` GitBranch string `json:"git_branch" arg:"-b,--git-branch,env:GIT_BRANCH" default:"main" help:"The git branch to checkout"` GitYamlPath string `json:"git_yaml_path" arg:"--git-yaml-path,env:GIT_YAML_ROOT" default:"" help:"The path where the yaml files are located"` - DaprAppPort int `json:"dapr_app_port" arg:"--dapr-app-port,env:DAPR_APP_PORT" default:"8080" help:"The port Dapr service should listen to"` - DaprPubsubName string `json:"dapr_pubsub_name" arg:"--dapr-pubsub-name,env:DAPR_PUBSUB_NAME" default:"azcagit-trigger" help:"The PubSub name for the trigger"` - DaprTopic string `json:"dapr_topic" arg:"--dapr-topic-name,env:DAPR_TOPIC_NAME,required" help:"The PubSub topic name for the trigger"` NotificationsEnabled bool `json:"notifications_enabled" arg:"--notifications-enabled,env:NOTIFICATIONS_ENABLED" default:"false" help:"Sets if Notifications should be sent to the git provider, should be disabled if no token is provided in git url"` NotificationGroup string `json:"notification_group" arg:"--notification-group,env:NOTIFICATION_GROUP" default:"apps" help:"The notification group used by gitops-promotion"` + CosmosDBAccount string `json:"cosmosdb_account" arg:"--cosmosdb-account,env:COSMOSDB_ACCOUNT,required" help:"The CosmosDB account to be used for cache"` + CosmosDBSqlDb string `json:"cosmosdb_sql_db" arg:"--cosmosdb-sql-db,env:COSMOSDB_SQL_DB" default:"azcagit" help:"The CosmosDB SQL database to be used for cache"` + CosmosDBCacheContainer string `json:"cosmosdb_cache_container" arg:"--cosmosdb-cache-container,env:COSMOSDB_CACHE_CONTAINER" default:"cache" help:"The CosmosDB container used for the cache"` DebugEnabled bool `json:"debug_enabled" arg:"--debug,env:DEBUG" default:"false" help:"Enabled debug logging"` } -func (cfg *Config) Redacted() Config { +func (cfg *ReconcileConfig) Redacted() ReconcileConfig { if cfg == nil { - return Config{} + return ReconcileConfig{} } redactedCfg := *cfg @@ -67,6 +68,19 @@ func redactUrl(u string) string { return parsed.String() } +type TriggerConfig struct { + SubscriptionID string `json:"subscription_id" arg:"-s,--subscription-id,env:AZURE_SUBSCRIPTION_ID,required" help:"Azure Subscription ID"` + JobName string `json:"job_name" arg:"--job-name,env:JOB_NAME,required" help:"The name of the container app job running azcagit"` + ResourceGroupName string `json:"resource_group_name" arg:"--resource-group-name,env:RESOURCE_GROUP_NAME,required" help:"The resource group name of where container app job running azcagit is located"` + ServiceBusNamespace string `json:"service_bus_namespace" arg:"--service-bus-namespace,env:SERVICE_BUS_NAMESPACE,required" help:"The namespace of the service bus"` + ServiceBusQueue string `json:"service_bus_queue" arg:"--service-bus-queue,env:SERVICE_BUS_QUEUE,required" help:"The queue name of where to consume the messages from the service bus"` +} + +type Config struct { + ReconcileCfg *ReconcileConfig `arg:"subcommand:reconcile" help:"run reconciliation"` + TriggerCfg *TriggerConfig `arg:"subcommand:trigger" help:"run trigger"` +} + func NewConfig(args []string) (Config, error) { cfg := Config{} @@ -83,5 +97,10 @@ func NewConfig(args []string) (Config, error) { return Config{}, err } + if parser.Subcommand() == nil { + parser.WriteHelp(os.Stderr) + return Config{}, fmt.Errorf("missing subcommand") + } + return cfg, nil } diff --git a/src/config/config_test.go b/src/config/config_test.go index 215e1f1..922125a 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewConfig(t *testing.T) { +func TestNewReconcileConfig(t *testing.T) { envVarsToClear := []string{ "RESOURCE_GROUP_NAME", "ENVIRONMENT", @@ -19,17 +19,17 @@ func TestNewConfig(t *testing.T) { "CONTAINER_REGISTRY_SERVER", "CONTAINER_REGISTRY_USERNAME", "CONTAINER_REGISTRY_PASSWORD", - "RECONCILE_INTERVAL", "CHECKOUT_PATH", "GIT_URL", "GIT_BRANCH", "GIT_YAML_ROOT", - "DAPR_APP_PORT", - "DAPR_PUBSUB_NAME", - "DAPR_TOPIC_NAME", "NOTIFICATIONS_ENABLED", "NOTIFICATION_GROUP", "DEBUG", + "COSMOSDB_ACCOUNT", + "COSMOSDB_SQL_DB", + "COSMOSDB_APP_CACHE_CONTAINER", + "COSMOSDB_JOB_CACHE_CONTAINER", } for _, envVar := range envVarsToClear { @@ -39,6 +39,7 @@ func TestNewConfig(t *testing.T) { args := []string{ "/foo/bar/bin", + "reconcile", "--resource-group-name", "foo", "--environment", @@ -55,40 +56,39 @@ func TestNewConfig(t *testing.T) { "westeurope", "--git-url", "https://github.com/foo/bar.git", - "--dapr-topic-name", - "ze-topic", + "--cosmosdb-account", + "ze-cosmosdb-account", } cfg, err := NewConfig(args[1:]) require.NoError(t, err) - require.Equal(t, Config{ - ResourceGroupName: "foo", - Environment: "foobar", - SubscriptionID: "bar", - ManagedEnvironmentID: "baz", - KeyVaultName: "ze-keyvault", - OwnContainerAppName: "azcagit", - OwnResourceGroupName: "platform", - Location: "westeurope", - ReconcileInterval: "5m", - CheckoutPath: "/tmp", - GitUrl: "https://github.com/foo/bar.git", - GitBranch: "main", - DaprAppPort: 8080, - DaprPubsubName: "azcagit-trigger", - DaprTopic: "ze-topic", - NotificationGroup: "apps", - }, cfg) + require.Equal(t, ReconcileConfig{ + ResourceGroupName: "foo", + Environment: "foobar", + SubscriptionID: "bar", + ManagedEnvironmentID: "baz", + KeyVaultName: "ze-keyvault", + OwnContainerJobName: "azcagit-reconcile", + OwnResourceGroupName: "platform", + Location: "westeurope", + CheckoutPath: "/tmp", + GitUrl: "https://github.com/foo/bar.git", + GitBranch: "main", + NotificationGroup: "apps", + CosmosDBAccount: "ze-cosmosdb-account", + CosmosDBSqlDb: "azcagit", + CosmosDBCacheContainer: "cache", + }, *cfg.ReconcileCfg) } -func TestRedactedConfig(t *testing.T) { - cfgWithUserAndPass := Config{ +func TestRedactedReconcileConfig(t *testing.T) { + cfgWithUserAndPass := ReconcileConfig{ ContainerRegistryPassword: "secret", // secretlint-disable GitUrl: "https://foo:bar@foobar.io/abc.git", // secretlint-disable } require.Equal(t, "redacted", cfgWithUserAndPass.Redacted().ContainerRegistryPassword) require.Equal(t, "https://foo:redacted@foobar.io/abc.git", cfgWithUserAndPass.Redacted().GitUrl) // secretlint-disable - cfg := Config{ + cfg := ReconcileConfig{ ContainerRegistryPassword: "", GitUrl: "https://foobar.io/abc.git", // secretlint-disable } diff --git a/src/main.go b/src/main.go index 4e43d22..46d155c 100644 --- a/src/main.go +++ b/src/main.go @@ -7,6 +7,9 @@ import ( "os/signal" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2" "github.com/go-logr/logr" "github.com/xenitab/azcagit/src/azure" "github.com/xenitab/azcagit/src/cache" @@ -18,8 +21,6 @@ import ( "github.com/xenitab/azcagit/src/remote" "github.com/xenitab/azcagit/src/secret" "github.com/xenitab/azcagit/src/source" - "github.com/xenitab/azcagit/src/trigger" - "golang.org/x/sync/errgroup" ) func main() { @@ -36,8 +37,6 @@ func main() { os.Exit(1) } - log.Info("configuration loaded", "config", cfg.Redacted()) - err = run(ctx, cfg) if err != nil { log.Error(err, "application returned an error") @@ -48,21 +47,43 @@ func main() { func run(ctx context.Context, cfg config.Config) error { log := logr.FromContextOrDiscard(ctx) - sourceClient, err := source.NewGitSource(cfg) + switch { + case cfg.ReconcileCfg != nil: + log.Info("reconcile configuration loaded", "config", cfg.ReconcileCfg.Redacted()) + return runReconcile(ctx, *cfg.ReconcileCfg) + case cfg.TriggerCfg != nil: + return runTrigger(ctx, *cfg.TriggerCfg) + } + + return fmt.Errorf("no subcommand executed") +} + +func runReconcile(ctx context.Context, cfg config.ReconcileConfig) error { + cred, err := azure.NewAzureCredential() if err != nil { return err } - _, _, err = sourceClient.Get(ctx) + cosmosDBClient, err := azure.NewCosmosDBClient(cfg.CosmosDBAccount, cfg.CosmosDBSqlDb, cfg.CosmosDBCacheContainer, cred) if err != nil { - return fmt.Errorf("unable to get source: %w", err) + return err } - cred, err := azure.NewAzureCredential() + revisionCache, err := cache.NewCosmosDBRevisionCache(cfg, cosmosDBClient) if err != nil { return err } + sourceClient, err := source.NewGitSource(cfg, revisionCache) + if err != nil { + return err + } + + _, _, err = sourceClient.Get(ctx) + if err != nil { + return fmt.Errorf("unable to get source: %w", err) + } + remoteAppClient, err := remote.NewAzureApp(cfg, cred) if err != nil { return err @@ -85,16 +106,24 @@ func run(ctx context.Context, cfg config.Config) error { metricsClient := metrics.NewAzureMetrics(cfg, cred) - appCache := cache.NewAppCache() - jobCache := cache.NewJobCache() - secretCache := cache.NewSecretCache() + appCache, err := cache.NewCosmosDBAppCache(cfg, cosmosDBClient) + if err != nil { + return err + } - reconciler, err := reconcile.NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache) + jobCache, err := cache.NewCosmosDBJobCache(cfg, cosmosDBClient) if err != nil { return err } - trig, err := trigger.NewDaprSubTrigger(cfg) + secretCache := cache.NewInMemSecretCache() + + notificationCache, err := cache.NewCosmosDBNotificationCache(cfg, cosmosDBClient) + if err != nil { + return err + } + + reconciler, err := reconcile.NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache, notificationCache) if err != nil { return err } @@ -102,46 +131,77 @@ func run(ctx context.Context, cfg config.Config) error { ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() - g, ctx := errgroup.WithContext(ctx) + err = reconciler.Run(ctx) + if err != nil { + return fmt.Errorf("reconcile error: %w", err) + } - g.Go(func() error { - return trig.Start() - }) + return nil +} + +func runTrigger(ctx context.Context, cfg config.TriggerConfig) error { + log := logr.FromContextOrDiscard(ctx) + + cred, err := azure.NewAzureCredential() + if err != nil { + return err + } + + namespaceFqdn := fmt.Sprintf("%s.servicebus.windows.net", cfg.ServiceBusNamespace) + sbClient, err := azservicebus.NewClient(namespaceFqdn, cred, nil) + if err != nil { + return err + } + + defer func() { + err := sbClient.Close(ctx) + if err != nil { + log.Error(err, "failed to close the service bus client") + } + }() - tickerInterval, err := time.ParseDuration(cfg.ReconcileInterval) + receiver, err := sbClient.NewReceiverForQueue(cfg.ServiceBusQueue, &azservicebus.ReceiverOptions{}) if err != nil { return err } - ticker := time.NewTicker(1 * time.Second) + peekedMessages, err := receiver.PeekMessages(ctx, 100, &azservicebus.PeekMessagesOptions{}) + if err != nil { + return err + } - reconcile := func(triggeredBy trigger.TriggeredBy) { - log.Info("reconcile triggered", "triggeredBy", triggeredBy) - err := reconciler.Run(ctx) + if len(peekedMessages) > 0 { + receivedMessages, err := receiver.ReceiveMessages(ctx, 100, &azservicebus.ReceiveMessagesOptions{}) if err != nil { - log.Error(err, "reconcile error") + return err } - ticker.Reset(tickerInterval) - } - -OUTER: - for { - select { - case <-ctx.Done(): - log.Info("context done, shutting down") - break OUTER - case <-ticker.C: - reconcile(trigger.TriggeredByTicker) - case triggeredBy := <-trig.WaitForTrigger(): - reconcile(triggeredBy) + for _, msg := range receivedMessages { + err := receiver.CompleteMessage(ctx, msg, &azservicebus.CompleteMessageOptions{}) + if err != nil { + return err + } } } - g.Go(func() error { - return trig.Stop() + jobClient, err := armappcontainers.NewJobsClient(cfg.SubscriptionID, cred, nil) + if err != nil { + return err + } + + res, err := jobClient.BeginStart(ctx, cfg.ResourceGroupName, cfg.JobName, armappcontainers.JobExecutionTemplate{}, &armappcontainers.JobsClientBeginStartOptions{}) + if err != nil { + return err + } + + _, err = res.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{ + Frequency: 5 * time.Second, }) - return g.Wait() + if err != nil { + return err + } + + return nil } func isDebugEnabled(args []string) bool { diff --git a/src/metrics/azure.go b/src/metrics/azure.go index a60ec40..a00505a 100644 --- a/src/metrics/azure.go +++ b/src/metrics/azure.go @@ -21,7 +21,7 @@ type AzureMetrics struct { var _ Metrics = (*AzureMetrics)(nil) -func NewAzureMetrics(cfg config.Config, credential azcore.TokenCredential) *AzureMetrics { +func NewAzureMetrics(cfg config.ReconcileConfig, credential azcore.TokenCredential) *AzureMetrics { // The `//` in `https://monitoring.azure.com//.default` is intentional and the required audience is `https://monitoring.azure.com/`, // right now something happens inside of the `runtime` which makes the audience `https://monitoring.azure.com` when there's a single `/`. authPolicy := runtime.NewBearerTokenPolicy(credential, []string{"https://monitoring.azure.com//.default"}, nil) @@ -33,9 +33,9 @@ func NewAzureMetrics(cfg config.Config, credential azcore.TokenCredential) *Azur } } -func generateCustomMetricsEndpoint(cfg config.Config) string { +func generateCustomMetricsEndpoint(cfg config.ReconcileConfig) string { azureRegion := sanitizeAzureLocation(cfg.Location) - resourceId := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.App/containerApps/%s", cfg.SubscriptionID, cfg.OwnResourceGroupName, cfg.OwnContainerAppName) + resourceId := fmt.Sprintf("subscriptions/%s/resourceGroups/%s/providers/Microsoft.App/jobs/%s", cfg.SubscriptionID, cfg.OwnResourceGroupName, cfg.OwnContainerJobName) return fmt.Sprintf("https://%s.monitoring.azure.com/%s/metrics", azureRegion, resourceId) } diff --git a/src/notification/notification.go b/src/notification/notification.go index 707d89d..78d1a62 100644 --- a/src/notification/notification.go +++ b/src/notification/notification.go @@ -13,10 +13,10 @@ type Notification interface { } type NotificationEvent struct { - Revision string - State NotificationState - Name string - Description string + Revision string `json:"revision"` + State NotificationState `json:"state"` + Name string `json:"name"` + Description string `json:"description"` } func (e *NotificationEvent) Equal(other NotificationEvent) bool { @@ -54,7 +54,7 @@ const ( NotificationProviderUnknown ) -func NewNotificationClient(cfg config.Config) (Notification, error) { +func NewNotificationClient(cfg config.ReconcileConfig) (Notification, error) { if !cfg.NotificationsEnabled { return NewDiscardNotification(), nil } diff --git a/src/reconcile/reconcile.go b/src/reconcile/reconcile.go index 26b9427..eafc20c 100644 --- a/src/reconcile/reconcile.go +++ b/src/reconcile/reconcile.go @@ -18,21 +18,20 @@ import ( ) type Reconciler struct { - cfg config.Config - sourceClient source.Source - remoteAppClient remote.App - remoteJobClient remote.Job - secretClient secret.Secret - notificationClient notification.Notification - metricsClient metrics.Metrics - appCache *cache.AppCache - jobCache *cache.JobCache - secretCache *cache.SecretCache - previousNotificationEvent notification.NotificationEvent + cfg config.ReconcileConfig + sourceClient source.Source + remoteAppClient remote.App + remoteJobClient remote.Job + secretClient secret.Secret + notificationClient notification.Notification + metricsClient metrics.Metrics + appCache cache.AppCache + jobCache cache.JobCache + secretCache *cache.InMemSecretCache + notificationCache cache.NotificationCache } -func NewReconciler(cfg config.Config, sourceClient source.Source, remoteAppClient remote.App, remoteJobClient remote.Job, secretClient secret.Secret, notificationClient notification.Notification, metricsClient metrics.Metrics, appCache *cache.AppCache, jobCache *cache.JobCache, secretCache *cache.SecretCache) (*Reconciler, error) { - previousNotificationEvent := notification.NotificationEvent{} +func NewReconciler(cfg config.ReconcileConfig, sourceClient source.Source, remoteAppClient remote.App, remoteJobClient remote.Job, secretClient secret.Secret, notificationClient notification.Notification, metricsClient metrics.Metrics, appCache cache.AppCache, jobCache cache.JobCache, secretCache *cache.InMemSecretCache, notificationCache cache.NotificationCache) (*Reconciler, error) { return &Reconciler{ cfg, sourceClient, @@ -44,7 +43,7 @@ func NewReconciler(cfg config.Config, sourceClient source.Source, remoteAppClien appCache, jobCache, secretCache, - previousNotificationEvent, + notificationCache, }, nil } @@ -95,6 +94,11 @@ func (r *Reconciler) run(ctx context.Context) (string, error) { return revision, err } + err = r.populateSecretCache(ctx, sources) + if err != nil { + return revision, err + } + var result *multierror.Error err = r.runSourceApps(ctx, sources) if err != nil { @@ -346,7 +350,10 @@ func (r *Reconciler) createOrUpdateAppsIfNeeded(ctx context.Context, sourceApps for _, name := range sourceApps.GetSortedNames() { sourceApp, _ := sourceApps.Get(name) remoteApp, ok := remoteApps.Get(name) - needsUpdate, updateReason := r.appCache.NeedsUpdate(name, remoteApp.App, sourceApp.Specification.App) + needsUpdate, updateReason, err := r.appCache.NeedsUpdate(ctx, name, remoteApp.App, sourceApp.Specification.App) + if err != nil { + return err + } if !needsUpdate { log.Info("skipping update, no changes", "app", name) continue @@ -364,7 +371,7 @@ func (r *Reconciler) createOrUpdateAppsIfNeeded(ctx context.Context, sourceApps continue } - err := r.remoteAppClient.Create(ctx, name, *sourceApp.Specification.App) + err = r.remoteAppClient.Create(ctx, name, *sourceApp.Specification.App) if err != nil { return fmt.Errorf("failed to create %s: %w", name, err) } @@ -380,7 +387,11 @@ func (r *Reconciler) createOrUpdateJobsIfNeeded(ctx context.Context, sourceJobs for _, name := range sourceJobs.GetSortedNames() { sourceJob, _ := sourceJobs.Get(name) remoteJob, ok := remoteJobs.Get(name) - needsUpdate, updateReason := r.jobCache.NeedsUpdate(name, remoteJob.Job, sourceJob.Specification.Job) + needsUpdate, updateReason, err := r.jobCache.NeedsUpdate(ctx, name, remoteJob.Job, sourceJob.Specification.Job) + if err != nil { + return err + } + if !needsUpdate { log.Info("skipping update, no changes", "job", name) continue @@ -398,7 +409,7 @@ func (r *Reconciler) createOrUpdateJobsIfNeeded(ctx context.Context, sourceJobs continue } - err := r.remoteJobClient.Create(ctx, name, *sourceJob.Specification.Job) + err = r.remoteJobClient.Create(ctx, name, *sourceJob.Specification.Job) if err != nil { return fmt.Errorf("failed to create %s: %w", name, err) } @@ -420,7 +431,10 @@ func (r *Reconciler) updateAppCache(ctx context.Context, sourceApps *source.Sour if !ok { return fmt.Errorf("unable to locate app %s after create or update", name) } - r.appCache.Set(name, remoteApp.App, sourceApp.Specification.App) + err := r.appCache.Set(ctx, name, remoteApp.App, sourceApp.Specification.App) + if err != nil { + return err + } } return nil @@ -438,7 +452,10 @@ func (r *Reconciler) updateJobCache(ctx context.Context, sourceJobs *source.Sour if !ok { return fmt.Errorf("unable to locate job %s after create or update", name) } - r.jobCache.Set(name, remoteJob.Job, sourceJob.Specification.Job) + err := r.jobCache.Set(ctx, name, remoteJob.Job, sourceJob.Specification.Job) + if err != nil { + return err + } } return nil @@ -470,27 +487,30 @@ func (r *Reconciler) filterSourceJobs(ctx context.Context, sourceJobs *source.So } } -func (r *Reconciler) populateSourceAppsSecrets(ctx context.Context, sourceApps *source.SourceApps) error { +func (r *Reconciler) populateSecretCache(ctx context.Context, sources *source.Sources) error { secretItems, err := r.secretClient.ListItems(ctx) if err != nil { return err } - for _, secretName := range sourceApps.GetUniqueRemoteSecretNames() { - item, ok := secretItems.Get(secretName) + for _, secretName := range sources.GetUniqueRemoteSecretNames() { + _, ok := secretItems.Get(secretName) if !ok { return fmt.Errorf("secret not found %q", secretName) } - if r.secretCache.NeedsUpdate(secretName, item.LastChange()) { - secretValue, changedAt, err := r.secretClient.Get(ctx, secretName) - if err != nil { - return err - } - r.secretCache.Set(secretName, secretValue, changedAt) + secretValue, changedAt, err := r.secretClient.Get(ctx, secretName) + if err != nil { + return err } + + r.secretCache.Set(secretName, secretValue, changedAt) } + return nil +} + +func (r *Reconciler) populateSourceAppsSecrets(ctx context.Context, sourceApps *source.SourceApps) error { for _, name := range sourceApps.GetSortedNames() { app, _ := sourceApps.Get(name) for i, remoteSecret := range app.GetRemoteSecrets() { @@ -503,7 +523,7 @@ func (r *Reconciler) populateSourceAppsSecrets(ctx context.Context, sourceApps * return fmt.Errorf("unable to get secret %d for app %q from cache", i, name) } - err = sourceApps.SetSecret(name, *remoteSecret.SecretName, secretValue) + err := sourceApps.SetSecret(name, *remoteSecret.SecretName, secretValue) if err != nil { return fmt.Errorf("unable to set secret %q for app %q", *remoteSecret.SecretName, name) } @@ -514,26 +534,6 @@ func (r *Reconciler) populateSourceAppsSecrets(ctx context.Context, sourceApps * } func (r *Reconciler) populateSourceJobsSecrets(ctx context.Context, sourceJobs *source.SourceJobs) error { - secretItems, err := r.secretClient.ListItems(ctx) - if err != nil { - return err - } - - for _, secretName := range sourceJobs.GetUniqueRemoteSecretNames() { - item, ok := secretItems.Get(secretName) - if !ok { - return fmt.Errorf("secret not found %q", secretName) - } - - if r.secretCache.NeedsUpdate(secretName, item.LastChange()) { - secretValue, changedAt, err := r.secretClient.Get(ctx, secretName) - if err != nil { - return err - } - r.secretCache.Set(secretName, secretValue, changedAt) - } - } - for _, name := range sourceJobs.GetSortedNames() { job, _ := sourceJobs.Get(name) for i, remoteSecret := range job.GetRemoteSecrets() { @@ -546,7 +546,7 @@ func (r *Reconciler) populateSourceJobsSecrets(ctx context.Context, sourceJobs * return fmt.Errorf("unable to get secret %d for job %q from cache", i, name) } - err = sourceJobs.SetSecret(name, *remoteSecret.SecretName, secretValue) + err := sourceJobs.SetSecret(name, *remoteSecret.SecretName, secretValue) if err != nil { return fmt.Errorf("unable to set secret %q for job %q", *remoteSecret.SecretName, name) } @@ -618,14 +618,24 @@ func (r *Reconciler) sendNotification(ctx context.Context, revision string, reco Description: description, } - if r.previousNotificationEvent.Equal(event) { - log.V(1).Info("skipping notification, events are equal", "current_event", event, "previous_event", r.previousNotificationEvent) + previousNotificationEvent, found, err := r.notificationCache.Get(ctx) + if err != nil { + log.V(1).Error(err, "unable to get previous notification event from cache, received error", "event", event) + return err + } + + if found && previousNotificationEvent.Equal(event) { + log.V(1).Info("skipping notification, events are equal", "current_event", event, "previous_event", previousNotificationEvent) return nil } - r.previousNotificationEvent = event + err = r.notificationCache.Set(ctx, event) + if err != nil { + log.V(1).Error(err, "unable to set current notification event in cache, received error", "event", event) + return err + } - err := r.notificationClient.Send(ctx, event) + err = r.notificationClient.Send(ctx, event) if err != nil { log.V(1).Error(err, "unable to send event, received error", "event", event) return err diff --git a/src/reconcile/reconcile_test.go b/src/reconcile/reconcile_test.go index a91b84c..d1bac7b 100644 --- a/src/reconcile/reconcile_test.go +++ b/src/reconcile/reconcile_test.go @@ -26,13 +26,14 @@ func TestReconciler(t *testing.T) { secretClient := secret.NewInMemSecret() notificationClient := notification.NewInMemNotification() metricsClient := metrics.NewInMemMetrics() - appCache := cache.NewAppCache() - jobCache := cache.NewJobCache() - secretCache := cache.NewSecretCache() + appCache := cache.NewInMemAppCache() + jobCache := cache.NewInMemJobCache() + secretCache := cache.NewInMemSecretCache() + notificationCache := cache.NewInMemNotificationCache() ctx := context.Background() - reconciler, err := NewReconciler(config.Config{}, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache) + reconciler, err := NewReconciler(config.ReconcileConfig{}, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache, notificationCache) require.NoError(t, err) resetClients := func() { @@ -55,7 +56,7 @@ func TestReconciler(t *testing.T) { notificationClient.SendResponse(nil) notificationClient.ResetNotifications() metricsClient.Reset() - reconciler.previousNotificationEvent = notification.NotificationEvent{} + notificationCache.Reset() } t.Run("everything is nil", func(t *testing.T) { @@ -1027,12 +1028,12 @@ func TestReconciler(t *testing.T) { t.Run("test populate registry", func(t *testing.T) { defer resetClients() - cfg := config.Config{ + cfg := config.ReconcileConfig{ ContainerRegistryServer: "foobar.io", ContainerRegistryUsername: "foo", ContainerRegistryPassword: "bar", } - reconciler, err := NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache) + reconciler, err := NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache, notificationCache) require.NoError(t, err) sourceClient.GetResponse(&source.Sources{ Apps: &source.SourceApps{ @@ -1248,10 +1249,10 @@ func TestReconciler(t *testing.T) { t.Run("test locationFilter", func(t *testing.T) { defer resetClients() - cfg := config.Config{ + cfg := config.ReconcileConfig{ Location: "foobar", } - reconciler, err := NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache) + reconciler, err := NewReconciler(cfg, sourceClient, remoteAppClient, remoteJobClient, secretClient, notificationClient, metricsClient, appCache, jobCache, secretCache, notificationCache) require.NoError(t, err) sourceClient.GetResponse(&source.Sources{ diff --git a/src/remote/azureapp.go b/src/remote/azureapp.go index bba6d19..06644a7 100644 --- a/src/remote/azureapp.go +++ b/src/remote/azureapp.go @@ -18,7 +18,7 @@ type AzureApp struct { var _ App = (*AzureApp)(nil) -func NewAzureApp(cfg config.Config, cred azcore.TokenCredential) (*AzureApp, error) { +func NewAzureApp(cfg config.ReconcileConfig, cred azcore.TokenCredential) (*AzureApp, error) { client, err := armappcontainers.NewContainerAppsClient(cfg.SubscriptionID, cred, nil) if err != nil { return nil, err diff --git a/src/remote/azurejob.go b/src/remote/azurejob.go index a82b862..8c885fc 100644 --- a/src/remote/azurejob.go +++ b/src/remote/azurejob.go @@ -18,7 +18,7 @@ type AzureJob struct { var _ Job = (*AzureJob)(nil) -func NewAzureJob(cfg config.Config, cred azcore.TokenCredential) (*AzureJob, error) { +func NewAzureJob(cfg config.ReconcileConfig, cred azcore.TokenCredential) (*AzureJob, error) { client, err := armappcontainers.NewJobsClient(cfg.SubscriptionID, cred, nil) if err != nil { return nil, err diff --git a/src/secret/keyvault.go b/src/secret/keyvault.go index a8cde61..dc3cb3f 100644 --- a/src/secret/keyvault.go +++ b/src/secret/keyvault.go @@ -16,7 +16,7 @@ type KeyVaultSecret struct { var _ Secret = (*KeyVaultSecret)(nil) -func NewKeyVaultSecret(cfg config.Config, cred azcore.TokenCredential) (*KeyVaultSecret, error) { +func NewKeyVaultSecret(cfg config.ReconcileConfig, cred azcore.TokenCredential) (*KeyVaultSecret, error) { vaultUrl := fmt.Sprintf("https://%s.vault.azure.net", cfg.KeyVaultName) client, err := azsecrets.NewClient(vaultUrl, cred, nil) if err != nil { diff --git a/src/source/app.go b/src/source/app.go index 0a6a568..1488f5b 100644 --- a/src/source/app.go +++ b/src/source/app.go @@ -213,7 +213,7 @@ func validateJsonIsAppKind(j []byte) (bool, error) { return true, nil } -func (app *SourceApp) Unmarshal(y []byte, cfg config.Config) (bool, error) { +func (app *SourceApp) Unmarshal(y []byte, cfg config.ReconcileConfig) (bool, error) { j, err := yaml.YAMLToJSON(y) if err != nil { return true, err @@ -317,7 +317,7 @@ func (app *SourceApp) ShoudRunInLocation(currentLocation string) bool { type SourceApps map[string]SourceApp -func (apps *SourceApps) Unmarshal(path string, y []byte, cfg config.Config) { +func (apps *SourceApps) Unmarshal(path string, y []byte, cfg config.ReconcileConfig) { if apps == nil { apps = toPtr(make(SourceApps)) } diff --git a/src/source/app_test.go b/src/source/app_test.go index d682cad..5093316 100644 --- a/src/source/app_test.go +++ b/src/source/app_test.go @@ -291,7 +291,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) app := SourceApp{} - isContainerApp, err := app.Unmarshal([]byte(c.rawYaml), config.Config{ + isContainerApp, err := app.Unmarshal([]byte(c.rawYaml), config.ReconcileConfig{ Location: "ze-location", ManagedEnvironmentID: "ze-managedEnvironmentID", }) @@ -477,7 +477,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) apps := SourceApps{} - apps.Unmarshal("foobar/baz.yaml", []byte(c.rawYaml), config.Config{ + apps.Unmarshal("foobar/baz.yaml", []byte(c.rawYaml), config.ReconcileConfig{ Location: "ze-location", ManagedEnvironmentID: "ze-managedEnvironmentID", }) @@ -830,7 +830,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) app := &SourceApp{} - isContainerApp, err := app.Unmarshal([]byte(c.input), config.Config{ + isContainerApp, err := app.Unmarshal([]byte(c.input), config.ReconcileConfig{ ManagedEnvironmentID: "ze-me-id", Location: "zefakeregion", }) diff --git a/src/source/common.go b/src/source/common.go index 6f67534..cd3d4fd 100644 --- a/src/source/common.go +++ b/src/source/common.go @@ -36,7 +36,7 @@ func sanitizeAzureLocation(filter LocationFilterSpecification) LocationFilterSpe return LocationFilterSpecification(lowercaseFilter) } -func getSourcesFromFiles(yamlFiles *map[string][]byte, cfg config.Config) *Sources { +func getSourcesFromFiles(yamlFiles *map[string][]byte, cfg config.ReconcileConfig) *Sources { apps := &SourceApps{} for path := range *yamlFiles { content := (*yamlFiles)[path] diff --git a/src/source/git.go b/src/source/git.go index 885a226..4ec73b0 100644 --- a/src/source/git.go +++ b/src/source/git.go @@ -12,20 +12,21 @@ import ( "github.com/fluxcd/pkg/git/gogit" "github.com/fluxcd/pkg/git/repository" "github.com/go-logr/logr" + "github.com/xenitab/azcagit/src/cache" "github.com/xenitab/azcagit/src/config" ) type GitSource struct { - cfg config.Config - lastRevision string + cfg config.ReconcileConfig + revisionCache cache.RevisionCache } var _ Source = (*GitSource)(nil) -func NewGitSource(cfg config.Config) (*GitSource, error) { +func NewGitSource(cfg config.ReconcileConfig, revisionCache cache.RevisionCache) (*GitSource, error) { return &GitSource{ - cfg: cfg, - lastRevision: "", + cfg, + revisionCache, }, nil } @@ -91,9 +92,19 @@ func (s *GitSource) checkout(ctx context.Context) (*map[string][]byte, string, e revision := commit.Hash.String() log.V(1).Info("current revision", "revision", revision) - if revision != s.lastRevision { - log.Info("new commit hash", "new_revision", revision, "last_revision", s.lastRevision) - s.lastRevision = revision + + lastRevision, err := s.revisionCache.Get(ctx) + if err != nil { + return nil, "", err + } + + if revision != lastRevision { + log.Info("new commit hash", "new_revision", revision, "last_revision", lastRevision) + + err := s.revisionCache.Set(ctx, revision) + if err != nil { + return nil, revision, err + } } yamlPath := filepath.Clean(tmpDir) diff --git a/src/source/git_test.go b/src/source/git_test.go index e65a80b..5b1a643 100644 --- a/src/source/git_test.go +++ b/src/source/git_test.go @@ -29,6 +29,7 @@ import ( "github.com/fluxcd/pkg/git/repository" "github.com/fluxcd/pkg/gittestserver" "github.com/stretchr/testify/require" + "github.com/xenitab/azcagit/src/cache" "github.com/xenitab/azcagit/src/config" ) @@ -94,12 +95,13 @@ func TestGitSource(t *testing.T) { require.NoError(t, err) repoURL := server.HTTPAddress() + "/" + repoPath - sourceClient, err := NewGitSource(config.Config{ + revisionCache := cache.NewInMemRevisionCache() + sourceClient, err := NewGitSource(config.ReconcileConfig{ GitUrl: repoURL, GitBranch: defaultBranch, ManagedEnvironmentID: "ze-managed-id", Location: "ze-location", - }) + }, revisionCache) require.NoError(t, err) tmp := t.TempDir() diff --git a/src/source/job.go b/src/source/job.go index 0e6fe52..8ec2ea6 100644 --- a/src/source/job.go +++ b/src/source/job.go @@ -213,7 +213,7 @@ func validateJsonIsJobKind(j []byte) (bool, error) { return true, nil } -func (job *SourceJob) Unmarshal(y []byte, cfg config.Config) (bool, error) { +func (job *SourceJob) Unmarshal(y []byte, cfg config.ReconcileConfig) (bool, error) { j, err := yaml.YAMLToJSON(y) if err != nil { return true, err @@ -317,7 +317,7 @@ func (job *SourceJob) ShoudRunInLocation(currentLocation string) bool { type SourceJobs map[string]SourceJob -func (jobs *SourceJobs) Unmarshal(path string, y []byte, cfg config.Config) { +func (jobs *SourceJobs) Unmarshal(path string, y []byte, cfg config.ReconcileConfig) { if jobs == nil { jobs = toPtr(make(SourceJobs)) } diff --git a/src/source/job_test.go b/src/source/job_test.go index 074c016..504fe2f 100644 --- a/src/source/job_test.go +++ b/src/source/job_test.go @@ -278,7 +278,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) job := SourceJob{} - isContainerJob, err := job.Unmarshal([]byte(c.rawYaml), config.Config{ + isContainerJob, err := job.Unmarshal([]byte(c.rawYaml), config.ReconcileConfig{ Location: "ze-location", ManagedEnvironmentID: "ze-EnvironmentID", }) @@ -464,7 +464,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) jobs := SourceJobs{} - jobs.Unmarshal("foobar/baz.yaml", []byte(c.rawYaml), config.Config{ + jobs.Unmarshal("foobar/baz.yaml", []byte(c.rawYaml), config.ReconcileConfig{ Location: "ze-location", ManagedEnvironmentID: "ze-EnvironmentID", }) @@ -817,7 +817,7 @@ spec: for i, c := range cases { t.Logf("Test #%d: %s", i, c.testDescription) job := &SourceJob{} - isContainerJob, err := job.Unmarshal([]byte(c.input), config.Config{ + isContainerJob, err := job.Unmarshal([]byte(c.input), config.ReconcileConfig{ ManagedEnvironmentID: "ze-me-id", Location: "zefakeregion", }) diff --git a/src/source/source.go b/src/source/source.go index 41ed924..0557874 100644 --- a/src/source/source.go +++ b/src/source/source.go @@ -2,6 +2,7 @@ package source import ( "context" + "sort" ) type Sources struct { @@ -9,6 +10,34 @@ type Sources struct { Jobs *SourceJobs } +func (srcs *Sources) GetUniqueRemoteSecretNames() []string { + secretsMap := make(map[string]struct{}) + + if srcs == nil { + return nil + } + + if srcs.Apps != nil { + for _, remoteSecretName := range srcs.Apps.GetUniqueRemoteSecretNames() { + secretsMap[remoteSecretName] = struct{}{} + } + } + + if srcs.Jobs != nil { + for _, remoteSecretName := range srcs.Jobs.GetUniqueRemoteSecretNames() { + secretsMap[remoteSecretName] = struct{}{} + } + } + + secrets := []string{} + for secret := range secretsMap { + secrets = append(secrets, secret) + } + sort.Strings(secrets) + + return secrets +} + type Source interface { Get(ctx context.Context) (*Sources, string, error) } diff --git a/src/trigger/daprsub.go b/src/trigger/daprsub.go deleted file mode 100644 index e6f6281..0000000 --- a/src/trigger/daprsub.go +++ /dev/null @@ -1,81 +0,0 @@ -package trigger - -import ( - "context" - "fmt" - "net/http" - - "github.com/dapr/go-sdk/service/common" - daprd "github.com/dapr/go-sdk/service/http" - - "github.com/xenitab/azcagit/src/config" -) - -type DaprSubTrigger struct { - service common.Service - triggerCh chan TriggeredBy -} - -var _ Trigger = (*DaprSubTrigger)(nil) - -var TriggeredByDaprSub TriggeredBy = "DaprSub" - -func NewDaprSubTrigger(cfg config.Config) (*DaprSubTrigger, error) { - service := daprd.NewService(fmt.Sprintf(":%d", cfg.DaprAppPort)) - triggerCh := make(chan TriggeredBy) - trigger := &DaprSubTrigger{ - service, - triggerCh, - } - - var subscription = &common.Subscription{ - PubsubName: cfg.DaprPubsubName, - Topic: cfg.DaprTopic, - Route: "/trigger", - } - - err := service.AddTopicEventHandler(subscription, trigger.triggerHandler) - if err != nil { - return nil, err - } - - return trigger, nil -} - -func (t *DaprSubTrigger) WaitForTrigger() <-chan TriggeredBy { - return t.triggerCh -} - -func (t *DaprSubTrigger) Start() error { - if err := t.service.Start(); err != nil && err != http.ErrServerClosed { - return err - } - return nil -} - -func (t *DaprSubTrigger) Stop() error { - return t.service.GracefulStop() -} - -func (t *DaprSubTrigger) triggerHandler(ctx context.Context, e *common.TopicEvent) (bool, error) { - triggerData := struct { - Trigger *bool `json:"trigger"` - }{} - - err := e.Struct(&triggerData) - if err != nil { - return false, err - } - - if triggerData.Trigger == nil { - return false, fmt.Errorf("trigger data not set") - } - - if !*triggerData.Trigger { - return false, fmt.Errorf("trigger data set to false") - } - - t.triggerCh <- TriggeredByDaprSub - - return false, nil -} diff --git a/src/trigger/daprsub_test.go b/src/trigger/daprsub_test.go deleted file mode 100644 index 04c3ae3..0000000 --- a/src/trigger/daprsub_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package trigger - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/require" - "github.com/xenitab/azcagit/src/config" - "golang.org/x/sync/errgroup" - - "github.com/dapr/go-sdk/service/common" -) - -func TestDaprSubTrigger(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - g, ctx := errgroup.WithContext(ctx) - cfg := config.Config{ - DaprAppPort: 8080, - DaprPubsubName: "sb", - DaprTopic: "azcagit_trigger", - } - trigger, err := NewDaprSubTrigger(cfg) - require.NoError(t, err) - - g.Go(func() error { - return trigger.Start() - }) - - for start := time.Now(); time.Since(start) < 5*time.Second; { - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", cfg.DaprAppPort)) - if err == nil { - conn.Close() - break - } - time.Sleep(10 * time.Millisecond) - } - - gClient, ctxClient := errgroup.WithContext(ctx) - httpClient := &http.Client{} - gClient.Go(func() error { - triggerData := struct { - Trigger bool `json:"trigger"` - }{ - Trigger: true, - } - event := &common.TopicEvent{ - PubsubName: "test_name", - ID: "test_id", - Data: triggerData, - } - b, err := json.Marshal(event) - require.NoError(t, err) - t.Logf("Event: %v", string(b)) - req, err := http.NewRequestWithContext(ctxClient, http.MethodPost, "http://localhost:8080/trigger", bytes.NewBuffer(b)) - require.NoError(t, err) - req.Header.Add("Content-Type", "application/json") - - res, err := httpClient.Do(req) - require.NoError(t, err) - b, err = io.ReadAll(res.Body) - require.NoError(t, err) - defer res.Body.Close() - - resData := struct { - Status string `json:"status"` - }{} - - err = json.Unmarshal(b, &resData) - require.NoError(t, err) - - require.Equal(t, "SUCCESS", resData.Status) - - return err - }) - - timeout := time.NewTimer(50 * time.Millisecond) - select { - case <-timeout.C: - t.Logf("Waiting for trigger timed out") - t.Fail() - case triggeredBy := <-trigger.WaitForTrigger(): - t.Logf("Received trigger: %s", triggeredBy) - require.Equal(t, TriggeredBy("DaprSub"), triggeredBy) - } - - require.NoError(t, gClient.Wait()) - - g.Go(func() error { - return trigger.Stop() - }) - - err = g.Wait() - require.NoError(t, err) -} diff --git a/src/trigger/trigger.go b/src/trigger/trigger.go deleted file mode 100644 index 67ba7fd..0000000 --- a/src/trigger/trigger.go +++ /dev/null @@ -1,11 +0,0 @@ -package trigger - -type Trigger interface { - WaitForTrigger() <-chan TriggeredBy - Start() error - Stop() error -} - -type TriggeredBy string - -var TriggeredByTicker TriggeredBy = "Ticker" diff --git a/terraform-module/azcagit.tf b/terraform-module/azcagit.tf index 7993f16..bfbe756 100644 --- a/terraform-module/azcagit.tf +++ b/terraform-module/azcagit.tf @@ -42,86 +42,199 @@ resource "azurerm_role_assignment" "azcagit_tenant" { principal_id = azuread_service_principal.azcagit.object_id } -resource "azurerm_container_app" "azcagit" { - name = "azcagit" - container_app_environment_id = azurerm_container_app_environment.this.id - resource_group_name = azurerm_resource_group.platform.name - revision_mode = "Single" +resource "azapi_resource" "azcagit_schedule" { + schema_validation_enabled = false - template { - container { - name = "azcagit" - image = "ghcr.io/xenitab/azcagit:${var.azcagit_version}" - args = [ - "--resource-group-name", azurerm_resource_group.tenant.name, - "--environment", var.environment, - "--subscription-id", data.azurerm_client_config.current.subscription_id, - "--managed-environment-id", azurerm_container_app_environment.this.id, - "--key-vault-name", azurerm_key_vault.tenant_kv.name, - "--own-resource-group-name", azurerm_resource_group.platform.name, - "--container-registry-server", azurerm_container_registry.tenant.login_server, - "--container-registry-username", azurerm_container_registry.tenant.admin_username, - "--location", azurerm_resource_group.tenant.location, - "--dapr-topic-name", azurerm_servicebus_topic.azcagit_trigger.name, - "--reconcile-interval", "5m", - "--git-branch", var.git_config.branch, - "--git-yaml-path", var.git_config.path, - "--notifications-enabled" - ] + type = "Microsoft.App/jobs@2023-04-01-preview" + name = "azcagit-reconcile" + location = azurerm_resource_group.platform.location + parent_id = azurerm_resource_group.platform.id - env { - name = "GIT_URL" - secret_name = "git-url" - } - env { - name = "CONTAINER_REGISTRY_PASSWORD" - secret_name = "container-registry-password" - } - env { - name = "AZURE_TENANT_ID" - secret_name = "azure-tenant-id" - } - env { - name = "AZURE_CLIENT_ID" - secret_name = "azure-client-id" - } - env { - name = "AZURE_CLIENT_SECRET" - secret_name = "azure-client-secret" + body = jsonencode({ + properties = { + configuration = { + replicaRetryLimit = 1 + replicaTimeout = 600 + scheduleTriggerConfig = { + cronExpression = "*/5 * * * *" + parallelism = 1 + replicaCompletionCount = 1 + } + secrets = [ + { + name = "git-url" + value = local.git_full_url + }, + { + name = "container-registry-password" + value = azurerm_container_registry.tenant.admin_password + }, + { + name = "azure-tenant-id" + value = data.azurerm_client_config.current.tenant_id + }, + { + name = "azure-client-id" + value = azuread_application.azcagit.application_id + }, + { + name = "azure-client-secret" + value = azuread_application_password.azcagit.value + }, + ] + triggerType = "Schedule" } + environmentId = azurerm_container_app_environment.this.id + template = { + containers = [ + { + name = "azcagit" + image = "ghcr.io/xenitab/azcagit:${var.azcagit_version}" + args = [ + "reconcile", + "--resource-group-name", azurerm_resource_group.tenant.name, + "--environment", var.environment, + "--subscription-id", data.azurerm_client_config.current.subscription_id, + "--managed-environment-id", azurerm_container_app_environment.this.id, + "--key-vault-name", azurerm_key_vault.tenant_kv.name, + "--own-resource-group-name", azurerm_resource_group.platform.name, + "--container-registry-server", azurerm_container_registry.tenant.login_server, + "--container-registry-username", azurerm_container_registry.tenant.admin_username, + "--cosmosdb-account", azurerm_cosmosdb_account.this.name, + "--location", azurerm_resource_group.tenant.location, + "--git-branch", var.git_config.branch, + "--git-yaml-path", var.git_config.path, + "--notifications-enabled" + ] + env = [ + { + name = "GIT_URL" + secretRef = "git-url" + }, + { + name = "CONTAINER_REGISTRY_PASSWORD" + secretRef = "container-registry-password" + }, + { + name = "AZURE_TENANT_ID" + secretRef = "azure-tenant-id" + }, + { + name = "AZURE_CLIENT_ID" + secretRef = "azure-client-id" + }, + { + name = "AZURE_CLIENT_SECRET" + secretRef = "azure-client-secret" + }, + ] - memory = "0.5Gi" - cpu = "0.25" + resources = { + cpu = "0.25" + memory = "0.5Gi" + } + } + ] + } } + }) +} - min_replicas = 1 - max_replicas = 1 - } +resource "azapi_resource" "azcagit_trigger" { + schema_validation_enabled = false - secret { - name = "git-url" - value = local.git_full_url - } - secret { - name = "container-registry-password" - value = azurerm_container_registry.tenant.admin_password - } - secret { - name = "azure-tenant-id" - value = data.azurerm_client_config.current.tenant_id - } - secret { - name = "azure-client-id" - value = azuread_application.azcagit.application_id - } - secret { - name = "azure-client-secret" - value = azuread_application_password.azcagit.value - } + type = "Microsoft.App/jobs@2023-04-01-preview" + name = "azcagit-trigger" + location = azurerm_resource_group.platform.location + parent_id = azurerm_resource_group.platform.id - dapr { - app_id = "azcagit" - app_port = 8080 - app_protocol = "http" - } + body = jsonencode({ + properties = { + configuration = { + replicaRetryLimit = 1 + replicaTimeout = 600 + eventTriggerConfig = { + replicaCompletionCount : 1 + parallelism : 1 + scale : { + maxExecutions : 1 + minExecutions : 0 + pollingInterval : 5 + rules : [ + { + name = "azure-servicebus-queue-rule" + type = "azure-servicebus" + metadata = { + messageCount : "1" + namespace : azurerm_servicebus_namespace.azcagit_trigger.name + queueName : azurerm_servicebus_queue.azcagit_trigger.name + } + auth = [ + { + secretRef = "service-bus-connection-string" + triggerParameter = "connection" + } + ] + } + ] + } + } + secrets = [ + { + name = "azure-tenant-id" + value = data.azurerm_client_config.current.tenant_id + }, + { + name = "azure-client-id" + value = azuread_application.azcagit.application_id + }, + { + name = "azure-client-secret" + value = azuread_application_password.azcagit.value + }, + { + name = "service-bus-connection-string" + value = azurerm_servicebus_namespace.azcagit_trigger.default_primary_connection_string + }, + ] + triggerType = "Event" + } + environmentId = azurerm_container_app_environment.this.id + template = { + containers = [ + { + name = "azcagit" + image = "ghcr.io/xenitab/azcagit:${var.azcagit_version}" + args = [ + "trigger", + "--subscription-id", data.azurerm_client_config.current.subscription_id, + "--job-name", azapi_resource.azcagit_schedule.name, + "--resource-group-name", azurerm_resource_group.platform.name, + "--service-bus-namespace", azurerm_servicebus_namespace.azcagit_trigger.name, + "--service-bus-queue", azurerm_servicebus_queue.azcagit_trigger.name, + ] + env = [ + { + name = "AZURE_TENANT_ID" + secretRef = "azure-tenant-id" + }, + { + name = "AZURE_CLIENT_ID" + secretRef = "azure-client-id" + }, + { + name = "AZURE_CLIENT_SECRET" + secretRef = "azure-client-secret" + }, + ] + + resources = { + cpu = "0.25" + memory = "0.5Gi" + } + } + ] + } + } + }) } diff --git a/terraform-module/main.tf b/terraform-module/main.tf index 7aa1d4e..548052f 100644 --- a/terraform-module/main.tf +++ b/terraform-module/main.tf @@ -8,6 +8,14 @@ terraform { version = "3.64.0" source = "hashicorp/azurerm" } + azapi = { + source = "Azure/azapi" + version = "1.7.0" + } + random = { + source = "hashicorp/random" + version = "3.5.1" + } } } diff --git a/terraform-module/platform.tf b/terraform-module/platform.tf index a9f6632..9035346 100644 --- a/terraform-module/platform.tf +++ b/terraform-module/platform.tf @@ -54,7 +54,6 @@ resource "azurerm_storage_account" "this" { account_tier = "Premium" account_kind = "FileStorage" account_replication_type = var.storage_configuration.account_replication_type - } resource "azurerm_storage_share" "this" { @@ -76,11 +75,11 @@ resource "azurerm_servicebus_namespace" "azcagit_trigger" { name = "sb${replace(local.eln, "-", "")}${var.unique_suffix}" location = azurerm_resource_group.platform.location resource_group_name = azurerm_resource_group.platform.name - sku = "Standard" + sku = "Basic" } resource "azuread_group" "azcagit_trigger" { - display_name = "aad-${local.eln}" + display_name = "aad-${local.eln}-trigger" security_enabled = true owners = var.aad_resource_owner_object_ids } @@ -102,28 +101,102 @@ resource "azurerm_role_assignment" "azcagit_trigger" { principal_id = azuread_group.azcagit_trigger.object_id } -resource "azurerm_servicebus_topic" "azcagit_trigger" { - name = "sbt-${local.eln}-trigger" +resource "azurerm_role_assignment" "azcagit_trigger_receiver" { + scope = azurerm_servicebus_namespace.azcagit_trigger.id + role_definition_name = "Azure Service Bus Data Receiver" + principal_id = azuread_service_principal.azcagit.object_id +} + +resource "azurerm_servicebus_queue" "azcagit_trigger" { + name = "sbq-${local.eln}-trigger" namespace_id = azurerm_servicebus_namespace.azcagit_trigger.id enable_partitioning = true } -resource "azurerm_container_app_environment_dapr_component" "azcagit_trigger" { - name = "azcagit-trigger" - container_app_environment_id = azurerm_container_app_environment.this.id - component_type = "pubsub.azure.servicebus" - version = "v1" - scopes = ["azcagit"] - secret { - name = "sb-root-connectionstring" - value = azurerm_servicebus_namespace.azcagit_trigger.default_primary_connection_string +resource "azurerm_cosmosdb_account" "this" { + name = "ca-${local.eln}-${var.unique_suffix}" + location = azurerm_resource_group.platform.location + resource_group_name = azurerm_resource_group.platform.name + offer_type = "Standard" + kind = "GlobalDocumentDB" + enable_automatic_failover = false + + consistency_policy { + consistency_level = "Session" + } + + geo_location { + location = azurerm_resource_group.platform.location + failover_priority = 0 } - metadata { - name = "connectionString" - secret_name = "sb-root-connectionstring" + capabilities { + name = "EnableServerless" + } +} + +resource "azurerm_cosmosdb_sql_database" "this" { + name = "azcagit" + resource_group_name = azurerm_resource_group.platform.name + account_name = azurerm_cosmosdb_account.this.name +} + +# Cosmos DB Built-in Data Contributor (https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#built-in-role-definitions) +# Actions: +# - Microsoft.DocumentDB/databaseAccounts/readMetadata +# - Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/* +# - Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/* +data "azurerm_cosmosdb_sql_role_definition" "data_contributor" { + resource_group_name = azurerm_resource_group.platform.name + account_name = azurerm_cosmosdb_account.this.name + role_definition_id = "00000000-0000-0000-0000-000000000002" +} + +resource "random_uuid" "azcagit_azurerm_cosmosdb_sql_role_assignment" {} + +resource "azurerm_cosmosdb_sql_role_assignment" "azcagit" { + name = random_uuid.azcagit_azurerm_cosmosdb_sql_role_assignment.result + resource_group_name = azurerm_resource_group.platform.name + account_name = azurerm_cosmosdb_account.this.name + scope = azurerm_cosmosdb_account.this.id + role_definition_id = data.azurerm_cosmosdb_sql_role_definition.data_contributor.id + principal_id = azuread_service_principal.azcagit.object_id +} + +resource "random_uuid" "current_user_azurerm_cosmosdb_sql_role_assignment" {} + +resource "azurerm_cosmosdb_sql_role_assignment" "current_user" { + for_each = { + for s in ["current"] : + s => s + if var.add_permissions_to_current_user + } + + name = random_uuid.current_user_azurerm_cosmosdb_sql_role_assignment.result + resource_group_name = azurerm_resource_group.platform.name + account_name = azurerm_cosmosdb_account.this.name + scope = azurerm_cosmosdb_account.this.id + role_definition_id = data.azurerm_cosmosdb_sql_role_definition.data_contributor.id + principal_id = data.azuread_client_config.current.object_id +} + +resource "azurerm_cosmosdb_sql_container" "cache" { + name = "cache" + resource_group_name = azurerm_resource_group.platform.name + account_name = azurerm_cosmosdb_account.this.name + database_name = azurerm_cosmosdb_sql_database.this.name + partition_key_path = "/partition_key" + partition_key_version = 1 + default_ttl = -1 + + indexing_policy { + indexing_mode = "consistent" + + included_path { + path = "/*" + } } } diff --git a/test/terraform/.terraform.lock.hcl b/test/terraform/.terraform.lock.hcl index 8017bab..2d60cf0 100644 --- a/test/terraform/.terraform.lock.hcl +++ b/test/terraform/.terraform.lock.hcl @@ -1,6 +1,26 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/azure/azapi" { + version = "1.7.0" + constraints = "1.7.0" + hashes = [ + "h1:hyHiXqegENqTo4e1ooCkay1mHhBRooHH/nGIF8PuS1w=", + "zh:39713f9aa01824a6db818dffcb2fff9175cf888c70c6805e1dced17d42dd0d24", + "zh:4bb38a1861491ea380d5fe65062b73299aab49ec5baf58ad55edd9c99b2f5072", + "zh:4d572e30973b8dd1936ee60a2fd3dab201ec55ea16521905b06ac7e8e41ecc57", + "zh:78200e4e0a0f515a0c5752cb79954a4cd06b9eb2c70241cebff7372c212e5d94", + "zh:78675e6345890d0e04041abd2c14cc5f4f011e9482484a45856f2ac7a9931e76", + "zh:a4e8ade7559febda858b406116595d8fab27290751c4b09817bbd56af79c146f", + "zh:a7761ed0fb78dfb773a81af953ba4a552642bbe76e9ad7e373e1a98875b9206f", + "zh:aaa8da2ead6eb3b37a74e4a123788c099635ee8ef6236b26a4eb6f27c1626f0b", + "zh:b1e146bee793751ee432e5eb538feda225e78c63cd79e1f3ef05ef7a5dd02b5d", + "zh:d415471c445eada01a7bedf5fe3132b937488b80d99454403991b3f96e092f85", + "zh:dc984774af24948ffe1f1611f443380e204a465e3512045b5c1e6c8e845a8d3f", + "zh:eb95f8d87116bc8b3a8b2be2d80216da21d4274329fbc69033d3c0062c3345cc", + ] +} + provider "registry.terraform.io/hashicorp/azuread" { version = "2.39.0" constraints = "2.39.0" @@ -40,3 +60,23 @@ provider "registry.terraform.io/hashicorp/azurerm" { "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + constraints = "3.5.1" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/test/terraform/main.tf b/test/terraform/main.tf index 5dc0264..7e135b2 100644 --- a/test/terraform/main.tf +++ b/test/terraform/main.tf @@ -20,12 +20,13 @@ provider "azuread" {} module "azcagit" { source = "../../terraform-module" - environment = var.environment - location_short = var.location_short - location = var.location - unique_suffix = var.unique_suffix - name = "azcagit" - git_config = var.git_config - azcagit_version = var.azcagit_version - aad_resource_owner_object_ids = var.aad_resource_owner_object_ids + environment = var.environment + location_short = var.location_short + location = var.location + unique_suffix = var.unique_suffix + name = "azcagit" + git_config = var.git_config + azcagit_version = var.azcagit_version + add_permissions_to_current_user = var.add_permissions_to_current_user + aad_resource_owner_object_ids = var.aad_resource_owner_object_ids } diff --git a/test/terraform/variables.tf b/test/terraform/variables.tf index f791835..0167f9b 100644 --- a/test/terraform/variables.tf +++ b/test/terraform/variables.tf @@ -39,6 +39,12 @@ variable "unique_suffix" { default = "" } +variable "add_permissions_to_current_user" { + description = "Enable if you want permissions be added to the current user" + type = bool + default = false +} + variable "aad_resource_owner_object_ids" { description = "Add the list of object_ids as owners to the Azure AD applications, service principals and groups" type = list(string) diff --git a/trigger-client/main.go b/trigger-client/main.go index e52d184..75a5c1d 100644 --- a/trigger-client/main.go +++ b/trigger-client/main.go @@ -32,8 +32,8 @@ func run(cfg config) error { } type config struct { - FullyQualifiedNamespace string `arg:"-n,--fully-qualified-namespace,required" help:"Service Bus Fully Qualified Namespace"` - Topic string `arg:"-t,--topic,required" help:"Service Bus Topic"` + ServiceBusNamespace string `arg:"-n,--namespace,required" help:"Service Bus namespace"` + ServiceBusQueue string `arg:"-q,--queue,required" help:"Service Bus queue"` } func newConfig(args []string) (config, error) { diff --git a/trigger-client/servicebus.go b/trigger-client/servicebus.go index 8c145be..6e3e8da 100644 --- a/trigger-client/servicebus.go +++ b/trigger-client/servicebus.go @@ -3,10 +3,10 @@ package main import ( "context" "encoding/json" + "fmt" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" - "github.com/dapr/go-sdk/service/common" ) type serviceBusClient struct { @@ -20,12 +20,13 @@ func newServiceBusClient(cfg config) (*serviceBusClient, error) { return nil, err } - client, err := azservicebus.NewClient(cfg.FullyQualifiedNamespace, credential, nil) + namespaceFqdn := fmt.Sprintf("%s.servicebus.windows.net", cfg.ServiceBusNamespace) + client, err := azservicebus.NewClient(namespaceFqdn, credential, nil) if err != nil { return nil, err } - sender, err := client.NewSender(cfg.Topic, &azservicebus.NewSenderOptions{}) + sender, err := client.NewSender(cfg.ServiceBusQueue, &azservicebus.NewSenderOptions{}) if err != nil { return nil, err } @@ -42,13 +43,8 @@ func (c *serviceBusClient) Trigger(ctx context.Context) error { }{ Trigger: true, } - event := &common.TopicEvent{ - PubsubName: "azcagit_trigger", - ID: "azcagit_trigger", - Data: triggerData, - } - b, err := json.Marshal(event) + b, err := json.Marshal(triggerData) if err != nil { return err }