From 6b0dedf77210ecaee725cc792434d7b6477c042c Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:26:24 -0700 Subject: [PATCH] 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 --- go.mod | 2 +- go.sum | 26 +- internal/authentication/azure/auth.go | 5 +- internal/authentication/azure/services.go | 36 ++ state/sqlserver/metadata.go | 298 ++++++++++++++++ state/sqlserver/metadata.yaml | 101 ++++++ state/sqlserver/migration.go | 92 +++-- state/sqlserver/sqlserver.go | 319 ++---------------- state/sqlserver/sqlserver_integration_test.go | 64 ++-- state/sqlserver/sqlserver_test.go | 235 ++++++------- tests/certification/go.mod | 2 +- tests/certification/go.sum | 27 +- 12 files changed, 707 insertions(+), 500 deletions(-) create mode 100644 internal/authentication/azure/services.go create mode 100644 state/sqlserver/metadata.go create mode 100644 state/sqlserver/metadata.yaml diff --git a/go.mod b/go.mod index c6bd7e2d5..49198adfc 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8856e6853..9c85bb493 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/authentication/azure/auth.go b/internal/authentication/azure/auth.go index 3eec15758..84dee6dc2 100644 --- a/internal/authentication/azure/auth.go +++ b/internal/authentication/azure/auth.go @@ -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 { diff --git a/internal/authentication/azure/services.go b/internal/authentication/azure/services.go new file mode 100644 index 000000000..1b19bf6ad --- /dev/null +++ b/internal/authentication/azure/services.go @@ -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", + } +} diff --git a/state/sqlserver/metadata.go b/state/sqlserver/metadata.go new file mode 100644 index 000000000..fc77375f5 --- /dev/null +++ b/state/sqlserver/metadata.go @@ -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 +} diff --git a/state/sqlserver/metadata.yaml b/state/sqlserver/metadata.yaml new file mode 100644 index 000000000..713631017 --- /dev/null +++ b/state/sqlserver/metadata.yaml @@ -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" diff --git a/state/sqlserver/migration.go b/state/sqlserver/migration.go index 778ed495b..fed3e99ac 100644 --- a/state/sqlserver/migration.go +++ b/state/sqlserver/migration.go @@ -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) diff --git a/state/sqlserver/sqlserver.go b/state/sqlserver/sqlserver.go index 0086fe162..6ca6d3fdf 100644 --- a/state/sqlserver/sqlserver.go +++ b/state/sqlserver/sqlserver.go @@ -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 { diff --git a/state/sqlserver/sqlserver_integration_test.go b/state/sqlserver/sqlserver_integration_test.go index e8dd58c47..8a9bec781 100644 --- a/state/sqlserver/sqlserver_integration_test.go +++ b/state/sqlserver/sqlserver_integration_test.go @@ -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) }) } diff --git a/state/sqlserver/sqlserver_test.go b/state/sqlserver/sqlserver_test.go index f93675ed0..3a7b785a1 100644 --- a/state/sqlserver/sqlserver_test.go +++ b/state/sqlserver/sqlserver_test.go @@ -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;")) -} diff --git a/tests/certification/go.mod b/tests/certification/go.mod index f8d7acb11..41a6f8f4a 100644 --- a/tests/certification/go.mod +++ b/tests/certification/go.mod @@ -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 diff --git a/tests/certification/go.sum b/tests/certification/go.sum index 2e77449ef..d83b9c6c8 100644 --- a/tests/certification/go.sum +++ b/tests/certification/go.sum @@ -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=