state.sqlserver: Add support for Azure AD auth (+ metadata.yaml) (#2790)

Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
Signed-off-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
Co-authored-by: Deepanshu Agarwal <deepanshu.agarwal1984@gmail.com>
This commit is contained in:
Alessandro (Ale) Segala 2023-04-19 10:26:24 -07:00 committed by GitHub
parent 289c784a6a
commit 6b0dedf772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 707 additions and 500 deletions

2
go.mod
View File

@ -52,7 +52,6 @@ require (
github.com/cyphar/filepath-securejoin v0.2.3
github.com/dancannon/gorethink v4.0.0+incompatible
github.com/dapr/kit v0.0.5-0.20230401092230-30d122f67bdc
github.com/denisenkom/go-mssqldb v0.12.3
github.com/didip/tollbooth/v7 v7.0.1
github.com/eclipse/paho.mqtt.golang v1.4.2
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5
@ -83,6 +82,7 @@ require (
github.com/lestrrat-go/jwx/v2 v2.0.9
github.com/machinebox/graphql v0.2.2
github.com/matoous/go-nanoid/v2 v2.0.0
github.com/microsoft/go-mssqldb v0.21.0
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
github.com/mrz1836/postmark v1.4.0
github.com/nacos-group/nacos-sdk-go/v2 v2.1.3

26
go.sum
View File

@ -418,10 +418,11 @@ github.com/AthenZ/athenz v1.10.39 h1:mtwHTF/v62ewY2Z5KWhuZgVXftBej1/Tn80zx4DcawY
github.com/AthenZ/athenz v1.10.39/go.mod h1:3Tg8HLsiQZp81BJY58JBeU2BR6B/H4/0MQGfCwhHNEA=
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 v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0-beta.1 h1:yLM4ZIC+NRvzwFGpXjUbf5FhPBVxJgmYXkjePgNAx64=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0-beta.1/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.4 h1:jpSh2461XzXBEw1MJwvVRJwZS0CAgqS0h6jBdoIFtLk=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.4/go.mod h1:oWa/ZXP08smIi12UyWVbVikBxoZHZCyxijZamTK1i8Q=
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0 h1:OrKZybbyagpgJiREiIVzH5mV/z9oS4rXqdX7i31DSF0=
@ -430,7 +431,7 @@ github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3 h1:x1shk+tVZ6kLwIQMn4
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3/go.mod h1:Fy3bbChFm4cZn6oIxYYqKB2FG3rBDxk3NZDLDJCHl+Q=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1 h1:bFa9IcjvrCber6gGgDAUZ+I2bO8J7s8JxXmu9fhi2ss=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1/go.mod h1:l3wvZkG9oW07GLBW5Cd0WwG5asOfJ8aqE8raUvNzLpk=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA=
@ -453,6 +454,7 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0 h1:TOtQFiO403wClfrZ
github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0/go.mod h1:U0IH4deB/maBcagR9SiNeIfgZ1BY/zYCq8SOiQ4vfRc=
github.com/Azure/go-amqp v0.18.1 h1:D5Ca+uijuTcj5g76sF+zT4OQZcFFY397+IGf/5Ip5Sc=
github.com/Azure/go-amqp v0.18.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 h1:UE9n9rkJF62ArLb1F3DEjRt8O3jLwMWdSoypKV4f3MU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -790,8 +792,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2U
github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU=
github.com/deepmap/oapi-codegen v1.11.0 h1:f/X2NdIkaBKsSdpeuwLnY/vDI0AtPUrmB5LMgc7YD+A=
github.com/deepmap/oapi-codegen v1.11.0/go.mod h1:k+ujhoQGxmQYBZBbxhOZNZf4j08qv5mC+OH+fFTnKxM=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -804,6 +804,7 @@ github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulV
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA=
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@ -1011,6 +1012,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
@ -1319,10 +1321,12 @@ github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFK
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8=
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
@ -1502,6 +1506,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/microsoft/go-mssqldb v0.21.0 h1:p2rpHIL7TlSv1QrbXJUAcbyRKnIT0C9rRkH2E4OjLn8=
github.com/microsoft/go-mssqldb v0.21.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
@ -1662,7 +1668,7 @@ github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDm
github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -2093,7 +2099,7 @@ golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@ -2103,6 +2109,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@ -2211,6 +2218,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -2226,7 +2234,6 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -2418,6 +2425,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -2888,6 +2896,8 @@ gopkg.in/kataras/go-serializer.v0 v0.0.4 h1:mVy3gjU4zZZBe+8JbZDRTMPJdrB0lzBNsLLR
gopkg.in/kataras/go-serializer.v0 v0.0.4/go.mod h1:v2jHg/3Wp7uncDNzenTsX75PRDxhzlxoo/qDvM4ZGxk=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

View File

@ -151,10 +151,7 @@ func (s EnvironmentSettings) GetTokenCredential() (azcore.TokenCredential, error
if file, ok := os.LookupEnv(azureFederatedTokenFile); ok {
if _, ok := os.LookupEnv(azureAuthorityHost); ok {
if tenantID, ok := os.LookupEnv(azureTenantID); ok {
workloadCred, err := azidentity.NewWorkloadIdentityCredential(tenantID, clientID, file, &azidentity.WorkloadIdentityCredentialOptions{
ClientOptions: policy.ClientOptions{},
},
)
workloadCred, err := azidentity.NewWorkloadIdentityCredential(tenantID, clientID, file, nil)
if err == nil {
creds = append(creds, workloadCred)
} else {

View File

@ -0,0 +1,36 @@
/*
Copyright 2021 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
)
const (
// Service configuration for Azure SQL. Namespaced with dapr.io
ServiceAzureSQL cloud.ServiceName = "dapr.io/azuresql"
)
func init() {
// Register additional services in the SDK's clouds
cloud.AzureChina.Services[ServiceAzureSQL] = cloud.ServiceConfiguration{
Audience: "https://database.chinacloudapi.cn",
}
cloud.AzureGovernment.Services[ServiceAzureSQL] = cloud.ServiceConfiguration{
Audience: "https://database.usgovcloudapi.net",
}
cloud.AzurePublic.Services[ServiceAzureSQL] = cloud.ServiceConfiguration{
Audience: "https://database.windows.net",
}
}

298
state/sqlserver/metadata.go Normal file
View File

@ -0,0 +1,298 @@
/*
Copyright 2023 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sqlserver
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"unicode"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
mssql "github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-mssqldb/msdsn"
"github.com/dapr/components-contrib/internal/authentication/azure"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/kit/ptr"
)
const (
connectionStringKey = "connectionString"
tableNameKey = "tableName"
metadataTableNameKey = "metadataTableName"
schemaKey = "schema"
keyTypeKey = "keyType"
keyLengthKey = "keyLength"
indexedPropertiesKey = "indexedProperties"
keyColumnName = "Key"
rowVersionColumnName = "RowVersion"
databaseNameKey = "databaseName"
cleanupIntervalKey = "cleanupIntervalInSeconds"
defaultKeyLength = 200
defaultSchema = "dbo"
defaultDatabase = "dapr"
defaultTable = "state"
defaultMetaTable = "dapr_metadata"
defaultCleanupInterval = time.Hour
)
type sqlServerMetadata struct {
ConnectionString string
DatabaseName string
TableName string
MetadataTableName string
Schema string
KeyType string
KeyLength int
IndexedProperties string
CleanupInterval *time.Duration `mapstructure:"cleanupIntervalInSeconds"`
UseAzureAD bool `mapstructure:"useAzureAD"`
// Internal properties
keyTypeParsed KeyType
keyLengthParsed int
indexedPropertiesParsed []IndexedProperty
azureEnv azure.EnvironmentSettings
}
func newMetadata() sqlServerMetadata {
return sqlServerMetadata{
TableName: defaultTable,
Schema: defaultSchema,
DatabaseName: defaultDatabase,
KeyLength: defaultKeyLength,
MetadataTableName: defaultMetaTable,
CleanupInterval: ptr.Of(defaultCleanupInterval),
}
}
func (m *sqlServerMetadata) Parse(meta map[string]string) error {
err := metadata.DecodeMetadata(meta, &m)
if err != nil {
return err
}
if m.ConnectionString == "" {
return errors.New("missing connection string")
}
if !isValidSQLName(m.TableName) {
return fmt.Errorf("invalid table name, accepted characters are (A-Z, a-z, 0-9, _)")
}
if !isValidSQLName(m.MetadataTableName) {
return fmt.Errorf("invalid metadata table name, accepted characters are (A-Z, a-z, 0-9, _)")
}
if !isValidSQLName(m.DatabaseName) {
return fmt.Errorf("invalid database name, accepted characters are (A-Z, a-z, 0-9, _)")
}
err = m.setKeyType()
if err != nil {
return err
}
if !isValidSQLName(m.Schema) {
return fmt.Errorf("invalid schema name, accepted characters are (A-Z, a-z, 0-9, _)")
}
err = m.setIndexedProperties()
if err != nil {
return err
}
// Cleanup interval
if m.CleanupInterval != nil {
// Non-positive value from meta means disable auto cleanup.
if *m.CleanupInterval <= 0 {
if meta[cleanupIntervalKey] == "" {
// Unfortunately the mapstructure decoder decodes an empty string to 0, a missing key would be nil however
m.CleanupInterval = ptr.Of(defaultCleanupInterval)
} else {
m.CleanupInterval = nil
}
}
}
// If using Azure AD
if m.UseAzureAD {
m.azureEnv, err = azure.NewEnvironmentSettings(meta)
if err != nil {
return err
}
}
return nil
}
// GetConnector returns the connector from the connection string or Azure AD.
// The returned connector can be used with sql.OpenDB.
func (m *sqlServerMetadata) GetConnector(setDatabase bool) (*mssql.Connector, bool, error) {
// Parse the connection string
config, err := msdsn.Parse(m.ConnectionString)
if err != nil {
return nil, false, fmt.Errorf("failed to parse connection string: %w", err)
}
// If setDatabase is true and the configuration (i.e. the connection string) does not contain a database, add it
// This is for backwards-compatibility reasons
if setDatabase && config.Database == "" {
config.Database = m.DatabaseName
}
// We need to check if the configuration has a database because the migrator needs it
hasDatabase := config.Database != ""
// Configure Azure AD authentication if needed
if m.UseAzureAD {
tokenCred, errToken := m.azureEnv.GetTokenCredential()
if errToken != nil {
return nil, false, errToken
}
conn, err := mssql.NewSecurityTokenConnector(config, func(ctx context.Context) (string, error) {
at, err := tokenCred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{
m.azureEnv.Cloud.Services[azure.ServiceAzureSQL].Audience,
},
})
if err != nil {
return "", err
}
return at.Token, nil
})
return conn, hasDatabase, err
}
conn := mssql.NewConnectorConfig(config)
return conn, hasDatabase, nil
}
// Validates and returns the key type.
func (m *sqlServerMetadata) setKeyType() error {
if m.KeyType != "" {
kt, err := KeyTypeFromString(m.KeyType)
if err != nil {
return err
}
m.keyTypeParsed = kt
} else {
m.keyTypeParsed = StringKeyType
}
if m.keyTypeParsed != StringKeyType {
return nil
}
if m.KeyLength <= 0 {
return fmt.Errorf("invalid key length value of %d", m.KeyLength)
} else {
m.keyLengthParsed = m.KeyLength
}
return nil
}
// Sets the validated index properties.
func (m *sqlServerMetadata) setIndexedProperties() error {
if m.IndexedProperties == "" {
return nil
}
var indexedProperties []IndexedProperty
err := json.Unmarshal([]byte(m.IndexedProperties), &indexedProperties)
if err != nil {
return err
}
err = m.validateIndexedProperties(indexedProperties)
if err != nil {
return err
}
m.indexedPropertiesParsed = indexedProperties
return nil
}
// Validates that all the mandator index properties are supplied and that the
// values are valid.
func (m *sqlServerMetadata) validateIndexedProperties(indexedProperties []IndexedProperty) error {
for _, p := range indexedProperties {
if p.ColumnName == "" {
return errors.New("indexed property column cannot be empty")
}
if p.Property == "" {
return errors.New("indexed property name cannot be empty")
}
if p.Type == "" {
return errors.New("indexed property type cannot be empty")
}
if !isValidSQLName(p.ColumnName) {
return fmt.Errorf("invalid indexed property column name, accepted characters are (A-Z, a-z, 0-9, _)")
}
if !isValidIndexedPropertyName(p.Property) {
return fmt.Errorf("invalid indexed property name, accepted characters are (A-Z, a-z, 0-9, _, ., [, ])")
}
if !isValidIndexedPropertyType(p.Type) {
return fmt.Errorf("invalid indexed property type, accepted characters are (A-Z, a-z, 0-9, _, (, ))")
}
}
return nil
}
func isLetterOrNumber(c rune) bool {
return unicode.IsNumber(c) || unicode.IsLetter(c)
}
func isValidSQLName(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '_')) {
return false
}
}
return true
}
func isValidIndexedPropertyName(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '_') || (c == '.') || (c == '[') || (c == ']')) {
return false
}
}
return true
}
func isValidIndexedPropertyType(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '(') || (c == ')')) {
return false
}
}
return true
}

View File

@ -0,0 +1,101 @@
# yaml-language-server: $schema=../../../component-metadata-schema.json
schemaVersion: "v1"
type: "state"
name: "sqlserver"
version: "v1"
status: "stable"
title: "SQL Server"
urls:
- title: "Reference"
url: "https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-sqlserver/"
capabilities:
# If actorStateStore is present, the metadata key actorStateStore can be used
- "actorStateStore"
- "crud"
- "transactional"
- "etag"
authenticationProfiles:
- title: "Connection string"
description: |
Authenticates using a connection string.
metadata:
- name: connectionString
required: true
sensitive: true
description: |
The connection string used to connect.
If the connection string contains the database, it must already exist. Otherwise, if the database is omitted, a default database named "Dapr" is created.
example: |
"Server=myServerName\myInstanceName;Database=myDataBase;User Id=myUsername;Password=myPassword;"
builtinAuthenticationProfiles:
- name: "azuread"
metadata:
- name: useAzureAD
required: true
type: bool
description: |
Must be set to `true` to enable the component to retrieve access tokens from Azure AD.
This authentication method only works with Azure SQL databases.
- name: connectionString
required: true
sensitive: true
description: |
The connection string or URL of the Azure SQL database, without credentials.
If the connection string contains the database, it must already exist. Otherwise, if the database is omitted, a default database named "Dapr" is created.
example: |
"sqlserver://myServerName.database.windows.net:1433?database=myDataBase"
metadata:
- name: tableName
description: |
The name of the table to use. Alpha-numeric with underscores.
example: |
"table_name"
default: |
"state"
- name: metadataTableName
description: |
Name of the table Dapr uses to store metadata properties.
example: |
"dapr_metadata"
default: |
"dapr_metadata"
- name: schema
description: |
The schema to use.
example: |
"dapr"
default: |
"dbo"
- name: keyType
description: |
The type of key used
allowedValues:
- "string"
- "uuid"
- "integer"
default: |
"string"
example: |
"string"
- name: keyLength
type: number
description: |
The max length of key. Ignored if "keyType" is not `string`.
example: |
200
default: |
200
- name: indexedProperties
description: |
List of indexed properties, as a string containing a JSON document.
example: |
'[{"column": "transactionid", "property": "id", "type": "int"}, {"column": "customerid", "property": "customer", "type": "nvarchar(100)"}]'
- name: cleanupIntervalInSeconds
type: number
description: |
Interval, in seconds, to clean up rows with an expired TTL. Default: 3600 (i.e. 1 hour).
Setting this to values <=0 disables the periodic cleanup.
default: |
"3600"
example: |
"1800", "-1"

View File

@ -17,7 +17,6 @@ import (
"context"
"database/sql"
"fmt"
"regexp"
)
type migrator interface {
@ -25,7 +24,7 @@ type migrator interface {
}
type migration struct {
store *SQLServer
metadata *sqlServerMetadata
}
type migrationResult struct {
@ -40,29 +39,29 @@ type migrationResult struct {
deleteWithoutETagCommand string
}
func newMigration(store *SQLServer) migrator {
func newMigration(metadata *sqlServerMetadata) migrator {
return &migration{
store: store,
metadata: metadata,
}
}
func (m *migration) newMigrationResult() migrationResult {
r := migrationResult{
bulkDeleteProcName: fmt.Sprintf("sp_BulkDelete_%s", m.store.tableName),
itemRefTableTypeName: fmt.Sprintf("[%s].%s_Table", m.store.schema, m.store.tableName),
upsertProcName: fmt.Sprintf("sp_Upsert_v3_%s", m.store.tableName),
getCommand: fmt.Sprintf("SELECT [Data], [RowVersion] FROM [%s].[%s] WHERE [Key] = @Key AND ([ExpireDate] IS NULL OR [ExpireDate] > GETDATE())", m.store.schema, m.store.tableName),
deleteWithETagCommand: fmt.Sprintf(`DELETE [%s].[%s] WHERE [Key]=@Key AND [RowVersion]=@RowVersion`, m.store.schema, m.store.tableName),
deleteWithoutETagCommand: fmt.Sprintf(`DELETE [%s].[%s] WHERE [Key]=@Key`, m.store.schema, m.store.tableName),
bulkDeleteProcName: fmt.Sprintf("sp_BulkDelete_%s", m.metadata.TableName),
itemRefTableTypeName: fmt.Sprintf("[%s].%s_Table", m.metadata.Schema, m.metadata.TableName),
upsertProcName: fmt.Sprintf("sp_Upsert_v3_%s", m.metadata.TableName),
getCommand: fmt.Sprintf("SELECT [Data], [RowVersion] FROM [%s].[%s] WHERE [Key] = @Key AND ([ExpireDate] IS NULL OR [ExpireDate] > GETDATE())", m.metadata.Schema, m.metadata.TableName),
deleteWithETagCommand: fmt.Sprintf(`DELETE [%s].[%s] WHERE [Key]=@Key AND [RowVersion]=@RowVersion`, m.metadata.Schema, m.metadata.TableName),
deleteWithoutETagCommand: fmt.Sprintf(`DELETE [%s].[%s] WHERE [Key]=@Key`, m.metadata.Schema, m.metadata.TableName),
}
r.bulkDeleteProcFullName = fmt.Sprintf("[%s].%s", m.store.schema, r.bulkDeleteProcName)
r.upsertProcFullName = fmt.Sprintf("[%s].%s", m.store.schema, r.upsertProcName)
r.bulkDeleteProcFullName = fmt.Sprintf("[%s].%s", m.metadata.Schema, r.bulkDeleteProcName)
r.upsertProcFullName = fmt.Sprintf("[%s].%s", m.metadata.Schema, r.upsertProcName)
//nolint:exhaustive
switch m.store.keyType {
switch m.metadata.keyTypeParsed {
case StringKeyType:
r.pkColumnType = fmt.Sprintf("NVARCHAR(%d)", m.store.keyLength)
r.pkColumnType = fmt.Sprintf("NVARCHAR(%d)", m.metadata.keyLengthParsed)
case UUIDKeyType:
r.pkColumnType = "uniqueidentifier"
@ -78,15 +77,16 @@ func (m *migration) newMigrationResult() migrationResult {
func (m *migration) executeMigrations(ctx context.Context) (migrationResult, error) {
r := m.newMigrationResult()
db, err := sql.Open("sqlserver", m.store.connectionString)
conn, hasDatabase, err := m.metadata.GetConnector(false)
if err != nil {
return r, err
}
db := sql.OpenDB(conn)
// If the user provides a database in the connection string to not attempt
// If the user provides a database in the connection string do not attempt
// to create the database. This work as the component did before adding the
// support to create the db.
if connStringContainsDatabase(m.store.connectionString) {
if hasDatabase {
// Schedule close of connection
defer db.Close()
} else {
@ -98,12 +98,12 @@ func (m *migration) executeMigrations(ctx context.Context) (migrationResult, err
// Close the existing connection
db.Close()
// Re connect with a database specific connection
m.store.connectionString = fmt.Sprintf("%s;database=%s;", m.store.connectionString, m.store.databaseName)
db, err = sql.Open("sqlserver", m.store.connectionString)
// Re connect with a database-specific connection
conn, _, err = m.metadata.GetConnector(true)
if err != nil {
return r, err
}
db = sql.OpenDB(conn)
// Schedule close of new connection
defer db.Close()
@ -124,7 +124,7 @@ func (m *migration) executeMigrations(ctx context.Context) (migrationResult, err
return r, fmt.Errorf("failed to create stored procedures: %w", err)
}
for _, ix := range m.store.indexedProperties {
for _, ix := range m.metadata.indexedPropertiesParsed {
err = m.ensureIndexedPropertyExists(ctx, db, ix)
if err != nil {
return r, err
@ -134,12 +134,6 @@ func (m *migration) executeMigrations(ctx context.Context) (migrationResult, err
return r, nil
}
func connStringContainsDatabase(connStr string) bool {
// This method is only going to be called once (or at least once per component), so we are not pre-compiling the regex to avoid keeping that as a global variable
return regexp.MustCompile(`(?i)(^|;)database=.+`).
MatchString(connStr)
}
func runCommand(ctx context.Context, db *sql.DB, tsql string) error {
if _, err := db.ExecContext(ctx, tsql); err != nil {
return err
@ -158,12 +152,12 @@ func (m *migration) ensureIndexedPropertyExists(ctx context.Context, db *sql.DB,
WHERE object_id = OBJECT_ID('[%s].%s')
AND name='%s'))
CREATE INDEX %s ON [%s].[%s]([%s])`,
m.store.schema,
m.store.tableName,
m.metadata.Schema,
m.metadata.TableName,
indexName,
indexName,
m.store.schema,
m.store.tableName,
m.metadata.Schema,
m.metadata.TableName,
ix.ColumnName)
return runCommand(ctx, db, tsql)
@ -174,7 +168,7 @@ func (m *migration) ensureDatabaseExists(ctx context.Context, db *sql.DB) error
tsql := fmt.Sprintf(`
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'%s')
CREATE DATABASE [%s]`,
m.store.databaseName, m.store.databaseName)
m.metadata.DatabaseName, m.metadata.DatabaseName)
return runCommand(ctx, db, tsql)
}
@ -184,7 +178,7 @@ func (m *migration) ensureSchemaExists(ctx context.Context, db *sql.DB) error {
tsql := fmt.Sprintf(`
IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = N'%s')
EXEC('CREATE SCHEMA [%s]')`,
m.store.schema, m.store.schema)
m.metadata.Schema, m.metadata.Schema)
return runCommand(ctx, db, tsql)
}
@ -199,15 +193,13 @@ func (m *migration) ensureTableExists(ctx context.Context, db *sql.DB, r migrati
[InsertDate] DateTime2 NOT NULL DEFAULT(GETDATE()),
[UpdateDate] DateTime2 NULL,
[ExpireDate] DateTime2 NULL,`,
m.store.schema, m.store.tableName, m.store.schema, m.store.tableName, r.pkColumnType, m.store.tableName)
m.metadata.Schema, m.metadata.TableName, m.metadata.Schema, m.metadata.TableName, r.pkColumnType, m.metadata.TableName)
if m.store.indexedProperties != nil {
for _, prop := range m.store.indexedProperties {
if prop.Type != "" {
tsql += fmt.Sprintf("\n [%s] AS CONVERT(%s, JSON_VALUE(Data, '$.%s')) PERSISTED,", prop.ColumnName, prop.Type, prop.Property)
} else {
tsql += fmt.Sprintf("\n [%s] AS JSON_VALUE(Data, '$.%s') PERSISTED,", prop.ColumnName, prop.Property)
}
for _, prop := range m.metadata.indexedPropertiesParsed {
if prop.Type != "" {
tsql += fmt.Sprintf("\n [%s] AS CONVERT(%s, JSON_VALUE(Data, '$.%s')) PERSISTED,", prop.ColumnName, prop.Type, prop.Property)
} else {
tsql += fmt.Sprintf("\n [%s] AS JSON_VALUE(Data, '$.%s') PERSISTED,", prop.ColumnName, prop.Property)
}
}
@ -224,7 +216,7 @@ func (m *migration) ensureTableExists(ctx context.Context, db *sql.DB, r migrati
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = '%[1]s' AND TABLE_NAME = '%[2]s'
AND COLUMN_NAME = 'ExpireDate')
ALTER TABLE [%[1]s].[%[2]s] ADD [ExpireDate] DateTime2 NULL`, m.store.schema, m.store.tableName)
ALTER TABLE [%[1]s].[%[2]s] ADD [ExpireDate] DateTime2 NULL`, m.metadata.Schema, m.metadata.TableName)
if err := runCommand(ctx, db, tsql); err != nil {
return fmt.Errorf("failed to ensure ExpireDate column: %w", err)
}
@ -234,7 +226,7 @@ func (m *migration) ensureTableExists(ctx context.Context, db *sql.DB, r migrati
CREATE TABLE [%[1]s].[%[2]s] (
[Key] %[3]s CONSTRAINT PK_%[4]s PRIMARY KEY,
[Value] NVARCHAR(MAX) NOT NULL
)`, m.store.schema, m.store.metaTableName, r.pkColumnType, m.store.metaTableName)
)`, m.metadata.Schema, m.metadata.MetadataTableName, r.pkColumnType, m.metadata.MetadataTableName)
if err := runCommand(ctx, db, tsql); err != nil {
return err
}
@ -251,7 +243,7 @@ func (m *migration) ensureTypeExists(ctx context.Context, db *sql.DB, mr migrati
[Key] %s NOT NULL,
[RowVersion] BINARY(8)
)
`, m.store.schema, m.store.tableName, m.store.schema, m.store.tableName, mr.pkColumnType)
`, m.metadata.Schema, m.metadata.TableName, m.metadata.Schema, m.metadata.TableName, mr.pkColumnType)
return runCommand(ctx, db, tsql)
}
@ -267,10 +259,10 @@ func (m *migration) ensureBulkDeleteStoredProcedureExists(ctx context.Context, d
JOIN @itemsToDelete i ON i.[Key] = x.[Key] AND (i.[RowVersion] IS NULL OR i.[RowVersion] = x.[RowVersion])`,
mr.bulkDeleteProcFullName,
mr.itemRefTableTypeName,
m.store.schema,
m.store.tableName,
m.store.schema,
m.store.tableName)
m.metadata.Schema,
m.metadata.TableName,
m.metadata.Schema,
m.metadata.TableName)
return m.createStoredProcedureIfNotExists(ctx, db, mr.bulkDeleteProcName, tsql)
}
@ -301,7 +293,7 @@ func (m *migration) createStoredProcedureIfNotExists(ctx context.Context, db *sq
BEGIN
execute ('%s')
END`,
m.store.schema,
m.metadata.Schema,
name,
escapedDefinition)
@ -385,7 +377,7 @@ func (m *migration) ensureUpsertStoredProcedureExists(ctx context.Context, db *s
`,
mr.upsertProcFullName,
mr.pkColumnType,
m.store.tableName,
m.metadata.TableName,
)
return m.createStoredProcedureIfNotExists(ctx, db, mr.upsertProcName, tsql)

View File

@ -1,5 +1,5 @@
/*
Copyright 2021 The Dapr Authors
Copyright 2023 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@ -20,14 +20,11 @@ import (
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
"unicode"
mssql "github.com/denisenkom/go-mssqldb"
mssql "github.com/microsoft/go-mssqldb"
internalsql "github.com/dapr/components-contrib/internal/component/sql"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/state"
"github.com/dapr/components-contrib/state/utils"
"github.com/dapr/kit/logger"
@ -65,27 +62,6 @@ const (
InvalidKeyType KeyType = "invalid"
)
const (
connectionStringKey = "connectionString"
tableNameKey = "tableName"
metadataTableNameKey = "metadataTableName"
schemaKey = "schema"
keyTypeKey = "keyType"
keyLengthKey = "keyLength"
indexedPropertiesKey = "indexedProperties"
keyColumnName = "Key"
rowVersionColumnName = "RowVersion"
databaseNameKey = "databaseName"
cleanupIntervalKey = "cleanupIntervalInSeconds"
defaultKeyLength = 200
defaultSchema = "dbo"
defaultDatabase = "dapr"
defaultTable = "state"
defaultMetaTable = "dapr_metadata"
defaultCleanupInterval = time.Hour
)
// New creates a new instance of a SQL Server transaction store.
func New(logger logger.Logger) state.Store {
s := &SQLServer{
@ -108,17 +84,9 @@ type IndexedProperty struct {
type SQLServer struct {
state.BulkStore
connectionString string
databaseName string
tableName string
metaTableName string
schema string
keyType KeyType
keyLength int
indexedProperties []IndexedProperty
migratorFactory func(*SQLServer) migrator
metadata sqlServerMetadata
cleanupInterval *time.Duration
migratorFactory func(*sqlServerMetadata) migrator
bulkDeleteCommand string
itemRefTableTypeName string
@ -133,59 +101,15 @@ type SQLServer struct {
gc internalsql.GarbageCollector
}
type sqlServerMetadata struct {
ConnectionString string
DatabaseName string
TableName string
MetadataTableName string
Schema string
KeyType string
KeyLength int
IndexedProperties string
}
func isLetterOrNumber(c rune) bool {
return unicode.IsNumber(c) || unicode.IsLetter(c)
}
func isValidSQLName(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '_')) {
return false
}
}
return true
}
func isValidIndexedPropertyName(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '_') || (c == '.') || (c == '[') || (c == ']')) {
return false
}
}
return true
}
func isValidIndexedPropertyType(s string) bool {
for _, c := range s {
if !(isLetterOrNumber(c) || (c == '(') || (c == ')')) {
return false
}
}
return true
}
// Init initializes the SQL server state store.
func (s *SQLServer) Init(ctx context.Context, metadata state.Metadata) error {
err := s.parseMetadata(metadata.Properties)
s.metadata = newMetadata()
err := s.metadata.Parse(metadata.Properties)
if err != nil {
return err
}
migration := s.migratorFactory(s)
migration := s.migratorFactory(&s.metadata)
mr, err := migration.executeMigrations(ctx)
if err != nil {
return err
@ -198,216 +122,45 @@ func (s *SQLServer) Init(ctx context.Context, metadata state.Metadata) error {
s.deleteWithETagCommand = mr.deleteWithETagCommand
s.deleteWithoutETagCommand = mr.deleteWithoutETagCommand
s.db, err = sql.Open("sqlserver", s.connectionString)
conn, _, err := s.metadata.GetConnector(true)
if err != nil {
return err
}
s.db = sql.OpenDB(conn)
if s.cleanupInterval != nil {
gc, err := internalsql.ScheduleGarbageCollector(internalsql.GCOptions{
Logger: s.logger,
UpdateLastCleanupQuery: fmt.Sprintf(`BEGIN TRANSACTION;
if s.metadata.CleanupInterval != nil {
err = s.startGC()
if err != nil {
return err
}
}
return nil
}
func (s *SQLServer) startGC() error {
gc, err := internalsql.ScheduleGarbageCollector(internalsql.GCOptions{
Logger: s.logger,
UpdateLastCleanupQuery: fmt.Sprintf(`BEGIN TRANSACTION;
BEGIN TRY
INSERT INTO [%[1]s].[%[2]s] ([Key], [Value]) VALUES ('last-cleanup', CONVERT(nvarchar(MAX), GETDATE(), 21));
INSERT INTO [%[1]s].[%[2]s] ([Key], [Value]) VALUES ('last-cleanup', CONVERT(nvarchar(MAX), GETDATE(), 21));
END TRY
BEGIN CATCH
UPDATE [%[1]s].[%[2]s] SET [Value] = CONVERT(nvarchar(MAX), GETDATE(), 21) WHERE [Key] = 'last-cleanup' AND Datediff_big(MS, [Value], GETUTCDATE()) > @Interval
UPDATE [%[1]s].[%[2]s] SET [Value] = CONVERT(nvarchar(MAX), GETDATE(), 21) WHERE [Key] = 'last-cleanup' AND Datediff_big(MS, [Value], GETUTCDATE()) > @Interval
END CATCH
COMMIT TRANSACTION;`, s.schema, s.metaTableName),
UpdateLastCleanupQueryParameterName: "Interval",
DeleteExpiredValuesQuery: fmt.Sprintf(
`DELETE FROM [%s].[%s] WHERE [ExpireDate] IS NOT NULL AND [ExpireDate] < GETDATE()`,
s.schema, s.tableName,
),
CleanupInterval: *s.cleanupInterval,
DBSql: s.db,
})
if err != nil {
return err
}
s.gc = gc
}
return nil
}
func (s *SQLServer) parseMetadata(meta map[string]string) error {
m := sqlServerMetadata{
TableName: defaultTable,
Schema: defaultSchema,
DatabaseName: defaultDatabase,
KeyLength: defaultKeyLength,
MetadataTableName: defaultMetaTable,
}
err := metadata.DecodeMetadata(meta, &m)
COMMIT TRANSACTION;`, s.metadata.Schema, s.metadata.MetadataTableName),
UpdateLastCleanupQueryParameterName: "Interval",
DeleteExpiredValuesQuery: fmt.Sprintf(
`DELETE FROM [%s].[%s] WHERE [ExpireDate] IS NOT NULL AND [ExpireDate] < GETDATE()`,
s.metadata.Schema, s.metadata.TableName,
),
CleanupInterval: *s.metadata.CleanupInterval,
DBSql: s.db,
})
if err != nil {
return err
}
if m.ConnectionString == "" {
return fmt.Errorf("missing connection string")
}
s.connectionString = m.ConnectionString
if err := s.setTable(m.TableName); err != nil {
return err
}
if err := s.setMetadataTable(m.MetadataTableName); err != nil {
return err
}
if err := s.setDatabase(m.DatabaseName); err != nil {
return err
}
if err := s.setKeyType(m.KeyType, m.KeyLength); err != nil {
return err
}
if err := s.setSchema(m.Schema); err != nil {
return err
}
if err := s.setIndexedProperties(m.IndexedProperties); err != nil {
return err
}
// Cleanup interval
if v := meta[cleanupIntervalKey]; v != "" {
cleanupIntervalInSec, err := strconv.ParseInt(v, 10, 0)
if err != nil {
return fmt.Errorf("invalid value for '%s': %s", cleanupIntervalKey, v)
}
// Non-positive value from meta means disable auto cleanup.
if cleanupIntervalInSec > 0 {
s.cleanupInterval = ptr.Of(time.Duration(cleanupIntervalInSec) * time.Second)
} else {
s.cleanupInterval = nil
}
} else {
s.cleanupInterval = ptr.Of(defaultCleanupInterval)
}
return nil
}
// Returns validated index properties.
func (s *SQLServer) setIndexedProperties(indexedPropertiesString string) error {
if indexedPropertiesString != "" {
var indexedProperties []IndexedProperty
err := json.Unmarshal([]byte(indexedPropertiesString), &indexedProperties)
if err != nil {
return err
}
err = s.validateIndexedProperties(indexedProperties)
if err != nil {
return err
}
s.indexedProperties = indexedProperties
}
return nil
}
// Validates that all the mandator index properties are supplied and that the
// values are valid.
func (s *SQLServer) validateIndexedProperties(indexedProperties []IndexedProperty) error {
for _, p := range indexedProperties {
if p.ColumnName == "" {
return errors.New("indexed property column cannot be empty")
}
if p.Property == "" {
return errors.New("indexed property name cannot be empty")
}
if p.Type == "" {
return errors.New("indexed property type cannot be empty")
}
if !isValidSQLName(p.ColumnName) {
return fmt.Errorf("invalid indexed property column name, accepted characters are (A-Z, a-z, 0-9, _)")
}
if !isValidIndexedPropertyName(p.Property) {
return fmt.Errorf("invalid indexed property name, accepted characters are (A-Z, a-z, 0-9, _, ., [, ])")
}
if !isValidIndexedPropertyType(p.Type) {
return fmt.Errorf("invalid indexed property type, accepted characters are (A-Z, a-z, 0-9, _, (, ))")
}
}
return nil
}
// Validates and returns the key type.
func (s *SQLServer) setKeyType(keyType string, keyLength int) error {
if keyType != "" {
kt, err := KeyTypeFromString(keyType)
if err != nil {
return err
}
s.keyType = kt
} else {
s.keyType = StringKeyType
}
if s.keyType != StringKeyType {
return nil
}
if keyLength <= 0 {
return fmt.Errorf("invalid key length value of %d", keyLength)
} else {
s.keyLength = keyLength
}
return nil
}
// Returns the schema name if set or the default value otherwise.
func (s *SQLServer) setSchema(schemaName string) error {
if !isValidSQLName(schemaName) {
return fmt.Errorf("invalid schema name, accepted characters are (A-Z, a-z, 0-9, _)")
}
s.schema = schemaName
return nil
}
// Returns the database name if set or the default value otherwise.
func (s *SQLServer) setDatabase(databaseName string) error {
if !isValidSQLName(databaseName) {
return fmt.Errorf("invalid database name, accepted characters are (A-Z, a-z, 0-9, _)")
}
s.databaseName = databaseName
return nil
}
// Returns the table name if set or the default value otherwise.
func (s *SQLServer) setTable(tableName string) error {
if !isValidSQLName(tableName) {
return fmt.Errorf("invalid table name, accepted characters are (A-Z, a-z, 0-9, _)")
}
s.tableName = tableName
return nil
}
func (s *SQLServer) setMetadataTable(tableName string) error {
if !isValidSQLName(tableName) {
return fmt.Errorf("invalid metadata table name, accepted characters are (A-Z, a-z, 0-9, _)")
}
s.metaTableName = tableName
s.gc = gc
return nil
}
@ -679,7 +432,7 @@ func (s *SQLServer) Close() error {
// GetCleanupInterval returns the cleanupInterval property.
// This is primarily used for tests.
func (s *SQLServer) GetCleanupInterval() *time.Duration {
return s.cleanupInterval
return s.metadata.CleanupInterval
}
func (s *SQLServer) CleanupExpired() error {

View File

@ -128,21 +128,21 @@ func getTestStoreWithKeyType(t *testing.T, kt KeyType, indexedProperties string)
logger: logger.NewLogger("test"),
}
err := store.Init(context.Background(), metadata)
assert.Nil(t, err)
require.NoError(t, err)
return store
}
func assertUserExists(t *testing.T, store *SQLServer, key string) (user, string) {
getRes, err := store.Get(context.Background(), &state.GetRequest{Key: key})
assert.Nil(t, err)
require.NoError(t, err)
assert.NotNil(t, getRes)
assert.NotNil(t, getRes.Data, "No data was returned")
require.NotNil(t, getRes.ETag)
var loaded user
err = json.Unmarshal(getRes.Data, &loaded)
assert.Nil(t, err)
require.NoError(t, err)
return loaded, *getRes.ETag
}
@ -158,16 +158,16 @@ func assertLoadedUserIsEqual(t *testing.T, store *SQLServer, key string, expecte
func assertUserDoesNotExist(t *testing.T, store *SQLServer, key string) {
_, err := store.Get(context.Background(), &state.GetRequest{Key: key})
assert.Nil(t, err)
require.NoError(t, err)
}
func assertDBQuery(t *testing.T, store *SQLServer, query string, assertReader func(t *testing.T, rows *sql.Rows)) {
db, err := sql.Open("sqlserver", store.connectionString)
assert.Nil(t, err)
db, err := sql.Open("sqlserver", store.metadata.ConnectionString)
require.NoError(t, err)
defer db.Close()
rows, err := db.Query(query)
assert.Nil(t, err)
require.NoError(t, err)
assert.Nil(t, rows.Err())
defer rows.Close()
@ -176,12 +176,12 @@ func assertDBQuery(t *testing.T, store *SQLServer, query string, assertReader fu
/* #nosec. */
func assertUserCountIsEqualTo(t *testing.T, store *SQLServer, expected int) {
tsql := fmt.Sprintf("SELECT count(*) FROM [%s].[%s]", store.schema, store.tableName)
tsql := fmt.Sprintf("SELECT count(*) FROM [%s].[%s]", store.metadata.Schema, store.metadata.TableName)
assertDBQuery(t, store, tsql, func(t *testing.T, rows *sql.Rows) {
assert.True(t, rows.Next())
var actual int
err := rows.Scan(&actual)
assert.Nil(t, err)
require.NoError(t, err)
assert.Equal(t, expected, actual)
})
}
@ -229,14 +229,14 @@ func testSingleOperations(t *testing.T) {
// Save and read
err := store.Set(context.Background(), &state.SetRequest{Key: john.ID, Value: john})
assert.Nil(t, err)
require.NoError(t, err)
johnV1, etagFromInsert := assertLoadedUserIsEqual(t, store, john.ID, john)
// Update with ETAG
waterJohn := johnV1
waterJohn.FavoriteBeverage = "Water"
err = store.Set(context.Background(), &state.SetRequest{Key: waterJohn.ID, Value: waterJohn, ETag: &etagFromInsert})
assert.Nil(t, err)
require.NoError(t, err)
// Get updated
johnV2, _ := assertLoadedUserIsEqual(t, store, waterJohn.ID, waterJohn)
@ -245,7 +245,7 @@ func testSingleOperations(t *testing.T) {
noEtagJohn := johnV2
noEtagJohn.FavoriteBeverage = "No Etag John"
err = store.Set(context.Background(), &state.SetRequest{Key: noEtagJohn.ID, Value: noEtagJohn})
assert.Nil(t, err)
require.NoError(t, err)
// 7. Get updated
johnV3, _ := assertLoadedUserIsEqual(t, store, noEtagJohn.ID, noEtagJohn)
@ -264,7 +264,7 @@ func testSingleOperations(t *testing.T) {
// 10. Delete with valid ETAG
err = store.Delete(context.Background(), &state.DeleteRequest{Key: johnV2.ID, ETag: &etag})
assert.Nil(t, err)
require.NoError(t, err)
assertUserDoesNotExist(t, store, johnV2.ID)
})
@ -292,10 +292,10 @@ func testIndexedProperties(t *testing.T) {
{Key: "4", Value: userWithPets{user{"4", "Maria", "Wine"}, 100}},
})
assert.Nil(t, err)
require.NoError(t, err)
// Check the database for computed columns
assertDBQuery(t, store, fmt.Sprintf("SELECT count(*) from [%s].[%s] WHERE PetsCount < 3", store.schema, usersTableName), func(t *testing.T, rows *sql.Rows) {
assertDBQuery(t, store, fmt.Sprintf("SELECT count(*) from [%s].[%s] WHERE PetsCount < 3", store.metadata.Schema, usersTableName), func(t *testing.T, rows *sql.Rows) {
assert.True(t, rows.Next())
var c int
@ -304,7 +304,7 @@ func testIndexedProperties(t *testing.T) {
})
// Ensure we can get by beverage
assertDBQuery(t, store, fmt.Sprintf("SELECT count(*) from [%s].[%s] WHERE FavoriteBeverage = '%s'", store.schema, usersTableName, "Coffee"), func(t *testing.T, rows *sql.Rows) {
assertDBQuery(t, store, fmt.Sprintf("SELECT count(*) from [%s].[%s] WHERE FavoriteBeverage = '%s'", store.metadata.Schema, usersTableName, "Coffee"), func(t *testing.T, rows *sql.Rows) {
assert.True(t, rows.Next())
var c int
@ -348,7 +348,7 @@ func testMultiOperations(t *testing.T) {
}
err := store.BulkSet(context.Background(), bulkSet)
assert.Nil(t, err)
require.NoError(t, err)
assertUserCountIsEqualTo(t, store, len(initialUsers))
// Ensure initial users are correctly stored
@ -397,7 +397,7 @@ func testMultiOperations(t *testing.T) {
state.SetRequest{Key: toInsert.ID, Value: toInsert},
},
})
assert.Nil(t, err)
require.NoError(t, err)
assertLoadedUserIsEqual(t, store, modified.ID, modified)
assertLoadedUserIsEqual(t, store, toInsert.ID, toInsert)
assertUserDoesNotExist(t, store, toDelete.ID)
@ -420,7 +420,7 @@ func testMultiOperations(t *testing.T) {
state.SetRequest{Key: modified.ID, Value: modified, ETag: &toModify.etag},
},
})
assert.Nil(t, err)
require.NoError(t, err)
assertLoadedUserIsEqual(t, store, modified.ID, modified)
assertUserDoesNotExist(t, store, toDelete.ID)
@ -525,7 +525,7 @@ func testBulkSet(t *testing.T) {
}
err := store.BulkSet(context.Background(), sets)
assert.Nil(t, err)
require.NoError(t, err)
totalUsers = len(sets)
assertUserCountIsEqualTo(t, store, totalUsers)
})
@ -540,7 +540,7 @@ func testBulkSet(t *testing.T) {
{Key: modified.ID, Value: modified, ETag: &toModifyETag},
{Key: toInsert.ID, Value: toInsert},
})
assert.Nil(t, err)
require.NoError(t, err)
assertLoadedUserIsEqual(t, store, modified.ID, modified)
assertLoadedUserIsEqual(t, store, toInsert.ID, toInsert)
totalUsers++
@ -559,7 +559,7 @@ func testBulkSet(t *testing.T) {
{Key: modified.ID, Value: modified},
{Key: toInsert.ID, Value: toInsert},
})
assert.Nil(t, err)
require.NoError(t, err)
assertLoadedUserIsEqual(t, store, modified.ID, modified)
assertLoadedUserIsEqual(t, store, toInsert.ID, toInsert)
totalUsers++
@ -626,7 +626,7 @@ func testBulkDelete(t *testing.T) {
sets[i] = state.SetRequest{Key: u.ID, Value: u}
}
err := store.BulkSet(context.Background(), sets)
assert.Nil(t, err)
require.NoError(t, err)
totalUsers := len(initialUsers)
assertUserCountIsEqualTo(t, store, totalUsers)
@ -639,7 +639,7 @@ func testBulkDelete(t *testing.T) {
{Key: deleted1},
{Key: deleted2},
})
assert.Nil(t, err)
require.NoError(t, err)
totalUsers -= 2
assertUserCountIsEqualTo(t, store, totalUsers)
assertUserDoesNotExist(t, store, deleted1)
@ -656,7 +656,7 @@ func testBulkDelete(t *testing.T) {
{Key: deleted1.ID, ETag: &deleted1Etag},
{Key: deleted2.ID, ETag: &deleted2Etag},
})
assert.Nil(t, err)
require.NoError(t, err)
totalUsers -= 2
assertUserCountIsEqualTo(t, store, totalUsers)
assertUserDoesNotExist(t, store, deleted1.ID)
@ -673,7 +673,7 @@ func testBulkDelete(t *testing.T) {
{Key: deleted1.ID, ETag: &deleted1Etag},
{Key: deleted2.ID},
})
assert.Nil(t, err)
require.NoError(t, err)
totalUsers -= 2
assertUserCountIsEqualTo(t, store, totalUsers)
assertUserDoesNotExist(t, store, deleted1.ID)
@ -708,10 +708,10 @@ func testInsertAndUpdateSetRecordDates(t *testing.T) {
u := user{"1", "John", "Coffee"}
err := store.Set(context.Background(), &state.SetRequest{Key: u.ID, Value: u})
assert.Nil(t, err)
require.NoError(t, err)
var originalInsertTime time.Time
getUserTsql := fmt.Sprintf("SELECT [InsertDate], [UpdateDate] from [%s].[%s] WHERE [Key]='%s'", store.schema, store.tableName, u.ID)
getUserTsql := fmt.Sprintf("SELECT [InsertDate], [UpdateDate] from [%s].[%s] WHERE [Key]='%s'", store.metadata.Schema, store.metadata.TableName, u.ID)
assertDBQuery(t, store, getUserTsql, func(t *testing.T, rows *sql.Rows) {
assert.True(t, rows.Next())
@ -730,13 +730,13 @@ func testInsertAndUpdateSetRecordDates(t *testing.T) {
modified := u
modified.FavoriteBeverage = beverageTea
err = store.Set(context.Background(), &state.SetRequest{Key: modified.ID, Value: modified})
assert.Nil(t, err)
require.NoError(t, err)
assertDBQuery(t, store, getUserTsql, func(t *testing.T, rows *sql.Rows) {
assert.True(t, rows.Next())
var insertDate, updateDate sql.NullTime
err := rows.Scan(&insertDate, &updateDate)
assert.Nil(t, err)
require.NoError(t, err)
assert.True(t, insertDate.Valid)
assert.Equal(t, originalInsertTime, insertDate.Time)
@ -754,7 +754,7 @@ func testConcurrentSets(t *testing.T) {
u := user{"1", "John", "Coffee"}
err := store.Set(context.Background(), &state.SetRequest{Key: u.ID, Value: u})
assert.Nil(t, err)
require.NoError(t, err)
_, etag := assertLoadedUserIsEqual(t, store, u.ID, u)
@ -805,7 +805,7 @@ func testMultipleInitializations(t *testing.T) {
store2 := &SQLServer{
logger: logger.NewLogger("test"),
}
err := store2.Init(context.Background(), createMetadata(store.schema, test.kt, test.indexedProperties))
err := store2.Init(context.Background(), createMetadata(store.metadata.Schema, test.kt, test.indexedProperties))
assert.NoError(t, err)
})
}

View File

@ -20,6 +20,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/state"
@ -55,140 +56,162 @@ func TestValidConfiguration(t *testing.T) {
"No schema": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName},
expected: SQLServer{
connectionString: sampleConnectionString,
tableName: sampleUserTableName,
schema: defaultSchema,
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
TableName: sampleUserTableName,
Schema: defaultSchema,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Custom schema": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, schemaKey: "mytest"},
expected: SQLServer{
connectionString: sampleConnectionString,
tableName: sampleUserTableName,
schema: "mytest",
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
TableName: sampleUserTableName,
Schema: "mytest",
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"String key type": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, keyTypeKey: "string"},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Unique identifier key type": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, keyTypeKey: "uuid"},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: UUIDKeyType,
keyLength: 0,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: UUIDKeyType,
keyLengthParsed: 0,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Integer identifier key type": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, keyTypeKey: "integer"},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: IntegerKeyType,
keyLength: 0,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: IntegerKeyType,
keyLengthParsed: 0,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Custom key length": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, keyLengthKey: "100"},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: StringKeyType,
keyLength: 100,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: StringKeyType,
keyLengthParsed: 100,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Single indexed property": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, indexedPropertiesKey: `[{"column": "Age","property":"age", "type":"int"}]`},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: StringKeyType,
keyLength: defaultKeyLength,
indexedProperties: []IndexedProperty{
{ColumnName: "Age", Property: "age", Type: "int"},
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
indexedPropertiesParsed: []IndexedProperty{
{ColumnName: "Age", Property: "age", Type: "int"},
},
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
},
},
"Multiple indexed properties": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, indexedPropertiesKey: `[{"column": "Age","property":"age", "type":"int"}, {"column": "Name","property":"name", "type":"nvarchar(100)"}]`},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: StringKeyType,
keyLength: defaultKeyLength,
indexedProperties: []IndexedProperty{
{ColumnName: "Age", Property: "age", Type: "int"},
{ColumnName: "Name", Property: "name", Type: "nvarchar(100)"},
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
indexedPropertiesParsed: []IndexedProperty{
{ColumnName: "Age", Property: "age", Type: "int"},
{ColumnName: "Name", Property: "name", Type: "nvarchar(100)"},
},
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
},
},
"Custom database": {
props: map[string]string{connectionStringKey: sampleConnectionString, tableNameKey: sampleUserTableName, databaseNameKey: "dapr_test_table"},
expected: SQLServer{
connectionString: sampleConnectionString,
schema: defaultSchema,
tableName: sampleUserTableName,
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: "dapr_test_table",
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
Schema: defaultSchema,
TableName: sampleUserTableName,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: "dapr_test_table",
MetadataTableName: defaultMetaTable,
},
},
},
"No table": {
props: map[string]string{connectionStringKey: sampleConnectionString},
expected: SQLServer{
connectionString: sampleConnectionString,
tableName: defaultTable,
schema: defaultSchema,
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: defaultDatabase,
metaTableName: defaultMetaTable,
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
TableName: defaultTable,
Schema: defaultSchema,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: defaultDatabase,
MetadataTableName: defaultMetaTable,
},
},
},
"Custom meta table": {
props: map[string]string{connectionStringKey: sampleConnectionString, "metadataTableName": "dapr_test_meta_table"},
expected: SQLServer{
connectionString: sampleConnectionString,
tableName: defaultTable,
schema: defaultSchema,
keyType: StringKeyType,
keyLength: defaultKeyLength,
databaseName: defaultDatabase,
metaTableName: "dapr_test_meta_table",
metadata: sqlServerMetadata{
ConnectionString: sampleConnectionString,
TableName: defaultTable,
Schema: defaultSchema,
keyTypeParsed: StringKeyType,
keyLengthParsed: defaultKeyLength,
DatabaseName: defaultDatabase,
MetadataTableName: "dapr_test_meta_table",
},
},
},
}
@ -197,7 +220,7 @@ func TestValidConfiguration(t *testing.T) {
t.Run(name, func(t *testing.T) {
sqlStore := &SQLServer{
logger: logger.NewLogger("test"),
migratorFactory: func(s *SQLServer) migrator {
migratorFactory: func(*sqlServerMetadata) migrator {
return &mockMigrator{}
},
}
@ -208,20 +231,20 @@ func TestValidConfiguration(t *testing.T) {
err := sqlStore.Init(context.Background(), metadata)
assert.NoError(t, err)
assert.Equal(t, tt.expected.connectionString, sqlStore.connectionString)
assert.Equal(t, tt.expected.tableName, sqlStore.tableName)
assert.Equal(t, tt.expected.schema, sqlStore.schema)
assert.Equal(t, tt.expected.keyType, sqlStore.keyType)
assert.Equal(t, tt.expected.keyLength, sqlStore.keyLength)
assert.Equal(t, tt.expected.databaseName, sqlStore.databaseName)
assert.Equal(t, tt.expected.metaTableName, sqlStore.metaTableName)
assert.Equal(t, tt.expected.metadata.ConnectionString, sqlStore.metadata.ConnectionString)
assert.Equal(t, tt.expected.metadata.TableName, sqlStore.metadata.TableName)
assert.Equal(t, tt.expected.metadata.Schema, sqlStore.metadata.Schema)
assert.Equal(t, tt.expected.metadata.keyTypeParsed, sqlStore.metadata.keyTypeParsed)
assert.Equal(t, tt.expected.metadata.keyLengthParsed, sqlStore.metadata.keyLengthParsed)
assert.Equal(t, tt.expected.metadata.DatabaseName, sqlStore.metadata.DatabaseName)
assert.Equal(t, tt.expected.metadata.MetadataTableName, sqlStore.metadata.MetadataTableName)
assert.Equal(t, len(tt.expected.indexedProperties), len(sqlStore.indexedProperties))
if len(tt.expected.indexedProperties) > 0 && len(tt.expected.indexedProperties) == len(sqlStore.indexedProperties) {
for i, e := range tt.expected.indexedProperties {
assert.Equal(t, e.ColumnName, sqlStore.indexedProperties[i].ColumnName)
assert.Equal(t, e.Property, sqlStore.indexedProperties[i].Property)
assert.Equal(t, e.Type, sqlStore.indexedProperties[i].Type)
assert.Equal(t, len(tt.expected.metadata.indexedPropertiesParsed), len(sqlStore.metadata.indexedPropertiesParsed))
if len(tt.expected.metadata.indexedPropertiesParsed) > 0 && len(tt.expected.metadata.indexedPropertiesParsed) == len(sqlStore.metadata.indexedPropertiesParsed) {
for i, e := range tt.expected.metadata.indexedPropertiesParsed {
assert.Equal(t, e.ColumnName, sqlStore.metadata.indexedPropertiesParsed[i].ColumnName)
assert.Equal(t, e.Property, sqlStore.metadata.indexedPropertiesParsed[i].Property)
assert.Equal(t, e.Type, sqlStore.metadata.indexedPropertiesParsed[i].Type)
}
}
})
@ -334,10 +357,10 @@ func TestInvalidConfiguration(t *testing.T) {
}
err := sqlStore.Init(context.Background(), metadata)
assert.Error(t, err)
require.Error(t, err)
if tt.expectedErr != "" {
assert.Contains(t, err.Error(), tt.expectedErr)
require.ErrorContains(t, err, tt.expectedErr)
}
})
}
@ -347,7 +370,7 @@ func TestInvalidConfiguration(t *testing.T) {
func TestExecuteMigrationFails(t *testing.T) {
sqlStore := &SQLServer{
logger: logger.NewLogger("test"),
migratorFactory: func(s *SQLServer) migrator {
migratorFactory: func(*sqlServerMetadata) migrator {
return &mockFailingMigrator{}
},
}
@ -371,17 +394,3 @@ func TestSupportedFeatures(t *testing.T) {
assert.Equal(t, state.FeatureETag, actual[0])
assert.Equal(t, state.FeatureTransactional, actual[1])
}
func TestConnStringContainsDatabase(t *testing.T) {
// Regular test - present
assert.True(t, connStringContainsDatabase(sampleConnectionString))
// Regular test - not present
assert.False(t, connStringContainsDatabase("server=localhost;user id=sa;password=Pass@Word1;port=1433;"))
// Case-insensitive test
assert.True(t, connStringContainsDatabase("server=localhost;user id=sa;password=Pass@Word1;port=1433;Database=sample;"))
// Beginning of string
assert.True(t, connStringContainsDatabase("Database=sample;server=localhost;user id=sa;password=Pass@Word1;port=1433;"))
}

View File

@ -95,7 +95,6 @@ require (
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/denisenkom/go-mssqldb v0.12.3 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/didip/tollbooth/v7 v7.0.1 // indirect
github.com/dubbogo/gost v1.13.1 // indirect
@ -193,6 +192,7 @@ require (
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/microsoft/durabletask-go v0.1.4 // indirect
github.com/microsoft/go-mssqldb v0.21.0 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect

View File

@ -54,17 +54,18 @@ github.com/AthenZ/athenz v1.10.39 h1:mtwHTF/v62ewY2Z5KWhuZgVXftBej1/Tn80zx4DcawY
github.com/AthenZ/athenz v1.10.39/go.mod h1:3Tg8HLsiQZp81BJY58JBeU2BR6B/H4/0MQGfCwhHNEA=
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 v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0-beta.1 h1:yLM4ZIC+NRvzwFGpXjUbf5FhPBVxJgmYXkjePgNAx64=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0-beta.1/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.4 h1:jpSh2461XzXBEw1MJwvVRJwZS0CAgqS0h6jBdoIFtLk=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0-beta.4/go.mod h1:oWa/ZXP08smIi12UyWVbVikBxoZHZCyxijZamTK1i8Q=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3 h1:x1shk+tVZ6kLwIQMn4r+pdz8szo3mA0jd8STmgh+aRk=
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3/go.mod h1:Fy3bbChFm4cZn6oIxYYqKB2FG3rBDxk3NZDLDJCHl+Q=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1 h1:bFa9IcjvrCber6gGgDAUZ+I2bO8J7s8JxXmu9fhi2ss=
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1/go.mod h1:l3wvZkG9oW07GLBW5Cd0WwG5asOfJ8aqE8raUvNzLpk=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
@ -83,6 +84,7 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0 h1:TOtQFiO403wClfrZ
github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v0.1.0/go.mod h1:U0IH4deB/maBcagR9SiNeIfgZ1BY/zYCq8SOiQ4vfRc=
github.com/Azure/go-amqp v0.18.1 h1:D5Ca+uijuTcj5g76sF+zT4OQZcFFY397+IGf/5Ip5Sc=
github.com/Azure/go-amqp v0.18.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 h1:UE9n9rkJF62ArLb1F3DEjRt8O3jLwMWdSoypKV4f3MU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@ -334,8 +336,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
@ -344,6 +344,7 @@ github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulV
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA=
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@ -515,6 +516,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
@ -767,10 +769,12 @@ github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFK
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8=
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
@ -920,6 +924,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microsoft/durabletask-go v0.1.4 h1:sHVFysoJQvG2Sp4wh2tblZBd4DDJmXrBdYCyUlo38MM=
github.com/microsoft/durabletask-go v0.1.4/go.mod h1:UOgcE09cx6SzBS31p4darJ7ve9P5pSVIStPShxDnvPo=
github.com/microsoft/go-mssqldb v0.21.0 h1:p2rpHIL7TlSv1QrbXJUAcbyRKnIT0C9rRkH2E4OjLn8=
github.com/microsoft/go-mssqldb v0.21.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
@ -1049,7 +1055,7 @@ github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDm
github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -1410,11 +1416,12 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
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-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
@ -1517,6 +1524,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -1529,7 +1537,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -1540,6 +1547,7 @@ golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
@ -1674,6 +1682,7 @@ golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1975,6 +1984,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=