This change updates the project dependencies for Go and Terraform (#172)

Go has been updated from v1.11 --> v1.12.5
Terraform has been udpated from v0.11.13 --> v0.12.1

The test harness has also been upgraded to perform a structural
comparison against the new terraform plan format introduced in v0.12.0
This commit is contained in:
Nicholas M. Iodice 2019-06-14 18:37:54 -05:00 коммит произвёл GitHub
Родитель 6468a73912
Коммит 883bcc58c6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
82 изменённых файлов: 1360 добавлений и 805 удалений

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

@ -1,6 +1,6 @@
ARM_SUBSCRIPTION_ID="<az-service-principal-subscription-id>"
GO_VERSION=1.11
TF_VERSION=0.11.13
GO_VERSION=1.12.5
TF_VERSION=0.12.1
BUILD_BUILDID=1
ARM_CLIENT_ID="<az-service-principal-client-id>"
ARM_CLIENT_SECRET="<az-service-principal-auth-secret>"

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

@ -35,9 +35,9 @@ pool:
variables:
- group: "KV Secrets"
- name: GO_VERSION
value: '1.11'
value: '1.12.5'
- name: TF_VERSION
value: '0.11.13'
value: '0.12.1'
- name: TEST_HARNESS_BASE_IMAGE_TAG
value: 'g$(GO_VERSION)t$(TF_VERSION)'
- name: TEST_HARNESS_BASE_IMAGE

11
go.mod
Просмотреть файл

@ -3,10 +3,11 @@ module github.com/microsoft/cobalt
go 1.12
require (
github.com/gruntwork-io/terratest v0.15.9
github.com/hashicorp/terraform v0.11.14
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gruntwork-io/terratest v0.16.1
github.com/hashicorp/terraform v0.12.1
github.com/magefile/mage v1.8.0
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v0.0.4
github.com/spf13/viper v1.3.2
github.com/pquerna/otp v1.2.0 // indirect
github.com/stretchr/testify v1.3.0
)

480
go.sum
Просмотреть файл

@ -1,226 +1,406 @@
cloud.google.com/go v0.15.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/azure-sdk-for-go v10.3.0-beta+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v9.10.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-ntlmssp v0.0.0-20170803034930-c92175d54006/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v10.15.4+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/apparentlymart/go-cidr v0.0.0-20170616213631-2bd8b58cf427 h1:2P/DTyNDU+7qJOB6E5KeIpdc3qcT9IYjyA8hZ9HGz50=
github.com/apparentlymart/go-cidr v0.0.0-20170616213631-2bd8b58cf427/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/apparentlymart/go-textseg v0.0.0-20170531203952-b836f5c4d331 h1:AIKxo1t7QE7MAqADwrmzMiaFC+QfHfXOk8lrmibN5Lk=
github.com/apparentlymart/go-textseg v0.0.0-20170531203952-b836f5c4d331/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/apparentlymart/go-cidr v1.0.0 h1:lGDvXx8Lv9QHjrAVP7jyzleG4F9+FkRhJcEsDFxeb8w=
github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7 h1:MBXhrxjNkjdqJysfNbKMMPFNXlz6EzpOnPcsoYBeD3E=
github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.16.36 h1:POeH34ZME++pr7GBGh+ZO6Y5kOwSMQpqp5BGUgooJ6k=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.18 h1:Hb3+b9HCqrOrbAtFstUWg7H5TQ+/EcklJtE8VShVs8o=
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.0.0-20161015143505-675b82c74c0e h1:giZ2nnSSH4ntzmoNPwdncPXXA2nWdlO7NiebK0gozNI=
github.com/bgentry/speakeasy v0.0.0-20161015143505-675b82c74c0e/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v0.0.0-20170202183821-4a1e882c79dc h1:J/iAaGTCZYfT/allw61NfW/CEoflFsNdhQJny4iLU+0=
github.com/blang/semver v0.0.0-20170202183821-4a1e882c79dc/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20160617131543-bea8f082b6fd/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/bbolt v1.3.1-coreos.1/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.2.0-rc.1.0.20170908195435-80aa810309d4+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v0.0.0-20160617170158-f0777076321a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dnaeon/go-vcr v0.0.0-20170218072653-87d4990451a8/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.1.1-0.20171002171727-8ebdfab36c66/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20160910222444-6b7015e65d36/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.2.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/gruntwork-io/terratest v0.15.9 h1:SBYLlpa6AUm+uRDAErUyQuCmdvP86omWdEGWhQrQPl0=
github.com/gruntwork-io/terratest v0.15.9/go.mod h1:NjUn6YXA5Skxt8Rs20t3isYx5Rl+EgvGB8/+RRXddqk=
github.com/hashicorp/atlas-go v0.0.0-20161107204910-1792bd8de119/go.mod h1:ckHDuH0pxfnmXZkq1niVSguIIV0pA65gifQv3so9llw=
github.com/hashicorp/aws-sdk-go-base v0.3.0/go.mod h1:ZIWACGGi0N7a4DZbf15yuE1JQORmWLtBcVM6F5SXNFU=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/gruntwork-io/terratest v0.16.1 h1:r4kQps7wevleXDik2fGMXnT+ehWAFMrRy/yQQQtA3bo=
github.com/gruntwork-io/terratest v0.16.1/go.mod h1:NjUn6YXA5Skxt8Rs20t3isYx5Rl+EgvGB8/+RRXddqk=
github.com/hashicorp/aws-sdk-go-base v0.2.0/go.mod h1:ZIWACGGi0N7a4DZbf15yuE1JQORmWLtBcVM6F5SXNFU=
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
github.com/hashicorp/go-azure-helpers v0.0.0-20190129193224-166dfd221bb2/go.mod h1:lu62V//auUow6k0IykxLK2DCNW8qTmpm8KqhYVWattA=
github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86 h1:hLYM35twiyKH44g36g+GFYODcrZQetEAY4+zrJtGea0=
github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU=
github.com/hashicorp/go-hclog v0.0.0-20170716174523-b4e5765d1e5f/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-getter v1.3.0 h1:pFMSFlI9l5NaeuzkpE3L7BYk9qQ9juTAgXW/H0cqxcU=
github.com/hashicorp/go-getter v1.3.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw=
github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v0.0.0-20180125190438-e53f54cbf51e/go.mod h1:JSqWYsict+jzcj0+xElxyrBQRPNoiWQuddnxArJ7XHQ=
github.com/hashicorp/go-plugin v1.0.1-0.20190430211030-5692942914bb/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc h1:wAa9fGALVHfjYxZuXRnmuJG2CnwRpJYOTvY6YdErAh0=
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-tfe v0.3.14/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-tfe v0.3.16/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl2 v0.0.0-20180308163058-5f8ed954abd8 h1:laCE8EKBOUVN6LwBt7Be9IX7i2RQ2cnfbt+Z5a+0PRI=
github.com/hashicorp/hcl2 v0.0.0-20180308163058-5f8ed954abd8/go.mod h1:xp1eMAxqhQKBxz+yQUTsig9bBMRRWRWw+rK3FJmHf/A=
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250 h1:fooK5IvDL/KIsi4LxF/JH68nVdrBSiGNPhS2JAQjtjo=
github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=
github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/memberlist v0.0.0-20170208211506-23ad4b7d7b38/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE=
github.com/hashicorp/serf v0.8.2-0.20171022020050-c20a0b1b1ea9/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
github.com/hashicorp/terraform v0.11.14 h1:2PnZWaQ9Apr+6QciC7JY0NsnPq7B+4Le406gn8ZPmuA=
github.com/hashicorp/terraform v0.11.14/go.mod h1:bES0uNzlesKO5m01e2zTbu1jO2KNXd1gvj22zBIDL3M=
github.com/hashicorp/vault v0.0.0-20161029210149-9a60bf2a50e4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
github.com/hashicorp/yamux v0.0.0-20160720233140-d1caa6c97c9f/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/hashicorp/hcl2 v0.0.0-20181208003705-670926858200/go.mod h1:ShfpTh661oAaxo7VcNxg0zcZW6jvMa7Moy2oFx7e5dE=
github.com/hashicorp/hcl2 v0.0.0-20190515223218-4b22149b7cef h1:xZRvbcwHY8zhaxDwgkmpAp2emwZkVn7p3gat0zhq2X0=
github.com/hashicorp/hcl2 v0.0.0-20190515223218-4b22149b7cef/go.mod h1:4oI94iqF3GB10QScn46WqbG0kgTUpha97SAzzg2+2ec=
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI=
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE=
github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
github.com/hashicorp/terraform v0.12.1 h1:A56pUiGbDZ372cvDKWixzVciTGZkqZ01hH58GTNQ4lg=
github.com/hashicorp/terraform v0.12.1/go.mod h1:T3xp83QKah46oRMg37OPBbEVjEwokcGdn7+aNQtQbKo=
github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70 h1:oZm5nE11yhzsTRz/YrUyDMSvixePqjoZihwn8ipuOYI=
github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70/go.mod h1:ItvqtvbC3K23FFET62ZwnkwtpbKZm8t8eMcWjmVVjD8=
github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/osext v0.0.0-20160811001526-c2c54e542fb7/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84=
github.com/magefile/mage v1.8.0 h1:mzL+xIopvPURVBwHG9A50JcjBO+xV3b5iZ7khFRI+5E=
github.com/magefile/mage v1.8.0/go.mod h1:IUDi13rsHje59lecXokTfGX0QIzO45uVPlXnJYsXepA=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
github.com/mattn/go-colorable v0.0.0-20160220075935-9cbef7c35391/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20161123143637-30a891c33c7c h1:YHHK/dEmr2Jo1cWD1VMB2waEeHJhHFp3CEylwWy/VcY=
github.com/mattn/go-isatty v0.0.0-20161123143637-30a891c33c7c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-shellwords v1.0.1/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/masterzen/winrm v0.0.0-20190223112901-5e5c9a7fe54b/go.mod h1:wr1VqkwW0AB5JS0QLy5GpVMS9E3VtRoSYXUYyVk46KY=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v0.0.0-20171129193617-33edc47170b5 h1:OYr3N2fY3e3kP/x/d81CJXlcZrIV2hH8gPnuRLpiME4=
github.com/mitchellh/cli v0.0.0-20171129193617-33edc47170b5/go.mod h1:oGumspjLm2kTyiT1QMGpFqRlmxnKHfCvhZEVnx+5UeE=
github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 h1:dECZqiJYhKdj9QlLpiQaRDXHDXRTdiyZI3owdDGhlYY=
github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3/go.mod h1:eOsF2yLPlBBJPvD+nhl5QMTBSOBbOph6N7j/IDUw7PY=
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM=
github.com/mitchellh/go-testing-interface v0.0.0-20170730050907-9a441910b168 h1:FW/lWFII8EehRx+hVNy5OkkIhWXz9NC69vO5Zr2RExY=
github.com/mitchellh/go-testing-interface v0.0.0-20170730050907-9a441910b168/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/hashstructure v0.0.0-20160209213820-6b17d669fac5 h1:h+4fp6yIoLPf/K2egDK3kvYM2zqb28gJIWWMiDzBdKM=
github.com/mitchellh/hashstructure v0.0.0-20160209213820-6b17d669fac5/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/mapstructure v0.0.0-20170307201123-53818660ed49/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/panicwrap v0.0.0-20161208170302-ba9e1a65e0f7/go.mod h1:QuAqW7/z+iv6aWFJdrA8kCbsF0OOJVKCICqTcYBexuY=
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v0.0.0-20170726202117-63d60e9d0dbc h1:gqYjvctjtX4GHzgfutJxZpvZ7XhGwQLGR5BASwhpO2o=
github.com/mitchellh/reflectwalk v0.0.0-20170726202117-63d60e9d0dbc/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/panicwrap v0.0.0-20190213213626-17011010aaa4/go.mod h1:YYMf4xtQnR8LRC0vKi3afvQ5QwRPQ17zjcpkBCufb+I=
github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v0.0.0-20171219111128-6bee943216c8 h1:lcb1zvdlaZyEbl2OXifN3uOYYyIvllofUbmp9bwbL+0=
github.com/posener/complete v0.0.0-20171219111128-6bee943216c8/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI=
github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/columnize v0.0.0-20161220214920-0fbbb3f0e3fb/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v0.0.0-20160927100844-b061729afc07/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/satori/uuid v0.0.0-20160927100844-b061729afc07/go.mod h1:B8HLsPLik/YNn6KKWVMDJ8nzCL8RP5WyfsnmvnAEwIU=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.0.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE=
github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M=
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
github.com/ugorji/go v0.0.0-20170107133203-ded73eae5db7/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU=
github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zclconf/go-cty v0.0.0-20180302160414-49fa5e03c418 h1:uZKhc0PzQtIg+6+BqQU1m0zzcIgY2hHJk/Xwf00QUNw=
github.com/zclconf/go-cty v0.0.0-20180302160414-49fa5e03c418/go.mod h1:LnDKxj8gN4aatfXUqmUNooaDjvmDcLPbAN3hYBIVoJE=
golang.org/x/crypto v0.0.0-20180211211603-9de5f2eaf759/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v0.0.0-20190426224007-b18a157db9e2/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v0.0.0-20190516203816-4fecf87372ec h1:MSeYjmyjucsFbecMTxg63ASg23lcSARP/kr9sClTFfk=
github.com/zclconf/go-cty v0.0.0-20190516203816-4fecf87372ec/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20180816225734-aabede6cba87/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20170928010508-bb50c06baba3/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab h1:9RfW3ktsOZxgo9YNbBAjq1FWzc/igwEcUzZz8IXgSbk=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9 h1:pfyU+l9dEu0vZzDDMsdAKa1gZbJYEn6urYXj/+Xkz7s=
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
google.golang.org/api v0.0.0-20171005000305-7a7376eff6a5/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v0.0.0-20150527042145-b667a5000b08/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20171002232614-f676e0f3ac63/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v0.0.0-20170809211603-7657092a1303/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

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

@ -1 +0,0 @@

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

@ -1 +0,0 @@

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

@ -1 +0,0 @@

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,5 +1,5 @@
data "azurerm_resource_group" "appgateway" {
name = "${var.resource_group_name}"
name = var.resource_group_name
}
locals {
@ -9,54 +9,54 @@ locals {
}
resource "azurerm_application_gateway" "appgateway" {
name = "${var.appgateway_name}"
resource_group_name = "${data.azurerm_resource_group.appgateway.name}"
location = "${data.azurerm_resource_group.appgateway.location}"
tags = "${var.resource_tags}"
name = var.appgateway_name
resource_group_name = data.azurerm_resource_group.appgateway.name
location = data.azurerm_resource_group.appgateway.location
tags = var.resource_tags
sku {
name = "${var.appgateway_sku_name}"
tier = "${var.appgateway_tier}"
capacity = "${var.appgateway_capacity}"
name = var.appgateway_sku_name
tier = var.appgateway_tier
capacity = var.appgateway_capacity
}
gateway_ip_configuration {
name = "${var.appgateway_ipconfig_name}"
subnet_id = "${var.virtual_network_subnet_id}"
name = var.appgateway_ipconfig_name
subnet_id = var.virtual_network_subnet_id
}
frontend_port {
name = "${var.appgateway_frontend_port_name}"
port = "${var.frontend_http_port}"
name = var.appgateway_frontend_port_name
port = var.frontend_http_port
}
frontend_ip_configuration {
name = "${var.appgateway_frontend_ip_configuration_name}"
public_ip_address_id = "${var.public_pip_id}"
name = var.appgateway_frontend_ip_configuration_name
public_ip_address_id = var.public_pip_id
}
ssl_certificate {
name = "${local.ssl_certificate_name}"
data = "${var.appgateway_ssl_private_pfx}"
name = local.ssl_certificate_name
data = var.appgateway_ssl_private_pfx
password = ""
}
authentication_certificate {
name = "${local.authentication_certificate_name}"
data = "${var.appgateway_ssl_public_cert}"
name = local.authentication_certificate_name
data = var.appgateway_ssl_public_cert
}
backend_address_pool {
name = "${var.appgateway_backend_address_pool_name}"
fqdns = ["${var.backendpool_fqdns}"]
name = var.appgateway_backend_address_pool_name
fqdns = var.backendpool_fqdns
}
backend_http_settings {
name = "${var.appgateway_backend_http_setting_name}"
cookie_based_affinity = "${var.backend_http_cookie_based_affinity}"
port = "${var.backend_http_port}"
protocol = "${var.backend_http_protocol}"
probe_name = "${local.backend_probe_name}"
name = var.appgateway_backend_http_setting_name
cookie_based_affinity = var.backend_http_cookie_based_affinity
port = var.backend_http_port
protocol = var.backend_http_protocol
probe_name = local.backend_probe_name
request_timeout = 1
pick_host_name_from_backend_address = true
}
@ -64,43 +64,42 @@ resource "azurerm_application_gateway" "appgateway" {
# TODO This is locked into a single api endpoint... We'll need to eventually support multiple endpoints
# but the count property is only supported at the resource level.
probe {
name = "${local.backend_probe_name}"
protocol = "${var.backend_http_protocol}"
name = local.backend_probe_name
protocol = var.backend_http_protocol
path = "/"
interval = "30"
timeout = "30"
unhealthy_threshold = "3"
interval = 30
timeout = 30
unhealthy_threshold = 3
pick_host_name_from_backend_http_settings = true
}
http_listener {
name = "${var.appgateway_listener_name}"
frontend_ip_configuration_name = "${var.appgateway_frontend_ip_configuration_name}"
frontend_port_name = "${var.appgateway_frontend_port_name}"
protocol = "${var.http_listener_protocol}"
ssl_certificate_name = "${local.ssl_certificate_name}"
name = var.appgateway_listener_name
frontend_ip_configuration_name = var.appgateway_frontend_ip_configuration_name
frontend_port_name = var.appgateway_frontend_port_name
protocol = var.http_listener_protocol
ssl_certificate_name = local.ssl_certificate_name
}
waf_configuration {
enabled = "true"
firewall_mode = "${var.appgateway_waf_config_firewall_mode}"
enabled = true
firewall_mode = var.appgateway_waf_config_firewall_mode
rule_set_type = "OWASP"
rule_set_version = "3.0"
}
request_routing_rule {
name = "${var.appgateway_request_routing_rule_name}"
http_listener_name = "${var.appgateway_listener_name}"
rule_type = "${var.request_routing_rule_type}"
backend_address_pool_name = "${var.appgateway_backend_address_pool_name}"
backend_http_settings_name = "${var.appgateway_backend_http_setting_name}"
name = var.appgateway_request_routing_rule_name
http_listener_name = var.appgateway_listener_name
rule_type = var.request_routing_rule_type
backend_address_pool_name = var.appgateway_backend_address_pool_name
backend_http_settings_name = var.appgateway_backend_http_setting_name
}
}
data "external" "app_gw_health" {
depends_on = [
"azurerm_application_gateway.appgateway",
]
depends_on = [azurerm_application_gateway.appgateway]
program = ["az", "network", "application-gateway", "show-backend-health", "-g", "${data.azurerm_resource_group.appgateway.name}", "-n", "${var.appgateway_name}", "-o", "json", "--query", "backendAddressPools[0].backendHttpSettingsCollection[0].servers[0].{address:address,health:health}"]
program = ["az", "network", "application-gateway", "show-backend-health", "-g", data.azurerm_resource_group.appgateway.name, "-n", var.appgateway_name, "-o", "json", "--query", "backendAddressPools[0].backendHttpSettingsCollection[0].servers[0].{address:address,health:health}"]
}

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

@ -1,22 +1,23 @@
output "appgateway_name" {
description = "The name of the Application Gateway created"
value = "${azurerm_application_gateway.appgateway.name}"
value = azurerm_application_gateway.appgateway.name
}
output "appgateway_ipconfig" {
description = "The Application Gateway IP Configuration"
value = "${azurerm_application_gateway.appgateway.gateway_ip_configuration}"
value = azurerm_application_gateway.appgateway.gateway_ip_configuration
}
output "appgateway_frontend_ip_configuration" {
description = "The Application Gateway Frontend IP Configuration"
value = "${azurerm_application_gateway.appgateway.frontend_ip_configuration}"
value = azurerm_application_gateway.appgateway.frontend_ip_configuration
}
output "appgateway_health_probe_backend_status" {
value = "${lookup(data.external.app_gw_health.result, "health")}"
value = data.external.app_gw_health.result["health"]
}
output "app_gateway_health_probe_backend_address" {
value = "${lookup(data.external.app_gw_health.result, "address")}"
value = data.external.app_gw_health.result["address"]
}

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

@ -1,150 +1,149 @@
# TODO convert all numeric string to true number scalar types once we're migrated to terraform V12. This
# change should take place across both module and template configuration defintions.
variable "resource_group_name" {
description = "Resource group name that the app gateway will be created in."
type = "string"
type = string
}
variable "virtual_network_name" {
description = "Virtual Network name that the app gateway will be created in."
type = "string"
type = string
}
variable "virtual_network_subnet_id" {
description = "Subnet id that the app gateway will be created in."
type = "string"
type = string
}
variable "appgateway_name" {
description = "The name of the application gateway"
type = "string"
type = string
}
variable "appgateway_ssl_private_pfx" {
description = "PFX certificate"
type = "string"
type = string
}
variable "appgateway_ssl_public_cert" {
description = "The contents of the Authentication Certificate which should be used"
type = "string"
type = string
}
variable "public_pip_id" {
description = "the public ip resource id of the frontend configuration"
type = "string"
type = string
}
variable "resource_tags" {
description = "Map of tags to apply to taggable resources in this module. By default the taggable resources are tagged with the name defined above and this map is merged in"
type = "map"
type = map(string)
default = {}
}
variable "appgateway_frontend_port_name" {
description = "The Frontend Port Name for the Appication Gateway to be created"
type = "string"
type = string
default = "https-frontend-port"
}
variable "appgateway_sku_name" {
description = "The SKU for the Appication Gateway to be created"
type = "string"
type = string
default = "WAF_Medium"
}
variable "appgateway_tier" {
description = "The tier of the application gateway. Small/Medium/Large. More details can be found at https://azure.microsoft.com/en-us/pricing/details/application-gateway/"
type = "string"
type = string
default = "WAF"
}
variable "appgateway_capacity" {
description = "The capacity of application gateway to be created"
type = "string"
type = number
default = 2
}
variable "appgateway_ipconfig_name" {
description = "The IP Config Name for the Appication Gateway to be created"
type = "string"
type = string
default = "subnet"
}
variable "frontend_http_port" {
description = "The frontend port for the Appication Gateway to be created"
type = "string"
type = number
default = 443
}
variable "appgateway_frontend_ip_configuration_name" {
description = "The Frontend IP configuration name for the Appication Gateway to be created"
type = "string"
type = string
default = "frontend"
}
variable "appgateway_backend_address_pool_name" {
description = "The Backend Addres Pool Name for the Appication Gateway to be created"
type = "string"
type = string
default = "backend_pool"
}
variable "appgateway_backend_http_setting_name" {
description = "The Backend Http Settings Name for the Appication Gateway to be created"
type = "string"
type = string
default = "backend_settings"
}
variable "backend_http_cookie_based_affinity" {
description = "The Backend Http cookie based affinity for the Appication Gateway to be created"
type = "string"
type = string
default = "Disabled"
}
variable "backend_http_port" {
description = "The backend port for the Appication Gateway to be created"
type = "string"
type = number
default = 443
}
variable "backend_http_protocol" {
description = "The backend protocol for the Appication Gateway to be created"
type = "string"
type = string
default = "Https"
}
variable "http_listener_protocol" {
description = "The Http Listener protocol for the Appication Gateway to be created"
type = "string"
type = string
default = "Https"
}
variable "appgateway_listener_name" {
description = "The Listener Name for the Appication Gateway to be created"
type = "string"
type = string
default = "http_proxy_listener"
}
variable "appgateway_request_routing_rule_name" {
description = "The rule name to request routing for the Appication Gateway to be created"
type = "string"
type = string
default = "request_proxy_routing_rule"
}
variable "request_routing_rule_type" {
description = "The rule type to request routing for the Appication Gateway to be created"
type = "string"
type = string
default = "Basic"
}
variable "appgateway_waf_config_firewall_mode" {
description = "The firewall mode on the waf gateway"
type = "string"
type = string
default = "Prevention"
}
variable "backendpool_fqdns" {
description = "A list of FQDN's which should be part of the Backend Address Pool."
type = "list"
type = list(string)
default = []
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,11 +1,12 @@
data "azurerm_resource_group" "appinsights" {
name = "${var.service_plan_resource_group_name}"
name = var.service_plan_resource_group_name
}
resource "azurerm_application_insights" "appinsights" {
name = "${var.appinsights_name}"
resource_group_name = "${data.azurerm_resource_group.appinsights.name}"
location = "${data.azurerm_resource_group.appinsights.location}"
application_type = "${var.appinsights_application_type}"
tags = "${var.resource_tags}"
name = var.appinsights_name
resource_group_name = data.azurerm_resource_group.appinsights.name
location = data.azurerm_resource_group.appinsights.location
application_type = var.appinsights_application_type
tags = var.resource_tags
}

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

@ -1,10 +1,11 @@
output "app_insights_app_id" {
description = "The App ID associated with this Application Insights component"
value = "${azurerm_application_insights.appinsights.app_id}"
value = azurerm_application_insights.appinsights.app_id
}
output "app_insights_instrumentation_key" {
description = "The Instrumentation Key for this Application Insights component."
value = "${azurerm_application_insights.appinsights.instrumentation_key}"
value = azurerm_application_insights.appinsights.instrumentation_key
sensitive = true
}

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

@ -1,20 +1,21 @@
variable "service_plan_resource_group_name" {
description = "The name of the resource group in which the service plan was created."
type = "string"
type = string
}
variable "resource_tags" {
description = "Map of tags to apply to taggable resources in this module (enter as a set of curly braces containing key-value pairs, as in: {\"tag1\" = \"value1\", \"tag2\" = \"value2\"}). By default the taggable resources are tagged with the name defined above and this map is merged in"
type = "map"
type = map(string)
default = {}
}
variable "appinsights_name" {
description = "Name of the App Insights to create"
type = "string"
type = string
}
variable "appinsights_application_type" {
description = "Type of the App Insights Application. Valid values are ios for iOS, java for Java web, MobileCenter for App Center, Node.JS for Node.js, other for General, phone for Windows Phone, store for Windows Store and web for ASP.NET."
type = "string"
type = string
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -4,40 +4,41 @@ locals {
}
resource "azurerm_monitor_action_group" "appmonitoring" {
count = "${var.action_group_email_receiver == "" ? 0 : 1}"
name = "${var.action_group_name}"
resource_group_name = "${var.resource_group_name}"
short_name = "${var.action_group_short_name}"
count = var.action_group_email_receiver == "" ? 0 : 1
name = var.action_group_name
resource_group_name = var.resource_group_name
short_name = var.action_group_short_name
email_receiver {
name = "${var.action_group_email_receiver_name}"
email_address = "${var.action_group_email_receiver}"
name = var.action_group_email_receiver_name
email_address = var.action_group_email_receiver
}
}
resource "azurerm_monitor_metric_alert" "appmonitoring" {
count = "${var.action_group_email_receiver == "" ? 0 : 1}"
name = "${var.metric_alert_name}"
resource_group_name = "${azurerm_monitor_action_group.appmonitoring.resource_group_name}"
scopes = ["${var.resource_ids}"]
frequency = "${var.metric_alert_frequency}"
window_size = "${var.metric_alert_period}"
count = var.action_group_email_receiver == "" ? 0 : 1
name = var.metric_alert_name
resource_group_name = azurerm_monitor_action_group.appmonitoring[0].resource_group_name
scopes = var.resource_ids
frequency = var.metric_alert_frequency
window_size = var.metric_alert_period
criteria {
metric_namespace = "${var.metric_alert_criteria_namespace}"
metric_name = "${var.metric_alert_criteria_name}"
aggregation = "${var.metric_alert_criteria_aggregation}"
operator = "${var.metric_alert_criteria_operator}"
threshold = "${var.metric_alert_criteria_threshold}"
metric_namespace = var.metric_alert_criteria_namespace
metric_name = var.metric_alert_criteria_name
aggregation = var.metric_alert_criteria_aggregation
operator = var.metric_alert_criteria_operator
threshold = var.metric_alert_criteria_threshold
dimension {
name = "${local.scaling_name}"
operator = "${local.scaling_operator}"
values = "${var.scaling_values}"
name = local.scaling_name
operator = local.scaling_operator
values = var.scaling_values
}
}
action {
action_group_id = "${azurerm_monitor_action_group.appmonitoring.id}"
action_group_id = azurerm_monitor_action_group.appmonitoring[0].id
}
}

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

@ -1,4 +1,5 @@
output "rule_resource_id" {
description = "The id of a metric alert rule."
value = "${azurerm_monitor_metric_alert.appmonitoring.*.id}"
value = azurerm_monitor_metric_alert.appmonitoring.*.id
}

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

@ -1,81 +1,82 @@
variable "resource_group_name" {
description = "The name of the resource group."
type = "string"
type = string
}
# action group attributes
variable "action_group_name" {
description = "The name of the action group."
type = "string"
type = string
}
variable "action_group_email_receiver" {
description = "The e-mail receiver for an alert rule resource."
type = "string"
type = string
default = ""
}
variable "action_group_email_receiver_name" {
description = "The e-mail receiver name for an alert group."
type = "string"
type = string
default = "E-mail Receiver"
}
variable "action_group_short_name" {
description = "The abbreviated name of the action group."
type = "string"
type = string
default = "Notify"
}
# metric alert attributes
variable "resource_ids" {
description = "Resource Ids to be monitored."
type = "list"
type = list(string)
}
variable "metric_alert_name" {
description = "The display name of a group of metric alert criteria."
type = "string"
type = string
}
variable "metric_alert_criteria_name" {
description = "A predefined Azure resource alert monitoring rule name."
type = "string"
type = string
}
variable "metric_alert_criteria_namespace" {
description = "A monitored resource namespace that holds metric alert criteria."
type = "string"
type = string
}
variable "metric_alert_criteria_aggregation" {
description = "The calculation used for building metric alert criteria."
type = "string"
type = string
}
variable "metric_alert_criteria_operator" {
description = "A logical operator used for building metric alert criteria."
type = "string"
type = string
}
variable "metric_alert_criteria_threshold" {
description = "The criteria threshold value that activates the metric alert."
type = "string"
type = string
}
variable "scaling_values" {
description = "App instance names made available from app service plan scaling options."
type = "list"
type = list(string)
}
variable "metric_alert_frequency" {
description = "The frequency with which the metric alert checks if the conditions are met."
type = "string"
type = string
default = "PT1M"
}
variable "metric_alert_period" {
description = "The look back window over which metric values are checked."
type = "string"
type = string
default = "PT5M"
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -4,35 +4,35 @@ locals {
}
data "azurerm_resource_group" "appsvc" {
name = "${var.service_plan_resource_group_name}"
name = var.service_plan_resource_group_name
}
data "azurerm_app_service_plan" "appsvc" {
name = "${var.service_plan_name}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
name = var.service_plan_name
resource_group_name = data.azurerm_resource_group.appsvc.name
}
resource "azurerm_app_service" "appsvc" {
name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
location = "${data.azurerm_resource_group.appsvc.location}"
app_service_plan_id = "${data.azurerm_app_service_plan.appsvc.id}"
tags = "${var.resource_tags}"
count = "${length(keys(var.app_service_name))}"
resource_group_name = data.azurerm_resource_group.appsvc.name
location = data.azurerm_resource_group.appsvc.location
app_service_plan_id = data.azurerm_app_service_plan.appsvc.id
tags = var.resource_tags
count = length(keys(var.app_service_name))
app_settings {
WEBSITES_ENABLE_APP_SERVICE_STORAGE = "${var.enable_storage}"
DOCKER_REGISTRY_SERVER_URL = "${var.docker_registry_server_url}"
DOCKER_REGISTRY_SERVER_USERNAME = "${var.docker_registry_server_username}"
DOCKER_REGISTRY_SERVER_PASSWORD = "${var.docker_registry_server_password}"
APPINSIGHTS_INSTRUMENTATIONKEY = "${var.app_insights_instrumentation_key}"
KEYVAULT_URI = "${var.vault_uri}"
app_settings = {
WEBSITES_ENABLE_APP_SERVICE_STORAGE = var.enable_storage
DOCKER_REGISTRY_SERVER_URL = var.docker_registry_server_url
DOCKER_REGISTRY_SERVER_USERNAME = var.docker_registry_server_username
DOCKER_REGISTRY_SERVER_PASSWORD = var.docker_registry_server_password
APPINSIGHTS_INSTRUMENTATIONKEY = var.app_insights_instrumentation_key
KEYVAULT_URI = var.vault_uri
}
site_config {
linux_fx_version = "${element(values(var.app_service_name), count.index)}"
always_on = "${var.site_config_always_on}"
virtual_network_name = "${var.vnet_name}"
linux_fx_version = element(values(var.app_service_name), count.index)
always_on = var.site_config_always_on
virtual_network_name = var.vnet_name
}
identity {
@ -43,26 +43,27 @@ resource "azurerm_app_service" "appsvc" {
resource "azurerm_app_service_slot" "appsvc_staging_slot" {
name = "staging"
app_service_name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
count = "${length(keys(var.app_service_name))}"
location = "${data.azurerm_resource_group.appsvc.location}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
app_service_plan_id = "${data.azurerm_app_service_plan.appsvc.id}"
depends_on = ["azurerm_app_service.appsvc"]
count = length(keys(var.app_service_name))
location = data.azurerm_resource_group.appsvc.location
resource_group_name = data.azurerm_resource_group.appsvc.name
app_service_plan_id = data.azurerm_app_service_plan.appsvc.id
depends_on = [azurerm_app_service.appsvc]
}
resource "azurerm_template_deployment" "access_restriction" {
name = "access_restriction"
count = "${var.vnet_name == "" ? 0 : length(keys(var.app_service_name))}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
count = var.vnet_name == "" ? 0 : length(keys(var.app_service_name))
resource_group_name = data.azurerm_resource_group.appsvc.name
parameters = {
service_name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
vnet_subnet_id = "${var.vnet_subnet_id}"
access_restriction_name = "${local.access_restriction_name}"
access_restriction_description = "${local.access_restriction_description}"
vnet_subnet_id = var.vnet_subnet_id
access_restriction_name = local.access_restriction_name
access_restriction_description = local.access_restriction_description
}
deployment_mode = "Incremental"
template_body = "${file("${path.module}/azuredeploy.json")}"
depends_on = ["azurerm_app_service.appsvc"]
template_body = file("${path.module}/azuredeploy.json")
depends_on = [azurerm_app_service.appsvc]
}

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

@ -1,19 +1,20 @@
output "app_service_uri" {
description = "The URL of the app service created"
value = ["${azurerm_app_service.appsvc.*.default_site_hostname}"]
value = azurerm_app_service.appsvc.*.default_site_hostname
}
output "app_service_ids" {
description = "The resource ids of the app service created"
value = ["${azurerm_app_service.appsvc.*.id}"]
value = azurerm_app_service.appsvc.*.id
}
output "app_service_identity_tenant_id" {
description = " The Tenant ID for the Service Principal associated with the Managed Service Identity of this App Service."
value = "${azurerm_app_service.appsvc.0.identity.0.tenant_id}"
value = azurerm_app_service.appsvc[0].identity[0].tenant_id
}
output "app_service_identity_object_ids" {
description = " The Principal IDs for the Service Principal associated with the Managed Service Identity for all App Services."
value = ["${azurerm_app_service.appsvc.*.identity.0.principal_id}"]
value = azurerm_app_service.appsvc.*.identity.0.principal_id
}

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

@ -1,75 +1,76 @@
variable "service_plan_resource_group_name" {
description = "The name of the resource group in which the service plan was created."
type = "string"
type = string
}
variable "service_plan_name" {
description = "The name of the service plan"
type = "string"
type = string
}
variable "resource_tags" {
description = "Map of tags to apply to taggable resources in this module. By default the taggable resources are tagged with the name defined above and this map is merged in"
type = "map"
type = map(string)
default = {}
}
variable "app_service_name" {
description = "The name of the app service to be created"
type = "map"
type = map(string)
default = {}
}
variable "enable_storage" {
description = "Determines whether or not a storage is attached to the app service."
type = "string"
default = "false"
type = bool
default = false
}
variable "vault_uri" {
description = "Specifies the URI of the Key Vault resource. Providing this will create a new app setting called KEYVAULT_URI containing the uri value."
type = "string"
type = string
default = ""
}
variable "docker_registry_server_url" {
description = "The docker registry server URL for app service to be created"
type = "string"
type = string
default = "index.docker.io"
}
variable "docker_registry_server_username" {
description = "The docker registry server username for app service to be created"
type = "string"
type = string
default = ""
}
variable "docker_registry_server_password" {
description = "The docker registry server password for app service to be created"
type = "string"
type = string
default = ""
}
variable "app_insights_instrumentation_key" {
description = "The Instrumentation Key for the Application Insights component used for app service to be created"
type = "string"
type = string
default = ""
}
variable "site_config_always_on" {
description = "Should the app be loaded at all times? Defaults to false."
type = "string"
default = "false"
type = bool
default = false
}
variable "vnet_name" {
description = "The vnet integration name."
type = "string"
description = "The vnet integration name"
type = string
default = ""
}
variable "vnet_subnet_id" {
description = "The vnet integration subnet gateway identifier."
type = "string"
type = string
default = ""
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,12 +1,13 @@
data "azurerm_resource_group" "container_registry" {
name = "${var.resource_group_name}"
name = var.resource_group_name
}
resource "azurerm_container_registry" "container_registry" {
name = "${var.container_registry_name}"
resource_group_name = "${data.azurerm_resource_group.container_registry.name}"
location = "${data.azurerm_resource_group.container_registry.location}"
sku = "${var.container_registry_sku}"
admin_enabled = "${var.container_registry_admin_enabled}"
tags = "${var.container_registry_tags}"
name = var.container_registry_name
resource_group_name = data.azurerm_resource_group.container_registry.name
location = data.azurerm_resource_group.container_registry.location
sku = var.container_registry_sku
admin_enabled = var.container_registry_admin_enabled
tags = var.container_registry_tags
}

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

@ -1,15 +1,16 @@
output "container_registry_id" {
description = "The Container Registry ID."
value = "${azurerm_container_registry.container_registry.id}"
value = azurerm_container_registry.container_registry.id
}
output "container_registry_login_server" {
description = "The URL that can be used to log into the container registry."
value = "${azurerm_container_registry.container_registry.login_server}"
value = azurerm_container_registry.container_registry.login_server
}
output "container_registry_admin_username" {
description = "The Username associated with the Container Registry Admin account - if the admin account is enabled."
value = "${azurerm_container_registry.container_registry.admin_username}"
value = azurerm_container_registry.container_registry.admin_username
sensitive = true
}

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

@ -1,11 +1,11 @@
variable "resource_group_name" {
description = "(Required) The name of the resource group in which to create the Container Registry. Changing this forces a new resource to be created."
type = "string"
type = string
}
variable "container_registry_name" {
description = "(Required) Specifies the name of the Container Registry. Changing this forces a new resource to be created."
type = "string"
type = string
}
variable "container_registry_admin_enabled" {
@ -15,12 +15,13 @@ variable "container_registry_admin_enabled" {
variable "container_registry_sku" {
description = "(Optional) The SKU name of the the container registry. Possible values are Basic, Standard and Premium."
type = "string"
type = string
default = "Basic"
}
variable "container_registry_tags" {
description = "(Optional) A mapping of tags to assign to the resource."
type = "map"
type = map(string)
default = {}
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,22 +1,23 @@
data "azurerm_resource_group" "cosmosdb" {
name = "${var.service_plan_resource_group_name}"
name = var.service_plan_resource_group_name
}
resource "azurerm_cosmosdb_account" "cosmosdb" {
name = "${var.cosmosdb_name}"
location = "${data.azurerm_resource_group.cosmosdb.location}"
resource_group_name = "${data.azurerm_resource_group.cosmosdb.name}"
name = var.cosmosdb_name
location = data.azurerm_resource_group.cosmosdb.location
resource_group_name = data.azurerm_resource_group.cosmosdb.name
offer_type = "Standard"
kind = "${var.cosmosdb_kind}"
kind = var.cosmosdb_kind
enable_automatic_failover = "${var.cosmosdb_automatic_failover}"
enable_automatic_failover = var.cosmosdb_automatic_failover
consistency_policy {
consistency_level = "${var.consistency_level}"
consistency_level = var.consistency_level
}
geo_location {
location = "${var.primary_replica_location}"
location = var.primary_replica_location
failover_priority = 0
}
}

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

@ -1,15 +1,16 @@
output "cosmosdb_endpoint" {
description = "The endpoint used to connect to the CosmosDB account."
value = "${azurerm_cosmosdb_account.cosmosdb.endpoint}"
value = azurerm_cosmosdb_account.cosmosdb.endpoint
}
output "cosmosdb_primary_master_key" {
description = "The Primary master key for the CosmosDB Account."
value = "${azurerm_cosmosdb_account.cosmosdb.primary_master_key}"
value = azurerm_cosmosdb_account.cosmosdb.primary_master_key
sensitive = true
}
output "cosmosdb_connection_strings" {
description = "A list of connection strings available for this CosmosDB account."
value = "${azurerm_cosmosdb_account.cosmosdb.connection_strings}"
value = azurerm_cosmosdb_account.cosmosdb.connection_strings
}

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

@ -1,16 +1,16 @@
variable "service_plan_resource_group_name" {
description = "Resource group name that the CosmosDB will be created in."
type = "string"
type = string
}
variable "cosmosdb_name" {
description = "The name that CosmosDB will be created with."
type = "string"
type = string
}
variable "cosmosdb_kind" {
description = "Determines the kind of CosmosDB to create. Can either be 'GlobalDocumentDB' or 'MongoDB'."
type = "string"
type = string
default = "GlobalDocumentDB"
}
@ -21,11 +21,12 @@ variable "cosmosdb_automatic_failover" {
variable "consistency_level" {
description = "The Consistency Level to use for this CosmosDB Account. Can be either 'BoundedStaleness', 'Eventual', 'Session', 'Strong' or 'ConsistentPrefix'."
type = "string"
type = string
default = "Session"
}
variable "primary_replica_location" {
description = "The name of the Azure region to host replicated data."
type = "string"
type = string
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,17 +1,18 @@
data "azurerm_key_vault" "vault" {
name = "${var.keyvault_name}"
resource_group_name = "${var.resource_group_name}"
name = var.keyvault_name
resource_group_name = var.resource_group_name
}
data "azurerm_client_config" "current" {}
data "azurerm_client_config" "current" {
}
resource "azurerm_key_vault_certificate" "kv_cert_import" {
count = "${var.key_vault_cert_import_filepath == "" ? 0 : 1}"
name = "${var.key_vault_cert_name}"
key_vault_id = "${data.azurerm_key_vault.vault.id}"
count = var.key_vault_cert_import_filepath == "" ? 0 : 1
name = var.key_vault_cert_name
key_vault_id = data.azurerm_key_vault.vault.id
certificate {
contents = "${filebase64("${var.key_vault_cert_import_filepath}")}"
contents = filebase64(var.key_vault_cert_import_filepath)
password = ""
}
@ -28,15 +29,15 @@ resource "azurerm_key_vault_certificate" "kv_cert_import" {
}
secret_properties {
content_type = "${var.key_vault_content_type}"
content_type = var.key_vault_content_type
}
}
}
resource "azurerm_key_vault_certificate" "kv_cert_self_assign" {
count = "${var.key_vault_cert_import_filepath == "" ? 1 : 0}"
name = "${var.key_vault_cert_name}"
key_vault_id = "${data.azurerm_key_vault.vault.id}"
count = var.key_vault_cert_import_filepath == "" ? 1 : 0
name = var.key_vault_cert_name
key_vault_id = data.azurerm_key_vault.vault.id
certificate_policy {
issuer_parameters {
@ -56,12 +57,12 @@ resource "azurerm_key_vault_certificate" "kv_cert_self_assign" {
}
trigger {
days_before_expiry = "${var.key_vault_cert_days_before_expiry}"
days_before_expiry = var.key_vault_cert_days_before_expiry
}
}
secret_properties {
content_type = "${var.key_vault_content_type}"
content_type = var.key_vault_content_type
}
x509_certificate_properties {
@ -79,29 +80,30 @@ resource "azurerm_key_vault_certificate" "kv_cert_self_assign" {
]
subject_alternative_names {
dns_names = ["${var.key_vault_cert_alt_names}"]
dns_names = var.key_vault_cert_alt_names
}
subject = "CN=${var.key_vault_cert_subject}"
validity_in_months = "${var.key_vault_cert_validity_months}"
validity_in_months = var.key_vault_cert_validity_months
}
}
}
data "external" "public_cert" {
depends_on = [
"azurerm_key_vault_certificate.kv_cert_self_assign",
"azurerm_key_vault_certificate.kv_cert_import",
azurerm_key_vault_certificate.kv_cert_self_assign,
azurerm_key_vault_certificate.kv_cert_import,
]
program = ["az", "keyvault", "certificate", "show", "--name", "${var.key_vault_cert_name}", "--vault-name", "${var.keyvault_name}", "-o", "json", "--query", "{cer:cer}"]
program = ["az", "keyvault", "certificate", "show", "--name", var.key_vault_cert_name, "--vault-name", var.keyvault_name, "-o", "json", "--query", "{cer:cer}"]
}
data "external" "private_pfx" {
depends_on = [
"azurerm_key_vault_certificate.kv_cert_self_assign",
"azurerm_key_vault_certificate.kv_cert_import",
azurerm_key_vault_certificate.kv_cert_self_assign,
azurerm_key_vault_certificate.kv_cert_import,
]
program = ["az", "keyvault", "secret", "show", "--name", "${var.key_vault_cert_name}", "--vault-name", "${var.keyvault_name}", "-o", "json", "--query", "{value:value}"]
program = ["az", "keyvault", "secret", "show", "--name", var.key_vault_cert_name, "--vault-name", var.keyvault_name, "-o", "json", "--query", "{value:value}"]
}

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

@ -1,17 +1,18 @@
output "cert_name" {
value = "${var.key_vault_cert_name}"
value = var.key_vault_cert_name
}
output "public_cert" {
value = "${lookup(data.external.public_cert.result, "cer")}"
value = data.external.public_cert.result["cer"]
sensitive = true
}
output "private_pfx" {
value = "${lookup(data.external.private_pfx.result, "value")}"
value = data.external.private_pfx.result["value"]
sensitive = true
}
output "vault_id" {
value = "${data.azurerm_key_vault.vault.id}"
value = data.azurerm_key_vault.vault.id
}

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

@ -1,51 +1,52 @@
variable "keyvault_name" {
type = "string"
type = string
description = "The name of the Key Vault where the Certificate should be created."
}
variable "resource_group_name" {
type = "string"
type = string
description = "Default resource group name that the network will be created in."
}
variable "key_vault_cert_name" {
description = "Name of the certifacte to create"
type = "string"
type = string
default = "pfx-certificate"
}
variable "key_vault_cert_alt_names" {
type = "list"
type = list(string)
description = "A list of alternative DNS names (FQDNs) identified by the Certificate. Changing this forces a new resource to be created."
default = [""]
}
variable "key_vault_content_type" {
type = "string"
type = string
description = " The Content-Type of the Certificate, such as application/x-pkcs12 for a PFX or application/x-pem-file for a PEM. Changing this forces a new resource to be created."
default = "application/x-pkcs12"
}
variable "key_vault_cert_import_filepath" {
type = "string"
type = string
description = "The base64-encoded certificate file path. Changing this forces a new resource to be created."
default = ""
}
variable "key_vault_cert_subject" {
type = "string"
type = string
description = "The Certificate's Subject. Changing this forces a new resource to be created."
default = ""
}
variable "key_vault_cert_validity_months" {
type = "string"
type = number
description = "The Certificates Validity Period in Months."
default = "12"
default = 12
}
variable "key_vault_cert_days_before_expiry" {
type = "string"
type = number
description = "The number of days before the Certificate expires that the action associated with this Trigger should run."
default = "30"
default = 30
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,11 +1,12 @@
resource "azurerm_key_vault_access_policy" "keyvault" {
count = "${var.instance_count}"
key_vault_id = "${var.vault_id}"
count = var.instance_count
key_vault_id = var.vault_id
tenant_id = "${var.tenant_id}"
object_id = "${var.object_ids[count.index]}"
tenant_id = var.tenant_id
object_id = var.object_ids[count.index]
key_permissions = "${var.key_permissions}"
secret_permissions = "${var.secret_permissions}"
certificate_permissions = "${var.certificate_permissions}"
key_permissions = var.key_permissions
secret_permissions = var.secret_permissions
certificate_permissions = var.certificate_permissions
}

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

@ -1,37 +1,38 @@
variable "vault_id" {
description = "Specifies the name of the Key Vault resource."
type = "string"
type = string
}
variable "tenant_id" {
description = "The Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Changing this forces a new resource to be created."
type = "string"
type = string
}
variable "instance_count" {
description = "the number of access policies that we need to create. Terraform requires the resource instance count to be known during the plan creation step. This will be removed once we upgrade to TF version 12"
type = "string"
type = string
}
variable "object_ids" {
description = "The object IDs of a user, service principal or security group in the Azure Active Directory tenant for the vault. The object ID must be unique for the list of access policies. Changing this forces a new resource to be created."
type = "list"
type = list(string)
}
variable "key_permissions" {
description = "List of key permissions, must be one or more from the following: backup, create, decrypt, delete, encrypt, get, import, list, purge, recover, restore, sign, unwrapKey, update, verify and wrapKey"
type = "list"
type = list(string)
default = ["create", "delete", "get", "list"]
}
variable "secret_permissions" {
type = "list"
type = list(string)
description = "List of secret permissions, must be one or more from the following: backup, delete, get, list, purge, recover, restore and set."
default = ["delete", "get", "set", "list"]
}
variable "certificate_permissions" {
type = "list"
type = list(string)
description = "List of storage permissions, must be one or more from the following: backup, delete, deletesas, get, getsas, list, listsas, purge, recover, regeneratekey, restore, set, setsas and update."
default = ["create", "delete", "get", "list"]
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -3,28 +3,30 @@ module "azure-provider" {
}
data "azurerm_resource_group" "kv" {
name = "${var.resource_group_name}"
name = var.resource_group_name
}
data "azurerm_client_config" "current" {}
data "azurerm_client_config" "current" {
}
resource "azurerm_key_vault" "keyvault" {
name = "${var.keyvault_name}"
location = "${data.azurerm_resource_group.kv.location}"
resource_group_name = "${data.azurerm_resource_group.kv.name}"
tenant_id = "${data.azurerm_client_config.current.tenant_id}"
name = var.keyvault_name
location = data.azurerm_resource_group.kv.location
resource_group_name = data.azurerm_resource_group.kv.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku {
name = "${var.keyvault_sku}"
name = var.keyvault_sku
}
access_policy {
tenant_id = "${data.azurerm_client_config.current.tenant_id}"
object_id = "${data.azurerm_client_config.current.service_principal_object_id}"
key_permissions = "${var.keyvault_key_permissions}"
secret_permissions = "${var.keyvault_secret_permissions}"
certificate_permissions = "${var.keyvault_certificate_permissions}"
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.service_principal_object_id
key_permissions = var.keyvault_key_permissions
secret_permissions = var.keyvault_secret_permissions
certificate_permissions = var.keyvault_certificate_permissions
}
tags = "${var.resource_tags}"
tags = var.resource_tags
}

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

@ -1,14 +1,15 @@
output "keyvault_id" {
description = "The id of the Keyvault"
value = "${azurerm_key_vault.keyvault.id}"
value = azurerm_key_vault.keyvault.id
}
output "keyvault_uri" {
description = "The uri of the keyvault"
value = "${azurerm_key_vault.keyvault.vault_uri}"
value = azurerm_key_vault.keyvault.vault_uri
}
output "keyvault_name" {
description = "The name of the Keyvault"
value = "${azurerm_key_vault.keyvault.name}"
value = azurerm_key_vault.keyvault.name
}

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

@ -1,40 +1,41 @@
variable "keyvault_name" {
description = "Name of the keyvault to create"
type = "string"
type = string
default = "spkeyvault"
}
variable "keyvault_sku" {
description = "SKU of the keyvault to create"
type = "string"
type = string
default = "standard"
}
variable "resource_group_name" {
type = "string"
type = string
description = "Default resource group name that the network will be created in."
}
variable "keyvault_key_permissions" {
description = "Permissions that the service principal has for accessing keys from KeyVault"
type = "list"
type = list(string)
default = ["create", "delete", "get"]
}
variable "keyvault_secret_permissions" {
description = "Permissions that the service principal has for accessing secrets from KeyVault"
type = "list"
type = list(string)
default = ["set", "delete", "get", "list"]
}
variable "keyvault_certificate_permissions" {
description = "Permissions that the service principal has for accessing certificates from KeyVault"
type = "list"
type = list(string)
default = ["create", "delete", "get", "list"]
}
variable "resource_tags" {
description = "Map of tags to apply to taggable resources in this module. By default the taggable resources are tagged with the name defined above and this map is merged in"
type = "map"
type = map(string)
default = {}
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -21,7 +21,7 @@ resource "azurerm_resource_group" "example" {
}
```
### Terraform (Version 0.11.13 or higher)
### Terraform (Version 0.12.1 or higher)
Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned. In Cobalt, we use Terraform for all our resource deployments on Azure.

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

@ -1,15 +1,12 @@
provider "azurerm" {
version = "~>1.27.0"
}
terraform {
required_version = "~> 0.11.13"
version = "~>1.30.1"
}
provider "null" {
version = "~>2.0.0"
version = "~>2.1.0"
}
provider "azuread" {
version = "~>0.4.0"
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,73 +1,73 @@
data "azurerm_resource_group" "svcplan" {
name = "${var.resource_group_name}"
name = var.resource_group_name
}
resource "azurerm_app_service_plan" "svcplan" {
name = "${var.service_plan_name}"
location = "${data.azurerm_resource_group.svcplan.location}"
resource_group_name = "${var.resource_group_name}"
kind = "${var.service_plan_kind}"
tags = "${var.resource_tags}"
reserved = "${var.service_plan_kind == "Linux" ? true : "${var.service_plan_reserved}"}"
name = var.service_plan_name
location = data.azurerm_resource_group.svcplan.location
resource_group_name = var.resource_group_name
kind = var.service_plan_kind
tags = var.resource_tags
reserved = var.service_plan_kind == "Linux" ? true : var.service_plan_reserved
sku {
tier = "${var.service_plan_tier}"
size = "${var.service_plan_size}"
capacity = "${var.service_plan_capacity}"
tier = var.service_plan_tier
size = var.service_plan_size
capacity = var.service_plan_capacity
}
}
resource "azurerm_monitor_autoscale_setting" "app_service_auto_scale" {
name = "${var.service_plan_name}-autoscale"
resource_group_name = "${var.resource_group_name}"
location = "${data.azurerm_resource_group.svcplan.location}"
target_resource_id = "${azurerm_app_service_plan.svcplan.id}"
resource_group_name = var.resource_group_name
location = data.azurerm_resource_group.svcplan.location
target_resource_id = azurerm_app_service_plan.svcplan.id
profile {
name = "Scale on CPU usage"
capacity {
default = 1
minimum = "${var.autoscale_capacity_minimum}"
maximum = "${azurerm_app_service_plan.svcplan.maximum_number_of_workers}"
minimum = var.autoscale_capacity_minimum
maximum = azurerm_app_service_plan.svcplan.maximum_number_of_workers
}
rule {
metric_trigger {
metric_name = "${var.autoscale_rule_out_metric_name}"
metric_resource_id = "${azurerm_app_service_plan.svcplan.id}"
metric_name = var.autoscale_rule_out_metric_name
metric_resource_id = azurerm_app_service_plan.svcplan.id
time_grain = "PT1M"
statistic = "${var.autoscale_rule_out_statistic}"
statistic = var.autoscale_rule_out_statistic
time_window = "PT5M"
time_aggregation = "${var.autoscale_rule_out_statistic}"
operator = "${var.autoscale_rule_out_operator}"
threshold = "${var.autoscale_rule_out_threshold}"
time_aggregation = var.autoscale_rule_out_statistic
operator = var.autoscale_rule_out_operator
threshold = var.autoscale_rule_out_threshold
}
scale_action {
direction = "Increase"
type = "ChangeCount"
value = "${var.autoscale_rule_out_action_change_count}"
value = var.autoscale_rule_out_action_change_count
cooldown = "PT10M"
}
}
rule {
metric_trigger {
metric_name = "${var.autoscale_rule_in_metric_name}"
metric_resource_id = "${azurerm_app_service_plan.svcplan.id}"
metric_name = var.autoscale_rule_in_metric_name
metric_resource_id = azurerm_app_service_plan.svcplan.id
time_grain = "PT1M"
statistic = "${var.autoscale_rule_in_statistic}"
statistic = var.autoscale_rule_in_statistic
time_window = "PT5M"
time_aggregation = "${var.autoscale_rule_in_statistic}"
operator = "${var.autoscale_rule_in_operator}"
threshold = "${var.autoscale_rule_in_threshold}"
time_aggregation = var.autoscale_rule_in_statistic
operator = var.autoscale_rule_in_operator
threshold = var.autoscale_rule_in_threshold
}
scale_action {
direction = "Decrease"
type = "ChangeCount"
value = "${var.autoscale_rule_in_action_change_count}"
value = var.autoscale_rule_in_action_change_count
cooldown = "PT1M"
}
}
@ -80,3 +80,4 @@ resource "azurerm_monitor_autoscale_setting" "app_service_auto_scale" {
}
}
}

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

@ -1,13 +1,14 @@
output "service_plan_name" {
description = "The name of the service plan created"
value = "${azurerm_app_service_plan.svcplan.name}"
value = azurerm_app_service_plan.svcplan.name
}
output "service_plan_kind" {
description = "The kind of service plan created"
value = "${azurerm_app_service_plan.svcplan.kind}"
value = azurerm_app_service_plan.svcplan.kind
}
output "app_service_plan_id" {
value = "${azurerm_app_service_plan.svcplan.id}"
value = azurerm_app_service_plan.svcplan.id
}

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

@ -1,111 +1,112 @@
variable "resource_group_name" {
description = "The name of the resource group in which to create the storage account. Changing this forces a new resource to be created. If omitted, will create a new RG based on the `name` above"
type = "string"
type = string
}
variable "resource_tags" {
description = "Map of tags to apply to taggable resources in this module. By default the taggable resources are tagged with the name defined above and this map is merged in"
type = "map"
type = map(string)
default = {}
}
variable "service_plan_name" {
description = "The name of the service plan to be created"
type = "string"
type = string
}
variable "service_plan_tier" {
description = "The tier under which the service plan is created. Details can be found at https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans"
type = "string"
type = string
default = "Standard"
}
variable "service_plan_size" {
description = "The compute and storage needed for the service plan to be deployed. Details can be found at https://azure.microsoft.com/en-us/pricing/details/app-service/windows/"
type = "string"
type = string
default = "S1"
}
variable "service_plan_kind" {
description = "The kind of Service Plan to be created. Possible values are Windows/Linux/FunctionApp/App"
type = "string"
type = string
default = "Linux"
}
variable "service_plan_capacity" {
description = "The capacity of Service Plan to be created."
type = "string"
default = "1"
type = number
default = 1
}
variable "service_plan_reserved" {
description = "Is the Service Plan to be created reserved. Possible values are true/false"
type = "string"
default = "true"
type = bool
default = true
}
variable "autoscale_capacity_minimum" {
description = "The minimum number of instances for this resource. Valid values are between 0 and 1000"
type = "string"
default = "1"
type = number
default = 1
}
variable "autoscale_rule_out_metric_name" {
description = "The name of the scale out rule metric that defines what the rule monitors, such as Percentage CPU for Virtual Machine Scale Sets and CpuPercentage for App Service Plan."
type = "string"
type = string
default = "CpuPercentage"
}
variable "autoscale_rule_out_statistic" {
description = "Specifies how the scale out rule metrics from multiple instances are combined. Possible values are Average, Min and Max."
type = "string"
type = string
default = "Average"
}
variable "autoscale_rule_out_operator" {
description = "Specifies how the scale out rule metrics from multiple instances are combined. Possible values are Average, Min and Max."
type = "string"
type = string
default = "GreaterThan"
}
variable "autoscale_rule_out_threshold" {
description = "Specifies the threshold of the scale out rule metric that triggers the scale action."
type = "string"
default = "70"
type = number
default = 70
}
variable "autoscale_rule_out_action_change_count" {
description = "Specifies the number of instances involved in the scaling action."
type = "string"
default = "1"
type = number
default = 1
}
variable "autoscale_rule_in_metric_name" {
description = "The name of the scale in rule metric that defines what the rule monitors, such as Percentage CPU for Virtual Machine Scale Sets and CpuPercentage for App Service Plan."
type = "string"
type = string
default = "CpuPercentage"
}
variable "autoscale_rule_in_statistic" {
description = "Specifies how the scale in rule metrics from multiple instances are combined. Possible values are Average, Min and Max."
type = "string"
type = string
default = "Average"
}
variable "autoscale_rule_in_operator" {
description = "Specifies how the scale in rule metrics from multiple instances are combined. Possible values are Average, Min and Max."
type = "string"
type = string
default = "GreaterThan"
}
variable "autoscale_rule_in_threshold" {
description = "Specifies the threshold of the scale in rule metric that triggers the scale action."
type = "string"
default = "25"
type = number
default = 25
}
variable "autoscale_rule_in_action_change_count" {
description = "Specifies the number of instances involved in the scaling action."
type = "string"
default = "1"
type = number
default = 1
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -3,23 +3,25 @@ module "azure-provider" {
}
locals {
sps_to_create = "${var.create_for_rbac == "true" ? 1 : 0}"
sps_to_create = var.create_for_rbac == true ? 1 : 0
}
data "azurerm_subscription" "sp" {}
data "azurerm_subscription" "sp" {
}
resource "azuread_application" "sp" {
count = "${local.sps_to_create}"
name = "${var.service_principal_display_name}"
count = local.sps_to_create
name = var.service_principal_display_name
}
resource "azuread_service_principal" "sp" {
count = "${local.sps_to_create}"
application_id = "${azuread_application.sp.application_id}"
count = local.sps_to_create
application_id = azuread_application.sp[0].application_id
}
resource "azurerm_role_assignment" "sp" {
role_definition_name = "${var.role_name}"
principal_id = "${var.create_for_rbac == "true" ? azuread_service_principal.sp.object_id : var.service_principle_object_id}"
scope = "${var.role_scope}"
role_definition_name = var.role_name
principal_id = var.create_for_rbac == true ? azuread_service_principal.sp[0].object_id : var.service_principle_object_id
scope = var.role_scope
}

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

@ -1,14 +1,15 @@
output "service_principal_object_id" {
description = "The ID of the Azure AD Service Principal"
value = "${azuread_service_principal.sp.*.object_id}"
value = azuread_service_principal.sp.*.object_id
}
output "service_principal_application_id" {
description = "The ID of the Azure AD Application"
value = "${azuread_service_principal.sp.*.application_id}"
value = azuread_service_principal.sp.*.application_id
}
output "service_principal_display_name" {
description = "The Display Name of the Azure AD Application associated with this Service Principal"
value = "${azuread_service_principal.sp.*.display_name}"
value = azuread_service_principal.sp.*.display_name
}

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

@ -1,26 +1,27 @@
variable "create_for_rbac" {
description = "Create a new Service Principle"
type = "string"
default = "true"
type = bool
default = true
}
variable "display_name" {
description = "Display name of the AD application"
type = "string"
type = string
}
variable "object_id" {
description = "Object Id of the service principle to assign a role"
type = "string"
type = string
default = ""
}
variable "role_name" {
description = "The name of the role definition to assign a service principle too."
type = "string"
type = string
}
variable "role_scope" {
description = "The scope at which the Role Assignment applies too, such as /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333"
type = "string"
type = string
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,43 +1,44 @@
data "azurerm_resource_group" "tmrg" {
name = "${var.resource_group_name}"
name = var.resource_group_name
}
resource "azurerm_traffic_manager_profile" "profile" {
name = "${var.traffic_manager_profile_name}"
resource_group_name = "${data.azurerm_resource_group.tmrg.name}"
name = var.traffic_manager_profile_name
resource_group_name = data.azurerm_resource_group.tmrg.name
traffic_routing_method = "Weighted"
dns_config {
relative_name = "${var.traffic_manager_dns_name}"
relative_name = var.traffic_manager_dns_name
ttl = 30
}
monitor_config {
protocol = "${var.traffic_manager_monitor_protocol}"
port = "${var.traffic_manager_monitor_port}"
protocol = var.traffic_manager_monitor_protocol
port = var.traffic_manager_monitor_port
path = "/"
}
tags = "${var.tags}"
tags = var.tags
}
resource "azurerm_public_ip" "pip" {
name = "${var.public_ip_name}"
location = "${data.azurerm_resource_group.tmrg.location}"
resource_group_name = "${data.azurerm_resource_group.tmrg.name}"
name = var.public_ip_name
location = data.azurerm_resource_group.tmrg.location
resource_group_name = data.azurerm_resource_group.tmrg.name
allocation_method = "Dynamic"
domain_name_label = "${var.public_ip_name}-dns"
tags = "${var.tags}"
tags = var.tags
}
resource "azurerm_traffic_manager_endpoint" "endpoint" {
name = "${var.endpoint_name}-ep"
resource_group_name = "${data.azurerm_resource_group.tmrg.name}"
profile_name = "${var.traffic_manager_profile_name}"
resource_group_name = data.azurerm_resource_group.tmrg.name
profile_name = var.traffic_manager_profile_name
target = "${var.endpoint_name}-dns"
target_resource_id = "${azurerm_public_ip.pip.id}"
target_resource_id = azurerm_public_ip.pip.id
type = "azureEndpoints"
weight = 1
depends_on = ["azurerm_traffic_manager_profile.profile"]
depends_on = [azurerm_traffic_manager_profile.profile]
}

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

@ -1,11 +1,12 @@
output "public_pip_id" {
value = "${azurerm_public_ip.pip.id}"
value = azurerm_public_ip.pip.id
}
output "tm_fqdn" {
value = "${azurerm_traffic_manager_profile.profile.fqdn}"
value = azurerm_traffic_manager_profile.profile.fqdn
}
output "public_pip_fqdn" {
value = "${azurerm_public_ip.pip.fqdn}"
value = azurerm_public_ip.pip.fqdn
}

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

@ -1,39 +1,40 @@
variable "traffic_manager_profile_name" {
type = "string"
type = string
}
variable "public_ip_name" {
type = "string"
type = string
}
variable "endpoint_name" {
type = "string"
type = string
}
variable "resource_group_name" {
type = "string"
type = string
}
variable "traffic_manager_dns_name" {
type = "string"
type = string
}
variable "traffic_manager_monitor_protocol" {
type = "string"
type = string
default = "https"
}
variable "traffic_manager_monitor_port" {
type = "string"
default = "443"
type = number
default = 443
}
variable "tags" {
description = "The tags to associate with the public ip address."
type = "map"
type = map(string)
default = {
tag1 = ""
tag2 = ""
}
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -3,3 +3,4 @@ terraform {
key = "terraform.tfstate"
}
}

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

@ -3,20 +3,21 @@ module "provider" {
}
resource "azurerm_resource_group" "main" {
name = "${var.prefix}"
location = "${var.resource_group_location}"
name = var.prefix
location = var.resource_group_location
}
module "service_plan" {
source = "../../modules/providers/azure/service-plan"
resource_group_name = "${azurerm_resource_group.main.name}"
resource_group_name = azurerm_resource_group.main.name
service_plan_name = "${azurerm_resource_group.main.name}-sp"
}
module "app_service" {
source = "../../modules/providers/azure/app-service"
app_service_name = "${var.app_service_name}"
service_plan_name = "${module.service_plan.service_plan_name}"
service_plan_resource_group_name = "${azurerm_resource_group.main.name}"
docker_registry_server_url = "${var.docker_registry_server_url}"
app_service_name = var.app_service_name
service_plan_name = module.service_plan.service_plan_name
service_plan_resource_group_name = azurerm_resource_group.main.name
docker_registry_server_url = var.docker_registry_server_url
}

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

@ -1,3 +1,4 @@
output "app_service_default_hostname" {
value = "http://${element(module.app_service.app_service_uri, 0)}"
}

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

@ -22,7 +22,7 @@ var tfOptions = &terraform.Options{
Upgrade: true,
Vars: map[string]interface{}{
"prefix": prefix,
"location": datacenter,
"resource_group_location": datacenter,
},
BackendConfig: map[string]interface{}{
"storage_account_name": os.Getenv("TF_VAR_remote_state_account"),

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

@ -10,15 +10,16 @@ import (
"github.com/microsoft/cobalt/test-harness/infratests"
)
var workspace = fmt.Sprintf("azure-simple-hw-%s", random.UniqueId())
var prefix = fmt.Sprintf("helloworld-unit-tst-%s", random.UniqueId())
var datacenter = "eastus"
var tf_options = &terraform.Options{
var tfOptions = &terraform.Options{
TerraformDir: "../../",
Upgrade: true,
Vars: map[string]interface{}{
"prefix": prefix,
"location": datacenter,
"resource_group_location": datacenter,
},
BackendConfig: map[string]interface{}{
"storage_account_name": os.Getenv("TF_VAR_remote_state_account"),
@ -29,22 +30,28 @@ var tf_options = &terraform.Options{
func TestAzureSimple(t *testing.T) {
testFixture := infratests.UnitTestFixture{
GoTest: t,
TfOptions: tf_options,
TfOptions: tfOptions,
ExpectedResourceCount: 8,
PlanAssertions: nil,
Workspace: workspace,
ExpectedResourceAttributeValues: infratests.ResourceDescription{
"azurerm_app_service.appsvc": infratests.AttributeValueMapping{
"module.app_service.azurerm_app_service.appsvc[0]": map[string]interface{}{
"resource_group_name": prefix,
"site_config.0.linux_fx_version": "DOCKER|appsvcsample/static-site:latest",
"app_settings.WEBSITES_ENABLE_APP_SERVICE_STORAGE": "false",
"app_settings": map[string]interface{}{
"WEBSITES_ENABLE_APP_SERVICE_STORAGE": "false",
},
"azurerm_app_service_plan.svcplan": infratests.AttributeValueMapping{
"site_config": []interface{}{
map[string]interface{}{"linux_fx_version": "DOCKER|appsvcsample/static-site:latest"},
},
},
"module.service_plan.azurerm_app_service_plan.svcplan": map[string]interface{}{
"kind": "Linux",
"reserved": "true",
"sku.0.size": "S1",
"sku.0.tier": "Standard",
"reserved": true,
"sku": []interface{}{
map[string]interface{}{"size": "S1", "tier": "Standard"},
},
"azurerm_resource_group.main": infratests.AttributeValueMapping{
},
"azurerm_resource_group.main": map[string]interface{}{
"location": datacenter,
"name": prefix,
},

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

@ -4,16 +4,17 @@ variable "prefix" {
variable "resource_group_location" {
description = "The Azure location where all resources in this example should be created."
type = "string"
type = string
}
variable "app_service_name" {
description = "The name key value pair where the key is the name assigned to the app service and value is the source container."
type = "map"
type = map(string)
}
variable "docker_registry_server_url" {
description = "The url of the container registry that will be utilized to pull container into the Web Apps for containers"
type = "string"
type = string
default = "https://index.docker.io"
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -1,6 +1,6 @@
locals {
prefix = "${lower(var.name)}-${lower(terraform.workspace)}"
prefix_short = "${format("%.20s", local.prefix)}"
prefix_short = format("%.20s", local.prefix)
tm_profile_name = "${local.prefix}-tf"
vnet_name = "${local.prefix}-vnet"
tm_endpoint_name = "${var.resource_group_location}_${var.name}"
@ -8,57 +8,57 @@ locals {
appgateway_name = "${local.prefix}-gateway"
public_pip_name = "${local.prefix}-ip"
kv_name = "${local.prefix_short}-kv"
resource_group_name = "${local.prefix}"
resource_group_name = local.prefix
}
resource "azurerm_resource_group" "svcplan" {
name = "${local.resource_group_name}"
location = "${var.resource_group_location}"
name = local.resource_group_name
location = var.resource_group_location
}
module "vnet" {
source = "github.com/Microsoft/bedrock/cluster/azure/vnet"
vnet_name = "${local.vnet_name}"
address_space = "${var.address_space}"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
resource_group_location = "${azurerm_resource_group.svcplan.location}"
subnet_names = ["${var.subnet_names}"]
subnet_prefixes = ["${var.subnet_prefixes}"]
subnet_service_endpoints = "${var.subnet_service_endpoints}"
vnet_name = local.vnet_name
address_space = var.address_space
resource_group_name = azurerm_resource_group.svcplan.name
resource_group_location = azurerm_resource_group.svcplan.location
subnet_names = var.subnet_names
subnet_prefixes = var.subnet_prefixes
subnet_service_endpoints = var.subnet_service_endpoints
}
module "keyvault" {
source = "../../modules/providers/azure/keyvault"
keyvault_name = "${local.kv_name}"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
keyvault_name = local.kv_name
resource_group_name = azurerm_resource_group.svcplan.name
}
module "traffic_manager" {
source = "../../modules/providers/azure/traffic-manager"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
traffic_manager_profile_name = "${local.tm_profile_name}"
public_ip_name = "${local.public_pip_name}"
endpoint_name = "${local.tm_endpoint_name}"
traffic_manager_profile_name = "${local.tm_profile_name}"
traffic_manager_dns_name = "${local.tm_dns_name}"
resource_group_name = azurerm_resource_group.svcplan.name
traffic_manager_profile_name = local.tm_profile_name
public_ip_name = local.public_pip_name
endpoint_name = local.tm_endpoint_name
traffic_manager_dns_name = local.tm_dns_name
}
module "keyvault_certificate" {
source = "../../modules/providers/azure/keyvault-cert"
keyvault_name = "${module.keyvault.keyvault_name}"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
key_vault_cert_subject = "${module.traffic_manager.public_pip_fqdn}"
key_vault_cert_alt_names = ["${module.app_service.app_service_uri}"]
keyvault_name = module.keyvault.keyvault_name
resource_group_name = azurerm_resource_group.svcplan.name
key_vault_cert_subject = module.traffic_manager.public_pip_fqdn
key_vault_cert_alt_names = module.app_service.app_service_uri
}
module "app_gateway" {
source = "../../modules/providers/azure/app-gateway"
appgateway_name = "${local.appgateway_name}"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
virtual_network_name = "${module.vnet.vnet_name}"
appgateway_ssl_private_pfx = "${module.keyvault_certificate.private_pfx}"
appgateway_ssl_public_cert = "${module.keyvault_certificate.public_cert}"
public_pip_id = "${module.traffic_manager.public_pip_id}"
virtual_network_subnet_id = "${module.vnet.vnet_subnet_ids[0]}"
backendpool_fqdns = "${module.app_service.app_service_uri}"
appgateway_name = local.appgateway_name
resource_group_name = azurerm_resource_group.svcplan.name
virtual_network_name = module.vnet.vnet_name
appgateway_ssl_private_pfx = module.keyvault_certificate.private_pfx
appgateway_ssl_public_cert = module.keyvault_certificate.public_cert
public_pip_id = module.traffic_manager.public_pip_id
virtual_network_subnet_id = module.vnet.vnet_subnet_ids[0]
backendpool_fqdns = module.app_service.app_service_uri
}

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

@ -9,52 +9,53 @@ module "provider" {
module "service_plan" {
source = "../../modules/providers/azure/service-plan"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
service_plan_name = "${local.service_plan_name}"
resource_group_name = azurerm_resource_group.svcplan.name
service_plan_name = local.service_plan_name
}
module "app_service" {
source = "../../modules/providers/azure/app-service"
app_service_name = "${var.app_service_name}"
service_plan_name = "${module.service_plan.service_plan_name}"
service_plan_resource_group_name = "${azurerm_resource_group.svcplan.name}"
app_insights_instrumentation_key = "${module.app_insight.app_insights_instrumentation_key}"
docker_registry_server_url = "${var.docker_registry_server_url}"
docker_registry_server_username = "${var.docker_registry_server_username}"
docker_registry_server_password = "${var.docker_registry_server_password}"
vnet_name = "${local.vnet_name}"
vnet_subnet_id = "${module.vnet.vnet_subnet_ids[0]}"
vault_uri = "${module.keyvault.keyvault_uri}"
app_service_name = var.app_service_name
service_plan_name = module.service_plan.service_plan_name
service_plan_resource_group_name = azurerm_resource_group.svcplan.name
app_insights_instrumentation_key = module.app_insight.app_insights_instrumentation_key
docker_registry_server_url = var.docker_registry_server_url
docker_registry_server_username = var.docker_registry_server_username
docker_registry_server_password = var.docker_registry_server_password
vnet_name = local.vnet_name
vnet_subnet_id = module.vnet.vnet_subnet_ids[0]
vault_uri = module.keyvault.keyvault_uri
}
module "app_insight" {
source = "../../modules/providers/azure/app-insights"
service_plan_resource_group_name = "${azurerm_resource_group.svcplan.name}"
appinsights_name = "${local.app_insights_name}"
appinsights_application_type = "${var.application_type}"
service_plan_resource_group_name = azurerm_resource_group.svcplan.name
appinsights_name = local.app_insights_name
appinsights_application_type = var.application_type
}
module "keyvault_appsvc_policy" {
source = "../../modules/providers/azure/keyvault-policy"
instance_count = "${length(keys(var.app_service_name))}"
vault_id = "${module.keyvault_certificate.vault_id}"
tenant_id = "${module.app_service.app_service_identity_tenant_id}"
object_ids = "${module.app_service.app_service_identity_object_ids}"
instance_count = length(keys(var.app_service_name))
vault_id = module.keyvault_certificate.vault_id
tenant_id = module.app_service.app_service_identity_tenant_id
object_ids = module.app_service.app_service_identity_object_ids
}
module "app_monitoring" {
source = "../../modules/providers/azure/app-monitoring"
resource_group_name = "${azurerm_resource_group.svcplan.name}"
resource_ids = ["${module.service_plan.app_service_plan_id}"]
action_group_name = "${var.action_group_name}"
action_group_email_receiver = "${var.action_group_email_receiver}"
metric_alert_name = "${var.metric_alert_name}"
metric_alert_frequency = "${var.metric_alert_frequency}"
metric_alert_period = "${var.metric_alert_period}"
metric_alert_criteria_namespace = "${var.metric_alert_criteria_namespace}"
metric_alert_criteria_name = "${var.metric_alert_criteria_name}"
metric_alert_criteria_aggregation = "${var.metric_alert_criteria_aggregation}"
metric_alert_criteria_operator = "${var.metric_alert_criteria_operator}"
metric_alert_criteria_threshold = "${var.metric_alert_criteria_threshold}"
scaling_values = "${var.scaling_values}"
resource_group_name = azurerm_resource_group.svcplan.name
resource_ids = [module.service_plan.app_service_plan_id]
action_group_name = var.action_group_name
action_group_email_receiver = var.action_group_email_receiver
metric_alert_name = var.metric_alert_name
metric_alert_frequency = var.metric_alert_frequency
metric_alert_period = var.metric_alert_period
metric_alert_criteria_namespace = var.metric_alert_criteria_namespace
metric_alert_criteria_name = var.metric_alert_criteria_name
metric_alert_criteria_aggregation = var.metric_alert_criteria_aggregation
metric_alert_criteria_operator = var.metric_alert_criteria_operator
metric_alert_criteria_threshold = var.metric_alert_criteria_threshold
scaling_values = var.scaling_values
}

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

@ -3,3 +3,4 @@ terraform {
key = "terraform.tfstate"
}
}

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

@ -1,15 +1,16 @@
output "app_gateway_health_probe_backend_status" {
value = "${module.app_gateway.appgateway_health_probe_backend_status}"
value = module.app_gateway.appgateway_health_probe_backend_status
}
output "app_gateway_health_probe_backend_address" {
value = "${module.app_gateway.app_gateway_health_probe_backend_address}"
value = module.app_gateway.app_gateway_health_probe_backend_address
}
output "tm_fqdn" {
value = "${module.traffic_manager.public_pip_fqdn}"
value = module.traffic_manager.public_pip_fqdn
}
output "public_cert" {
value = "${module.keyvault_certificate.public_cert}"
value = module.keyvault_certificate.public_cert
}

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

@ -1,6 +1,7 @@
package test
import (
"encoding/json"
"os"
"testing"
@ -11,7 +12,7 @@ import (
var region = "eastus"
var workspace = "azsimple"
var tf_options = &terraform.Options{
var tfOptions = &terraform.Options{
TerraformDir: "../../",
Upgrade: true,
Vars: map[string]interface{}{
@ -23,72 +24,98 @@ var tf_options = &terraform.Options{
},
}
// helper function to parse blocks of JSON into a generic Go map
func asMap(t *testing.T, jsonString string) map[string]interface{} {
var theMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonString), &theMap); err != nil {
t.Fatal(err)
}
return theMap
}
func TestTemplate(t *testing.T) {
expectedAppServicePlan := asMap(t, `{
"name": "cobalt-backend-api-`+workspace+`",
"site_config": [{"linux_fx_version": "DOCKER|msftcse/cobalt-azure-simple:0.1"}]
}`)
expectedAppGatewayPlan := asMap(t, `{
"authentication_certificate": [{"name": "gateway-public-key"}],
"frontend_port": [{"port": 443}],
"http_listener": [{"protocol": "Https"}],
"backend_http_settings": [{"port": 443, "protocol": "Https"}],
"probe": [{"protocol": "Https", "timeout": 30, "unhealthy_threshold": 3}],
"sku": [{"capacity": 2, "name": "WAF_Medium", "tier": "WAF"}]
}`)
expectedAutoScalePlan := asMap(t, `{
"enabled": true,
"notification": [{
"email": [{
"send_to_subscription_administrator": true,
"send_to_subscription_co_administrator": true
}]
}],
"profile": [{
"rule": [{
"metric_trigger": [{
"metric_name": "CpuPercentage",
"operator": "GreaterThan",
"statistic": "Average",
"threshold": 70,
"time_aggregation": "Average",
"time_grain": "PT1M",
"time_window": "PT5M"
}],
"scale_action": [{
"cooldown": "PT10M",
"direction": "Increase",
"type": "ChangeCount",
"value": 1
}]
},{
"metric_trigger": [{
"metric_name": "CpuPercentage",
"operator": "GreaterThan",
"statistic": "Average",
"threshold": 25,
"time_aggregation": "Average",
"time_grain": "PT1M",
"time_window": "PT5M"
}],
"scale_action": [{
"cooldown": "PT1M",
"direction": "Decrease",
"type": "ChangeCount",
"value": 1
}]
}]
}]
}`)
testFixture := infratests.UnitTestFixture{
GoTest: t,
TfOptions: tf_options,
TfOptions: tfOptions,
Workspace: workspace,
PlanAssertions: nil,
ExpectedResourceCount: 28,
ExpectedResourceAttributeValues: infratests.ResourceDescription{
"azurerm_resource_group.svcplan": infratests.AttributeValueMapping{
"azurerm_resource_group.svcplan": map[string]interface{}{
"location": region,
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_app_service_slot.appsvc_staging_slot": infratests.AttributeValueMapping{
"module.app_gateway.data.azurerm_resource_group.appgateway": map[string]interface{}{
"name": "cobalt-az-simple-" + workspace,
},
"module.app_insight.data.azurerm_resource_group.appinsights": map[string]interface{}{
"name": "cobalt-az-simple-" + workspace,
},
"module.app_service.azurerm_app_service_slot.appsvc_staging_slot[0]": map[string]interface{}{
"name": "staging",
},
"azurerm_app_service.appsvc": infratests.AttributeValueMapping{
"name": "cobalt-backend-api-" + workspace,
"site_config.0.linux_fx_version": "DOCKER|msftcse/cobalt-azure-simple:0.1",
},
"data.azurerm_resource_group.appgateway": infratests.AttributeValueMapping{
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_application_gateway.appgateway": infratests.AttributeValueMapping{
"authentication_certificate.0.name": "gateway-public-key",
"frontend_port.0.port": "443",
"http_listener.0.protocol": "Https",
"backend_http_settings.0.port": "443",
"backend_http_settings.0.protocol": "Https",
"probe.0.protocol": "Https",
"probe.0.timeout": "30",
"probe.0.unhealthy_threshold": "3",
"sku.0.capacity": "2",
"sku.0.name": "WAF_Medium",
"sku.0.tier": "WAF",
},
"data.azurerm_resource_group.appinsights": infratests.AttributeValueMapping{
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_monitor_autoscale_setting.app_service_auto_scale": infratests.AttributeValueMapping{
"enabled": "true",
"name": "cobalt-az-simple-" + workspace + "-sp-autoscale",
"notification.0.email.0.send_to_subscription_administrator": "true",
"notification.0.email.0.send_to_subscription_co_administrator": "true",
"profile.0.rule.0.metric_trigger.0.metric_name": "CpuPercentage",
"profile.0.rule.0.metric_trigger.0.operator": "GreaterThan",
"profile.0.rule.0.metric_trigger.0.statistic": "Average",
"profile.0.rule.0.metric_trigger.0.threshold": "70",
"profile.0.rule.0.metric_trigger.0.time_aggregation": "Average",
"profile.0.rule.0.metric_trigger.0.time_grain": "PT1M",
"profile.0.rule.0.metric_trigger.0.time_window": "PT5M",
"profile.0.rule.0.scale_action.0.cooldown": "PT10M",
"profile.0.rule.0.scale_action.0.direction": "Increase",
"profile.0.rule.0.scale_action.0.type": "ChangeCount",
"profile.0.rule.0.scale_action.0.value": "1",
"profile.0.rule.1.metric_trigger.0.metric_name": "CpuPercentage",
"profile.0.rule.1.metric_trigger.0.operator": "GreaterThan",
"profile.0.rule.1.metric_trigger.0.statistic": "Average",
"profile.0.rule.1.metric_trigger.0.threshold": "25",
"profile.0.rule.1.metric_trigger.0.time_aggregation": "Average",
"profile.0.rule.1.metric_trigger.0.time_grain": "PT1M",
"profile.0.rule.1.metric_trigger.0.time_window": "PT5M",
"profile.0.rule.1.scale_action.0.cooldown": "PT1M",
"profile.0.rule.1.scale_action.0.direction": "Decrease",
"profile.0.rule.1.scale_action.0.type": "ChangeCount",
"profile.0.rule.1.scale_action.0.value": "1",
},
"module.app_service.azurerm_app_service.appsvc[0]": expectedAppServicePlan,
"module.app_gateway.azurerm_application_gateway.appgateway": expectedAppGatewayPlan,
"module.service_plan.azurerm_monitor_autoscale_setting.app_service_auto_scale": expectedAutoScalePlan,
},
}

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

@ -1,7 +1,7 @@
# Resource Group
variable "resource_group_location" {
description = "The deployment location of resource group container all the resources"
type = "string"
type = string
}
variable "application_type" {
@ -11,17 +11,17 @@ variable "application_type" {
variable "name" {
description = "The name of the deployment. This will be used across the resource created in this solution"
type = "string"
type = string
}
variable "app_service_name" {
description = "The name key value pair where the key is the name assigned to the app service and value is the source container"
type = "map"
type = map(string)
}
variable "subnet_service_endpoints" {
description = "The list of service endpoints that will be given to each subnet"
type = "list"
type = list(string)
default = ["Microsoft.Web"]
}
@ -53,67 +53,67 @@ variable "appgateway_http_listener_protocol" {
# Monitoring Service
variable "action_group_name" {
description = "The name of the action group."
type = "string"
type = string
default = "Simple Default Action Group"
}
variable "action_group_email_receiver" {
description = "The e-mail receiver for an alert rule resource."
type = "string"
type = string
default = ""
}
variable "metric_alert_name" {
description = "The display name of a group of metric alert criteria."
type = "string"
type = string
default = "Simple Default Metric Alerts"
}
variable "metric_alert_frequency" {
description = "The frequency with which the metric alert checks if the conditions are met."
type = "string"
type = string
default = "PT1M"
}
variable "metric_alert_period" {
description = "The look back window over which metric values are checked. Value must be greater than 'frequency'."
type = "string"
type = string
default = "PT5M"
}
variable "metric_alert_criteria_namespace" {
description = "A monitored resource namespace with configurable metric alert criteria."
type = "string"
type = string
default = "Microsoft.Web/serverfarms"
}
variable "metric_alert_criteria_name" {
description = "A predefined Azure resource alert monitoring rule name."
type = "string"
type = string
default = "CpuPercentage"
}
variable "metric_alert_criteria_aggregation" {
description = "The calculation used for building metric alert criteria."
type = "string"
type = string
default = "Average"
}
variable "metric_alert_criteria_operator" {
description = "A logical operator used for building metric alert criteria."
type = "string"
type = string
default = "GreaterThan"
}
variable "metric_alert_criteria_threshold" {
description = "The criteria threshold value that activates the metric alert."
type = "string"
default = "50"
type = number
default = 50
}
variable "scaling_values" {
description = "Targets app instances made available from app service plan scaling options."
type = "list"
type = list(string)
default = ["*"]
}
@ -121,18 +121,19 @@ variable "scaling_values" {
variable "docker_registry_server_url" {
description = "The url of the container registry that will be utilized to pull container into the Web Apps for containers"
type = "string"
type = string
default = "index.docker.io"
}
variable "docker_registry_server_username" {
description = "The username used to authenticate with the container registry"
type = "string"
type = string
default = ""
}
variable "docker_registry_server_password" {
description = "The password used to authenticate with the container regitry"
type = "string"
type = string
default = ""
}

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

@ -0,0 +1,4 @@
terraform {
required_version = ">= 0.12"
}

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

@ -199,8 +199,8 @@ Our test harness uses a base docker image to pre-package dependencies like Terra
##### Script Arguments
- `-g` | `--go_version`: Golang version specification. This argument drives the version of the `golang` stretch base image. **Defaults** to `1.11`.
- `-t` | `--tf_version`: Terraform version specification. This argument drives which terraform version release this image will use.. **Defaults** to `0.11.13`
- `-g` | `--go_version`: Golang version specification. This argument drives the version of the `golang` stretch base image. **Defaults** to `1.12.5`.
- `-t` | `--tf_version`: Terraform version specification. This argument drives which terraform version release this image will use.. **Defaults** to `0.12.1`
Keep in mind that the terraform version should align with the version from the provider [module](/infra/modules/providers/azure/provider/main.tf#L6)

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

@ -110,8 +110,8 @@ function build_image(){
--build-arg tfver=$tf_version
}
declare go_version="1.11"
declare tf_version="0.11.13"
declare go_version="1.12.5"
declare tf_version="0.12.1"
parseInput "$@"
declare docker_img="msftcse/cobalt-test-base"

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

@ -34,7 +34,7 @@ ENV GOPATH $HOME/go
ENV GOBIN /usr/local/go
# Install Terraform
ARG tfver=0.11.13
ARG tfver=0.12.1
ENV TF_VERSION=$tfver
RUN wget --quiet https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip \
&& unzip terraform_${TF_VERSION}_linux_amd64.zip \

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

@ -31,7 +31,7 @@ Golang version specification. This argument drives the version of the `golang` s
Terraform version specification. This argument drives which terraform version release this image will use.
```shell
docker build -f "test-harness\docker\base-images\Dockerfile" -t msftcse/cobalt-test-base:1.11 . --build-arg gover=1.11 tfver=0.11.13
docker build -f "test-harness\docker\base-images\Dockerfile" -t msftcse/cobalt-test-base:1.12.5 . --build-arg gover=1.12.5 tfver=0.12.1
```
## Contributing

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

@ -1,5 +1,5 @@
/*
This file provides abstractions that simplify the process of integration-testing terraform templates. The goal
Package infratests This file provides abstractions that simplify the process of integration-testing terraform templates. The goal
is to minimize the boiler plate code required to effectively test terraform templates in order to reduce
the effort required to write robust template integration-tests.
*/

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

@ -0,0 +1,17 @@
/*
Package infratests this file provides a model for the JSON representation of a terraform plan. It describes
a minimal set of metadata produced by the plan and can be expanded to support other attributes
if needed
*/
package infratests
// TerraformPlan a JSON schema for the output of `terraform plan <planfile>`
type TerraformPlan struct {
ResourceChanges []struct {
Address string `json:"address"`
Change struct {
Actions []string `json:"actions"`
After map[string]interface{} `json:"after"`
} `json:"change"`
} `json:"resource_changes"`
}

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

@ -1,30 +1,28 @@
/*
This file provides abstractions that simplify the process of unit-testing terraform templates. The goal
Package infratests This file provides abstractions that simplify the process of unit-testing terraform templates. The goal
is to minimize the boiler plate code required to effectively test terraform templates in order to reduce
the effort required to write robust template unit-tests.
*/
package infratests
import (
"encoding/json"
"errors"
"fmt"
"os"
"path"
"os/exec"
"path/filepath"
"testing"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
terraformCore "github.com/hashicorp/terraform/terraform"
)
// AttributeValueMapping Identifies mapping between attributes and values
type AttributeValueMapping map[string]string
// ResourceDescription Identifies mappings between resources and attributes
type ResourceDescription map[string]AttributeValueMapping
type ResourceDescription map[string]map[string]interface{}
// TerraformPlanValidation A function that can run an assertion over a terraform plan
type TerraformPlanValidation func(goTest *testing.T, plan *terraformCore.Plan)
type TerraformPlanValidation func(goTest *testing.T, plan TerraformPlan)
// UnitTestFixture Holds metadata required to execute a unit test against a test against a terraform template
type UnitTestFixture struct {
@ -55,7 +53,7 @@ func RunUnitTests(fixture *UnitTestFixture) {
defer terraform.RunTerraformCommand(fixture.GoTest, fixture.TfOptions, "workspace", "delete", workspaceName)
defer terraform.WorkspaceSelectOrNew(fixture.GoTest, fixture.TfOptions, "default")
tfPlanFilePath := random.UniqueId() + ".plan"
tfPlanFilePath := filepath.FromSlash(fmt.Sprintf("%s/%s.plan", os.TempDir(), random.UniqueId()))
terraform.RunTerraformCommand(
fixture.GoTest,
fixture.TfOptions,
@ -72,17 +70,7 @@ func RunUnitTests(fixture *UnitTestFixture) {
// - The resource <--> attribute <--> attribute value mappings match the parameters from the test fixture
// - The plan passes any user-defined assertions
func validateTerraformPlanFile(fixture *UnitTestFixture, tfPlanFilePath string) {
file, err := os.Open(path.Join(fixture.TfOptions.TerraformDir, tfPlanFilePath))
if err != nil {
fixture.GoTest.Fatal(err)
}
defer file.Close()
plan, err := terraformCore.ReadPlan(file)
if err != nil {
fixture.GoTest.Fatal(err)
}
plan := parseTerraformPlan(fixture, tfPlanFilePath)
validatePlanCreateProperties(fixture, plan)
validatePlanResourceKeyValues(fixture, plan)
@ -94,74 +82,86 @@ func validateTerraformPlanFile(fixture *UnitTestFixture, tfPlanFilePath string)
}
}
func parseTerraformPlan(fixture *UnitTestFixture, filePath string) TerraformPlan {
// Note: when the PR linked below is merged and the new build is used by this codebase,
// we can leverage Terratest to run this for us. Currently with large plan outputs,
// a buffer overflow will happen in Terratest because the default max character limit
// may be exceeded for large plan files:
//
// - Issue: https://github.com/gruntwork-io/terratest/issues/203
// - PR: https://github.com/gruntwork-io/terratest/pull/317
//
// jsonBytes := []bytes(terraform.RunTerraformCommand(
// fixture.GoTest,
// fixture.TfOptions,
// terraform.FormatArgs(&terraform.Options{}, "show", "-json", filePath)...))
cmd := exec.Command("terraform", "show", "-json", filePath)
cmd.Dir = fixture.TfOptions.TerraformDir
jsonBytes, cmdErr := cmd.Output()
if cmdErr != nil {
fixture.GoTest.Fatal(cmdErr)
}
fmt.Println("Got terraform plan...", string(jsonBytes))
var plan TerraformPlan
jsonErr := json.Unmarshal(jsonBytes, &plan)
if jsonErr != nil {
fixture.GoTest.Fatal(jsonErr)
}
return plan
}
// Validates high level attributes of a terraform plan creat properties. This includes:
// - The plan is not empty
// - The plan contains the correct number of resources
// - The plan is only creating resources
func validatePlanCreateProperties(fixture *UnitTestFixture, plan *terraformCore.Plan) {
if plan.Diff.Empty() {
// - The plan is not executing any destructive actions
func validatePlanCreateProperties(fixture *UnitTestFixture, plan TerraformPlan) {
if len(plan.ResourceChanges) == 0 {
fixture.GoTest.Fatal(errors.New("Plan diff was unexpectedly empty"))
}
// plans should contain diffs of type `DiffCreate` otherwise the test may accidentally remove resources
for _, module := range plan.Diff.Modules {
if module.ChangeType() != terraformCore.DiffCreate {
if len(plan.ResourceChanges) != fixture.ExpectedResourceCount {
fixture.GoTest.Fatal(fmt.Errorf(
"Plan unexpectedly had %d resources instead of %d", len(plan.ResourceChanges), fixture.ExpectedResourceCount))
}
// a unit test should never create a destructive action like deleting a resource
allowedActions := map[string]bool{"create": true, "read": true}
for _, resource := range plan.ResourceChanges {
for _, action := range resource.Change.Actions {
if !allowedActions[action] {
fixture.GoTest.Fatal(
fmt.Errorf("Plan unexpectedly contained an update of type '%s'", string(module.ChangeType())))
fmt.Errorf("Plan unexpectedly actions other than `create`: %s", resource.Change.Actions))
}
}
resourceCount := 0
for _, module := range plan.Diff.Modules {
resourceCount += len(module.Resources)
}
// every plan should have the correct number of resources
if resourceCount != fixture.ExpectedResourceCount {
fixture.GoTest.Fatal(fmt.Errorf(
"Plan unexpectedly had %d resources instead of %d", resourceCount, fixture.ExpectedResourceCount))
}
}
// validates that the resource <--> attribute <--> attribute value mappings match the parameters
// from the test fixture
func validatePlanResourceKeyValues(fixture *UnitTestFixture, plan *terraformCore.Plan) {
// collect actual resource attrubte value mappings by iterating over the TF plan
seen := ResourceDescription{}
for _, module := range plan.Diff.Modules {
for resource, resourceDiffs := range module.Resources {
seen[resource] = AttributeValueMapping{}
for attribute, vals := range resourceDiffs.Attributes {
seen[resource][attribute] = vals.New
}
}
}
// verify that for each of the expected resource attribute value mappings that the expected
// values are found in the terraform plan
for resource, expectedMappings := range fixture.ExpectedResourceAttributeValues {
_, resourceFound := seen[resource]
if !resourceFound {
fixture.GoTest.Fatal(fmt.Errorf(
"Plan unexpectedly did not contain a change for resource '%s'", resource))
// verifies that the attribute value mappings for each resource specified by the client exist
// as a subset of the actual values defined in the terraform plan.
func validatePlanResourceKeyValues(fixture *UnitTestFixture, plan TerraformPlan) {
theRealPlanAsMap := planToMap(plan)
theExpectedPlanAsMap := resourceDescriptionToMap(fixture.ExpectedResourceAttributeValues)
if err := verifyTargetsExistInMap(theRealPlanAsMap, theExpectedPlanAsMap); err != nil {
fixture.GoTest.Fatal(err)
}
}
for expectedAttr, expectedVal := range expectedMappings {
actualVal, attrFound := seen[resource][expectedAttr]
if !attrFound {
fixture.GoTest.Fatal(fmt.Errorf(
"Plan unexpectedly did not contain a change for '%s.%s'", resource, expectedAttr))
// transforms the output of `terraform show -json <planfile>` into a generic map
func planToMap(plan TerraformPlan) map[string]interface{} {
mp := make(map[string]interface{})
for _, resource := range plan.ResourceChanges {
mp[resource.Address] = resource.Change.After
}
return mp
}
if expectedVal != actualVal {
fixture.GoTest.Fatal(fmt.Errorf(
"Plan unexpectedly had value '%s' instead of '%s' for '%s.%s'",
actualVal,
expectedVal,
resource,
expectedAttr,
))
}
}
// transforms a resource description into a generic map
func resourceDescriptionToMap(resources ResourceDescription) map[string]interface{} {
mp := make(map[string]interface{})
for key, value := range resources {
mp[key] = value
}
return mp
}

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

@ -0,0 +1,128 @@
/*
Package infratests This file provides validation utilities that can be used by the core testing constructs
*/
package infratests
import (
"fmt"
"reflect"
)
// This function validates that a set of search targets exist in a map. The intended use case is to allow
// a user of this library to provide a set of *expected values* that should exist within a larger set of
// *actual values.* In other words, this is a map equality check that does not care about ordering in keys/lists
// and will not fail if the *expected values* are a subset of the *actual values.*
//
// In the case that the *expected values* are found in the *actual values,* nil will be returned. Otherwise an
// error describing the mismatch will be returned.
//
// Some examples (in pseudo-code):
// - Exact Match:
// a := {"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}
// verifyTargetsExistInMap(a, a) --> nil
//
// - Subset Match #1:
// a := {"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}
// b := {"key1":[{"key2" : "foo"}]}
// verifyTargetsExistInMap(a, b) --> nil
//
// - Subset Match #2:
// a := {"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}
// b := {"key1":[]}
// verifyTargetsExistInMap(a, b) --> nil
//
// - Subset Match #3:
// a := {"key1":{"key2": "foo", "key3", "bar"}}
// b := {"key1":{"key3", "bar"}}
// verifyTargetsExistInMap(a, b) --> nil
//
// - Mismatch #1:
// a := {"key1":{"key2": "foo", "key3", "bar"}}
// b := {"key1":[]}
// verifyTargetsExistInMap(a, b) --> ERROR: wrong type for key `key1`
//
// - Mismatch #2:
// a := {"key1":{"key2": "foo", "key3", "bar"}}
// b := {"key1":{"key4": "foo"}}
// verifyTargetsExistInMap(a, b) --> ERROR: `key4` not found
//
// - Mismatch #3:
// a := {"key1":{"key2": "foo", "key3", "bar"}}
// b := {"key1":{"key2": "bar"}}
// verifyTargetsExistInMap(a, b) --> ERROR: wrong value for `key2`
//
// The algorithm used to do the equality check is to execute a DFS search in parallel for both maps. In the case
// that a mismatch (the values do not match, or the shape of the maps is different in a way that indicates a mismatch)
// is identified, the search will be terminated. If no mismatches are found then `nil` is returned
func verifyTargetsExistInMap(dataSource map[string]interface{}, searchTargets map[string]interface{}) error {
for targetKey := range searchTargets {
candidateMatch, candidateExists := dataSource[targetKey]
target, targetExists := searchTargets[targetKey]
// both maps should contain the target search key
if !candidateExists || !targetExists {
return fmt.Errorf("Unexpectedly could not find key '%s'", targetKey)
}
// the values for the key should be the same type
if !isSameType(candidateMatch, target) {
return fmt.Errorf("Unexpectedly found type '%T' instead of '%T'", candidateMatch, target)
}
// the key is found and both values are of the same type. time to look for a subset match
switch typedTarget := target.(type) {
case bool, float32, float64, int, string:
if typedTarget != candidateMatch {
return fmt.Errorf("Expected %s but got %s", typedTarget, candidateMatch)
}
case []interface{}:
if err := verifyTargetsExistInList(candidateMatch.([]interface{}), typedTarget); err != nil {
return err
}
case map[string]interface{}:
if err := verifyTargetsExistInMap(candidateMatch.(map[string]interface{}), typedTarget); err != nil {
return err
}
default:
return fmt.Errorf("Comparison for type '%T' in a map not implemented", typedTarget)
}
}
return nil
}
// This function has the same semantics as `verifyTargetsExistInMap` (so the documentation will not be repeated)
// except that it works for lists.
func verifyTargetsExistInList(dataSource []interface{}, searchTargets []interface{}) error {
for _, target := range searchTargets {
matchFound := false
switch typedTarget := target.(type) {
case bool, float32, float64, int, string:
for _, candidateMatch := range dataSource {
matchFound = matchFound || typedTarget == candidateMatch
}
case map[string]interface{}:
for _, candidateMatch := range dataSource {
if isSameType(candidateMatch, typedTarget) {
err := verifyTargetsExistInMap(candidateMatch.(map[string]interface{}), typedTarget)
matchFound = matchFound || err == nil
}
}
default:
return fmt.Errorf("Comparison for type '%T' in a list not yet implemented", typedTarget)
}
if !matchFound {
return fmt.Errorf("Unexpectedly did not find '%s' in '%s'", target, dataSource)
}
}
return nil
}
// return true if the values have the same type, false otherwise
func isSameType(a interface{}, b interface{}) bool {
return reflect.TypeOf(a) == reflect.TypeOf(b)
}

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

@ -0,0 +1,89 @@
package infratests
import (
"encoding/json"
"testing"
)
var tests = []struct {
dataSourceJSON string
searchTargetsJSON string
shouldPass bool
}{
{
`{"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}`,
`{"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}`,
true,
}, {
`{"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}`,
`{"key1":[{"key2" : "foo"}]}`,
true,
}, {
`{"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}`,
`{"key1":[]}`,
true,
}, {
`{"key1":{"key2": "foo", "key3": "bar"}}`,
`{"key1":{"key3": "bar"}}`,
true,
}, {
`{"key1":[{"key2" : "foo"}, {"key2" : "bar"}]}`,
`{}`,
true,
}, {
`{"key1":{"key2": "foo", "key3": "bar"}}`,
`{"key1":[]}`,
false, // key has wrong type
}, {
`{"key1":{"key2": "foo", "key3": "bar"}}`,
`{"key1":{"key4": "foo"}}`,
false, // key does not exist in data source
}, {
`{"key1":{"key2": "foo", "key3": "bar"}}`,
`{"key1":{"key2": "bar"}}`,
false, // key has wrong value
}, {
`{"key1":[1, 2, 3]}`,
`{"key1":[4]}`,
false, // list value does not exist in parent
}, {
`{"key1":[{"key2": "foo"}]}`,
`{"key1":[{"key3": "foo"}]}`,
false, // list value does not exist in parent
}, {
`{"key1":[{"key2": "foo"}]}`,
`{"key1":[{"key2": "bar"}]}`,
false, // list value does not exist in parent
},
}
func TestVerifyTargets(t *testing.T) {
for _, test := range tests {
err := verifyTargetsExistInMap(
jsonToMap(t, test.dataSourceJSON),
jsonToMap(t, test.searchTargetsJSON))
// the test should pass but there was an error
if test.shouldPass && err != nil {
t.Errorf("Search Targets `%s` were unexpectedly not found in Data Source `%s`. %s",
test.searchTargetsJSON,
test.dataSourceJSON,
err)
}
// the test should not pass but there was no error
if !test.shouldPass && err == nil {
t.Errorf("Search Targets `%s` were unexpectedly found in Data Source `%s`",
test.searchTargetsJSON,
test.dataSourceJSON)
}
}
}
func jsonToMap(t *testing.T, jsonStr string) map[string]interface{} {
var theMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &theMap); err != nil {
t.Errorf("Unable to parse JSON `%s`. Error = `%s`", jsonStr, err)
}
return theMap
}

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

@ -23,10 +23,16 @@ func RunAllTargets() {
mg.Deps(CleanAll)
mg.Deps(LintCheckGo)
mg.Deps(LintCheckTerraform)
mg.Deps(RunTestHarnessUnitTests)
mg.Deps(RunUnitTests)
mg.Deps(RunIntegrationTests)
}
// RunTestHarnessUnitTests run unit test for the test harness itself
func RunTestHarnessUnitTests() error {
return sh.RunV("go", "test", "github.com/microsoft/cobalt/test-harness/infratests")
}
// RunUnitTests A build step that runs unit tests
func RunUnitTests() error {
fmt.Println("INFO: Running unit tests...")