Bundle new Vault plugin: Terraform secrets (#10931)

* Bundle Terraform secrets engine

* update go.mod/sum

* vendor update

* add changelog entry

* add secrets terraform
This commit is contained in:
Clint 2021-02-19 16:38:56 -06:00 committed by GitHub
parent 0017b78919
commit 2aff402279
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 13919 additions and 4 deletions

3
changelog/10931.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
secrets/terraform: New secret engine for managing Terraform Cloud API tokens
```

View File

@ -392,6 +392,7 @@ func TestPredict_Plugins(t *testing.T) {
"redshift-database-plugin",
"snowflake-database-plugin",
"ssh",
"terraform",
"totp",
"transform",
"transit",

1
go.mod
View File

@ -96,6 +96,7 @@ require (
github.com/hashicorp/vault-plugin-secrets-kv v0.7.0
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798
github.com/hashicorp/vault-plugin-secrets-terraform v0.1.0
github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77
github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4

13
go.sum
View File

@ -467,7 +467,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -588,6 +587,7 @@ github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cR
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a h1:FmnBDwGwlTgugDGbVxwV8UavqSMACbGrUpfc98yFLR4=
github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a/go.mod h1:xbXnmKqX9/+RhPkJ4zrEx4738HacP72aaUPlT2RZ4sU=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
@ -596,11 +596,15 @@ github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-tfe v0.12.0 h1:teL523WPxwYzL5Gjc2QFxExndrMfWY4BXS2/olVpULM=
github.com/hashicorp/go-tfe v0.12.0/go.mod h1:oT0AG5u/ROzWiw8JZFLDY6FLh6AZnJIG0Ahhvp10txg=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
@ -683,6 +687,8 @@ github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0 h1:uTtKxt5qfwTj6Pq
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.2.0/go.mod h1:JOqn2mWJJbTp9NaC0CSCc3q5HQA99LfeSqgpC3YS+oA=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798 h1:G3S7rF/zHfQnYZglk+WvjzBuJyjQAnP0xdGL/4i3jzM=
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798/go.mod h1:GiFI8Bxwx3+fn0A3SyVp9XdYQhm3cOgN8GzwKxyJ9So=
github.com/hashicorp/vault-plugin-secrets-terraform v0.1.0 h1:g+r6TKJsD2aM0kUNWByuL4ffZTbZH/xO/sqDwTltOu0=
github.com/hashicorp/vault-plugin-secrets-terraform v0.1.0/go.mod h1:7r/0t51X/ZtSRh/TjBk7gCm1CUMk50aqLAx811OsGQ8=
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw=
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@ -836,7 +842,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
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-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-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI=
github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
@ -984,7 +989,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
@ -1118,6 +1122,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.171+incompatible h1:K3fcS92NS8cRntIdu8Uqy2ZSePvX73nNhOkKuPGJLXQ=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.171+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
@ -1373,7 +1379,6 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -22,6 +22,7 @@ import (
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
logicalMongoAtlas "github.com/hashicorp/vault-plugin-secrets-mongodbatlas"
logicalOpenLDAP "github.com/hashicorp/vault-plugin-secrets-openldap"
logicalTerraform "github.com/hashicorp/vault-plugin-secrets-terraform"
credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
credAws "github.com/hashicorp/vault/builtin/credential/aws"
@ -130,6 +131,7 @@ func newRegistry() *registry {
"postgresql": logicalPostgres.Factory, // Deprecated
"rabbitmq": logicalRabbit.Factory,
"ssh": logicalSsh.Factory,
"terraform": logicalTerraform.Factory,
"totp": logicalTotp.Factory,
"transit": logicalTransit.Factory,
},

View File

@ -65,6 +65,7 @@ vault secrets enable pki
vault secrets enable postgresql
vault secrets enable rabbitmq
vault secrets enable ssh
vault secrets enable terraform
vault secrets enable totp
vault secrets enable transit

12
vendor/github.com/hashicorp/go-slug/.gitignore generated vendored Normal file
View File

@ -0,0 +1,12 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

373
vendor/github.com/hashicorp/go-slug/LICENSE generated vendored Normal file
View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

70
vendor/github.com/hashicorp/go-slug/README.md generated vendored Normal file
View File

@ -0,0 +1,70 @@
# go-slug
[![Build Status](https://travis-ci.org/hashicorp/go-slug.svg?branch=master)](https://travis-ci.org/hashicorp/go-slug)
[![GitHub license](https://img.shields.io/github/license/hashicorp/go-slug.svg)](https://github.com/hashicorp/go-slug/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/hashicorp/go-slug?status.svg)](https://godoc.org/github.com/hashicorp/go-slug)
[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/go-slug)](https://goreportcard.com/report/github.com/hashicorp/go-slug)
[![GitHub issues](https://img.shields.io/github/issues/hashicorp/go-slug.svg)](https://github.com/hashicorp/go-slug/issues)
Package `go-slug` offers functions for packing and unpacking Terraform Enterprise
compatible slugs. Slugs are gzip compressed tar files containing Terraform configuration files.
## Installation
Installation can be done with a normal `go get`:
```
go get -u github.com/hashicorp/go-slug
```
## Documentation
For the complete usage of `go-slug`, see the full [package docs](https://godoc.org/github.com/hashicorp/go-slug).
## Example
Packing or unpacking a slug is pretty straight forward as shown in the
following example:
```go
package main
import (
"bytes"
"io/ioutil"
"log"
"os"
slug "github.com/hashicorp/go-slug"
)
func main() {
// First create a buffer for storing the slug.
buf := bytes.NewBuffer(nil)
// Then call the Pack function with a directory path containing the
// configuration files and an io.Writer to write the slug to.
if _, err := slug.Pack("testdata/archive-dir", buf, false); err != nil {
log.Fatal(err)
}
// Create a directory to unpack the slug contents into.
dst, err := ioutil.TempDir("", "slug")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dst)
// Unpacking a slug is done by calling the Unpack function with an
// io.Reader to read the slug from and a directory path of an existing
// directory to store the unpacked configuration files.
if err := slug.Unpack(buf, dst); err != nil {
log.Fatal(err)
}
}
```
## Issues and Contributing
If you find an issue with this package, please report an issue. If you'd like,
we welcome any contributions. Fork this repository and submit a pull request.

3
vendor/github.com/hashicorp/go-slug/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/hashicorp/go-slug
go 1.12

303
vendor/github.com/hashicorp/go-slug/slug.go generated vendored Normal file
View File

@ -0,0 +1,303 @@
package slug
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// Meta provides detailed information about a slug.
type Meta struct {
// The list of files contained in the slug.
Files []string
// Total size of the slug in bytes.
Size int64
}
// Pack creates a slug from a src directory, and writes the new slug
// to w. Returns metadata about the slug and any errors.
//
// When dereference is set to true, symlinks with a target outside of
// the src directory will be dereferenced. When dereference is set to
// false symlinks with a target outside the src directory are omitted
// from the slug.
func Pack(src string, w io.Writer, dereference bool) (*Meta, error) {
// Gzip compress all the output data.
gzipW := gzip.NewWriter(w)
// Tar the file contents.
tarW := tar.NewWriter(gzipW)
// Load the ignore rule configuration, which will use
// defaults if no .terraformignore is configured
ignoreRules := parseIgnoreFile(src)
// Track the metadata details as we go.
meta := &Meta{}
// Walk the tree of files.
err := filepath.Walk(src, packWalkFn(src, src, src, tarW, meta, dereference, ignoreRules))
if err != nil {
return nil, err
}
// Flush the tar writer.
if err := tarW.Close(); err != nil {
return nil, fmt.Errorf("Failed to close the tar archive: %v", err)
}
// Flush the gzip writer.
if err := gzipW.Close(); err != nil {
return nil, fmt.Errorf("Failed to close the gzip writer: %v", err)
}
return meta, nil
}
func packWalkFn(root, src, dst string, tarW *tar.Writer, meta *Meta, dereference bool, ignoreRules []rule) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Get the relative path from the current src directory.
subpath, err := filepath.Rel(src, path)
if err != nil {
return fmt.Errorf("Failed to get relative path for file %q: %v", path, err)
}
if subpath == "." {
return nil
}
if m := matchIgnoreRule(subpath, ignoreRules); m {
return nil
}
// Catch directories so we don't end up with empty directories,
// the files are ignored correctly
if info.IsDir() {
if m := matchIgnoreRule(subpath+string(os.PathSeparator), ignoreRules); m {
return nil
}
}
// Get the relative path from the initial root directory.
subpath, err = filepath.Rel(root, strings.Replace(path, src, dst, 1))
if err != nil {
return fmt.Errorf("Failed to get relative path for file %q: %v", path, err)
}
if subpath == "." {
return nil
}
// Check the file type and if we need to write the body.
keepFile, writeBody := checkFileMode(info.Mode())
if !keepFile {
return nil
}
fm := info.Mode()
header := &tar.Header{
Name: filepath.ToSlash(subpath),
ModTime: info.ModTime(),
Mode: int64(fm.Perm()),
}
switch {
case info.IsDir():
header.Typeflag = tar.TypeDir
header.Name += "/"
case fm.IsRegular():
header.Typeflag = tar.TypeReg
header.Size = info.Size()
case fm&os.ModeSymlink != 0:
target, err := filepath.EvalSymlinks(path)
if err != nil {
return fmt.Errorf("Failed to get symbolic link destination for %q: %v", path, err)
}
// If the target is within the current source, we
// create the symlink using a relative path.
if strings.Contains(target, src) {
link, err := filepath.Rel(filepath.Dir(path), target)
if err != nil {
return fmt.Errorf("Failed to get relative path for symlink destination %q: %v", target, err)
}
header.Typeflag = tar.TypeSymlink
header.Linkname = filepath.ToSlash(link)
// Break out of the case as a symlink
// doesn't need any additional config.
break
}
if !dereference {
// Return early as the symlink has a target outside of the
// src directory and we don't want to dereference symlinks.
return nil
}
// Get the file info for the target.
info, err = os.Lstat(target)
if err != nil {
return fmt.Errorf("Failed to get file info from file %q: %v", target, err)
}
// If the target is a directory we can recurse into the target
// directory by calling the packWalkFn with updated arguments.
if info.IsDir() {
return filepath.Walk(target, packWalkFn(root, target, path, tarW, meta, dereference, ignoreRules))
}
// Dereference this symlink by updating the header with the target file
// details and set writeBody to true so the body will be written.
header.Typeflag = tar.TypeReg
header.ModTime = info.ModTime()
header.Mode = int64(info.Mode().Perm())
header.Size = info.Size()
writeBody = true
default:
return fmt.Errorf("Unexpected file mode %v", fm)
}
// Write the header first to the archive.
if err := tarW.WriteHeader(header); err != nil {
return fmt.Errorf("Failed writing archive header for file %q: %v", path, err)
}
// Account for the file in the list.
meta.Files = append(meta.Files, header.Name)
// Skip writing file data for certain file types (above).
if !writeBody {
return nil
}
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("Failed opening file %q for archiving: %v", path, err)
}
defer f.Close()
size, err := io.Copy(tarW, f)
if err != nil {
return fmt.Errorf("Failed copying file %q to archive: %v", path, err)
}
// Add the size we copied to the body.
meta.Size += size
return nil
}
}
// Unpack is used to read and extract the contents of a slug to
// the dst directory. Returns any errors.
func Unpack(r io.Reader, dst string) error {
// Decompress as we read.
uncompressed, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("Failed to uncompress slug: %v", err)
}
// Untar as we read.
untar := tar.NewReader(uncompressed)
// Unpackage all the contents into the directory.
for {
header, err := untar.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("Failed to untar slug: %v", err)
}
// Get rid of absolute paths.
path := header.Name
if path[0] == '/' {
path = path[1:]
}
path = filepath.Join(dst, path)
// Make the directories to the path.
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("Failed to create directory %q: %v", dir, err)
}
// If we have a symlink, just link it.
if header.Typeflag == tar.TypeSymlink {
if err := os.Symlink(header.Linkname, path); err != nil {
return fmt.Errorf("Failed creating symlink %q => %q: %v",
path, header.Linkname, err)
}
continue
}
// Only unpack regular files from this point on.
if header.Typeflag == tar.TypeDir {
continue
} else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA {
return fmt.Errorf("Failed creating %q: unsupported type %c", path,
header.Typeflag)
}
// Open a handle to the destination.
fh, err := os.Create(path)
if err != nil {
// This mimics tar's behavior wrt the tar file containing duplicate files
// and it allowing later ones to clobber earlier ones even if the file
// has perms that don't allow overwriting.
if os.IsPermission(err) {
os.Chmod(path, 0600)
fh, err = os.Create(path)
}
if err != nil {
return fmt.Errorf("Failed creating file %q: %v", path, err)
}
}
// Copy the contents.
_, err = io.Copy(fh, untar)
fh.Close()
if err != nil {
return fmt.Errorf("Failed to copy slug file %q: %v", path, err)
}
// Restore the file mode. We have to do this after writing the file,
// since it is possible we have a read-only mode.
mode := header.FileInfo().Mode()
if err := os.Chmod(path, mode); err != nil {
return fmt.Errorf("Failed setting permissions on %q: %v", path, err)
}
}
return nil
}
// checkFileMode is used to examine an os.FileMode and determine if it should
// be included in the archive, and if it has a data body which needs writing.
func checkFileMode(m os.FileMode) (keep, body bool) {
switch {
case m.IsDir():
return true, false
case m.IsRegular():
return true, true
case m&os.ModeSymlink != 0:
return true, false
}
return false, false
}

225
vendor/github.com/hashicorp/go-slug/terraformignore.go generated vendored Normal file
View File

@ -0,0 +1,225 @@
package slug
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"text/scanner"
)
func parseIgnoreFile(rootPath string) []rule {
// Look for .terraformignore at our root path/src
file, err := os.Open(filepath.Join(rootPath, ".terraformignore"))
defer file.Close()
// If there's any kind of file error, punt and use the default ignore patterns
if err != nil {
// Only show the error debug if an error *other* than IsNotExist
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Error reading .terraformignore, default exclusions will apply: %v \n", err)
}
return defaultExclusions
}
return readRules(file)
}
func readRules(input io.Reader) []rule {
rules := defaultExclusions
scanner := bufio.NewScanner(input)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
pattern := scanner.Text()
// Ignore blank lines
if len(pattern) == 0 {
continue
}
// Trim spaces
pattern = strings.TrimSpace(pattern)
// Ignore comments
if pattern[0] == '#' {
continue
}
// New rule structure
rule := rule{}
// Exclusions
if pattern[0] == '!' {
rule.excluded = true
pattern = pattern[1:]
}
// If it is a directory, add ** so we catch descendants
if pattern[len(pattern)-1] == os.PathSeparator {
pattern = pattern + "**"
}
// If it starts with /, it is absolute
if pattern[0] == os.PathSeparator {
pattern = pattern[1:]
} else {
// Otherwise prepend **/
pattern = "**" + string(os.PathSeparator) + pattern
}
rule.val = pattern
rule.dirs = strings.Split(pattern, string(os.PathSeparator))
rules = append(rules, rule)
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading .terraformignore, default exclusions will apply: %v \n", err)
return defaultExclusions
}
return rules
}
func matchIgnoreRule(path string, rules []rule) bool {
matched := false
path = filepath.FromSlash(path)
for _, rule := range rules {
match, _ := rule.match(path)
if match {
matched = !rule.excluded
}
}
if matched {
debug(true, path, "Skipping excluded path:", path)
}
return matched
}
type rule struct {
val string // the value of the rule itself
excluded bool // ! is present, an exclusion rule
dirs []string // directories of the rule
regex *regexp.Regexp // regular expression to match for the rule
}
func (r *rule) match(path string) (bool, error) {
if r.regex == nil {
if err := r.compile(); err != nil {
return false, filepath.ErrBadPattern
}
}
b := r.regex.MatchString(path)
debug(false, path, path, r.regex, b)
return b, nil
}
func (r *rule) compile() error {
regStr := "^"
pattern := r.val
// Go through the pattern and convert it to a regexp.
// Use a scanner to support utf-8 chars.
var scan scanner.Scanner
scan.Init(strings.NewReader(pattern))
sl := string(os.PathSeparator)
escSL := sl
if sl == `\` {
escSL += `\`
}
for scan.Peek() != scanner.EOF {
ch := scan.Next()
if ch == '*' {
if scan.Peek() == '*' {
// is some flavor of "**"
scan.Next()
// Treat **/ as ** so eat the "/"
if string(scan.Peek()) == sl {
scan.Next()
}
if scan.Peek() == scanner.EOF {
// is "**EOF" - to align with .gitignore just accept all
regStr += ".*"
} else {
// is "**"
// Note that this allows for any # of /'s (even 0) because
// the .* will eat everything, even /'s
regStr += "(.*" + escSL + ")?"
}
} else {
// is "*" so map it to anything but "/"
regStr += "[^" + escSL + "]*"
}
} else if ch == '?' {
// "?" is any char except "/"
regStr += "[^" + escSL + "]"
} else if ch == '.' || ch == '$' {
// Escape some regexp special chars that have no meaning
// in golang's filepath.Match
regStr += `\` + string(ch)
} else if ch == '\\' {
// escape next char. Note that a trailing \ in the pattern
// will be left alone (but need to escape it)
if sl == `\` {
// On windows map "\" to "\\", meaning an escaped backslash,
// and then just continue because filepath.Match on
// Windows doesn't allow escaping at all
regStr += escSL
continue
}
if scan.Peek() != scanner.EOF {
regStr += `\` + string(scan.Next())
} else {
regStr += `\`
}
} else {
regStr += string(ch)
}
}
regStr += "$"
re, err := regexp.Compile(regStr)
if err != nil {
return err
}
r.regex = re
return nil
}
/*
Default rules as they would appear in .terraformignore:
.git/
.terraform/
!.terraform/modules/
*/
var defaultExclusions = []rule{
{
val: strings.Join([]string{"**", ".git", "**"}, string(os.PathSeparator)),
excluded: false,
},
{
val: strings.Join([]string{"**", ".terraform", "**"}, string(os.PathSeparator)),
excluded: false,
},
{
val: strings.Join([]string{"**", ".terraform", "modules", "**"}, string(os.PathSeparator)),
excluded: true,
},
}
func debug(printAll bool, path string, message ...interface{}) {
logLevel := os.Getenv("TF_IGNORE") == "trace"
debugPath := os.Getenv("TF_IGNORE_DEBUG")
isPath := debugPath != ""
if isPath {
isPath = strings.Contains(path, debugPath)
}
if logLevel {
if printAll || isPath {
fmt.Println(message...)
}
}
}

1
vendor/github.com/hashicorp/go-tfe/.go-version generated vendored Normal file
View File

@ -0,0 +1 @@
1.14.10

354
vendor/github.com/hashicorp/go-tfe/LICENSE generated vendored Normal file
View File

@ -0,0 +1,354 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

115
vendor/github.com/hashicorp/go-tfe/README.md generated vendored Normal file
View File

@ -0,0 +1,115 @@
Terraform Cloud/Enterprise Go Client
==============================
[![Build Status](https://circleci.com/gh/hashicorp/go-tfe.svg?style=shield)](https://circleci.com/gh/hashicorp/go-tfe)
[![GitHub license](https://img.shields.io/github/license/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/hashicorp/go-tfe?status.svg)](https://godoc.org/github.com/hashicorp/go-tfe)
[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/go-tfe)](https://goreportcard.com/report/github.com/hashicorp/go-tfe)
[![GitHub issues](https://img.shields.io/github/issues/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/issues)
The official Go API client for [Terraform Cloud/Enterprise](https://www.hashicorp.com/products/terraform).
This client supports the [Terraform Cloud V2 API](https://www.terraform.io/docs/cloud/api/index.html).
As Terraform Enterprise is a self-hosted distribution of Terraform Cloud, this
client supports both Cloud and Enterprise use cases. In all package
documentation and API, the platform will always be stated as 'Terraform
Enterprise' - but a feature will be explicitly noted as only supported in one or
the other, if applicable (rare).
Note this client is in beta and is subject to change (though it is generally
quite stable). We will indicate any breaking changes by releasing new versions.
Until the release of v1.0, any minor version changes will indicate possible
breaking changes. Patch version changes will be used for both bugfixes and
non-breaking changes.
## Installation
Installation can be done with a normal `go get`:
```
go get -u github.com/hashicorp/go-tfe
```
## Usage
```go
import tfe "github.com/hashicorp/go-tfe"
```
Construct a new TFE client, then use the various endpoints on the client to
access different parts of the Terraform Enterprise API. For example, to list
all organizations:
```go
config := &tfe.Config{
Token: "insert-your-token-here",
}
client, err := tfe.NewClient(config)
if err != nil {
log.Fatal(err)
}
orgs, err := client.Organizations.List(context.Background(), tfe.OrganizationListOptions{})
if err != nil {
log.Fatal(err)
}
```
## Documentation
For complete usage of the API client, see the full [package docs](https://godoc.org/github.com/hashicorp/go-tfe).
## API Coverage
Most of the [Terraform Cloud/Enterprise V2 API](https://www.terraform.io/docs/cloud/api/index.html) is supported in this
client. Currently, the separate [Admin API](https://www.terraform.io/docs/cloud/api/admin/index.html) - applicable only
to Terraform Enterprise - is not.
## Examples
See the [examples directory](https://github.com/hashicorp/go-tfe/tree/master/examples).
## Running tests
See [TESTS.md](https://github.com/hashicorp/go-tfe/tree/master/TESTS.md).
## Issues and Contributing
If you find an issue with this package, please report an issue. If you'd like,
we welcome any contributions. Fork this repository and submit a pull request.
## Releases
Documentation updates and test fixes that only touch test files don't require a release or tag. You can just merge these changes into master once they have been approved.
### Creating a release
1. Merge your approved branch into master.
1. [Create a new release in GitHub](https://help.github.com/en/github/administering-a-repository/creating-releases).
- Click on "Releases" and then "Draft a new release"
- Set the `tag version` to a new tag, using [Semantic Versioning](https://semver.org/) as a guideline.
- Set the `target` as master.
- Set the `Release title` to the tag you created, `vX.Y.Z`
- Use the description section to describe why you're releasing and what changes you've made. You should include links to merged PRs
- Consider using the following headers in the description of your release:
- BREAKING CHANGES: Use this for any changes that aren't backwards compatible. Include details on how to handle these changes.
- FEATURES: Use this for any large new features added,
- ENHANCEMENTS: Use this for smaller new features added
- BUG FIXES: Use this for any bugs that were fixed.
- NOTES: Use this section if you need to include any additional notes on things like upgrading, upcoming deprecations, or any other information you might want to highlight.
Markdown example:
```markdown
ENHANCEMENTS
* Add description of new small feature (#3)[link-to-pull-request]
BUG FIXES
* Fix description of a bug (#2)[link-to-pull-request]
* Fix description of another bug (#1)[link-to-pull-request]
```
- Don't attach any binaries. The zip and tar.gz assets are automatically created and attached after you publish your release.
- Click "Publish release" to save and publish your release.

76
vendor/github.com/hashicorp/go-tfe/TESTS.md generated vendored Normal file
View File

@ -0,0 +1,76 @@
## Running tests
### 1. (Optional) Create repositories for policy sets and registry modules
If you are planning to run the full suite of tests or work on policy sets or registry modules, you'll need to set up repositories for them in GitHub.
Your policy set repository will need the following:
1. A policy set stored in a subdirectory `policy-sets/foo`
1. A branch other than master named `policies`
Your registry module repository will need to be a [valid module](https://www.terraform.io/docs/cloud/registry/publish.html#preparing-a-module-repository).
It will need the following:
1. To be named `terraform-<PROVIDER>-<NAME>`
1. At least one valid SemVer tag in the format `x.y.z`
[terraform-random-module](ttps://github.com/caseylang/terraform-random-module) is a good example repo.
### 2. Set up environment variables
##### Required:
Tests are run against an actual backend so they require a valid backend address and token.
1. `TFE_ADDRESS` - URL of a Terraform Cloud or Terraform Enterprise instance to be used for testing, including scheme. Example: `https://tfe.local`
1. `TFE_TOKEN` - A [user API token](https://www.terraform.io/docs/cloud/users-teams-organizations/users.html#api-tokens) for the Terraform Cloud or Terraform Enterprise instance being used for testing.
##### Optional:
1. `GITHUB_TOKEN` - [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). Required for running any tests that use VCS (OAuth clients, policy sets, etc).
1. `GITHUB_POLICY_SET_IDENTIFIER` - GitHub policy set repository identifier in the format `username/repository`. Required for running policy set tests.
1. `GITHUB_REGISTRY_MODULE_IDENTIFIER` - GitHub registry module repository identifier in the format `username/repository`. Required for running registry module tests.
You can set your environment variables up however you prefer. The following are instructions for setting up environment variables using [envchain](https://github.com/sorah/envchain).
1. Make sure you have envchain installed. [Instructions for this can be found in the envchain README](https://github.com/sorah/envchain#installation).
1. Pick a namespace for storing your environment variables. I suggest `go-tfe` or something similar.
1. For each environment variable you need to set, run the following command:
```sh
envchain --set YOUR_NAMESPACE_HERE ENVIRONMENT_VARIABLE_HERE
```
**OR**
Set all of the environment variables at once with the following command:
```sh
envchain --set YOUR_NAMESPACE_HERE TFE_ADDRESS TFE_TOKEN GITHUB_TOKEN GITHUB_POLICY_SET_IDENTIFIER
```
### 3. Make sure run queue settings are correct
In order for the tests relating to queuing and capacity to pass, FRQ (fair run queuing) should be
enabled with a limit of 2 concurrent runs per organization on the Terraform Cloud or Terraform Enterprise instance you are using for testing.
### 4. Run the tests
#### Running all the tests
As running the all of the tests takes about ~20 minutes, make sure to add a timeout to your
command (as the default timeout is 10m).
##### With envchain:
```sh
$ envchain YOUR_NAMESPACE_HERE go test ./... -timeout=30m
```
##### Without envchain:
```sh
$ go test ./... -timeout=30m
```
#### Running specific tests
The commands below use notification configurations as an example.
##### With envchain:
```sh
$ envchain YOUR_NAMESPACE_HERE go test -run TestNotificationConfiguration -v ./...
```
##### Without envchain:
```sh
$ go test -run TestNotificationConfiguration -v ./...
```

205
vendor/github.com/hashicorp/go-tfe/agent_pool.go generated vendored Normal file
View File

@ -0,0 +1,205 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ AgentPools = (*agentPools)(nil)
// AgentPools describes all the agent pool related methods that the Terraform
// Cloud API supports. Note that agents are not available in Terraform Enterprise.
//
// TFE API docs: https://www.terraform.io/docs/cloud/api/agents.html
type AgentPools interface {
// List all the agent pools of the given organization.
List(ctx context.Context, organization string, options AgentPoolListOptions) (*AgentPoolList, error)
// Create a new agent pool with the given options.
Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error)
// Read a agent pool by its ID.
Read(ctx context.Context, agentPoolID string) (*AgentPool, error)
// Update an agent pool by its ID.
Update(ctx context.Context, agentPool string, options AgentPoolUpdateOptions) (*AgentPool, error)
// Delete an agent pool by its ID.
Delete(ctx context.Context, agentPoolID string) error
}
// agentPools implements AgentPools.
type agentPools struct {
client *Client
}
// AgentPoolList represents a list of agent pools.
type AgentPoolList struct {
*Pagination
Items []*AgentPool
}
// AgentPool represents a Terraform Cloud agent pool.
type AgentPool struct {
ID string `jsonapi:"primary,agent-pools"`
Name string `jsonapi:"attr,name"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
}
// AgentPoolListOptions represents the options for listing agent pools.
type AgentPoolListOptions struct {
ListOptions
}
// List all the agent pools of the given organization.
func (s *agentPools) List(ctx context.Context, organization string, options AgentPoolListOptions) (*AgentPoolList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/agent-pools", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
poolList := &AgentPoolList{}
err = s.client.do(ctx, req, poolList)
if err != nil {
return nil, err
}
return poolList, nil
}
// AgentPoolCreateOptions represents the options for creating an agent pool.
type AgentPoolCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,agent-pools"`
// A name to identify the agent pool.
Name *string `jsonapi:"attr,name"`
}
func (o AgentPoolCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
return nil
}
// Create a new agent pool with the given options.
func (s *agentPools) Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/agent-pools", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
pool := &AgentPool{}
err = s.client.do(ctx, req, pool)
if err != nil {
return nil, err
}
return pool, nil
}
// Read a single agent pool by its ID.
func (s *agentPools) Read(ctx context.Context, agentpoolID string) (*AgentPool, error) {
if !validStringID(&agentpoolID) {
return nil, errors.New("invalid value for agent pool ID")
}
u := fmt.Sprintf("agent-pools/%s", url.QueryEscape(agentpoolID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
pool := &AgentPool{}
err = s.client.do(ctx, req, pool)
if err != nil {
return nil, err
}
return pool, nil
}
// AgentPoolUpdateOptions represents the options for updating an agent pool.
type AgentPoolUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,agent-pools"`
// A new name to identify the agent pool.
Name *string `jsonapi:"attr,name"`
}
func (o AgentPoolUpdateOptions) valid() error {
if o.Name != nil && !validStringID(o.Name) {
return errors.New("invalid value for name")
}
return nil
}
// Update an agent pool by its ID.
func (s *agentPools) Update(ctx context.Context, agentPoolID string, options AgentPoolUpdateOptions) (*AgentPool, error) {
if !validStringID(&agentPoolID) {
return nil, errors.New("invalid value for agent pool ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("agent-pools/%s", url.QueryEscape(agentPoolID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
k := &AgentPool{}
err = s.client.do(ctx, req, k)
if err != nil {
return nil, err
}
return k, nil
}
// Delete an agent pool by its ID.
func (s *agentPools) Delete(ctx context.Context, agentPoolID string) error {
if !validStringID(&agentPoolID) {
return errors.New("invalid value for agent pool ID")
}
u := fmt.Sprintf("agent-pools/%s", url.QueryEscape(agentPoolID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

145
vendor/github.com/hashicorp/go-tfe/agent_token.go generated vendored Normal file
View File

@ -0,0 +1,145 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ AgentTokens = (*agentTokens)(nil)
// AgentTokens describes all the agent token related methods that the
// Terraform Cloud API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/cloud/api/agent-tokens.html
type AgentTokens interface {
// List all the agent tokens of the given agent pool.
List(ctx context.Context, agentPoolID string) (*AgentTokenList, error)
// Generate a new agent token with the given options.
Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error)
// Read an agent token by its ID.
Read(ctx context.Context, agentTokenID string) (*AgentToken, error)
// Delete an agent token by its ID.
Delete(ctx context.Context, agentTokenID string) error
}
// agentTokens implements AgentTokens.
type agentTokens struct {
client *Client
}
// AgentTokenList represents a list of agent tokens.
type AgentTokenList struct {
*Pagination
Items []*AgentToken
}
// AgentToken represents a Terraform Cloud agent token.
type AgentToken struct {
ID string `jsonapi:"primary,authentication-tokens"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
}
// List all the agent tokens of the given agent pool.
func (s *agentTokens) List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) {
if !validStringID(&agentPoolID) {
return nil, errors.New("invalid value for agent pool ID")
}
u := fmt.Sprintf("agent-pools/%s/authentication-tokens", url.QueryEscape(agentPoolID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
tokenList := &AgentTokenList{}
err = s.client.do(ctx, req, tokenList)
if err != nil {
return nil, err
}
return tokenList, nil
}
// AgentTokenGenerateOptions represents the options for creating an agent token.
type AgentTokenGenerateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,agent-tokens"`
// Description of the token
Description *string `jsonapi:"attr,description"`
}
// Generate a new agent token with the given options.
func (s *agentTokens) Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error) {
if !validStringID(&agentPoolID) {
return nil, errors.New("invalid value for agent pool ID")
}
if !validString(options.Description) {
return nil, errors.New("agent token description can't be blank")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("agent-pools/%s/authentication-tokens", url.QueryEscape(agentPoolID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
at := &AgentToken{}
err = s.client.do(ctx, req, at)
if err != nil {
return nil, err
}
return at, err
}
// Read an agent token by its ID.
func (s *agentTokens) Read(ctx context.Context, agentTokenID string) (*AgentToken, error) {
if !validStringID(&agentTokenID) {
return nil, errors.New("invalid value for agent token ID")
}
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(agentTokenID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
at := &AgentToken{}
err = s.client.do(ctx, req, at)
if err != nil {
return nil, err
}
return at, err
}
// Delete an agent token by its ID.
func (s *agentTokens) Delete(ctx context.Context, agentTokenID string) error {
if !validStringID(&agentTokenID) {
return errors.New("invalid value for agent token ID")
}
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(agentTokenID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

132
vendor/github.com/hashicorp/go-tfe/apply.go generated vendored Normal file
View File

@ -0,0 +1,132 @@
package tfe
import (
"context"
"errors"
"fmt"
"io"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Applies = (*applies)(nil)
// Applies describes all the apply related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/apply.html
type Applies interface {
// Read an apply by its ID.
Read(ctx context.Context, applyID string) (*Apply, error)
// Logs retrieves the logs of an apply.
Logs(ctx context.Context, applyID string) (io.Reader, error)
}
// applies implements Applys.
type applies struct {
client *Client
}
// ApplyStatus represents an apply state.
type ApplyStatus string
//List all available apply statuses.
const (
ApplyCanceled ApplyStatus = "canceled"
ApplyCreated ApplyStatus = "created"
ApplyErrored ApplyStatus = "errored"
ApplyFinished ApplyStatus = "finished"
ApplyMFAWaiting ApplyStatus = "mfa_waiting"
ApplyPending ApplyStatus = "pending"
ApplyQueued ApplyStatus = "queued"
ApplyRunning ApplyStatus = "running"
ApplyUnreachable ApplyStatus = "unreachable"
)
// Apply represents a Terraform Enterprise apply.
type Apply struct {
ID string `jsonapi:"primary,applies"`
LogReadURL string `jsonapi:"attr,log-read-url"`
ResourceAdditions int `jsonapi:"attr,resource-additions"`
ResourceChanges int `jsonapi:"attr,resource-changes"`
ResourceDestructions int `jsonapi:"attr,resource-destructions"`
Status ApplyStatus `jsonapi:"attr,status"`
StatusTimestamps *ApplyStatusTimestamps `jsonapi:"attr,status-timestamps"`
}
// ApplyStatusTimestamps holds the timestamps for individual apply statuses.
type ApplyStatusTimestamps struct {
CanceledAt time.Time `json:"canceled-at"`
ErroredAt time.Time `json:"errored-at"`
FinishedAt time.Time `json:"finished-at"`
ForceCanceledAt time.Time `json:"force-canceled-at"`
QueuedAt time.Time `json:"queued-at"`
StartedAt time.Time `json:"started-at"`
}
// Read an apply by its ID.
func (s *applies) Read(ctx context.Context, applyID string) (*Apply, error) {
if !validStringID(&applyID) {
return nil, errors.New("invalid value for apply ID")
}
u := fmt.Sprintf("applies/%s", url.QueryEscape(applyID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
a := &Apply{}
err = s.client.do(ctx, req, a)
if err != nil {
return nil, err
}
return a, nil
}
// Logs retrieves the logs of an apply.
func (s *applies) Logs(ctx context.Context, applyID string) (io.Reader, error) {
if !validStringID(&applyID) {
return nil, errors.New("invalid value for apply ID")
}
// Get the apply to make sure it exists.
a, err := s.Read(ctx, applyID)
if err != nil {
return nil, err
}
// Return an error if the log URL is empty.
if a.LogReadURL == "" {
return nil, fmt.Errorf("apply %s does not have a log URL", applyID)
}
u, err := url.Parse(a.LogReadURL)
if err != nil {
return nil, fmt.Errorf("invalid log URL: %v", err)
}
done := func() (bool, error) {
a, err := s.Read(ctx, a.ID)
if err != nil {
return false, err
}
switch a.Status {
case ApplyCanceled, ApplyErrored, ApplyFinished, ApplyUnreachable:
return true, nil
default:
return false, nil
}
}
return &LogReader{
client: s.client,
ctx: ctx,
done: done,
logURL: u,
}, nil
}

View File

@ -0,0 +1,208 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"os"
"time"
slug "github.com/hashicorp/go-slug"
)
// Compile-time proof of interface implementation.
var _ ConfigurationVersions = (*configurationVersions)(nil)
// ConfigurationVersions describes all the configuration version related
// methods that the Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/configuration-versions.html
type ConfigurationVersions interface {
// List returns all configuration versions of a workspace.
List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error)
// Create is used to create a new configuration version. The created
// configuration version will be usable once data is uploaded to it.
Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error)
// Read a configuration version by its ID.
Read(ctx context.Context, cvID string) (*ConfigurationVersion, error)
// Upload packages and uploads Terraform configuration files. It requires
// the upload URL from a configuration version and the full path to the
// configuration files on disk.
Upload(ctx context.Context, url string, path string) error
}
// configurationVersions implements ConfigurationVersions.
type configurationVersions struct {
client *Client
}
// ConfigurationStatus represents a configuration version status.
type ConfigurationStatus string
//List all available configuration version statuses.
const (
ConfigurationErrored ConfigurationStatus = "errored"
ConfigurationPending ConfigurationStatus = "pending"
ConfigurationUploaded ConfigurationStatus = "uploaded"
)
// ConfigurationSource represents a source of a configuration version.
type ConfigurationSource string
// List all available configuration version sources.
const (
ConfigurationSourceAPI ConfigurationSource = "tfe-api"
ConfigurationSourceBitbucket ConfigurationSource = "bitbucket"
ConfigurationSourceGithub ConfigurationSource = "github"
ConfigurationSourceGitlab ConfigurationSource = "gitlab"
ConfigurationSourceTerraform ConfigurationSource = "terraform"
)
// ConfigurationVersionList represents a list of configuration versions.
type ConfigurationVersionList struct {
*Pagination
Items []*ConfigurationVersion
}
// ConfigurationVersion is a representation of an uploaded or ingressed
// Terraform configuration in TFE. A workspace must have at least one
// configuration version before any runs may be queued on it.
type ConfigurationVersion struct {
ID string `jsonapi:"primary,configuration-versions"`
AutoQueueRuns bool `jsonapi:"attr,auto-queue-runs"`
Error string `jsonapi:"attr,error"`
ErrorMessage string `jsonapi:"attr,error-message"`
Source ConfigurationSource `jsonapi:"attr,source"`
Speculative bool `jsonapi:"attr,speculative "`
Status ConfigurationStatus `jsonapi:"attr,status"`
StatusTimestamps *CVStatusTimestamps `jsonapi:"attr,status-timestamps"`
UploadURL string `jsonapi:"attr,upload-url"`
}
// CVStatusTimestamps holds the timestamps for individual configuration version
// statuses.
type CVStatusTimestamps struct {
FinishedAt time.Time `json:"finished-at"`
QueuedAt time.Time `json:"queued-at"`
StartedAt time.Time `json:"started-at"`
}
// ConfigurationVersionListOptions represents the options for listing
// configuration versions.
type ConfigurationVersionListOptions struct {
ListOptions
}
// List returns all configuration versions of a workspace.
func (s *configurationVersions) List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/configuration-versions", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
cvl := &ConfigurationVersionList{}
err = s.client.do(ctx, req, cvl)
if err != nil {
return nil, err
}
return cvl, nil
}
// ConfigurationVersionCreateOptions represents the options for creating a
// configuration version.
type ConfigurationVersionCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,configuration-versions"`
// When true, runs are queued automatically when the configuration version
// is uploaded.
AutoQueueRuns *bool `jsonapi:"attr,auto-queue-runs,omitempty"`
// When true, this configuration version can only be used for planning.
Speculative *bool `jsonapi:"attr,speculative,omitempty"`
}
// Create is used to create a new configuration version. The created
// configuration version will be usable once data is uploaded to it.
func (s *configurationVersions) Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/configuration-versions", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
cv := &ConfigurationVersion{}
err = s.client.do(ctx, req, cv)
if err != nil {
return nil, err
}
return cv, nil
}
// Read a configuration version by its ID.
func (s *configurationVersions) Read(ctx context.Context, cvID string) (*ConfigurationVersion, error) {
if !validStringID(&cvID) {
return nil, errors.New("invalid value for configuration version ID")
}
u := fmt.Sprintf("configuration-versions/%s", url.QueryEscape(cvID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
cv := &ConfigurationVersion{}
err = s.client.do(ctx, req, cv)
if err != nil {
return nil, err
}
return cv, nil
}
// Upload packages and uploads Terraform configuration files. It requires the
// upload URL from a configuration version and the path to the configuration
// files on disk.
func (s *configurationVersions) Upload(ctx context.Context, url, path string) error {
file, err := os.Stat(path)
if err != nil {
return err
}
if !file.Mode().IsDir() {
return errors.New("path needs to be an existing directory")
}
body := bytes.NewBuffer(nil)
_, err = slug.Pack(path, body, true)
if err != nil {
return err
}
req, err := s.client.newRequest("PUT", url, body)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

131
vendor/github.com/hashicorp/go-tfe/cost_estimate.go generated vendored Normal file
View File

@ -0,0 +1,131 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ CostEstimates = (*costEstimates)(nil)
// CostEstimates describes all the costEstimate related methods that
// the Terraform Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/ (TBD)
type CostEstimates interface {
// Read a costEstimate by its ID.
Read(ctx context.Context, costEstimateID string) (*CostEstimate, error)
// Logs retrieves the logs of a costEstimate.
Logs(ctx context.Context, costEstimateID string) (io.Reader, error)
}
// costEstimates implements CostEstimates.
type costEstimates struct {
client *Client
}
// CostEstimateStatus represents a costEstimate state.
type CostEstimateStatus string
// List all available costEstimate statuses.
const (
CostEstimateCanceled CostEstimateStatus = "canceled"
CostEstimateErrored CostEstimateStatus = "errored"
CostEstimateFinished CostEstimateStatus = "finished"
CostEstimatePending CostEstimateStatus = "pending"
CostEstimateQueued CostEstimateStatus = "queued"
CostEstimateSkippedDueToTargeting CostEstimateStatus = "skipped_due_to_targeting"
)
// CostEstimate represents a Terraform Enterprise costEstimate.
type CostEstimate struct {
ID string `jsonapi:"primary,cost-estimates"`
DeltaMonthlyCost string `jsonapi:"attr,delta-monthly-cost"`
ErrorMessage string `jsonapi:"attr,error-message"`
MatchedResourcesCount int `jsonapi:"attr,matched-resources-count"`
PriorMonthlyCost string `jsonapi:"attr,prior-monthly-cost"`
ProposedMonthlyCost string `jsonapi:"attr,proposed-monthly-cost"`
ResourcesCount int `jsonapi:"attr,resources-count"`
Status CostEstimateStatus `jsonapi:"attr,status"`
StatusTimestamps *CostEstimateStatusTimestamps `jsonapi:"attr,status-timestamps"`
UnmatchedResourcesCount int `jsonapi:"attr,unmatched-resources-count"`
}
// CostEstimateStatusTimestamps holds the timestamps for individual costEstimate statuses.
type CostEstimateStatusTimestamps struct {
CanceledAt time.Time `json:"canceled-at"`
ErroredAt time.Time `json:"errored-at"`
FinishedAt time.Time `json:"finished-at"`
PendingAt time.Time `json:"pending-at"`
QueuedAt time.Time `json:"queued-at"`
SkippedDueToTargetingAt time.Time `json:"skipped-due-to-targeting-at"`
}
// Read a costEstimate by its ID.
func (s *costEstimates) Read(ctx context.Context, costEstimateID string) (*CostEstimate, error) {
if !validStringID(&costEstimateID) {
return nil, errors.New("invalid value for cost estimate ID")
}
u := fmt.Sprintf("cost-estimates/%s", url.QueryEscape(costEstimateID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
ce := &CostEstimate{}
err = s.client.do(ctx, req, ce)
if err != nil {
return nil, err
}
return ce, nil
}
// Logs retrieves the logs of a costEstimate.
func (s *costEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) {
if !validStringID(&costEstimateID) {
return nil, errors.New("invalid value for cost estimate ID")
}
// Loop until the context is canceled or the cost estimate is finished
// running. The cost estimate logs are not streamed and so only available
// once the estimate is finished.
for {
// Get the costEstimate to make sure it exists.
ce, err := s.Read(ctx, costEstimateID)
if err != nil {
return nil, err
}
switch ce.Status {
case CostEstimateQueued:
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(1000 * time.Millisecond):
continue
}
}
u := fmt.Sprintf("cost-estimates/%s/output", url.QueryEscape(costEstimateID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
logs := bytes.NewBuffer(nil)
err = s.client.do(ctx, req, logs)
if err != nil {
return nil, err
}
return logs, nil
}
}

15
vendor/github.com/hashicorp/go-tfe/go.mod generated vendored Normal file
View File

@ -0,0 +1,15 @@
module github.com/hashicorp/go-tfe
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-querystring v1.0.0
github.com/hashicorp/go-cleanhttp v0.5.0
github.com/hashicorp/go-retryablehttp v0.5.2
github.com/hashicorp/go-slug v0.4.1
github.com/hashicorp/go-uuid v1.0.1
github.com/stretchr/testify v1.3.0
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
)
go 1.14

23
vendor/github.com/hashicorp/go-tfe/go.sum generated vendored Normal file
View File

@ -0,0 +1,23 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
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-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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 h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

96
vendor/github.com/hashicorp/go-tfe/ip_ranges.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
package tfe
import (
"context"
"encoding/json"
"fmt"
"github.com/hashicorp/go-retryablehttp"
)
// Compile-time proof of interface implementation.
var _ IPRanges = (*ipRanges)(nil)
// IP Ranges provides a list of Terraform Cloud and Enterprise's IP ranges.
//
// TFE API docs: https://www.terraform.io/docs/cloud/api/ip-ranges.html
type IPRanges interface {
// Retrieve TFC IP ranges. If `modifiedSince` is not an empty string
// then it will only return the IP ranges changes since that date.
// The format for `modifiedSince` can be found here:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
Read(ctx context.Context, modifiedSince string) (*IPRange, error)
}
type ipRanges struct {
client *Client
}
type IPRange struct {
// List of IP ranges in CIDR notation used for connections from user site to Terraform Cloud APIs
API []string `json:"api"`
// List of IP ranges in CIDR notation used for notifications
Notifications []string `json:"notifications"`
// List of IP ranges in CIDR notation used for outbound requests from Sentinel policies
Sentinel []string `json:"sentinel"`
// List of IP ranges in CIDR notation used for connecting to VCS providers
VCS []string `json:"vcs"`
}
func (i *ipRanges) Read(ctx context.Context, modifiedSince string) (*IPRange, error) {
i.client.baseURL.Path = "/api/"
req, err := i.client.newRequest("GET", "meta/ip-ranges", nil)
if err != nil {
return nil, err
}
if modifiedSince != "" {
req.Header.Add("If-Modified-Since", modifiedSince)
}
ir := &IPRange{}
err = i.customDo(ctx, req, ir)
if err != nil {
return nil, err
}
return ir, nil
}
// The IP ranges API is not returning jsonapi like every other endpoint
// which means we need to handle it differently.
func (i *ipRanges) customDo(ctx context.Context, req *retryablehttp.Request, ir *IPRange) error {
// Wait will block until the limiter can obtain a new token
// or returns an error if the given context is canceled.
if err := i.client.limiter.Wait(ctx); err != nil {
return err
}
// Add the context to the request.
req = req.WithContext(ctx)
// Execute the request and check the response.
resp, err := i.client.http.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return ctx.Err()
default:
return err
}
}
defer resp.Body.Close()
if resp.StatusCode < 200 && resp.StatusCode >= 400 {
return fmt.Errorf("Error HTTP response while retrieving IP ranges: %d", resp.StatusCode)
} else if resp.StatusCode == 304 {
return nil
}
err = json.NewDecoder(resp.Body).Decode(ir)
if err != nil {
return err
}
return nil
}

143
vendor/github.com/hashicorp/go-tfe/logreader.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
package tfe
import (
"context"
"fmt"
"io"
"math"
"net/http"
"net/url"
"time"
)
// LogReader implements io.Reader for streaming logs.
type LogReader struct {
client *Client
ctx context.Context
done func() (bool, error)
logURL *url.URL
offset int64
reads int
startOfText bool
endOfText bool
}
// backoff will perform exponential backoff based on the iteration and
// limited by the provided min and max (in milliseconds) durations.
func backoff(min, max float64, iter int) time.Duration {
backoff := math.Pow(2, float64(iter)/5) * min
if backoff > max {
backoff = max
}
return time.Duration(backoff) * time.Millisecond
}
func (r *LogReader) Read(l []byte) (int, error) {
if written, err := r.read(l); err != io.ErrNoProgress {
return written, err
}
// Loop until we can any data, the context is canceled or the
// run is finsished. If we would return right away without any
// data, we could and up causing a io.ErrNoProgress error.
for r.reads = 1; ; r.reads++ {
select {
case <-r.ctx.Done():
return 0, r.ctx.Err()
case <-time.After(backoff(500, 2000, r.reads)):
if written, err := r.read(l); err != io.ErrNoProgress {
return written, err
}
}
}
}
func (r *LogReader) read(l []byte) (int, error) {
// Update the query string.
r.logURL.RawQuery = fmt.Sprintf("limit=%d&offset=%d", len(l), r.offset)
// Create a new request.
req, err := http.NewRequest("GET", r.logURL.String(), nil)
if err != nil {
return 0, err
}
req = req.WithContext(r.ctx)
// Attach the default headers.
for k, v := range r.client.headers {
req.Header[k] = v
}
// Retrieve the next chunk.
resp, err := r.client.http.HTTPClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
// Basic response checking.
if err := checkResponseCode(resp); err != nil {
return 0, err
}
// Read the retrieved chunk.
written, err := resp.Body.Read(l)
if err != nil && err != io.EOF {
// Ignore io.EOF errors returned when reading from the response
// body as this indicates the end of the chunk and not the end
// of the logfile.
return written, err
}
if written > 0 {
// Check for an STX (Start of Text) ASCII control marker.
if !r.startOfText && l[0] == byte(2) {
r.startOfText = true
// Remove the STX marker from the received chunk.
copy(l[:written-1], l[1:])
l[written-1] = byte(0)
r.offset++
written--
// Return early if we only received the STX marker.
if written == 0 {
return 0, io.ErrNoProgress
}
}
// If we found an STX ASCII control character, start looking for
// the ETX (End of Text) control character.
if r.startOfText && l[written-1] == byte(3) {
r.endOfText = true
// Remove the ETX marker from the received chunk.
l[written-1] = byte(0)
r.offset++
written--
}
}
// Check if we need to continue the loop and wait 500 miliseconds
// before checking if there is a new chunk available or that the
// run is finished and we are done reading all chunks.
if written == 0 {
if (r.startOfText && r.endOfText) || // The logstream finished without issues.
(r.startOfText && r.reads%10 == 0) || // The logstream terminated unexpectedly.
(!r.startOfText && r.reads > 1) { // The logstream doesn't support STX/ETX.
done, err := r.done()
if err != nil {
return 0, err
}
if done {
return 0, io.EOF
}
}
return 0, io.ErrNoProgress
}
// Update the offset for the next read.
r.offset += int64(written)
return written, nil
}

View File

@ -0,0 +1,319 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ NotificationConfigurations = (*notificationConfigurations)(nil)
// NotificationConfigurations describes all the Notification Configuration
// related methods that the Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/notification-configurations.html
type NotificationConfigurations interface {
// List all the notification configurations within a workspace.
List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error)
// Create a new notification configuration with the given options.
Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error)
// Read a notification configuration by its ID.
Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)
// Update an existing notification configuration.
Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error)
// Delete a notification configuration by its ID.
Delete(ctx context.Context, notificationConfigurationID string) error
// Verify a notification configuration by its ID.
Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)
}
// notificationConfigurations implements NotificationConfigurations.
type notificationConfigurations struct {
client *Client
}
// List of available notification triggers.
const (
NotificationTriggerCreated string = "run:created"
NotificationTriggerPlanning string = "run:planning"
NotificationTriggerNeedsAttention string = "run:needs_attention"
NotificationTriggerApplying string = "run:applying"
NotificationTriggerCompleted string = "run:completed"
NotificationTriggerErrored string = "run:errored"
)
// NotificationDestinationType represents the destination type of the
// notification configuration.
type NotificationDestinationType string
// List of available notification destination types.
const (
NotificationDestinationTypeEmail NotificationDestinationType = "email"
NotificationDestinationTypeGeneric NotificationDestinationType = "generic"
NotificationDestinationTypeSlack NotificationDestinationType = "slack"
)
// NotificationConfigurationList represents a list of Notification
// Configurations.
type NotificationConfigurationList struct {
*Pagination
Items []*NotificationConfiguration
}
// NotificationConfiguration represents a Notification Configuration.
type NotificationConfiguration struct {
ID string `jsonapi:"primary,notification-configurations"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
DeliveryResponses []*DeliveryResponse `jsonapi:"attr,delivery-responses"`
DestinationType NotificationDestinationType `jsonapi:"attr,destination-type"`
Enabled bool `jsonapi:"attr,enabled"`
Name string `jsonapi:"attr,name"`
Token string `jsonapi:"attr,token"`
Triggers []string `jsonapi:"attr,triggers"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
URL string `jsonapi:"attr,url"`
// EmailAddresses is only available for TFE users. It is not available in TFC.
EmailAddresses []string `jsonapi:"attr,email-addresses"`
// Relations
Subscribable *Workspace `jsonapi:"relation,subscribable"`
EmailUsers []*User `jsonapi:"relation,users"`
}
// DeliveryResponse represents a notification configuration delivery response.
type DeliveryResponse struct {
Body string `json:"body"`
Code int `json:"code"`
Headers http.Header `json:"headers"`
SentAt time.Time `json:"sent-at,iso8601"`
Successful bool `json:"successful"`
URL string `json:"url"`
}
// NotificationConfigurationListOptions represents the options for listing
// notification configurations.
type NotificationConfigurationListOptions struct {
ListOptions
}
// List all the notification configurations associated with a workspace.
func (s *notificationConfigurations) List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
ncl := &NotificationConfigurationList{}
err = s.client.do(ctx, req, ncl)
if err != nil {
return nil, err
}
return ncl, nil
}
// NotificationConfigurationCreateOptions represents the options for
// creating a new notification configuration.
type NotificationConfigurationCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,notification-configurations"`
// The destination type of the notification configuration
DestinationType *NotificationDestinationType `jsonapi:"attr,destination-type"`
// Whether the notification configuration should be enabled or not
Enabled *bool `jsonapi:"attr,enabled"`
// The name of the notification configuration
Name *string `jsonapi:"attr,name"`
// The token of the notification configuration
Token *string `jsonapi:"attr,token,omitempty"`
// The list of run events that will trigger notifications.
Triggers []string `jsonapi:"attr,triggers,omitempty"`
// The url of the notification configuration
URL *string `jsonapi:"attr,url,omitempty"`
// The list of email addresses that will receive notification emails.
// EmailAddresses is only available for TFE users. It is not available in TFC.
EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"`
// The list of users belonging to the organization that will receive notification emails.
EmailUsers []*User `jsonapi:"relation,users,omitempty"`
}
func (o NotificationConfigurationCreateOptions) valid() error {
if o.DestinationType == nil {
return errors.New("destination type is required")
}
if o.Enabled == nil {
return errors.New("enabled is required")
}
if !validString(o.Name) {
return errors.New("name is required")
}
if *o.DestinationType == NotificationDestinationTypeGeneric || *o.DestinationType == NotificationDestinationTypeSlack {
if o.URL == nil {
return errors.New("url is required")
}
}
return nil
}
// Creates a notification configuration with the given options.
func (s *notificationConfigurations) Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
nc := &NotificationConfiguration{}
err = s.client.do(ctx, req, nc)
if err != nil {
return nil, err
}
return nc, nil
}
// Read a notification configuration by its ID.
func (s *notificationConfigurations) Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {
if !validStringID(&notificationConfigurationID) {
return nil, errors.New("invalid value for notification configuration ID")
}
u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
nc := &NotificationConfiguration{}
err = s.client.do(ctx, req, nc)
if err != nil {
return nil, err
}
return nc, nil
}
// NotificationConfigurationUpdateOptions represents the options for
// updating a existing notification configuration.
type NotificationConfigurationUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,notification-configurations"`
// Whether the notification configuration should be enabled or not
Enabled *bool `jsonapi:"attr,enabled,omitempty"`
// The name of the notification configuration
Name *string `jsonapi:"attr,name,omitempty"`
// The token of the notification configuration
Token *string `jsonapi:"attr,token,omitempty"`
// The list of run events that will trigger notifications.
Triggers []string `jsonapi:"attr,triggers,omitempty"`
// The url of the notification configuration
URL *string `jsonapi:"attr,url,omitempty"`
// The list of email addresses that will receive notification emails.
// EmailAddresses is only available for TFE users. It is not available in TFC.
EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"`
// The list of users belonging to the organization that will receive notification emails.
EmailUsers []*User `jsonapi:"relation,users,omitempty"`
}
// Updates a notification configuration with the given options.
func (s *notificationConfigurations) Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error) {
if !validStringID(&notificationConfigurationID) {
return nil, errors.New("invalid value for notification configuration ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
nc := &NotificationConfiguration{}
err = s.client.do(ctx, req, nc)
if err != nil {
return nil, err
}
return nc, nil
}
// Delete a notifications configuration by its ID.
func (s *notificationConfigurations) Delete(ctx context.Context, notificationConfigurationID string) error {
if !validStringID(&notificationConfigurationID) {
return errors.New("invalid value for notification configuration ID")
}
u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Verifies a notification configuration by delivering a verification
// payload to the configured url.
func (s *notificationConfigurations) Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {
if !validStringID(&notificationConfigurationID) {
return nil, errors.New("invalid value for notification configuration ID")
}
u := fmt.Sprintf(
"notification-configurations/%s/actions/verify", url.QueryEscape(notificationConfigurationID))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
nc := &NotificationConfiguration{}
err = s.client.do(ctx, req, nc)
if err != nil {
return nil, err
}
return nc, nil
}

210
vendor/github.com/hashicorp/go-tfe/oauth_client.go generated vendored Normal file
View File

@ -0,0 +1,210 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ OAuthClients = (*oAuthClients)(nil)
// OAuthClients describes all the OAuth client related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/oauth-clients.html
type OAuthClients interface {
// List all the OAuth clients for a given organization.
List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error)
// Create an OAuth client to connect an organization and a VCS provider.
Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error)
// Read an OAuth client by its ID.
Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error)
// Delete an OAuth client by its ID.
Delete(ctx context.Context, oAuthClientID string) error
}
// oAuthClients implements OAuthClients.
type oAuthClients struct {
client *Client
}
// ServiceProviderType represents a VCS type.
type ServiceProviderType string
// List of available VCS types.
const (
ServiceProviderAzureDevOpsServer ServiceProviderType = "ado_server"
ServiceProviderAzureDevOpsServices ServiceProviderType = "ado_services"
ServiceProviderBitbucket ServiceProviderType = "bitbucket_hosted"
// Bitbucket Server v5.4.0 and above
ServiceProviderBitbucketServer ServiceProviderType = "bitbucket_server"
// Bitbucket Server v5.3.0 and below
ServiceProviderBitbucketServerLegacy ServiceProviderType = "bitbucket_server_legacy"
ServiceProviderGithub ServiceProviderType = "github"
ServiceProviderGithubEE ServiceProviderType = "github_enterprise"
ServiceProviderGitlab ServiceProviderType = "gitlab_hosted"
ServiceProviderGitlabCE ServiceProviderType = "gitlab_community_edition"
ServiceProviderGitlabEE ServiceProviderType = "gitlab_enterprise_edition"
)
// OAuthClientList represents a list of OAuth clients.
type OAuthClientList struct {
*Pagination
Items []*OAuthClient
}
// OAuthClient represents a connection between an organization and a VCS
// provider.
type OAuthClient struct {
ID string `jsonapi:"primary,oauth-clients"`
APIURL string `jsonapi:"attr,api-url"`
CallbackURL string `jsonapi:"attr,callback-url"`
ConnectPath string `jsonapi:"attr,connect-path"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
HTTPURL string `jsonapi:"attr,http-url"`
Key string `jsonapi:"attr,key"`
RSAPublicKey string `jsonapi:"attr,rsa-public-key"`
ServiceProvider ServiceProviderType `jsonapi:"attr,service-provider"`
ServiceProviderName string `jsonapi:"attr,service-provider-display-name"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
OAuthTokens []*OAuthToken `jsonapi:"relation,oauth-tokens"`
}
// OAuthClientListOptions represents the options for listing
// OAuth clients.
type OAuthClientListOptions struct {
ListOptions
}
// List all the OAuth clients for a given organization.
func (s *oAuthClients) List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
ocl := &OAuthClientList{}
err = s.client.do(ctx, req, ocl)
if err != nil {
return nil, err
}
return ocl, nil
}
// OAuthClientCreateOptions represents the options for creating an OAuth client.
type OAuthClientCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,oauth-clients"`
// The base URL of your VCS provider's API.
APIURL *string `jsonapi:"attr,api-url"`
// The homepage of your VCS provider.
HTTPURL *string `jsonapi:"attr,http-url"`
// The token string you were given by your VCS provider.
OAuthToken *string `jsonapi:"attr,oauth-token-string"`
// Private key associated with this vcs provider - only available for ado_server
PrivateKey *string `jsonapi:"attr,private-key"`
// The VCS provider being connected with.
ServiceProvider *ServiceProviderType `jsonapi:"attr,service-provider"`
}
func (o OAuthClientCreateOptions) valid() error {
if !validString(o.APIURL) {
return errors.New("API URL is required")
}
if !validString(o.HTTPURL) {
return errors.New("HTTP URL is required")
}
if !validString(o.OAuthToken) {
return errors.New("OAuth token is required")
}
if o.ServiceProvider == nil {
return errors.New("service provider is required")
}
if validString(o.PrivateKey) && *o.ServiceProvider != *ServiceProvider(ServiceProviderAzureDevOpsServer) {
return errors.New("Private Key can only be present with Azure DevOps Server service provider")
}
return nil
}
// Create an OAuth client to connect an organization and a VCS provider.
func (s *oAuthClients) Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
oc := &OAuthClient{}
err = s.client.do(ctx, req, oc)
if err != nil {
return nil, err
}
return oc, nil
}
// Read an OAuth client by its ID.
func (s *oAuthClients) Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error) {
if !validStringID(&oAuthClientID) {
return nil, errors.New("invalid value for OAuth client ID")
}
u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
oc := &OAuthClient{}
err = s.client.do(ctx, req, oc)
if err != nil {
return nil, err
}
return oc, err
}
// Delete an OAuth client by its ID.
func (s *oAuthClients) Delete(ctx context.Context, oAuthClientID string) error {
if !validStringID(&oAuthClientID) {
return errors.New("invalid value for OAuth client ID")
}
u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

150
vendor/github.com/hashicorp/go-tfe/oauth_token.go generated vendored Normal file
View File

@ -0,0 +1,150 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ OAuthTokens = (*oAuthTokens)(nil)
// OAuthTokens describes all the OAuth token related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/oauth-tokens.html
type OAuthTokens interface {
// List all the OAuth tokens for a given organization.
List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error)
// Read a OAuth token by its ID.
Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error)
// Update an existing OAuth token.
Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error)
// Delete a OAuth token by its ID.
Delete(ctx context.Context, oAuthTokenID string) error
}
// oAuthTokens implements OAuthTokens.
type oAuthTokens struct {
client *Client
}
// OAuthTokenList represents a list of OAuth tokens.
type OAuthTokenList struct {
*Pagination
Items []*OAuthToken
}
// OAuthToken represents a VCS configuration including the associated
// OAuth token
type OAuthToken struct {
ID string `jsonapi:"primary,oauth-tokens"`
UID string `jsonapi:"attr,uid"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
HasSSHKey bool `jsonapi:"attr,has-ssh-key"`
ServiceProviderUser string `jsonapi:"attr,service-provider-user"`
// Relations
OAuthClient *OAuthClient `jsonapi:"relation,oauth-client"`
}
// OAuthTokenListOptions represents the options for listing
// OAuth tokens.
type OAuthTokenListOptions struct {
ListOptions
}
// List all the OAuth tokens for a given organization.
func (s *oAuthTokens) List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/oauth-tokens", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
otl := &OAuthTokenList{}
err = s.client.do(ctx, req, otl)
if err != nil {
return nil, err
}
return otl, nil
}
// Read an OAuth token by its ID.
func (s *oAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) {
if !validStringID(&oAuthTokenID) {
return nil, errors.New("invalid value for OAuth token ID")
}
u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
ot := &OAuthToken{}
err = s.client.do(ctx, req, ot)
if err != nil {
return nil, err
}
return ot, err
}
// OAuthTokenUpdateOptions represents the options for updating an OAuth token.
type OAuthTokenUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,oauth-tokens"`
// A private SSH key to be used for git clone operations.
PrivateSSHKey *string `jsonapi:"attr,ssh-key"`
}
// Update an existing OAuth token.
func (s *oAuthTokens) Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error) {
if !validStringID(&oAuthTokenID) {
return nil, errors.New("invalid value for OAuth token ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
ot := &OAuthToken{}
err = s.client.do(ctx, req, ot)
if err != nil {
return nil, err
}
return ot, err
}
// Delete an OAuth token by its ID.
func (s *oAuthTokens) Delete(ctx context.Context, oAuthTokenID string) error {
if !validStringID(&oAuthTokenID) {
return errors.New("invalid value for OAuth token ID")
}
u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

372
vendor/github.com/hashicorp/go-tfe/organization.go generated vendored Normal file
View File

@ -0,0 +1,372 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Organizations = (*organizations)(nil)
// Organizations describes all the organization related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/organizations.html
type Organizations interface {
// List all the organizations visible to the current user.
List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error)
// Create a new organization with the given options.
Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error)
// Read an organization by its name.
Read(ctx context.Context, organization string) (*Organization, error)
// Update attributes of an existing organization.
Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error)
// Delete an organization by its name.
Delete(ctx context.Context, organization string) error
// Capacity shows the current run capacity of an organization.
Capacity(ctx context.Context, organization string) (*Capacity, error)
// Entitlements shows the entitlements of an organization.
Entitlements(ctx context.Context, organization string) (*Entitlements, error)
// RunQueue shows the current run queue of an organization.
RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error)
}
// organizations implements Organizations.
type organizations struct {
client *Client
}
// AuthPolicyType represents an authentication policy type.
type AuthPolicyType string
// List of available authentication policies.
const (
AuthPolicyPassword AuthPolicyType = "password"
AuthPolicyTwoFactor AuthPolicyType = "two_factor_mandatory"
)
// EnterprisePlanType represents an enterprise plan type.
type EnterprisePlanType string
// List of available enterprise plan types.
const (
EnterprisePlanDisabled EnterprisePlanType = "disabled"
EnterprisePlanPremium EnterprisePlanType = "premium"
EnterprisePlanPro EnterprisePlanType = "pro"
EnterprisePlanTrial EnterprisePlanType = "trial"
)
// OrganizationList represents a list of organizations.
type OrganizationList struct {
*Pagination
Items []*Organization
}
// Organization represents a Terraform Enterprise organization.
type Organization struct {
Name string `jsonapi:"primary,organizations"`
CollaboratorAuthPolicy AuthPolicyType `jsonapi:"attr,collaborator-auth-policy"`
CostEstimationEnabled bool `jsonapi:"attr,cost-estimation-enabled"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Email string `jsonapi:"attr,email"`
EnterprisePlan EnterprisePlanType `jsonapi:"attr,enterprise-plan"`
ExternalID string `jsonapi:"attr,external-id"`
OwnersTeamSAMLRoleID string `jsonapi:"attr,owners-team-saml-role-id"`
Permissions *OrganizationPermissions `jsonapi:"attr,permissions"`
SAMLEnabled bool `jsonapi:"attr,saml-enabled"`
SessionRemember int `jsonapi:"attr,session-remember"`
SessionTimeout int `jsonapi:"attr,session-timeout"`
TrialExpiresAt time.Time `jsonapi:"attr,trial-expires-at,iso8601"`
TwoFactorConformant bool `jsonapi:"attr,two-factor-conformant"`
}
// Capacity represents the current run capacity of an organization.
type Capacity struct {
Organization string `jsonapi:"primary,organization-capacity"`
Pending int `jsonapi:"attr,pending"`
Running int `jsonapi:"attr,running"`
}
// Entitlements represents the entitlements of an organization.
type Entitlements struct {
ID string `jsonapi:"primary,entitlement-sets"`
Agents bool `jsonapi:"attr,agents"`
AuditLogging bool `jsonapi:"attr,audit-logging"`
CostEstimation bool `jsonapi:"attr,cost-estimation"`
Operations bool `jsonapi:"attr,operations"`
PrivateModuleRegistry bool `jsonapi:"attr,private-module-registry"`
SSO bool `jsonapi:"attr,sso"`
Sentinel bool `jsonapi:"attr,sentinel"`
StateStorage bool `jsonapi:"attr,state-storage"`
Teams bool `jsonapi:"attr,teams"`
VCSIntegrations bool `jsonapi:"attr,vcs-integrations"`
}
// RunQueue represents the current run queue of an organization.
type RunQueue struct {
*Pagination
Items []*Run
}
// OrganizationPermissions represents the organization permissions.
type OrganizationPermissions struct {
CanCreateTeam bool `json:"can-create-team"`
CanCreateWorkspace bool `json:"can-create-workspace"`
CanCreateWorkspaceMigration bool `json:"can-create-workspace-migration"`
CanDestroy bool `json:"can-destroy"`
CanTraverse bool `json:"can-traverse"`
CanUpdate bool `json:"can-update"`
CanUpdateAPIToken bool `json:"can-update-api-token"`
CanUpdateOAuth bool `json:"can-update-oauth"`
CanUpdateSentinel bool `json:"can-update-sentinel"`
}
// OrganizationListOptions represents the options for listing organizations.
type OrganizationListOptions struct {
ListOptions
}
// List all the organizations visible to the current user.
func (s *organizations) List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error) {
req, err := s.client.newRequest("GET", "organizations", &options)
if err != nil {
return nil, err
}
orgl := &OrganizationList{}
err = s.client.do(ctx, req, orgl)
if err != nil {
return nil, err
}
return orgl, nil
}
// OrganizationCreateOptions represents the options for creating an organization.
type OrganizationCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,organizations"`
// Name of the organization.
Name *string `jsonapi:"attr,name"`
// Admin email address.
Email *string `jsonapi:"attr,email"`
// Session expiration (minutes).
SessionRemember *int `jsonapi:"attr,session-remember,omitempty"`
// Session timeout after inactivity (minutes).
SessionTimeout *int `jsonapi:"attr,session-timeout,omitempty"`
// Authentication policy.
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
// Enable Cost Estimation
CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"`
// The name of the "owners" team
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
}
func (o OrganizationCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
if !validString(o.Email) {
return errors.New("email is required")
}
return nil
}
// Create a new organization with the given options.
func (s *organizations) Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error) {
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := s.client.newRequest("POST", "organizations", &options)
if err != nil {
return nil, err
}
org := &Organization{}
err = s.client.do(ctx, req, org)
if err != nil {
return nil, err
}
return org, nil
}
// Read an organization by its name.
func (s *organizations) Read(ctx context.Context, organization string) (*Organization, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
org := &Organization{}
err = s.client.do(ctx, req, org)
if err != nil {
return nil, err
}
return org, nil
}
// OrganizationUpdateOptions represents the options for updating an organization.
type OrganizationUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,organizations"`
// New name for the organization.
Name *string `jsonapi:"attr,name,omitempty"`
// New admin email address.
Email *string `jsonapi:"attr,email,omitempty"`
// Session expiration (minutes).
SessionRemember *int `jsonapi:"attr,session-remember,omitempty"`
// Session timeout after inactivity (minutes).
SessionTimeout *int `jsonapi:"attr,session-timeout,omitempty"`
// Authentication policy.
CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"`
// Enable Cost Estimation
CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"`
// The name of the "owners" team
OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"`
}
// Update attributes of an existing organization.
func (s *organizations) Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
org := &Organization{}
err = s.client.do(ctx, req, org)
if err != nil {
return nil, err
}
return org, nil
}
// Delete an organization by its name.
func (s *organizations) Delete(ctx context.Context, organization string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Capacity shows the currently used capacity of an organization.
func (s *organizations) Capacity(ctx context.Context, organization string) (*Capacity, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/capacity", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
c := &Capacity{}
err = s.client.do(ctx, req, c)
if err != nil {
return nil, err
}
return c, nil
}
// Entitlements shows the entitlements of an organization.
func (s *organizations) Entitlements(ctx context.Context, organization string) (*Entitlements, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/entitlement-set", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
e := &Entitlements{}
err = s.client.do(ctx, req, e)
if err != nil {
return nil, err
}
return e, nil
}
// RunQueueOptions represents the options for showing the queue.
type RunQueueOptions struct {
ListOptions
}
// RunQueue shows the current run queue of an organization.
func (s *organizations) RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/runs/queue", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
rq := &RunQueue{}
err = s.client.do(ctx, req, rq)
if err != nil {
return nil, err
}
return rq, nil
}

View File

@ -0,0 +1,178 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ OrganizationMemberships = (*organizationMemberships)(nil)
// OrganizationMemberships describes all the organization membership related methods that
// the Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/cloud/api/organization-memberships.html
type OrganizationMemberships interface {
// List all the organization memberships of the given organization.
List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error)
// Create a new organization membership with the given options.
Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error)
// Read an organization membership by ID
Read(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error)
// Read an organization membership by ID with options
ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error)
// Delete an organization membership by its ID.
Delete(ctx context.Context, organizationMembershipID string) error
}
// organizationMemberships implements OrganizationMemberships.
type organizationMemberships struct {
client *Client
}
// OrganizationMembershipStatus represents an organization membership status.
type OrganizationMembershipStatus string
// List all available organization membership statuses.
const (
OrganizationMembershipActive = "active"
OrganizationMembershipInvited = "invited"
)
// OrganizationMembershipList represents a list of organization memberships.
type OrganizationMembershipList struct {
*Pagination
Items []*OrganizationMembership
}
// OrganizationMembership represents a Terraform Enterprise organization membership.
type OrganizationMembership struct {
ID string `jsonapi:"primary,organization-memberships"`
Status OrganizationMembershipStatus `jsonapi:"attr,status"`
Email string `jsonapi:"attr,email"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
User *User `jsonapi:"relation,user"`
Teams []*Team `jsonapi:"relation,teams"`
}
// OrganizationMembershipListOptions represents the options for listing organization memberships.
type OrganizationMembershipListOptions struct {
ListOptions
Include string `url:"include"`
}
// List all the organization memberships of the given organization.
func (s *organizationMemberships) List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/organization-memberships", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
ml := &OrganizationMembershipList{}
err = s.client.do(ctx, req, ml)
if err != nil {
return nil, err
}
return ml, nil
}
// OrganizationMembershipCreateOptions represents the options for creating an organization membership.
type OrganizationMembershipCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,organization-memberships"`
// User's email address.
Email *string `jsonapi:"attr,email"`
}
func (o OrganizationMembershipCreateOptions) valid() error {
if o.Email == nil {
return errors.New("email is required")
}
return nil
}
// Create an organization membership with the given options.
func (s *organizationMemberships) Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
options.ID = ""
u := fmt.Sprintf("organizations/%s/organization-memberships", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
m := &OrganizationMembership{}
err = s.client.do(ctx, req, m)
if err != nil {
return nil, err
}
return m, nil
}
// Read an organization membership by its ID.
func (s *organizationMemberships) Read(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error) {
return s.ReadWithOptions(ctx, organizationMembershipID, OrganizationMembershipReadOptions{})
}
// OrganizationMembershipReadOptions represents the options for reading organization memberships.
type OrganizationMembershipReadOptions struct {
Include string `url:"include"`
}
// Read an organization membership by ID with options
func (s *organizationMemberships) ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error) {
if !validStringID(&organizationMembershipID) {
return nil, errors.New("invalid value for membership")
}
u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID))
req, err := s.client.newRequest("GET", u, &options)
mem := &OrganizationMembership{}
err = s.client.do(ctx, req, mem)
if err != nil {
return nil, err
}
return mem, nil
}
// Delete an organization membership by its ID.
func (s *organizationMemberships) Delete(ctx context.Context, organizationMembershipID string) error {
if !validStringID(&organizationMembershipID) {
return errors.New("invalid value for membership")
}
u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

View File

@ -0,0 +1,99 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ OrganizationTokens = (*organizationTokens)(nil)
// OrganizationTokens describes all the organization token related methods
// that the Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/organization-tokens.html
type OrganizationTokens interface {
// Generate a new organization token, replacing any existing token.
Generate(ctx context.Context, organization string) (*OrganizationToken, error)
// Read an organization token.
Read(ctx context.Context, organization string) (*OrganizationToken, error)
// Delete an organization token.
Delete(ctx context.Context, organization string) error
}
// organizationTokens implements OrganizationTokens.
type organizationTokens struct {
client *Client
}
// OrganizationToken represents a Terraform Enterprise organization token.
type OrganizationToken struct {
ID string `jsonapi:"primary,authentication-tokens"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
}
// Generate a new organization token, replacing any existing token.
func (s *organizationTokens) Generate(ctx context.Context, organization string) (*OrganizationToken, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
ot := &OrganizationToken{}
err = s.client.do(ctx, req, ot)
if err != nil {
return nil, err
}
return ot, err
}
// Read an organization token.
func (s *organizationTokens) Read(ctx context.Context, organization string) (*OrganizationToken, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
ot := &OrganizationToken{}
err = s.client.do(ctx, req, ot)
if err != nil {
return nil, err
}
return ot, err
}
// Delete an organization token.
func (s *organizationTokens) Delete(ctx context.Context, organization string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

136
vendor/github.com/hashicorp/go-tfe/plan.go generated vendored Normal file
View File

@ -0,0 +1,136 @@
package tfe
import (
"context"
"errors"
"fmt"
"io"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Plans = (*plans)(nil)
// Plans describes all the plan related methods that the Terraform Enterprise
// API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/plan.html
type Plans interface {
// Read a plan by its ID.
Read(ctx context.Context, planID string) (*Plan, error)
// Logs retrieves the logs of a plan.
Logs(ctx context.Context, planID string) (io.Reader, error)
}
// plans implements Plans.
type plans struct {
client *Client
}
// PlanStatus represents a plan state.
type PlanStatus string
//List all available plan statuses.
const (
PlanCanceled PlanStatus = "canceled"
PlanCreated PlanStatus = "created"
PlanErrored PlanStatus = "errored"
PlanFinished PlanStatus = "finished"
PlanMFAWaiting PlanStatus = "mfa_waiting"
PlanPending PlanStatus = "pending"
PlanQueued PlanStatus = "queued"
PlanRunning PlanStatus = "running"
PlanUnreachable PlanStatus = "unreachable"
)
// Plan represents a Terraform Enterprise plan.
type Plan struct {
ID string `jsonapi:"primary,plans"`
HasChanges bool `jsonapi:"attr,has-changes"`
LogReadURL string `jsonapi:"attr,log-read-url"`
ResourceAdditions int `jsonapi:"attr,resource-additions"`
ResourceChanges int `jsonapi:"attr,resource-changes"`
ResourceDestructions int `jsonapi:"attr,resource-destructions"`
Status PlanStatus `jsonapi:"attr,status"`
StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"`
// Relations
Exports []*PlanExport `jsonapi:"relation,exports"`
}
// PlanStatusTimestamps holds the timestamps for individual plan statuses.
type PlanStatusTimestamps struct {
CanceledAt time.Time `json:"canceled-at"`
ErroredAt time.Time `json:"errored-at"`
FinishedAt time.Time `json:"finished-at"`
ForceCanceledAt time.Time `json:"force-canceled-at"`
QueuedAt time.Time `json:"queued-at"`
StartedAt time.Time `json:"started-at"`
}
// Read a plan by its ID.
func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) {
if !validStringID(&planID) {
return nil, errors.New("invalid value for plan ID")
}
u := fmt.Sprintf("plans/%s", url.QueryEscape(planID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
p := &Plan{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, nil
}
// Logs retrieves the logs of a plan.
func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) {
if !validStringID(&planID) {
return nil, errors.New("invalid value for plan ID")
}
// Get the plan to make sure it exists.
p, err := s.Read(ctx, planID)
if err != nil {
return nil, err
}
// Return an error if the log URL is empty.
if p.LogReadURL == "" {
return nil, fmt.Errorf("plan %s does not have a log URL", planID)
}
u, err := url.Parse(p.LogReadURL)
if err != nil {
return nil, fmt.Errorf("invalid log URL: %v", err)
}
done := func() (bool, error) {
p, err := s.Read(ctx, p.ID)
if err != nil {
return false, err
}
switch p.Status {
case PlanCanceled, PlanErrored, PlanFinished, PlanUnreachable:
return true, nil
default:
return false, nil
}
}
return &LogReader{
client: s.client,
ctx: ctx,
done: done,
logURL: u,
}, nil
}

175
vendor/github.com/hashicorp/go-tfe/plan_export.go generated vendored Normal file
View File

@ -0,0 +1,175 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ PlanExports = (*planExports)(nil)
// PlanExports describes all the plan export related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/plan-exports.html
type PlanExports interface {
// Export a plan by its ID with the given options.
Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error)
// Read a plan export by its ID.
Read(ctx context.Context, planExportID string) (*PlanExport, error)
// Delete a plan export by its ID.
Delete(ctx context.Context, planExportID string) error
// Download the data of an plan export.
Download(ctx context.Context, planExportID string) ([]byte, error)
}
// planExports implements PlanExports.
type planExports struct {
client *Client
}
// PlanExportDataType represents the type of data exported from a plan.
type PlanExportDataType string
// List all available plan export data types.
const (
PlanExportSentinelMockBundleV0 PlanExportDataType = "sentinel-mock-bundle-v0"
)
// PlanExportStatus represents a plan export state.
type PlanExportStatus string
// List all available plan export statuses.
const (
PlanExportCanceled PlanExportStatus = "canceled"
PlanExportErrored PlanExportStatus = "errored"
PlanExportExpired PlanExportStatus = "expired"
PlanExportFinished PlanExportStatus = "finished"
PlanExportPending PlanExportStatus = "pending"
PlanExportQueued PlanExportStatus = "queued"
)
// PlanExportStatusTimestamps holds the timestamps for plan export statuses.
type PlanExportStatusTimestamps struct {
CanceledAt time.Time `json:"canceled-at"`
ErroredAt time.Time `json:"errored-at"`
ExpiredAt time.Time `json:"expired-at"`
FinishedAt time.Time `json:"finished-at"`
QueuedAt time.Time `json:"queued-at"`
}
// PlanExport represents an export of Terraform Enterprise plan data.
type PlanExport struct {
ID string `jsonapi:"primary,plan-exports"`
DataType PlanExportDataType `jsonapi:"attr,data-type"`
Status PlanExportStatus `jsonapi:"attr,status"`
StatusTimestamps *PlanExportStatusTimestamps `jsonapi:"attr,status-timestamps"`
}
// PlanExportCreateOptions represents the options for exporting data from a plan.
type PlanExportCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,plan-exports"`
// The plan to export.
Plan *Plan `jsonapi:"relation,plan"`
// The name of the policy set.
DataType *PlanExportDataType `jsonapi:"attr,data-type"`
}
func (o PlanExportCreateOptions) valid() error {
if o.Plan == nil {
return errors.New("plan is required")
}
if o.DataType == nil {
return errors.New("data type is required")
}
return nil
}
func (s *planExports) Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error) {
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := s.client.newRequest("POST", "plan-exports", &options)
if err != nil {
return nil, err
}
pe := &PlanExport{}
err = s.client.do(ctx, req, pe)
if err != nil {
return nil, err
}
return pe, err
}
// Read a plan export by its ID.
func (s *planExports) Read(ctx context.Context, planExportID string) (*PlanExport, error) {
if !validStringID(&planExportID) {
return nil, errors.New("invalid value for plan export ID")
}
u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
pe := &PlanExport{}
err = s.client.do(ctx, req, pe)
if err != nil {
return nil, err
}
return pe, nil
}
// Delete a plan export by ID.
func (s *planExports) Delete(ctx context.Context, planExportID string) error {
if !validStringID(&planExportID) {
return errors.New("invalid value for plan export ID")
}
u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Download a plan export's data. Data is exported in a .tar.gz format.
func (s *planExports) Download(ctx context.Context, planExportID string) ([]byte, error) {
if !validStringID(&planExportID) {
return nil, errors.New("invalid value for plan export ID")
}
u := fmt.Sprintf("plan-exports/%s/download", url.QueryEscape(planExportID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = s.client.do(ctx, req, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

286
vendor/github.com/hashicorp/go-tfe/policy.go generated vendored Normal file
View File

@ -0,0 +1,286 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Policies = (*policies)(nil)
// Policies describes all the policy related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/policies.html
type Policies interface {
// List all the policies for a given organization
List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error)
// Create a policy and associate it with an organization.
Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error)
// Read a policy by its ID.
Read(ctx context.Context, policyID string) (*Policy, error)
// Update an existing policy.
Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error)
// Delete a policy by its ID.
Delete(ctx context.Context, policyID string) error
// Upload the policy content of the policy.
Upload(ctx context.Context, policyID string, content []byte) error
// Upload the policy content of the policy.
Download(ctx context.Context, policyID string) ([]byte, error)
}
// policies implements Policies.
type policies struct {
client *Client
}
// EnforcementLevel represents an enforcement level.
type EnforcementLevel string
// List the available enforcement types.
const (
EnforcementAdvisory EnforcementLevel = "advisory"
EnforcementHard EnforcementLevel = "hard-mandatory"
EnforcementSoft EnforcementLevel = "soft-mandatory"
)
// PolicyList represents a list of policies..
type PolicyList struct {
*Pagination
Items []*Policy
}
// Policy represents a Terraform Enterprise policy.
type Policy struct {
ID string `jsonapi:"primary,policies"`
Name string `jsonapi:"attr,name"`
Description string `jsonapi:"attr,description"`
Enforce []*Enforcement `jsonapi:"attr,enforce"`
PolicySetCount int `jsonapi:"attr,policy-set-count"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
}
// Enforcement describes a enforcement.
type Enforcement struct {
Path string `json:"path"`
Mode EnforcementLevel `json:"mode"`
}
// PolicyListOptions represents the options for listing policies.
type PolicyListOptions struct {
ListOptions
// A search string (partial policy name) used to filter the results.
Search *string `url:"search[name],omitempty"`
}
// List all the policies for a given organization
func (s *policies) List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
pl := &PolicyList{}
err = s.client.do(ctx, req, pl)
if err != nil {
return nil, err
}
return pl, nil
}
// PolicyCreateOptions represents the options for creating a new policy.
type PolicyCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,policies"`
// The name of the policy.
Name *string `jsonapi:"attr,name"`
// A description of the policy's purpose.
Description *string `jsonapi:"attr,description,omitempty"`
// The enforcements of the policy.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce"`
}
// EnforcementOptions represents the enforcement options of a policy.
type EnforcementOptions struct {
Path *string `json:"path,omitempty"`
Mode *EnforcementLevel `json:"mode"`
}
func (o PolicyCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
if o.Enforce == nil {
return errors.New("enforce is required")
}
for _, e := range o.Enforce {
if !validString(e.Path) {
return errors.New("enforcement path is required")
}
if e.Mode == nil {
return errors.New("enforcement mode is required")
}
}
return nil
}
// Create a policy and associate it with an organization.
func (s *policies) Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
p := &Policy{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, err
}
// Read a policy by its ID.
func (s *policies) Read(ctx context.Context, policyID string) (*Policy, error) {
if !validStringID(&policyID) {
return nil, errors.New("invalid value for policy ID")
}
u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
p := &Policy{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, err
}
// PolicyUpdateOptions represents the options for updating a policy.
type PolicyUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,policies"`
// A description of the policy's purpose.
Description *string `jsonapi:"attr,description,omitempty"`
// The enforcements of the policy.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce,omitempty"`
}
// Update an existing policy.
func (s *policies) Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error) {
if !validStringID(&policyID) {
return nil, errors.New("invalid value for policy ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
p := &Policy{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, err
}
// Delete a policy by its ID.
func (s *policies) Delete(ctx context.Context, policyID string) error {
if !validStringID(&policyID) {
return errors.New("invalid value for policy ID")
}
u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Upload the policy content of the policy.
func (s *policies) Upload(ctx context.Context, policyID string, content []byte) error {
if !validStringID(&policyID) {
return errors.New("invalid value for policy ID")
}
u := fmt.Sprintf("policies/%s/upload", url.QueryEscape(policyID))
req, err := s.client.newRequest("PUT", u, content)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Download the policy content of the policy.
func (s *policies) Download(ctx context.Context, policyID string) ([]byte, error) {
if !validStringID(&policyID) {
return nil, errors.New("invalid value for policy ID")
}
u := fmt.Sprintf("policies/%s/download", url.QueryEscape(policyID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = s.client.do(ctx, req, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

222
vendor/github.com/hashicorp/go-tfe/policy_check.go generated vendored Normal file
View File

@ -0,0 +1,222 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ PolicyChecks = (*policyChecks)(nil)
// PolicyChecks describes all the policy check related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/policy-checks.html
type PolicyChecks interface {
// List all policy checks of the given run.
List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error)
// Read a policy check by its ID.
Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error)
// Override a soft-mandatory or warning policy.
Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error)
// Logs retrieves the logs of a policy check.
Logs(ctx context.Context, policyCheckID string) (io.Reader, error)
}
// policyChecks implements PolicyChecks.
type policyChecks struct {
client *Client
}
// PolicyScope represents a policy scope.
type PolicyScope string
// List all available policy scopes.
const (
PolicyScopeOrganization PolicyScope = "organization"
PolicyScopeWorkspace PolicyScope = "workspace"
)
// PolicyStatus represents a policy check state.
type PolicyStatus string
//List all available policy check statuses.
const (
PolicyCanceled PolicyStatus = "canceled"
PolicyErrored PolicyStatus = "errored"
PolicyHardFailed PolicyStatus = "hard_failed"
PolicyOverridden PolicyStatus = "overridden"
PolicyPasses PolicyStatus = "passed"
PolicyPending PolicyStatus = "pending"
PolicyQueued PolicyStatus = "queued"
PolicySoftFailed PolicyStatus = "soft_failed"
PolicyUnreachable PolicyStatus = "unreachable"
)
// PolicyCheckList represents a list of policy checks.
type PolicyCheckList struct {
*Pagination
Items []*PolicyCheck
}
// PolicyCheck represents a Terraform Enterprise policy check..
type PolicyCheck struct {
ID string `jsonapi:"primary,policy-checks"`
Actions *PolicyActions `jsonapi:"attr,actions"`
Permissions *PolicyPermissions `jsonapi:"attr,permissions"`
Result *PolicyResult `jsonapi:"attr,result"`
Scope PolicyScope `jsonapi:"attr,scope"`
Status PolicyStatus `jsonapi:"attr,status"`
StatusTimestamps *PolicyStatusTimestamps `jsonapi:"attr,status-timestamps"`
Run *Run `jsonapi:"relation,run"`
}
// PolicyActions represents the policy check actions.
type PolicyActions struct {
IsOverridable bool `json:"is-overridable"`
}
// PolicyPermissions represents the policy check permissions.
type PolicyPermissions struct {
CanOverride bool `json:"can-override"`
}
// PolicyResult represents the complete policy check result,
type PolicyResult struct {
AdvisoryFailed int `json:"advisory-failed"`
Duration int `json:"duration"`
HardFailed int `json:"hard-failed"`
Passed int `json:"passed"`
Result bool `json:"result"`
// Sentinel *sentinel.EvalResult `json:"sentinel"`
SoftFailed int `json:"soft-failed"`
TotalFailed int `json:"total-failed"`
}
// PolicyStatusTimestamps holds the timestamps for individual policy check
// statuses.
type PolicyStatusTimestamps struct {
ErroredAt time.Time `json:"errored-at"`
HardFailedAt time.Time `json:"hard-failed-at"`
PassedAt time.Time `json:"passed-at"`
QueuedAt time.Time `json:"queued-at"`
SoftFailedAt time.Time `json:"soft-failed-at"`
}
// PolicyCheckListOptions represents the options for listing policy checks.
type PolicyCheckListOptions struct {
ListOptions
}
// List all policy checks of the given run.
func (s *policyChecks) List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) {
if !validStringID(&runID) {
return nil, errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s/policy-checks", url.QueryEscape(runID))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
pcl := &PolicyCheckList{}
err = s.client.do(ctx, req, pcl)
if err != nil {
return nil, err
}
return pcl, nil
}
// Read a policy check by its ID.
func (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) {
if !validStringID(&policyCheckID) {
return nil, errors.New("invalid value for policy check ID")
}
u := fmt.Sprintf("policy-checks/%s", url.QueryEscape(policyCheckID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
pc := &PolicyCheck{}
err = s.client.do(ctx, req, pc)
if err != nil {
return nil, err
}
return pc, nil
}
// Override a soft-mandatory or warning policy.
func (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) {
if !validStringID(&policyCheckID) {
return nil, errors.New("invalid value for policy check ID")
}
u := fmt.Sprintf("policy-checks/%s/actions/override", url.QueryEscape(policyCheckID))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
pc := &PolicyCheck{}
err = s.client.do(ctx, req, pc)
if err != nil {
return nil, err
}
return pc, nil
}
// Logs retrieves the logs of a policy check.
func (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) {
if !validStringID(&policyCheckID) {
return nil, errors.New("invalid value for policy check ID")
}
// Loop until the context is canceled or the policy check is finished
// running. The policy check logs are not streamed and so only available
// once the check is finished.
for {
pc, err := s.Read(ctx, policyCheckID)
if err != nil {
return nil, err
}
switch pc.Status {
case PolicyPending, PolicyQueued:
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(500 * time.Millisecond):
continue
}
}
u := fmt.Sprintf("policy-checks/%s/output", url.QueryEscape(policyCheckID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
logs := bytes.NewBuffer(nil)
err = s.client.do(ctx, req, logs)
if err != nil {
return nil, err
}
return logs, nil
}
}

414
vendor/github.com/hashicorp/go-tfe/policy_set.go generated vendored Normal file
View File

@ -0,0 +1,414 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ PolicySets = (*policySets)(nil)
// PolicySets describes all the policy set related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/policies.html
type PolicySets interface {
// List all the policy sets for a given organization.
List(ctx context.Context, organization string, options PolicySetListOptions) (*PolicySetList, error)
// Create a policy set and associate it with an organization.
Create(ctx context.Context, organization string, options PolicySetCreateOptions) (*PolicySet, error)
// Read a policy set by its ID.
Read(ctx context.Context, policySetID string) (*PolicySet, error)
// Update an existing policy set.
Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error)
// Add policies to a policy set. This function can only be used when
// there is no VCS repository associated with the policy set.
AddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error
// Remove policies from a policy set. This function can only be used
// when there is no VCS repository associated with the policy set.
RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error
// Add workspaces to a policy set.
AddWorkspaces(ctx context.Context, policySetID string, options PolicySetAddWorkspacesOptions) error
// Remove workspaces from a policy set.
RemoveWorkspaces(ctx context.Context, policySetID string, options PolicySetRemoveWorkspacesOptions) error
// Delete a policy set by its ID.
Delete(ctx context.Context, policyID string) error
}
// policySets implements PolicySets.
type policySets struct {
client *Client
}
// PolicySetList represents a list of policy sets.
type PolicySetList struct {
*Pagination
Items []*PolicySet
}
// PolicySet represents a Terraform Enterprise policy set.
type PolicySet struct {
ID string `jsonapi:"primary,policy-sets"`
Name string `jsonapi:"attr,name"`
Description string `jsonapi:"attr,description"`
Global bool `jsonapi:"attr,global"`
PoliciesPath string `jsonapi:"attr,policies-path"`
PolicyCount int `jsonapi:"attr,policy-count"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkspaceCount int `jsonapi:"attr,workspace-count"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
Policies []*Policy `jsonapi:"relation,policies"`
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
}
// PolicySetListOptions represents the options for listing policy sets.
type PolicySetListOptions struct {
ListOptions
// A search string (partial policy set name) used to filter the results.
Search *string `url:"search[name],omitempty"`
}
// List all the policies for a given organization.
func (s *policySets) List(ctx context.Context, organization string, options PolicySetListOptions) (*PolicySetList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/policy-sets", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
psl := &PolicySetList{}
err = s.client.do(ctx, req, psl)
if err != nil {
return nil, err
}
return psl, nil
}
// PolicySetCreateOptions represents the options for creating a new policy set.
type PolicySetCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,policy-sets"`
// The name of the policy set.
Name *string `jsonapi:"attr,name"`
// The description of the policy set.
Description *string `jsonapi:"attr,description,omitempty"`
// Whether or not the policy set is global.
Global *bool `jsonapi:"attr,global,omitempty"`
// The sub-path within the attached VCS repository to ingress. All
// files and directories outside of this sub-path will be ignored.
// This option may only be specified when a VCS repo is present.
PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"`
// The initial members of the policy set.
Policies []*Policy `jsonapi:"relation,policies,omitempty"`
// VCS repository information. When present, the policies and
// configuration will be sourced from the specified VCS repository
// instead of being defined within the policy set itself. Note that
// this option is mutually exclusive with the Policies option and
// both cannot be used at the same time.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
// The initial list of workspaces for which the policy set should be enforced.
Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"`
}
func (o PolicySetCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
return nil
}
// Create a policy set and associate it with an organization.
func (s *policySets) Create(ctx context.Context, organization string, options PolicySetCreateOptions) (*PolicySet, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/policy-sets", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
ps := &PolicySet{}
err = s.client.do(ctx, req, ps)
if err != nil {
return nil, err
}
return ps, err
}
// Read a policy set by its ID.
func (s *policySets) Read(ctx context.Context, policySetID string) (*PolicySet, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
u := fmt.Sprintf("policy-sets/%s", url.QueryEscape(policySetID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
ps := &PolicySet{}
err = s.client.do(ctx, req, ps)
if err != nil {
return nil, err
}
return ps, err
}
// PolicySetUpdateOptions represents the options for updating a policy set.
type PolicySetUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,policy-sets"`
/// The name of the policy set.
Name *string `jsonapi:"attr,name,omitempty"`
// The description of the policy set.
Description *string `jsonapi:"attr,description,omitempty"`
// Whether or not the policy set is global.
Global *bool `jsonapi:"attr,global,omitempty"`
// The sub-path within the attached VCS repository to ingress. All
// files and directories outside of this sub-path will be ignored.
// This option may only be specified when a VCS repo is present.
PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"`
// VCS repository information. When present, the policies and
// configuration will be sourced from the specified VCS repository
// instead of being defined within the policy set itself. Note that
// specifying this option may only be used on policy sets with no
// directly-attached policies (*PolicySet.Policies). Specifying this
// option when policies are already present will result in an error.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
}
func (o PolicySetUpdateOptions) valid() error {
if o.Name != nil && !validStringID(o.Name) {
return errors.New("invalid value for name")
}
return nil
}
// Update an existing policy set.
func (s *policySets) Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("policy-sets/%s", url.QueryEscape(policySetID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
ps := &PolicySet{}
err = s.client.do(ctx, req, ps)
if err != nil {
return nil, err
}
return ps, err
}
// PolicySetAddPoliciesOptions represents the options for adding policies
// to a policy set.
type PolicySetAddPoliciesOptions struct {
/// The policies to add to the policy set.
Policies []*Policy
}
func (o PolicySetAddPoliciesOptions) valid() error {
if o.Policies == nil {
return errors.New("policies is required")
}
if len(o.Policies) == 0 {
return errors.New("must provide at least one policy")
}
return nil
}
// Add policies to a policy set
func (s *policySets) AddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return err
}
u := fmt.Sprintf("policy-sets/%s/relationships/policies", url.QueryEscape(policySetID))
req, err := s.client.newRequest("POST", u, options.Policies)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// PolicySetRemovePoliciesOptions represents the options for removing
// policies from a policy set.
type PolicySetRemovePoliciesOptions struct {
/// The policies to remove from the policy set.
Policies []*Policy
}
func (o PolicySetRemovePoliciesOptions) valid() error {
if o.Policies == nil {
return errors.New("policies is required")
}
if len(o.Policies) == 0 {
return errors.New("must provide at least one policy")
}
return nil
}
// Remove policies from a policy set
func (s *policySets) RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return err
}
u := fmt.Sprintf("policy-sets/%s/relationships/policies", url.QueryEscape(policySetID))
req, err := s.client.newRequest("DELETE", u, options.Policies)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// PolicySetAddWorkspacesOptions represents the options for adding workspaces
// to a policy set.
type PolicySetAddWorkspacesOptions struct {
/// The workspaces to add to the policy set.
Workspaces []*Workspace
}
func (o PolicySetAddWorkspacesOptions) valid() error {
if o.Workspaces == nil {
return errors.New("workspaces is required")
}
if len(o.Workspaces) == 0 {
return errors.New("must provide at least one workspace")
}
return nil
}
// Add workspaces to a policy set.
func (s *policySets) AddWorkspaces(ctx context.Context, policySetID string, options PolicySetAddWorkspacesOptions) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return err
}
u := fmt.Sprintf("policy-sets/%s/relationships/workspaces", url.QueryEscape(policySetID))
req, err := s.client.newRequest("POST", u, options.Workspaces)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// PolicySetRemoveWorkspacesOptions represents the options for removing
// workspaces from a policy set.
type PolicySetRemoveWorkspacesOptions struct {
/// The workspaces to remove from the policy set.
Workspaces []*Workspace
}
func (o PolicySetRemoveWorkspacesOptions) valid() error {
if o.Workspaces == nil {
return errors.New("workspaces is required")
}
if len(o.Workspaces) == 0 {
return errors.New("must provide at least one workspace")
}
return nil
}
// Remove workspaces from a policy set.
func (s *policySets) RemoveWorkspaces(ctx context.Context, policySetID string, options PolicySetRemoveWorkspacesOptions) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return err
}
u := fmt.Sprintf("policy-sets/%s/relationships/workspaces", url.QueryEscape(policySetID))
req, err := s.client.newRequest("DELETE", u, options.Workspaces)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// Delete a policy set by its ID.
func (s *policySets) Delete(ctx context.Context, policySetID string) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
u := fmt.Sprintf("policy-sets/%s", url.QueryEscape(policySetID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

View File

@ -0,0 +1,230 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ PolicySetParameters = (*policySetParameters)(nil)
// PolicySetParameters describes all the parameter related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/policy-set-params.html
type PolicySetParameters interface {
// List all the parameters associated with the given policy-set.
List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error)
// Create is used to create a new parameter.
Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error)
// Read a parameter by its ID.
Read(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error)
// Update values of an existing parameter.
Update(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error)
// Delete a parameter by its ID.
Delete(ctx context.Context, policySetID string, parameterID string) error
}
// policySetParameters implements Parameters.
type policySetParameters struct {
client *Client
}
// PolicySetParameterList represents a list of parameters.
type PolicySetParameterList struct {
*Pagination
Items []*PolicySetParameter
}
// PolicySetParameter represents a Policy Set parameter
type PolicySetParameter struct {
ID string `jsonapi:"primary,vars"`
Key string `jsonapi:"attr,key"`
Value string `jsonapi:"attr,value"`
Category CategoryType `jsonapi:"attr,category"`
Sensitive bool `jsonapi:"attr,sensitive"`
// Relations
PolicySet *PolicySet `jsonapi:"relation,configurable"`
}
// PolicySetParameterListOptions represents the options for listing parameters.
type PolicySetParameterListOptions struct {
ListOptions
}
func (o PolicySetParameterListOptions) valid() error {
return nil
}
// List all the parameters associated with the given policy-set.
func (s *policySetParameters) List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return nil, err
}
u := fmt.Sprintf("policy-sets/%s/parameters", policySetID)
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
vl := &PolicySetParameterList{}
err = s.client.do(ctx, req, vl)
if err != nil {
return nil, err
}
return vl, nil
}
// PolicySetParameterCreateOptions represents the options for creating a new parameter.
type PolicySetParameterCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,vars"`
// The name of the parameter.
Key *string `jsonapi:"attr,key"`
// The value of the parameter.
Value *string `jsonapi:"attr,value,omitempty"`
// The Category of the parameter, should always be "policy-set"
Category *CategoryType `jsonapi:"attr,category"`
// Whether the value is sensitive.
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
}
func (o PolicySetParameterCreateOptions) valid() error {
if !validString(o.Key) {
return errors.New("key is required")
}
if o.Category == nil {
return errors.New("category is required")
}
if *o.Category != CategoryPolicySet {
return errors.New("category must be policy-set")
}
return nil
}
// Create is used to create a new parameter.
func (s *policySetParameters) Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("policy-sets/%s/parameters", url.QueryEscape(policySetID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
p := &PolicySetParameter{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, nil
}
// Read a parameter by its ID.
func (s *policySetParameters) Read(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
if !validStringID(&parameterID) {
return nil, errors.New("invalid value for parameter ID")
}
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
p := &PolicySetParameter{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, err
}
// PolicySetParameterUpdateOptions represents the options for updating a parameter.
type PolicySetParameterUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,vars"`
// The name of the parameter.
Key *string `jsonapi:"attr,key,omitempty"`
// The value of the parameter.
Value *string `jsonapi:"attr,value,omitempty"`
// Whether the value is sensitive.
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
}
// Update values of an existing parameter.
func (s *policySetParameters) Update(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error) {
if !validStringID(&policySetID) {
return nil, errors.New("invalid value for policy set ID")
}
if !validStringID(&parameterID) {
return nil, errors.New("invalid value for parameter ID")
}
// Make sure we don't send a user provided ID.
options.ID = parameterID
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
p := &PolicySetParameter{}
err = s.client.do(ctx, req, p)
if err != nil {
return nil, err
}
return p, nil
}
// Delete a parameter by its ID.
func (s *policySetParameters) Delete(ctx context.Context, policySetID string, parameterID string) error {
if !validStringID(&policySetID) {
return errors.New("invalid value for policy set ID")
}
if !validStringID(&parameterID) {
return errors.New("invalid value for parameter ID")
}
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

417
vendor/github.com/hashicorp/go-tfe/registry_module.go generated vendored Normal file
View File

@ -0,0 +1,417 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ RegistryModules = (*registryModules)(nil)
// RegistryModules describes all the registry module related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/cloud/api/modules.html
type RegistryModules interface {
// Create a registry module without a VCS repo
Create(ctx context.Context, organization string, options RegistryModuleCreateOptions) (*RegistryModule, error)
// Create a registry module version
CreateVersion(ctx context.Context, organization string, name string, provider string, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error)
// Create and publish a registry module with a VCS repo
CreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error)
// Read a registry module
Read(ctx context.Context, organization string, name string, provider string) (*RegistryModule, error)
// Delete a registry module
Delete(ctx context.Context, organization string, name string) error
// Delete a specific registry module provider
DeleteProvider(ctx context.Context, organization string, name string, provider string) error
// Delete a specific registry module version
DeleteVersion(ctx context.Context, organization string, name string, provider string, version string) error
}
// registryModules implements RegistryModules.
type registryModules struct {
client *Client
}
// RegistryModuleStatus represents the status of the registry module
type RegistryModuleStatus string
// List of available registry module statuses
const (
RegistryModuleStatusPending RegistryModuleStatus = "pending"
RegistryModuleStatusNoVersionTags RegistryModuleStatus = "no_version_tags"
RegistryModuleStatusSetupFailed RegistryModuleStatus = "setup_failed"
RegistryModuleStatusSetupComplete RegistryModuleStatus = "setup_complete"
)
// RegistryModuleVersionStatus represents the status of a specific version of a registry module
type RegistryModuleVersionStatus string
// List of available registry module version statuses
const (
RegistryModuleVersionStatusPending RegistryModuleVersionStatus = "pending"
RegistryModuleVersionStatusCloning RegistryModuleVersionStatus = "cloning"
RegistryModuleVersionStatusCloneFailed RegistryModuleVersionStatus = "clone_failed"
RegistryModuleVersionStatusRegIngressReqFailed RegistryModuleVersionStatus = "reg_ingress_req_failed"
RegistryModuleVersionStatusRegIngressing RegistryModuleVersionStatus = "reg_ingressing"
RegistryModuleVersionStatusRegIngressFailed RegistryModuleVersionStatus = "reg_ingress_failed"
RegistryModuleVersionStatusOk RegistryModuleVersionStatus = "ok"
)
// RegistryModule represents a registry module
type RegistryModule struct {
ID string `jsonapi:"primary,registry-modules"`
Name string `jsonapi:"attr,name"`
Provider string `jsonapi:"attr,provider"`
Permissions *RegistryModulePermissions `jsonapi:"attr,permissions"`
Status RegistryModuleStatus `jsonapi:"attr,status"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
VersionStatuses []RegistryModuleVersionStatuses `jsonapi:"attr,version-statuses"`
CreatedAt string `jsonapi:"attr,created-at"`
UpdatedAt string `jsonapi:"attr,updated-at"`
// Relations
Organization *Organization `jsonapi:"relation,organization"`
}
// RegistryModuleVersion represents a registry module version
type RegistryModuleVersion struct {
ID string `jsonapi:"primary,registry-module-versions"`
Source string `jsonapi:"attr,source"`
Status RegistryModuleVersionStatus `jsonapi:"attr,status"`
Version string `jsonapi:"attr,version"`
CreatedAt string `jsonapi:"attr,created-at"`
UpdatedAt string `jsonapi:"attr,updated-at"`
// Relations
RegistryModule *RegistryModule `jsonapi:"relation,registry-module"`
}
type RegistryModulePermissions struct {
CanDelete bool `json:"can-delete"`
CanResync bool `json:"can-resync"`
CanRetry bool `json:"can-retry"`
}
type RegistryModuleVersionStatuses struct {
Version string `json:"version"`
Status RegistryModuleVersionStatus `json:"status"`
Error string `json:"error"`
}
// RegistryModuleCreateOptions is used when creating a registry module without a VCS repo
type RegistryModuleCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,registry-modules"`
Name *string `jsonapi:"attr,name"`
Provider *string `jsonapi:"attr,provider"`
}
func (o RegistryModuleCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
if !validString(o.Provider) {
return errors.New("provider is required")
}
if !validStringID(o.Provider) {
return errors.New("invalid value for provider")
}
return nil
}
// Create a new registry module without a VCS repo
func (r *registryModules) Create(ctx context.Context, organization string, options RegistryModuleCreateOptions) (*RegistryModule, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf(
"organizations/%s/registry-modules",
url.QueryEscape(organization),
)
req, err := r.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
rm := &RegistryModule{}
err = r.client.do(ctx, req, rm)
if err != nil {
return nil, err
}
return rm, nil
}
// RegistryModuleCreateVersionOptions is used when creating a registry module version
type RegistryModuleCreateVersionOptions struct {
// For internal use only!
ID string `jsonapi:"primary,registry-module-versions"`
Version *string `jsonapi:"attr,version"`
}
func (o RegistryModuleCreateVersionOptions) valid() error {
if !validString(o.Version) {
return errors.New("version is required")
}
if !validStringID(o.Version) {
return errors.New("invalid value for version")
}
return nil
}
// Create a new registry module version
func (r *registryModules) CreateVersion(ctx context.Context, organization string, name string, provider string, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validString(&name) {
return nil, errors.New("name is required")
}
if !validStringID(&name) {
return nil, errors.New("invalid value for name")
}
if !validString(&provider) {
return nil, errors.New("provider is required")
}
if !validStringID(&provider) {
return nil, errors.New("invalid value for provider")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf(
"registry-modules/%s/%s/%s/versions",
url.QueryEscape(organization),
url.QueryEscape(name),
url.QueryEscape(provider),
)
req, err := r.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
rmv := &RegistryModuleVersion{}
err = r.client.do(ctx, req, rmv)
if err != nil {
return nil, err
}
return rmv, nil
}
// RegistryModuleCreateWithVCSConnectionOptions is used when creating a registry module with a VCS repo
type RegistryModuleCreateWithVCSConnectionOptions struct {
ID string `jsonapi:"primary,registry-modules"`
// VCS repository information
VCSRepo *RegistryModuleVCSRepoOptions `jsonapi:"attr,vcs-repo"`
}
func (o RegistryModuleCreateWithVCSConnectionOptions) valid() error {
if o.VCSRepo == nil {
return errors.New("vcs repo is required")
}
return o.VCSRepo.valid()
}
type RegistryModuleVCSRepoOptions struct {
Identifier *string `json:"identifier"`
OAuthTokenID *string `json:"oauth-token-id"`
DisplayIdentifier *string `json:"display-identifier"`
}
func (o RegistryModuleVCSRepoOptions) valid() error {
if !validString(o.Identifier) {
return errors.New("identifier is required")
}
if !validString(o.OAuthTokenID) {
return errors.New("oauth token ID is required")
}
if !validString(o.DisplayIdentifier) {
return errors.New("display identifier is required")
}
return nil
}
// CreateWithVCSConnection is used to create and publish a new registry module with a VCS repo
func (r *registryModules) CreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error) {
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := r.client.newRequest("POST", "registry-modules", &options)
if err != nil {
return nil, err
}
rm := &RegistryModule{}
err = r.client.do(ctx, req, rm)
if err != nil {
return nil, err
}
return rm, nil
}
// Read a specific registry module
func (r *registryModules) Read(ctx context.Context, organization string, name string, provider string) (*RegistryModule, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validString(&name) {
return nil, errors.New("name is required")
}
if !validStringID(&name) {
return nil, errors.New("invalid value for name")
}
if !validString(&provider) {
return nil, errors.New("provider is required")
}
if !validStringID(&provider) {
return nil, errors.New("invalid value for provider")
}
u := fmt.Sprintf(
"registry-modules/show/%s/%s/%s",
url.QueryEscape(organization),
url.QueryEscape(name),
url.QueryEscape(provider),
)
req, err := r.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
rm := &RegistryModule{}
err = r.client.do(ctx, req, rm)
if err != nil {
return nil, err
}
return rm, nil
}
// Delete is used to delete the entire registry module
func (r *registryModules) Delete(ctx context.Context, organization string, name string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
if !validString(&name) {
return errors.New("name is required")
}
if !validStringID(&name) {
return errors.New("invalid value for name")
}
u := fmt.Sprintf(
"registry-modules/actions/delete/%s/%s",
url.QueryEscape(organization),
url.QueryEscape(name),
)
req, err := r.client.newRequest("POST", u, nil)
if err != nil {
return err
}
return r.client.do(ctx, req, nil)
}
// DeleteProvider is used to delete the specific registry module provider
func (r *registryModules) DeleteProvider(ctx context.Context, organization string, name string, provider string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
if !validString(&name) {
return errors.New("name is required")
}
if !validStringID(&name) {
return errors.New("invalid value for name")
}
if !validString(&provider) {
return errors.New("provider is required")
}
if !validStringID(&provider) {
return errors.New("invalid value for provider")
}
u := fmt.Sprintf(
"registry-modules/actions/delete/%s/%s/%s",
url.QueryEscape(organization),
url.QueryEscape(name),
url.QueryEscape(provider),
)
req, err := r.client.newRequest("POST", u, nil)
if err != nil {
return err
}
return r.client.do(ctx, req, nil)
}
// DeleteVersion is used to delete the specific registry module version
func (r *registryModules) DeleteVersion(ctx context.Context, organization string, name string, provider string, version string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
if !validString(&name) {
return errors.New("name is required")
}
if !validStringID(&name) {
return errors.New("invalid value for name")
}
if !validString(&provider) {
return errors.New("provider is required")
}
if !validStringID(&provider) {
return errors.New("invalid value for provider")
}
if !validString(&version) {
return errors.New("version is required")
}
if !validStringID(&version) {
return errors.New("invalid value for version")
}
u := fmt.Sprintf(
"registry-modules/actions/delete/%s/%s/%s/%s",
url.QueryEscape(organization),
url.QueryEscape(name),
url.QueryEscape(provider),
url.QueryEscape(version),
)
req, err := r.client.newRequest("POST", u, nil)
if err != nil {
return err
}
return r.client.do(ctx, req, nil)
}

354
vendor/github.com/hashicorp/go-tfe/run.go generated vendored Normal file
View File

@ -0,0 +1,354 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Runs = (*runs)(nil)
// Runs describes all the run related methods that the Terraform Enterprise
// API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/run.html
type Runs interface {
// List all the runs of the given workspace.
List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error)
// Create a new run with the given options.
Create(ctx context.Context, options RunCreateOptions) (*Run, error)
// Read a run by its ID.
Read(ctx context.Context, runID string) (*Run, error)
// ReadWithOptions reads a run by its ID using the options supplied
ReadWithOptions(ctx context.Context, runID string, options *RunReadOptions) (*Run, error)
// Apply a run by its ID.
Apply(ctx context.Context, runID string, options RunApplyOptions) error
// Cancel a run by its ID.
Cancel(ctx context.Context, runID string, options RunCancelOptions) error
// Force-cancel a run by its ID.
ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error
// Discard a run by its ID.
Discard(ctx context.Context, runID string, options RunDiscardOptions) error
}
// runs implements Runs.
type runs struct {
client *Client
}
// RunStatus represents a run state.
type RunStatus string
//List all available run statuses.
const (
RunApplied RunStatus = "applied"
RunApplyQueued RunStatus = "apply_queued"
RunApplying RunStatus = "applying"
RunCanceled RunStatus = "canceled"
RunConfirmed RunStatus = "confirmed"
RunCostEstimated RunStatus = "cost_estimated"
RunCostEstimating RunStatus = "cost_estimating"
RunDiscarded RunStatus = "discarded"
RunErrored RunStatus = "errored"
RunPending RunStatus = "pending"
RunPlanQueued RunStatus = "plan_queued"
RunPlanned RunStatus = "planned"
RunPlannedAndFinished RunStatus = "planned_and_finished"
RunPlanning RunStatus = "planning"
RunPolicyChecked RunStatus = "policy_checked"
RunPolicyChecking RunStatus = "policy_checking"
RunPolicyOverride RunStatus = "policy_override"
RunPolicySoftFailed RunStatus = "policy_soft_failed"
)
// RunSource represents a source type of a run.
type RunSource string
// List all available run sources.
const (
RunSourceAPI RunSource = "tfe-api"
RunSourceConfigurationVersion RunSource = "tfe-configuration-version"
RunSourceUI RunSource = "tfe-ui"
)
// RunList represents a list of runs.
type RunList struct {
*Pagination
Items []*Run
}
// Run represents a Terraform Enterprise run.
type Run struct {
ID string `jsonapi:"primary,runs"`
Actions *RunActions `jsonapi:"attr,actions"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
ForceCancelAvailableAt time.Time `jsonapi:"attr,force-cancel-available-at,iso8601"`
HasChanges bool `jsonapi:"attr,has-changes"`
IsDestroy bool `jsonapi:"attr,is-destroy"`
Message string `jsonapi:"attr,message"`
Permissions *RunPermissions `jsonapi:"attr,permissions"`
PositionInQueue int `jsonapi:"attr,position-in-queue"`
Source RunSource `jsonapi:"attr,source"`
Status RunStatus `jsonapi:"attr,status"`
StatusTimestamps *RunStatusTimestamps `jsonapi:"attr,status-timestamps"`
TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"`
// Relations
Apply *Apply `jsonapi:"relation,apply"`
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
CostEstimate *CostEstimate `jsonapi:"relation,cost-estimate"`
CreatedBy *User `jsonapi:"relation,created-by"`
Plan *Plan `jsonapi:"relation,plan"`
PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"`
Workspace *Workspace `jsonapi:"relation,workspace"`
}
// RunActions represents the run actions.
type RunActions struct {
IsCancelable bool `json:"is-cancelable"`
IsConfirmable bool `json:"is-confirmable"`
IsDiscardable bool `json:"is-discardable"`
IsForceCancelable bool `json:"is-force-cancelable"`
}
// RunPermissions represents the run permissions.
type RunPermissions struct {
CanApply bool `json:"can-apply"`
CanCancel bool `json:"can-cancel"`
CanDiscard bool `json:"can-discard"`
CanForceCancel bool `json:"can-force-cancel"`
CanForceExecute bool `json:"can-force-execute"`
}
// RunStatusTimestamps holds the timestamps for individual run statuses.
type RunStatusTimestamps struct {
ErroredAt time.Time `json:"errored-at"`
FinishedAt time.Time `json:"finished-at"`
QueuedAt time.Time `json:"queued-at"`
StartedAt time.Time `json:"started-at"`
ApplyingAt time.Time `json:"applying-at"`
AppliedAt time.Time `json:"applied-at"`
PlanningAt time.Time `json:"planning-at"`
PlannedAt time.Time `json:"planned-at"`
PlannedAndFinishedAt time.Time `json:"planned-and-finished-at"`
PlanQueuabledAt time.Time `json:"plan-queueable-at"`
}
// RunListOptions represents the options for listing runs.
type RunListOptions struct {
ListOptions
// A list of relations to include. See available resources:
// https://www.terraform.io/docs/cloud/api/run.html#available-related-resources
Include *string `url:"include"`
}
// List all the runs of the given workspace.
func (s *runs) List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/runs", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
rl := &RunList{}
err = s.client.do(ctx, req, rl)
if err != nil {
return nil, err
}
return rl, nil
}
// RunCreateOptions represents the options for creating a new run.
type RunCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,runs"`
// Specifies if this plan is a destroy plan, which will destroy all
// provisioned resources.
IsDestroy *bool `jsonapi:"attr,is-destroy,omitempty"`
// Specifies the message to be associated with this run.
Message *string `jsonapi:"attr,message,omitempty"`
// Specifies the configuration version to use for this run. If the
// configuration version object is omitted, the run will be created using the
// workspace's latest configuration version.
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
// Specifies the workspace where the run will be executed.
Workspace *Workspace `jsonapi:"relation,workspace"`
// If non-empty, requests that Terraform should create a plan including
// actions only for the given objects (specified using resource address
// syntax) and the objects they depend on.
//
// This capability is provided for exceptional circumstances only, such as
// recovering from mistakes or working around existing Terraform
// limitations. Terraform will generally mention the -target command line
// option in its error messages describing situations where setting this
// argument may be appropriate. This argument should not be used as part
// of routine workflow and Terraform will emit warnings reminding about
// this whenever this property is set.
TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"`
}
func (o RunCreateOptions) valid() error {
if o.Workspace == nil {
return errors.New("workspace is required")
}
return nil
}
// Create a new run with the given options.
func (s *runs) Create(ctx context.Context, options RunCreateOptions) (*Run, error) {
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := s.client.newRequest("POST", "runs", &options)
if err != nil {
return nil, err
}
r := &Run{}
err = s.client.do(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// Read a run by its ID.
func (s *runs) Read(ctx context.Context, runID string) (*Run, error) {
return s.ReadWithOptions(ctx, runID, nil)
}
// RunReadOptions represents the options for reading a run.
type RunReadOptions struct {
Include string `url:"include"`
}
// Read a run by its ID with the given options.
func (s *runs) ReadWithOptions(ctx context.Context, runID string, options *RunReadOptions) (*Run, error) {
if !validStringID(&runID) {
return nil, errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s", url.QueryEscape(runID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
r := &Run{}
err = s.client.do(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// RunApplyOptions represents the options for applying a run.
type RunApplyOptions struct {
// An optional comment about the run.
Comment *string `json:"comment,omitempty"`
}
// Apply a run by its ID.
func (s *runs) Apply(ctx context.Context, runID string, options RunApplyOptions) error {
if !validStringID(&runID) {
return errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s/actions/apply", url.QueryEscape(runID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// RunCancelOptions represents the options for canceling a run.
type RunCancelOptions struct {
// An optional explanation for why the run was canceled.
Comment *string `json:"comment,omitempty"`
}
// Cancel a run by its ID.
func (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOptions) error {
if !validStringID(&runID) {
return errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s/actions/cancel", url.QueryEscape(runID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// RunForceCancelOptions represents the options for force-canceling a run.
type RunForceCancelOptions struct {
// An optional comment explaining the reason for the force-cancel.
Comment *string `json:"comment,omitempty"`
}
// ForceCancel is used to forcefully cancel a run by its ID.
func (s *runs) ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error {
if !validStringID(&runID) {
return errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s/actions/force-cancel", url.QueryEscape(runID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// RunDiscardOptions represents the options for discarding a run.
type RunDiscardOptions struct {
// An optional explanation for why the run was discarded.
Comment *string `json:"comment,omitempty"`
}
// Discard a run by its ID.
func (s *runs) Discard(ctx context.Context, runID string, options RunDiscardOptions) error {
if !validStringID(&runID) {
return errors.New("invalid value for run ID")
}
u := fmt.Sprintf("runs/%s/actions/discard", url.QueryEscape(runID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

177
vendor/github.com/hashicorp/go-tfe/run_trigger.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ RunTriggers = (*runTriggers)(nil)
// RunTriggers describes all the Run Trigger
// related methods that the Terraform Cloud API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/cloud/api/run-triggers.html
type RunTriggers interface {
// List all the run triggers within a workspace.
List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error)
// Create a new run trigger with the given options.
Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error)
// Read a run trigger by its ID.
Read(ctx context.Context, RunTriggerID string) (*RunTrigger, error)
// Delete a run trigger by its ID.
Delete(ctx context.Context, RunTriggerID string) error
}
// runTriggers implements RunTriggers.
type runTriggers struct {
client *Client
}
// RunTriggerList represents a list of Run Triggers
type RunTriggerList struct {
*Pagination
Items []*RunTrigger
}
// RunTrigger represents a run trigger.
type RunTrigger struct {
ID string `jsonapi:"primary,run-triggers"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
SourceableName string `jsonapi:"attr,sourceable-name"`
WorkspaceName string `jsonapi:"attr,workspace-name"`
// Relations
// TODO: this will eventually need to be polymorphic
Sourceable *Workspace `jsonapi:"relation,sourceable"`
Workspace *Workspace `jsonapi:"relation,workspace"`
}
// RunTriggerListOptions represents the options for listing
// run triggers.
type RunTriggerListOptions struct {
ListOptions
RunTriggerType *string `url:"filter[run-trigger][type]"`
}
func (o RunTriggerListOptions) valid() error {
if !validString(o.RunTriggerType) {
return errors.New("run-trigger type is required")
}
if *o.RunTriggerType != "inbound" && *o.RunTriggerType != "outbound" {
return errors.New("invalid value for run-trigger type")
}
return nil
}
// List all the run triggers associated with a workspace.
func (s *runTriggers) List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
rtl := &RunTriggerList{}
err = s.client.do(ctx, req, rtl)
if err != nil {
return nil, err
}
return rtl, nil
}
// RunTriggerCreateOptions represents the options for
// creating a new run trigger.
type RunTriggerCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,run-triggers"`
// The source workspace
Sourceable *Workspace `jsonapi:"relation,sourceable"`
}
func (o RunTriggerCreateOptions) valid() error {
if o.Sourceable == nil {
return errors.New("sourceable is required")
}
return nil
}
// Creates a run trigger with the given options.
func (s *runTriggers) Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
rt := &RunTrigger{}
err = s.client.do(ctx, req, rt)
if err != nil {
return nil, err
}
return rt, nil
}
// Read a run trigger by its ID.
func (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigger, error) {
if !validStringID(&runTriggerID) {
return nil, errors.New("invalid value for run trigger ID")
}
u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
rt := &RunTrigger{}
err = s.client.do(ctx, req, rt)
if err != nil {
return nil, err
}
return rt, nil
}
// Delete a run trigger by its ID.
func (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error {
if !validStringID(&runTriggerID) {
return errors.New("invalid value for run trigger ID")
}
u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

198
vendor/github.com/hashicorp/go-tfe/ssh_key.go generated vendored Normal file
View File

@ -0,0 +1,198 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ SSHKeys = (*sshKeys)(nil)
// SSHKeys describes all the SSH key related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/ssh-keys.html
type SSHKeys interface {
// List all the SSH keys for a given organization
List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error)
// Create an SSH key and associate it with an organization.
Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error)
// Read an SSH key by its ID.
Read(ctx context.Context, sshKeyID string) (*SSHKey, error)
// Update an SSH key by its ID.
Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error)
// Delete an SSH key by its ID.
Delete(ctx context.Context, sshKeyID string) error
}
// sshKeys implements SSHKeys.
type sshKeys struct {
client *Client
}
// SSHKeyList represents a list of SSH keys.
type SSHKeyList struct {
*Pagination
Items []*SSHKey
}
// SSHKey represents a SSH key.
type SSHKey struct {
ID string `jsonapi:"primary,ssh-keys"`
Name string `jsonapi:"attr,name"`
}
// SSHKeyListOptions represents the options for listing SSH keys.
type SSHKeyListOptions struct {
ListOptions
}
// List all the SSH keys for a given organization
func (s *sshKeys) List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
kl := &SSHKeyList{}
err = s.client.do(ctx, req, kl)
if err != nil {
return nil, err
}
return kl, nil
}
// SSHKeyCreateOptions represents the options for creating an SSH key.
type SSHKeyCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,ssh-keys"`
// A name to identify the SSH key.
Name *string `jsonapi:"attr,name"`
// The content of the SSH private key.
Value *string `jsonapi:"attr,value"`
}
func (o SSHKeyCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validString(o.Value) {
return errors.New("value is required")
}
return nil
}
// Create an SSH key and associate it with an organization.
func (s *sshKeys) Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
k := &SSHKey{}
err = s.client.do(ctx, req, k)
if err != nil {
return nil, err
}
return k, nil
}
// Read an SSH key by its ID.
func (s *sshKeys) Read(ctx context.Context, sshKeyID string) (*SSHKey, error) {
if !validStringID(&sshKeyID) {
return nil, errors.New("invalid value for SSH key ID")
}
u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
k := &SSHKey{}
err = s.client.do(ctx, req, k)
if err != nil {
return nil, err
}
return k, nil
}
// SSHKeyUpdateOptions represents the options for updating an SSH key.
type SSHKeyUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,ssh-keys"`
// A new name to identify the SSH key.
Name *string `jsonapi:"attr,name,omitempty"`
// Updated content of the SSH private key.
Value *string `jsonapi:"attr,value,omitempty"`
}
// Update an SSH key by its ID.
func (s *sshKeys) Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error) {
if !validStringID(&sshKeyID) {
return nil, errors.New("invalid value for SSH key ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
k := &SSHKey{}
err = s.client.do(ctx, req, k)
if err != nil {
return nil, err
}
return k, nil
}
// Delete an SSH key by its ID.
func (s *sshKeys) Delete(ctx context.Context, sshKeyID string) error {
if !validStringID(&sshKeyID) {
return errors.New("invalid value for SSH key ID")
}
u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

247
vendor/github.com/hashicorp/go-tfe/state_version.go generated vendored Normal file
View File

@ -0,0 +1,247 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ StateVersions = (*stateVersions)(nil)
// StateVersions describes all the state version related methods that
// the Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/state-versions.html
type StateVersions interface {
// List all the state versions for a given workspace.
List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error)
// Create a new state version for the given workspace.
Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error)
// Read a state version by its ID.
Read(ctx context.Context, svID string) (*StateVersion, error)
// ReadWithOptions reads a state version by its ID using the options supplied
ReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error)
// Current reads the latest available state from the given workspace.
Current(ctx context.Context, workspaceID string) (*StateVersion, error)
// CurrentWithOptions reads the latest available state from the given workspace using the options supplied
CurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error)
// Download retrieves the actual stored state of a state version
Download(ctx context.Context, url string) ([]byte, error)
}
// stateVersions implements StateVersions.
type stateVersions struct {
client *Client
}
// StateVersionList represents a list of state versions.
type StateVersionList struct {
*Pagination
Items []*StateVersion
}
// StateVersion represents a Terraform Enterprise state version.
type StateVersion struct {
ID string `jsonapi:"primary,state-versions"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
DownloadURL string `jsonapi:"attr,hosted-state-download-url"`
Serial int64 `jsonapi:"attr,serial"`
VCSCommitSHA string `jsonapi:"attr,vcs-commit-sha"`
VCSCommitURL string `jsonapi:"attr,vcs-commit-url"`
// Relations
Run *Run `jsonapi:"relation,run"`
Outputs []*StateVersionOutput `jsonapi:"relation,outputs"`
}
// StateVersionListOptions represents the options for listing state versions.
type StateVersionListOptions struct {
ListOptions
Organization *string `url:"filter[organization][name]"`
Workspace *string `url:"filter[workspace][name]"`
}
func (o StateVersionListOptions) valid() error {
if !validString(o.Organization) {
return errors.New("organization is required")
}
if !validString(o.Workspace) {
return errors.New("workspace is required")
}
return nil
}
// List all the state versions for a given workspace.
func (s *stateVersions) List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error) {
if err := options.valid(); err != nil {
return nil, err
}
req, err := s.client.newRequest("GET", "state-versions", &options)
if err != nil {
return nil, err
}
svl := &StateVersionList{}
err = s.client.do(ctx, req, svl)
if err != nil {
return nil, err
}
return svl, nil
}
// StateVersionCreateOptions represents the options for creating a state version.
type StateVersionCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,state-versions"`
// The lineage of the state.
Lineage *string `jsonapi:"attr,lineage,omitempty"`
// The MD5 hash of the state version.
MD5 *string `jsonapi:"attr,md5"`
// The serial of the state.
Serial *int64 `jsonapi:"attr,serial"`
// The base64 encoded state.
State *string `jsonapi:"attr,state"`
// Force can be set to skip certain validations. Wrong use
// of this flag can cause data loss, so USE WITH CAUTION!
Force *bool `jsonapi:"attr,force"`
// Specifies the run to associate the state with.
Run *Run `jsonapi:"relation,run,omitempty"`
}
func (o StateVersionCreateOptions) valid() error {
if !validString(o.MD5) {
return errors.New("MD5 is required")
}
if o.Serial == nil {
return errors.New("serial is required")
}
if !validString(o.State) {
return errors.New("state is required")
}
return nil
}
// Create a new state version for the given workspace.
func (s *stateVersions) Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/state-versions", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
sv := &StateVersion{}
err = s.client.do(ctx, req, sv)
if err != nil {
return nil, err
}
return sv, nil
}
// StateVersionReadOptions represents the options for reading state version.
type StateVersionReadOptions struct {
Include string `url:"include"`
}
// Read a state version by its ID.
func (s *stateVersions) ReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error) {
if !validStringID(&svID) {
return nil, errors.New("invalid value for state version ID")
}
u := fmt.Sprintf("state-versions/%s", url.QueryEscape(svID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
sv := &StateVersion{}
err = s.client.do(ctx, req, sv)
if err != nil {
return nil, err
}
return sv, nil
}
// Read a state version by its ID.
func (s *stateVersions) Read(ctx context.Context, svID string) (*StateVersion, error) {
return s.ReadWithOptions(ctx, svID, nil)
}
// StateVersionCurrentOptions represents the options for reading the current state version.
type StateVersionCurrentOptions struct {
Include string `url:"include"`
}
// CurrentWithOptions reads the latest available state from the given workspace using the options supplied.
func (s *stateVersions) CurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/current-state-version", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
sv := &StateVersion{}
err = s.client.do(ctx, req, sv)
if err != nil {
return nil, err
}
return sv, nil
}
// Current reads the latest available state from the given workspace.
func (s *stateVersions) Current(ctx context.Context, workspaceID string) (*StateVersion, error) {
return s.CurrentWithOptions(ctx, workspaceID, nil)
}
// Download retrieves the actual stored state of a state version
func (s *stateVersions) Download(ctx context.Context, url string) ([]byte, error) {
req, err := s.client.newRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
var buf bytes.Buffer
err = s.client.do(ctx, req, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@ -0,0 +1,52 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ StateVersionOutputs = (*stateVersionOutputs)(nil)
//State version outputs are the output values from a Terraform state file.
//They include the name and value of the output, as well as a sensitive boolean
//if the value should be hidden by default in UIs.
//
// TFE API docs: https://www.terraform.io/docs/cloud/api/state-version-outputs.html
type StateVersionOutputs interface {
Read(ctx context.Context, outputID string) (*StateVersionOutput, error)
}
type stateVersionOutputs struct {
client *Client
}
type StateVersionOutput struct {
ID string `jsonapi:"primary,state-version-outputs"`
Name string `jsonapi:"attr,name"`
Sensitive bool `jsonapi:"attr,sensitive"`
Type string `jsonapi:"attr,type"`
Value string `jsonapi:"attr,value"`
}
func (s *stateVersionOutputs) Read(ctx context.Context, outputID string) (*StateVersionOutput, error) {
if !validStringID(&outputID) {
return nil, errors.New("invalid value for run ID")
}
u := fmt.Sprintf("state-version-outputs/%s", url.QueryEscape(outputID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
so := &StateVersionOutput{}
err = s.client.do(ctx, req, so)
if err != nil {
return nil, err
}
return so, nil
}

229
vendor/github.com/hashicorp/go-tfe/team.go generated vendored Normal file
View File

@ -0,0 +1,229 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ Teams = (*teams)(nil)
// Teams describes all the team related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/teams.html
type Teams interface {
// List all the teams of the given organization.
List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error)
// Create a new team with the given options.
Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error)
// Read a team by its ID.
Read(ctx context.Context, teamID string) (*Team, error)
// Update a team by its ID.
Update(ctx context.Context, teamID string, options TeamUpdateOptions) (*Team, error)
// Delete a team by its ID.
Delete(ctx context.Context, teamID string) error
}
// teams implements Teams.
type teams struct {
client *Client
}
// TeamList represents a list of teams.
type TeamList struct {
*Pagination
Items []*Team
}
// Team represents a Terraform Enterprise team.
type Team struct {
ID string `jsonapi:"primary,teams"`
Name string `jsonapi:"attr,name"`
OrganizationAccess *OrganizationAccess `jsonapi:"attr,organization-access"`
Visibility string `jsonapi:"attr,visibility"`
Permissions *TeamPermissions `jsonapi:"attr,permissions"`
UserCount int `jsonapi:"attr,users-count"`
// Relations
Users []*User `jsonapi:"relation,users"`
OrganizationMemberships []*OrganizationMembership `jsonapi:"relation,organization-memberships"`
}
// OrganizationAccess represents the team's permissions on its organization
type OrganizationAccess struct {
ManagePolicies bool `json:"manage-policies"`
ManageWorkspaces bool `json:"manage-workspaces"`
ManageVCSSettings bool `json:"manage-vcs-settings"`
}
// TeamPermissions represents the current user's permissions on the team.
type TeamPermissions struct {
CanDestroy bool `json:"can-destroy"`
CanUpdateMembership bool `json:"can-update-membership"`
}
// TeamListOptions represents the options for listing teams.
type TeamListOptions struct {
ListOptions
Include string `url:"include"`
}
// List all the teams of the given organization.
func (s *teams) List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
tl := &TeamList{}
err = s.client.do(ctx, req, tl)
if err != nil {
return nil, err
}
return tl, nil
}
// TeamCreateOptions represents the options for creating a team.
type TeamCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,teams"`
// Name of the team.
Name *string `jsonapi:"attr,name"`
// The team's organization access
OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"`
// The team's visibility ("secret", "organization")
Visibility *string `jsonapi:"attr,visibility,omitempty"`
}
// OrganizationAccessOptions represents the organization access options of a team.
type OrganizationAccessOptions struct {
ManagePolicies *bool `json:"manage-policies,omitempty"`
ManageWorkspaces *bool `json:"manage-workspaces,omitempty"`
ManageVCSSettings *bool `json:"manage-vcs-settings,omitempty"`
}
func (o TeamCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
return nil
}
// Create a new team with the given options.
func (s *teams) Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
t := &Team{}
err = s.client.do(ctx, req, t)
if err != nil {
return nil, err
}
return t, nil
}
// Read a single team by its ID.
func (s *teams) Read(ctx context.Context, teamID string) (*Team, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
t := &Team{}
err = s.client.do(ctx, req, t)
if err != nil {
return nil, err
}
return t, nil
}
// TeamUpdateOptions represents the options for updating a team.
type TeamUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,teams"`
// New name for the team
Name *string `jsonapi:"attr,name,omitempty"`
// The team's organization access
OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"`
// The team's visibility ("secret", "organization")
Visibility *string `jsonapi:"attr,visibility,omitempty"`
}
// Update a team by its ID.
func (s *teams) Update(ctx context.Context, teamID string, options TeamUpdateOptions) (*Team, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
t := &Team{}
err = s.client.do(ctx, req, t)
if err != nil {
return nil, err
}
return t, nil
}
// Delete a team by its ID.
func (s *teams) Delete(ctx context.Context, teamID string) error {
if !validStringID(&teamID) {
return errors.New("invalid value for team ID")
}
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

274
vendor/github.com/hashicorp/go-tfe/team_access.go generated vendored Normal file
View File

@ -0,0 +1,274 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ TeamAccesses = (*teamAccesses)(nil)
// TeamAccesses describes all the team access related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/team-access.html
type TeamAccesses interface {
// List all the team accesses for a given workspace.
List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error)
// Add team access for a workspace.
Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error)
// Read a team access by its ID.
Read(ctx context.Context, teamAccessID string) (*TeamAccess, error)
// Update a team access by its ID.
Update(ctx context.Context, teamAccessID string, options TeamAccessUpdateOptions) (*TeamAccess, error)
// Remove team access from a workspace.
Remove(ctx context.Context, teamAccessID string) error
}
// teamAccesses implements TeamAccesses.
type teamAccesses struct {
client *Client
}
// AccessType represents a team access type.
type AccessType string
// RunsPermissionType represents the permissiontype to a workspace's runs.
type RunsPermissionType string
// VariablesPermissionType represents the permissiontype to a workspace's variables.
type VariablesPermissionType string
// StateVersionsPermissionType represents the permissiontype to a workspace's state versions.
type StateVersionsPermissionType string
// SentinelMocksPermissionType represents the permissiontype to a workspace's Sentinel mocks.
type SentinelMocksPermissionType string
// WorkspaceLockingPermissionType represents the permissiontype to lock or unlock a workspace.
type WorkspaceLockingPermissionType bool
// List all available team access types and permissions.
const (
AccessAdmin AccessType = "admin"
AccessPlan AccessType = "plan"
AccessRead AccessType = "read"
AccessWrite AccessType = "write"
AccessCustom AccessType = "custom"
RunsPermissionRead RunsPermissionType = "read"
RunsPermissionPlan RunsPermissionType = "plan"
RunsPermissionApply RunsPermissionType = "apply"
VariablesPermissionNone VariablesPermissionType = "none"
VariablesPermissionRead VariablesPermissionType = "read"
VariablesPermissionWrite VariablesPermissionType = "write"
StateVersionsPermissionNone StateVersionsPermissionType = "none"
StateVersionsPermissionReadOutputs StateVersionsPermissionType = "read-outputs"
StateVersionsPermissionRead StateVersionsPermissionType = "read"
StateVersionsPermissionWrite StateVersionsPermissionType = "write"
SentinelMocksPermissionNone SentinelMocksPermissionType = "none"
SentinelMocksPermissionRead SentinelMocksPermissionType = "read"
)
// TeamAccessList represents a list of team accesses.
type TeamAccessList struct {
*Pagination
Items []*TeamAccess
}
// TeamAccess represents the workspace access for a team.
type TeamAccess struct {
ID string `jsonapi:"primary,team-workspaces"`
Access AccessType `jsonapi:"attr,access"`
Runs RunsPermissionType `jsonapi:"attr,runs"`
Variables VariablesPermissionType `jsonapi:"attr,variables"`
StateVersions StateVersionsPermissionType `jsonapi:"attr,state-versions"`
SentinelMocks SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks"`
WorkspaceLocking bool `jsonapi:"attr,workspace-locking"`
// Relations
Team *Team `jsonapi:"relation,team"`
Workspace *Workspace `jsonapi:"relation,workspace"`
}
// TeamAccessListOptions represents the options for listing team accesses.
type TeamAccessListOptions struct {
ListOptions
WorkspaceID *string `url:"filter[workspace][id],omitempty"`
}
func (o TeamAccessListOptions) valid() error {
if !validString(o.WorkspaceID) {
return errors.New("workspace ID is required")
}
if !validStringID(o.WorkspaceID) {
return errors.New("invalid value for workspace ID")
}
return nil
}
// List all the team accesses for a given workspace.
func (s *teamAccesses) List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error) {
if err := options.valid(); err != nil {
return nil, err
}
req, err := s.client.newRequest("GET", "team-workspaces", &options)
if err != nil {
return nil, err
}
tal := &TeamAccessList{}
err = s.client.do(ctx, req, tal)
if err != nil {
return nil, err
}
return tal, nil
}
// TeamAccessAddOptions represents the options for adding team access.
type TeamAccessAddOptions struct {
// For internal use only!
ID string `jsonapi:"primary,team-workspaces"`
// The type of access to grant.
Access *AccessType `jsonapi:"attr,access"`
// Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are
// read-only and reflect the Access level's implicit permissions.
Runs *RunsPermissionType `jsonapi:"attr,runs,omitempty"`
Variables *VariablesPermissionType `jsonapi:"attr,variables,omitempty"`
StateVersions *StateVersionsPermissionType `jsonapi:"attr,state-versions,omitempty"`
SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"`
WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"`
// The team to add to the workspace
Team *Team `jsonapi:"relation,team"`
// The workspace to which the team is to be added.
Workspace *Workspace `jsonapi:"relation,workspace"`
}
func (o TeamAccessAddOptions) valid() error {
if o.Access == nil {
return errors.New("access is required")
}
if o.Team == nil {
return errors.New("team is required")
}
if o.Workspace == nil {
return errors.New("workspace is required")
}
return nil
}
// Add team access for a workspace.
func (s *teamAccesses) Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error) {
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := s.client.newRequest("POST", "team-workspaces", &options)
if err != nil {
return nil, err
}
ta := &TeamAccess{}
err = s.client.do(ctx, req, ta)
if err != nil {
return nil, err
}
return ta, nil
}
// Read a team access by its ID.
func (s *teamAccesses) Read(ctx context.Context, teamAccessID string) (*TeamAccess, error) {
if !validStringID(&teamAccessID) {
return nil, errors.New("invalid value for team access ID")
}
u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
ta := &TeamAccess{}
err = s.client.do(ctx, req, ta)
if err != nil {
return nil, err
}
return ta, nil
}
// TeamAccessUpdateOptions represents the options for updating team access.
type TeamAccessUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,team-workspaces"`
// The type of access to grant.
Access *AccessType `jsonapi:"attr,access,omitempty"`
// Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are
// read-only and reflect the Access level's implicit permissions.
Runs *RunsPermissionType `jsonapi:"attr,runs,omitempty"`
Variables *VariablesPermissionType `jsonapi:"attr,variables,omitempty"`
StateVersions *StateVersionsPermissionType `jsonapi:"attr,state-versions,omitempty"`
SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"`
WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"`
}
// Update team access for a workspace
func (s *teamAccesses) Update(ctx context.Context, teamAccessID string, options TeamAccessUpdateOptions) (*TeamAccess, error) {
if !validStringID(&teamAccessID) {
return nil, errors.New("invalid value for team access ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
ta := &TeamAccess{}
err = s.client.do(ctx, req, ta)
if err != nil {
return nil, err
}
return ta, err
}
// Remove team access from a workspace.
func (s *teamAccesses) Remove(ctx context.Context, teamAccessID string) error {
if !validStringID(&teamAccessID) {
return errors.New("invalid value for team access ID")
}
u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

251
vendor/github.com/hashicorp/go-tfe/team_member.go generated vendored Normal file
View File

@ -0,0 +1,251 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
// Compile-time proof of interface implementation.
var _ TeamMembers = (*teamMembers)(nil)
// TeamMembers describes all the team member related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/team-members.html
type TeamMembers interface {
// List returns all Users of a team calling ListUsers
// See ListOrganizationMemberships for fetching memberships
List(ctx context.Context, teamID string) ([]*User, error)
// ListUsers returns the Users of this team.
ListUsers(ctx context.Context, teamID string) ([]*User, error)
// ListOrganizationMemberships returns the OrganizationMemberships of this team.
ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error)
// Add multiple users to a team.
Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error
// Remove multiple users from a team.
Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error
}
// teamMembers implements TeamMembers.
type teamMembers struct {
client *Client
}
type teamMemberUser struct {
Username string `jsonapi:"primary,users"`
}
type teamMemberOrgMembership struct {
ID string `jsonapi:"primary,organization-memberships"`
}
// List returns all Users of a team calling ListUsers
// See ListOrganizationMemberships for fetching memberships
func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) {
return s.ListUsers(ctx, teamID)
}
// ListUsers returns the Users of this team.
func (s *teamMembers) ListUsers(ctx context.Context, teamID string) ([]*User, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
options := struct {
Include string `url:"include"`
}{
Include: "users",
}
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
t := &Team{}
err = s.client.do(ctx, req, t)
if err != nil {
return nil, err
}
return t.Users, nil
}
// ListOrganizationMemberships returns the OrganizationMemberships of this team.
func (s *teamMembers) ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
options := struct {
Include string `url:"include"`
}{
Include: "organization-memberships",
}
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
req, err := s.client.newRequest("GET", u, options)
if err != nil {
return nil, err
}
t := &Team{}
err = s.client.do(ctx, req, t)
if err != nil {
return nil, err
}
return t.OrganizationMemberships, nil
}
// TeamMemberAddOptions represents the options for
// adding or removing team members.
type TeamMemberAddOptions struct {
Usernames []string
OrganizationMembershipIDs []string
}
func (o *TeamMemberAddOptions) valid() error {
if o.Usernames == nil && o.OrganizationMembershipIDs == nil {
return errors.New("usernames or organization membership ids are required")
}
if o.Usernames != nil && o.OrganizationMembershipIDs != nil {
return errors.New("only one of usernames or organization membership ids can be provided")
}
if o.Usernames != nil && len(o.Usernames) == 0 {
return errors.New("invalid value for usernames")
}
if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {
return errors.New("invalid value for organization membership ids")
}
return nil
}
// kind returns "users" or "organization-memberships"
// depending on which is defined
func (o *TeamMemberAddOptions) kind() string {
if o.Usernames != nil && len(o.Usernames) != 0 {
return "users"
}
return "organization-memberships"
}
// Add multiple users to a team.
func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error {
if !validStringID(&teamID) {
return errors.New("invalid value for team ID")
}
if err := options.valid(); err != nil {
return err
}
usersOrMemberships := options.kind()
URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships)
var req *retryablehttp.Request
if usersOrMemberships == "users" {
var err error
var members []*teamMemberUser
for _, name := range options.Usernames {
members = append(members, &teamMemberUser{Username: name})
}
req, err = s.client.newRequest("POST", URL, members)
if err != nil {
return err
}
} else {
var err error
var members []*teamMemberOrgMembership
for _, ID := range options.OrganizationMembershipIDs {
members = append(members, &teamMemberOrgMembership{ID: ID})
}
req, err = s.client.newRequest("POST", URL, members)
if err != nil {
return err
}
}
return s.client.do(ctx, req, nil)
}
// TeamMemberRemoveOptions represents the options for
// adding or removing team members.
type TeamMemberRemoveOptions struct {
Usernames []string
OrganizationMembershipIDs []string
}
func (o *TeamMemberRemoveOptions) valid() error {
if o.Usernames == nil && o.OrganizationMembershipIDs == nil {
return errors.New("usernames or organization membership ids are required")
}
if o.Usernames != nil && o.OrganizationMembershipIDs != nil {
return errors.New("only one of usernames or organization membership ids can be provided")
}
if o.Usernames != nil && len(o.Usernames) == 0 {
return errors.New("invalid value for usernames")
}
if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {
return errors.New("invalid value for organization membership ids")
}
return nil
}
// kind returns "users" or "organization-memberships"
// depending on which is defined
func (o *TeamMemberRemoveOptions) kind() string {
if o.Usernames != nil && len(o.Usernames) != 0 {
return "users"
}
return "organization-memberships"
}
// Remove multiple users from a team.
func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error {
if !validStringID(&teamID) {
return errors.New("invalid value for team ID")
}
if err := options.valid(); err != nil {
return err
}
usersOrMemberships := options.kind()
URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships)
var req *retryablehttp.Request
if usersOrMemberships == "users" {
var err error
var members []*teamMemberUser
for _, name := range options.Usernames {
members = append(members, &teamMemberUser{Username: name})
}
req, err = s.client.newRequest("DELETE", URL, members)
if err != nil {
return err
}
} else {
var err error
var members []*teamMemberOrgMembership
for _, ID := range options.OrganizationMembershipIDs {
members = append(members, &teamMemberOrgMembership{ID: ID})
}
req, err = s.client.newRequest("DELETE", URL, members)
if err != nil {
return err
}
}
return s.client.do(ctx, req, nil)
}

99
vendor/github.com/hashicorp/go-tfe/team_token.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ TeamTokens = (*teamTokens)(nil)
// TeamTokens describes all the team token related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/team-tokens.html
type TeamTokens interface {
// Generate a new team token, replacing any existing token.
Generate(ctx context.Context, teamID string) (*TeamToken, error)
// Read a team token by its ID.
Read(ctx context.Context, teamID string) (*TeamToken, error)
// Delete a team token by its ID.
Delete(ctx context.Context, teamID string) error
}
// teamTokens implements TeamTokens.
type teamTokens struct {
client *Client
}
// TeamToken represents a Terraform Enterprise team token.
type TeamToken struct {
ID string `jsonapi:"primary,authentication-tokens"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
}
// Generate a new team token, replacing any existing token.
func (s *teamTokens) Generate(ctx context.Context, teamID string) (*TeamToken, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
tt := &TeamToken{}
err = s.client.do(ctx, req, tt)
if err != nil {
return nil, err
}
return tt, err
}
// Read a team token by its ID.
func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error) {
if !validStringID(&teamID) {
return nil, errors.New("invalid value for team ID")
}
u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
tt := &TeamToken{}
err = s.client.do(ctx, req, tt)
if err != nil {
return nil, err
}
return tt, err
}
// Delete a team token by its ID.
func (s *teamTokens) Delete(ctx context.Context, teamID string) error {
if !validStringID(&teamID) {
return errors.New("invalid value for team ID")
}
u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

36
vendor/github.com/hashicorp/go-tfe/testing.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package tfe
import (
"context"
"testing"
)
// TestAccountDetails represents the basic account information
// of a TFE/TFC user.
//
// See FetchTestAccountDetails for more information.
type TestAccountDetails struct {
ID string `json:"id" jsonapi:"primary,users"`
Username string `jsonapi:"attr,username"`
Email string `jsonapi:"attr,email"`
}
// FetchTestAccountDetails returns TestAccountDetails
// of the user running the tests.
//
// Use this helper to fetch the username and email
// address associated with the token used to run the tests.
func FetchTestAccountDetails(t *testing.T, client *Client) *TestAccountDetails {
tad := &TestAccountDetails{}
req, err := client.newRequest("GET", "account/details", nil)
if err != nil {
t.Fatalf("could not create account details request: %v", err)
}
ctx := context.Background()
err = client.do(ctx, req, tad)
if err != nil {
t.Fatalf("could not fetch test user details: %v", err)
}
return tad
}

730
vendor/github.com/hashicorp/go-tfe/tfe.go generated vendored Normal file
View File

@ -0,0 +1,730 @@
package tfe
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/google/go-querystring/query"
"github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"github.com/svanharmelen/jsonapi"
"golang.org/x/time/rate"
)
const (
userAgent = "go-tfe"
headerRateLimit = "X-RateLimit-Limit"
headerRateReset = "X-RateLimit-Reset"
headerAPIVersion = "TFP-API-Version"
// DefaultAddress of Terraform Enterprise.
DefaultAddress = "https://app.terraform.io"
// DefaultBasePath on which the API is served.
DefaultBasePath = "/api/v2/"
// PingEndpoint is a no-op API endpoint used to configure the rate limiter
PingEndpoint = "ping"
)
var (
// ErrWorkspaceLocked is returned when trying to lock a
// locked workspace.
ErrWorkspaceLocked = errors.New("workspace already locked")
// ErrWorkspaceNotLocked is returned when trying to unlock
// a unlocked workspace.
ErrWorkspaceNotLocked = errors.New("workspace already unlocked")
// ErrUnauthorized is returned when a receiving a 401.
ErrUnauthorized = errors.New("unauthorized")
// ErrResourceNotFound is returned when a receiving a 404.
ErrResourceNotFound = errors.New("resource not found")
)
// RetryLogHook allows a function to run before each retry.
type RetryLogHook func(attemptNum int, resp *http.Response)
// Config provides configuration details to the API client.
type Config struct {
// The address of the Terraform Enterprise API.
Address string
// The base path on which the API is served.
BasePath string
// API token used to access the Terraform Enterprise API.
Token string
// Headers that will be added to every request.
Headers http.Header
// A custom HTTP client to use.
HTTPClient *http.Client
// RetryLogHook is invoked each time a request is retried.
RetryLogHook RetryLogHook
}
// DefaultConfig returns a default config structure.
func DefaultConfig() *Config {
config := &Config{
Address: os.Getenv("TFE_ADDRESS"),
BasePath: DefaultBasePath,
Token: os.Getenv("TFE_TOKEN"),
Headers: make(http.Header),
HTTPClient: cleanhttp.DefaultPooledClient(),
}
// Set the default address if none is given.
if config.Address == "" {
config.Address = DefaultAddress
}
// Set the default user agent.
config.Headers.Set("User-Agent", userAgent)
return config
}
// Client is the Terraform Enterprise API client. It provides the basic
// connectivity and configuration for accessing the TFE API.
type Client struct {
baseURL *url.URL
token string
headers http.Header
http *retryablehttp.Client
limiter *rate.Limiter
retryLogHook RetryLogHook
retryServerErrors bool
remoteAPIVersion string
AgentPools AgentPools
AgentTokens AgentTokens
Applies Applies
ConfigurationVersions ConfigurationVersions
CostEstimates CostEstimates
NotificationConfigurations NotificationConfigurations
OAuthClients OAuthClients
OAuthTokens OAuthTokens
Organizations Organizations
OrganizationMemberships OrganizationMemberships
OrganizationTokens OrganizationTokens
Plans Plans
PlanExports PlanExports
Policies Policies
PolicyChecks PolicyChecks
PolicySetParameters PolicySetParameters
PolicySets PolicySets
RegistryModules RegistryModules
Runs Runs
RunTriggers RunTriggers
SSHKeys SSHKeys
StateVersionOutputs StateVersionOutputs
StateVersions StateVersions
Teams Teams
TeamAccess TeamAccesses
TeamMembers TeamMembers
TeamTokens TeamTokens
Users Users
UserTokens UserTokens
Variables Variables
Workspaces Workspaces
Meta Meta
}
// Meta contains any Terraform Cloud APIs which provide data about the API itself.
type Meta struct {
IPRanges IPRanges
}
// NewClient creates a new Terraform Enterprise API client.
func NewClient(cfg *Config) (*Client, error) {
config := DefaultConfig()
// Layer in the provided config for any non-blank values.
if cfg != nil {
if cfg.Address != "" {
config.Address = cfg.Address
}
if cfg.BasePath != "" {
config.BasePath = cfg.BasePath
}
if cfg.Token != "" {
config.Token = cfg.Token
}
for k, v := range cfg.Headers {
config.Headers[k] = v
}
if cfg.HTTPClient != nil {
config.HTTPClient = cfg.HTTPClient
}
if cfg.RetryLogHook != nil {
config.RetryLogHook = cfg.RetryLogHook
}
}
// Parse the address to make sure its a valid URL.
baseURL, err := url.Parse(config.Address)
if err != nil {
return nil, fmt.Errorf("invalid address: %v", err)
}
baseURL.Path = config.BasePath
if !strings.HasSuffix(baseURL.Path, "/") {
baseURL.Path += "/"
}
// This value must be provided by the user.
if config.Token == "" {
return nil, fmt.Errorf("missing API token")
}
// Create the client.
client := &Client{
baseURL: baseURL,
token: config.Token,
headers: config.Headers,
retryLogHook: config.RetryLogHook,
}
client.http = &retryablehttp.Client{
Backoff: client.retryHTTPBackoff,
CheckRetry: client.retryHTTPCheck,
ErrorHandler: retryablehttp.PassthroughErrorHandler,
HTTPClient: config.HTTPClient,
RetryWaitMin: 100 * time.Millisecond,
RetryWaitMax: 400 * time.Millisecond,
RetryMax: 30,
}
meta, err := client.getRawAPIMetadata()
if err != nil {
return nil, err
}
// Configure the rate limiter.
client.configureLimiter(meta.RateLimit)
// Save the API version so we can return it from the RemoteAPIVersion
// method later.
client.remoteAPIVersion = meta.APIVersion
// Create the services.
client.AgentPools = &agentPools{client: client}
client.AgentTokens = &agentTokens{client: client}
client.Applies = &applies{client: client}
client.ConfigurationVersions = &configurationVersions{client: client}
client.CostEstimates = &costEstimates{client: client}
client.NotificationConfigurations = &notificationConfigurations{client: client}
client.OAuthClients = &oAuthClients{client: client}
client.OAuthTokens = &oAuthTokens{client: client}
client.Organizations = &organizations{client: client}
client.OrganizationMemberships = &organizationMemberships{client: client}
client.OrganizationTokens = &organizationTokens{client: client}
client.Plans = &plans{client: client}
client.PlanExports = &planExports{client: client}
client.Policies = &policies{client: client}
client.PolicyChecks = &policyChecks{client: client}
client.PolicySetParameters = &policySetParameters{client: client}
client.PolicySets = &policySets{client: client}
client.RegistryModules = &registryModules{client: client}
client.Runs = &runs{client: client}
client.RunTriggers = &runTriggers{client: client}
client.SSHKeys = &sshKeys{client: client}
client.StateVersionOutputs = &stateVersionOutputs{client: client}
client.StateVersions = &stateVersions{client: client}
client.Teams = &teams{client: client}
client.TeamAccess = &teamAccesses{client: client}
client.TeamMembers = &teamMembers{client: client}
client.TeamTokens = &teamTokens{client: client}
client.Users = &users{client: client}
client.UserTokens = &userTokens{client: client}
client.Variables = &variables{client: client}
client.Workspaces = &workspaces{client: client}
client.Meta = Meta{
IPRanges: &ipRanges{client: client},
}
return client, nil
}
// RemoteAPIVersion returns the server's declared API version string.
//
// A Terraform Cloud or Enterprise API server returns its API version in an
// HTTP header field in all responses. The NewClient function saves the
// version number returned in its initial setup request and RemoteAPIVersion
// returns that cached value.
//
// The API protocol calls for this string to be a dotted-decimal version number
// like 2.3.0, where the first number indicates the API major version while the
// second indicates a minor version which may have introduced some
// backward-compatible additional features compared to its predecessor.
//
// Explicit API versioning was added to the Terraform Cloud and Enterprise
// APIs as a later addition, so older servers will not return version
// information. In that case, this function returns an empty string as the
// version.
func (c *Client) RemoteAPIVersion() string {
return c.remoteAPIVersion
}
// SetFakeRemoteAPIVersion allows setting a given string as the client's remoteAPIVersion,
// overriding the value pulled from the API header during client initialization.
//
// This is intended for use in tests, when you may want to configure your TFE client to
// return something different than the actual API version in order to test error handling.
func (c *Client) SetFakeRemoteAPIVersion(fakeAPIVersion string) {
c.remoteAPIVersion = fakeAPIVersion
}
// RetryServerErrors configures the retry HTTP check to also retry
// unexpected errors or requests that failed with a server error.
func (c *Client) RetryServerErrors(retry bool) {
c.retryServerErrors = retry
}
// retryHTTPCheck provides a callback for Client.CheckRetry which
// will retry both rate limit (429) and server (>= 500) errors.
func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err error) (bool, error) {
if ctx.Err() != nil {
return false, ctx.Err()
}
if err != nil {
return c.retryServerErrors, err
}
if resp.StatusCode == 429 || (c.retryServerErrors && resp.StatusCode >= 500) {
return true, nil
}
return false, nil
}
// retryHTTPBackoff provides a generic callback for Client.Backoff which
// will pass through all calls based on the status code of the response.
func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
if c.retryLogHook != nil {
c.retryLogHook(attemptNum, resp)
}
// Use the rate limit backoff function when we are rate limited.
if resp != nil && resp.StatusCode == 429 {
return rateLimitBackoff(min, max, attemptNum, resp)
}
// Set custom duration's when we experience a service interruption.
min = 700 * time.Millisecond
max = 900 * time.Millisecond
return retryablehttp.LinearJitterBackoff(min, max, attemptNum, resp)
}
// rateLimitBackoff provides a callback for Client.Backoff which will use the
// X-RateLimit_Reset header to determine the time to wait. We add some jitter
// to prevent a thundering herd.
//
// min and max are mainly used for bounding the jitter that will be added to
// the reset time retrieved from the headers. But if the final wait time is
// less then min, min will be used instead.
func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
// rnd is used to generate pseudo-random numbers.
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
// First create some jitter bounded by the min and max durations.
jitter := time.Duration(rnd.Float64() * float64(max-min))
if resp != nil {
if v := resp.Header.Get(headerRateReset); v != "" {
if reset, _ := strconv.ParseFloat(v, 64); reset > 0 {
// Only update min if the given time to wait is longer.
if wait := time.Duration(reset * 1e9); wait > min {
min = wait
}
}
}
}
return min + jitter
}
type rawAPIMetadata struct {
// APIVersion is the raw API version string reported by the server in the
// TFP-API-Version response header, or an empty string if that header
// field was not included in the response.
APIVersion string
// RateLimit is the raw API version string reported by the server in the
// X-RateLimit-Limit response header, or an empty string if that header
// field was not included in the response.
RateLimit string
}
func (c *Client) getRawAPIMetadata() (rawAPIMetadata, error) {
var meta rawAPIMetadata
// Create a new request.
u, err := c.baseURL.Parse(PingEndpoint)
if err != nil {
return meta, err
}
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return meta, err
}
// Attach the default headers.
for k, v := range c.headers {
req.Header[k] = v
}
req.Header.Set("Accept", "application/vnd.api+json")
req.Header.Set("Authorization", "Bearer "+c.token)
// Make a single request to retrieve the rate limit headers.
resp, err := c.http.HTTPClient.Do(req)
if err != nil {
return meta, err
}
resp.Body.Close()
meta.APIVersion = resp.Header.Get(headerAPIVersion)
meta.RateLimit = resp.Header.Get(headerRateLimit)
return meta, nil
}
// configureLimiter configures the rate limiter.
func (c *Client) configureLimiter(rawLimit string) {
// Set default values for when rate limiting is disabled.
limit := rate.Inf
burst := 0
if v := rawLimit; v != "" {
if rateLimit, _ := strconv.ParseFloat(v, 64); rateLimit > 0 {
// Configure the limit and burst using a split of 2/3 for the limit and
// 1/3 for the burst. This enables clients to burst 1/3 of the allowed
// calls before the limiter kicks in. The remaining calls will then be
// spread out evenly using intervals of time.Second / limit which should
// prevent hitting the rate limit.
limit = rate.Limit(rateLimit * 0.66)
burst = int(rateLimit * 0.33)
}
}
// Create a new limiter using the calculated values.
c.limiter = rate.NewLimiter(limit, burst)
}
// newRequest creates an API request. A relative URL path can be provided in
// path, in which case it is resolved relative to the apiVersionPath of the
// Client. Relative URL paths should always be specified without a preceding
// slash.
// If v is supplied, the value will be JSONAPI encoded and included as the
// request body. If the method is GET, the value will be parsed and added as
// query parameters.
func (c *Client) newRequest(method, path string, v interface{}) (*retryablehttp.Request, error) {
u, err := c.baseURL.Parse(path)
if err != nil {
return nil, err
}
// Create a request specific headers map.
reqHeaders := make(http.Header)
reqHeaders.Set("Authorization", "Bearer "+c.token)
var body interface{}
switch method {
case "GET":
reqHeaders.Set("Accept", "application/vnd.api+json")
if v != nil {
q, err := query.Values(v)
if err != nil {
return nil, err
}
u.RawQuery = q.Encode()
}
case "DELETE", "PATCH", "POST":
reqHeaders.Set("Accept", "application/vnd.api+json")
reqHeaders.Set("Content-Type", "application/vnd.api+json")
if v != nil {
if body, err = serializeRequestBody(v); err != nil {
return nil, err
}
}
case "PUT":
reqHeaders.Set("Accept", "application/json")
reqHeaders.Set("Content-Type", "application/octet-stream")
body = v
}
req, err := retryablehttp.NewRequest(method, u.String(), body)
if err != nil {
return nil, err
}
// Set the default headers.
for k, v := range c.headers {
req.Header[k] = v
}
// Set the request specific headers.
for k, v := range reqHeaders {
req.Header[k] = v
}
return req, nil
}
// Helper method that serializes the given ptr or ptr slice into a JSON
// request. It automatically uses jsonapi or json serialization, depending
// on the body type's tags.
func serializeRequestBody(v interface{}) (interface{}, error) {
// The body can be a slice of pointers or a pointer. In either
// case we want to choose the serialization type based on the
// individual record type. To determine that type, we need
// to either follow the pointer or examine the slice element type.
// There are other theoretical possiblities (e. g. maps,
// non-pointers) but they wouldn't work anyway because the
// json-api library doesn't support serializing other things.
var modelType reflect.Type
bodyType := reflect.TypeOf(v)
invalidBodyError := errors.New("go-tfe bug: DELETE/PATCH/POST body must be nil, ptr, or ptr slice")
switch bodyType.Kind() {
case reflect.Slice:
sliceElem := bodyType.Elem()
if sliceElem.Kind() != reflect.Ptr {
return nil, invalidBodyError
}
modelType = sliceElem.Elem()
case reflect.Ptr:
modelType = reflect.ValueOf(v).Elem().Type()
default:
return nil, invalidBodyError
}
// Infer whether the request uses jsonapi or regular json
// serialization based on how the fields are tagged.
jsonApiFields := 0
jsonFields := 0
for i := 0; i < modelType.NumField(); i++ {
structField := modelType.Field(i)
if structField.Tag.Get("jsonapi") != "" {
jsonApiFields++
}
if structField.Tag.Get("json") != "" {
jsonFields++
}
}
if jsonApiFields > 0 && jsonFields > 0 {
// Defining a struct with both json and jsonapi tags doesn't
// make sense, because a struct can only be serialized
// as one or another. If this does happen, it's a bug
// in the library that should be fixed at development time
return nil, errors.New("go-tfe bug: struct can't use both json and jsonapi attributes")
}
if jsonFields > 0 {
return json.Marshal(v)
} else {
buf := bytes.NewBuffer(nil)
if err := jsonapi.MarshalPayloadWithoutIncluded(buf, v); err != nil {
return nil, err
}
return buf, nil
}
}
// do sends an API request and returns the API response. The API response
// is JSONAPI decoded and the document's primary data is stored in the value
// pointed to by v, or returned as an error if an API error has occurred.
// If v implements the io.Writer interface, the raw response body will be
// written to v, without attempting to first decode it.
//
// The provided ctx must be non-nil. If it is canceled or times out, ctx.Err()
// will be returned.
func (c *Client) do(ctx context.Context, req *retryablehttp.Request, v interface{}) error {
// Wait will block until the limiter can obtain a new token
// or returns an error if the given context is canceled.
if err := c.limiter.Wait(ctx); err != nil {
return err
}
// Add the context to the request.
req = req.WithContext(ctx)
// Execute the request and check the response.
resp, err := c.http.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return ctx.Err()
default:
return err
}
}
defer resp.Body.Close()
// Basic response checking.
if err := checkResponseCode(resp); err != nil {
return err
}
// Return here if decoding the response isn't needed.
if v == nil {
return nil
}
// If v implements io.Writer, write the raw response body.
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, resp.Body)
return err
}
// Get the value of v so we can test if it's a struct.
dst := reflect.Indirect(reflect.ValueOf(v))
// Return an error if v is not a struct or an io.Writer.
if dst.Kind() != reflect.Struct {
return fmt.Errorf("v must be a struct or an io.Writer")
}
// Try to get the Items and Pagination struct fields.
items := dst.FieldByName("Items")
pagination := dst.FieldByName("Pagination")
// Unmarshal a single value if v does not contain the
// Items and Pagination struct fields.
if !items.IsValid() || !pagination.IsValid() {
return jsonapi.UnmarshalPayload(resp.Body, v)
}
// Return an error if v.Items is not a slice.
if items.Type().Kind() != reflect.Slice {
return fmt.Errorf("v.Items must be a slice")
}
// Create a temporary buffer and copy all the read data into it.
body := bytes.NewBuffer(nil)
reader := io.TeeReader(resp.Body, body)
// Unmarshal as a list of values as v.Items is a slice.
raw, err := jsonapi.UnmarshalManyPayload(reader, items.Type().Elem())
if err != nil {
return err
}
// Make a new slice to hold the results.
sliceType := reflect.SliceOf(items.Type().Elem())
result := reflect.MakeSlice(sliceType, 0, len(raw))
// Add all of the results to the new slice.
for _, v := range raw {
result = reflect.Append(result, reflect.ValueOf(v))
}
// Pointer-swap the result.
items.Set(result)
// As we are getting a list of values, we need to decode
// the pagination details out of the response body.
p, err := parsePagination(body)
if err != nil {
return err
}
// Pointer-swap the decoded pagination details.
pagination.Set(reflect.ValueOf(p))
return nil
}
// ListOptions is used to specify pagination options when making API requests.
// Pagination allows breaking up large result sets into chunks, or "pages".
type ListOptions struct {
// The page number to request. The results vary based on the PageSize.
PageNumber int `url:"page[number],omitempty"`
// The number of elements returned in a single page.
PageSize int `url:"page[size],omitempty"`
}
// Pagination is used to return the pagination details of an API request.
type Pagination struct {
CurrentPage int `json:"current-page"`
PreviousPage int `json:"prev-page"`
NextPage int `json:"next-page"`
TotalPages int `json:"total-pages"`
TotalCount int `json:"total-count"`
}
func parsePagination(body io.Reader) (*Pagination, error) {
var raw struct {
Meta struct {
Pagination Pagination `json:"pagination"`
} `json:"meta"`
}
// JSON decode the raw response.
if err := json.NewDecoder(body).Decode(&raw); err != nil {
return &Pagination{}, err
}
return &raw.Meta.Pagination, nil
}
// checkResponseCode can be used to check the status code of an HTTP request.
func checkResponseCode(r *http.Response) error {
if r.StatusCode >= 200 && r.StatusCode <= 299 {
return nil
}
switch r.StatusCode {
case 401:
return ErrUnauthorized
case 404:
return ErrResourceNotFound
case 409:
switch {
case strings.HasSuffix(r.Request.URL.Path, "actions/lock"):
return ErrWorkspaceLocked
case strings.HasSuffix(r.Request.URL.Path, "actions/unlock"):
return ErrWorkspaceNotLocked
case strings.HasSuffix(r.Request.URL.Path, "actions/force-unlock"):
return ErrWorkspaceNotLocked
}
}
// Decode the error payload.
errPayload := &jsonapi.ErrorsPayload{}
err := json.NewDecoder(r.Body).Decode(errPayload)
if err != nil || len(errPayload.Errors) == 0 {
return fmt.Errorf(r.Status)
}
// Parse and format the errors.
var errs []string
for _, e := range errPayload.Errors {
if e.Detail == "" {
errs = append(errs, e.Title)
} else {
errs = append(errs, fmt.Sprintf("%s\n\n%s", e.Title, e.Detail))
}
}
return fmt.Errorf(strings.Join(errs, "\n"))
}

76
vendor/github.com/hashicorp/go-tfe/type_helpers.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
package tfe
// Access returns a pointer to the given team access type.
func Access(v AccessType) *AccessType {
return &v
}
// RunsPermission returns a pointer to the given team runs permission type.
func RunsPermission(v RunsPermissionType) *RunsPermissionType {
return &v
}
// VariablesPermission returns a pointer to the given team variables permission type.
func VariablesPermission(v VariablesPermissionType) *VariablesPermissionType {
return &v
}
// StateVersionsPermission returns a pointer to the given team state versions permission type.
func StateVersionsPermission(v StateVersionsPermissionType) *StateVersionsPermissionType {
return &v
}
// SentinelMocksPermission returns a pointer to the given team Sentinel mocks permission type.
func SentinelMocksPermission(v SentinelMocksPermissionType) *SentinelMocksPermissionType {
return &v
}
// AuthPolicy returns a pointer to the given authentication poliy.
func AuthPolicy(v AuthPolicyType) *AuthPolicyType {
return &v
}
// Bool returns a pointer to the given bool
func Bool(v bool) *bool {
return &v
}
// Category returns a pointer to the given category type.
func Category(v CategoryType) *CategoryType {
return &v
}
// EnforcementMode returns a pointer to the given enforcement level.
func EnforcementMode(v EnforcementLevel) *EnforcementLevel {
return &v
}
// Int returns a pointer to the given int.
func Int(v int) *int {
return &v
}
// Int64 returns a pointer to the given int64.
func Int64(v int64) *int64 {
return &v
}
// NotificationDestination returns a pointer to the given notification configuration destination type
func NotificationDestination(v NotificationDestinationType) *NotificationDestinationType {
return &v
}
// PlanExportType returns a pointer to the given plan export data type.
func PlanExportType(v PlanExportDataType) *PlanExportDataType {
return &v
}
// ServiceProvider returns a pointer to the given service provider type.
func ServiceProvider(v ServiceProviderType) *ServiceProviderType {
return &v
}
// String returns a pointer to the given string.
func String(v string) *string {
return &v
}

93
vendor/github.com/hashicorp/go-tfe/user.go generated vendored Normal file
View File

@ -0,0 +1,93 @@
package tfe
import (
"context"
)
// Compile-time proof of interface implementation.
var _ Users = (*users)(nil)
// Users describes all the user related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/user.html
type Users interface {
// ReadCurrent reads the details of the currently authenticated user.
ReadCurrent(ctx context.Context) (*User, error)
// Update attributes of the currently authenticated user.
Update(ctx context.Context, options UserUpdateOptions) (*User, error)
}
// users implements Users.
type users struct {
client *Client
}
// User represents a Terraform Enterprise user.
type User struct {
ID string `jsonapi:"primary,users"`
AvatarURL string `jsonapi:"attr,avatar-url"`
Email string `jsonapi:"attr,email"`
IsServiceAccount bool `jsonapi:"attr,is-service-account"`
TwoFactor *TwoFactor `jsonapi:"attr,two-factor"`
UnconfirmedEmail string `jsonapi:"attr,unconfirmed-email"`
Username string `jsonapi:"attr,username"`
V2Only bool `jsonapi:"attr,v2-only"`
// Relations
// AuthenticationTokens *AuthenticationTokens `jsonapi:"relation,authentication-tokens"`
}
// TwoFactor represents the organization permissions.
type TwoFactor struct {
Enabled bool `json:"enabled"`
Verified bool `json:"verified"`
}
// ReadCurrent reads the details of the currently authenticated user.
func (s *users) ReadCurrent(ctx context.Context) (*User, error) {
req, err := s.client.newRequest("GET", "account/details", nil)
if err != nil {
return nil, err
}
u := &User{}
err = s.client.do(ctx, req, u)
if err != nil {
return nil, err
}
return u, nil
}
// UserUpdateOptions represents the options for updating a user.
type UserUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,users"`
// New username.
Username *string `jsonapi:"attr,username,omitempty"`
// New email address (must be consumed afterwards to take effect).
Email *string `jsonapi:"attr,email,omitempty"`
}
// Update attributes of the currently authenticated user.
func (s *users) Update(ctx context.Context, options UserUpdateOptions) (*User, error) {
// Make sure we don't send a user provided ID.
options.ID = ""
req, err := s.client.newRequest("PATCH", "account/update", &options)
if err != nil {
return nil, err
}
u := &User{}
err = s.client.do(ctx, req, u)
if err != nil {
return nil, err
}
return u, nil
}

135
vendor/github.com/hashicorp/go-tfe/user_token.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ UserTokens = (*userTokens)(nil)
// UserTokens describes all the user token related methods that the
// Terraform Cloud/Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/enterprise/api/user-tokens.html
type UserTokens interface {
// List all the tokens of the given user ID.
List(ctx context.Context, userID string) (*UserTokenList, error)
// Generate a new user token
Generate(ctx context.Context, userID string, options UserTokenGenerateOptions) (*UserToken, error)
// Read a user token by its ID.
Read(ctx context.Context, tokenID string) (*UserToken, error)
// Delete a user token by its ID.
Delete(ctx context.Context, tokenID string) error
}
// userTokens implements UserTokens.
type userTokens struct {
client *Client
}
// UserTokenList is a list of tokens for the given user ID.
type UserTokenList struct {
*Pagination
Items []*UserToken
}
// UserToken represents a Terraform Enterprise user token.
type UserToken struct {
ID string `jsonapi:"primary,authentication-tokens"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
Token string `jsonapi:"attr,token"`
}
// UserTokenGenerateOptions the options for creating a user token.
type UserTokenGenerateOptions struct {
// Description of the token
Description string `jsonapi:"attr,description,omitempty"`
}
// Generate a new user token
func (s *userTokens) Generate(ctx context.Context, userID string, options UserTokenGenerateOptions) (*UserToken, error) {
if !validStringID(&userID) {
return nil, errors.New("invalid value for user ID")
}
u := fmt.Sprintf("users/%s/authentication-tokens", url.QueryEscape(userID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
ut := &UserToken{}
err = s.client.do(ctx, req, ut)
if err != nil {
return nil, err
}
return ut, err
}
// List shows existing user tokens
func (s *userTokens) List(ctx context.Context, userID string) (*UserTokenList, error) {
if !validStringID(&userID) {
return nil, errors.New("invalid value for user ID")
}
u := fmt.Sprintf("users/%s/authentication-tokens", url.QueryEscape(userID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
tl := &UserTokenList{}
err = s.client.do(ctx, req, tl)
if err != nil {
return nil, err
}
return tl, err
}
// Read a user token by its ID.
func (s *userTokens) Read(ctx context.Context, tokenID string) (*UserToken, error) {
if !validStringID(&tokenID) {
return nil, errors.New("invalid value for token ID")
}
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(tokenID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
tt := &UserToken{}
err = s.client.do(ctx, req, tt)
if err != nil {
return nil, err
}
return tt, err
}
// Delete a user token by its ID.
func (s *userTokens) Delete(ctx context.Context, tokenID string) error {
if !validStringID(&tokenID) {
return errors.New("invalid value for token ID")
}
u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(tokenID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

19
vendor/github.com/hashicorp/go-tfe/validations.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
package tfe
import (
"regexp"
)
// A regular expression used to validate common string ID patterns.
var reStringID = regexp.MustCompile(`^[a-zA-Z0-9\-\._]+$`)
// validString checks if the given input is present and non-empty.
func validString(v *string) bool {
return v != nil && *v != ""
}
// validStringID checks if the given string pointer is non-nil and
// contains a typical string identifier.
func validStringID(v *string) bool {
return v != nil && reStringID.MatchString(*v)
}

244
vendor/github.com/hashicorp/go-tfe/variable.go generated vendored Normal file
View File

@ -0,0 +1,244 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// Compile-time proof of interface implementation.
var _ Variables = (*variables)(nil)
// Variables describes all the variable related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/variables.html
type Variables interface {
// List all the variables associated with the given workspace.
List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error)
// Create is used to create a new variable.
Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error)
// Read a variable by its ID.
Read(ctx context.Context, workspaceID string, variableID string) (*Variable, error)
// Update values of an existing variable.
Update(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error)
// Delete a variable by its ID.
Delete(ctx context.Context, workspaceID string, variableID string) error
}
// variables implements Variables.
type variables struct {
client *Client
}
// CategoryType represents a category type.
type CategoryType string
//List all available categories.
const (
CategoryEnv CategoryType = "env"
CategoryPolicySet CategoryType = "policy-set"
CategoryTerraform CategoryType = "terraform"
)
// VariableList represents a list of variables.
type VariableList struct {
*Pagination
Items []*Variable
}
// Variable represents a Terraform Enterprise variable.
type Variable struct {
ID string `jsonapi:"primary,vars"`
Key string `jsonapi:"attr,key"`
Value string `jsonapi:"attr,value"`
Description string `jsonapi:"attr,description"`
Category CategoryType `jsonapi:"attr,category"`
HCL bool `jsonapi:"attr,hcl"`
Sensitive bool `jsonapi:"attr,sensitive"`
// Relations
Workspace *Workspace `jsonapi:"relation,configurable"`
}
// VariableListOptions represents the options for listing variables.
type VariableListOptions struct {
ListOptions
}
// List all the variables associated with the given workspace.
func (s *variables) List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/vars", workspaceID)
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
vl := &VariableList{}
err = s.client.do(ctx, req, vl)
if err != nil {
return nil, err
}
return vl, nil
}
// VariableCreateOptions represents the options for creating a new variable.
type VariableCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,vars"`
// The name of the variable.
Key *string `jsonapi:"attr,key"`
// The value of the variable.
Value *string `jsonapi:"attr,value,omitempty"`
// The description of the variable.
Description *string `jsonapi:"attr,description,omitempty"`
// Whether this is a Terraform or environment variable.
Category *CategoryType `jsonapi:"attr,category"`
// Whether to evaluate the value of the variable as a string of HCL code.
HCL *bool `jsonapi:"attr,hcl,omitempty"`
// Whether the value is sensitive.
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
}
func (o VariableCreateOptions) valid() error {
if !validString(o.Key) {
return errors.New("key is required")
}
if o.Category == nil {
return errors.New("category is required")
}
return nil
}
// Create is used to create a new variable.
func (s *variables) Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/vars", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
v := &Variable{}
err = s.client.do(ctx, req, v)
if err != nil {
return nil, err
}
return v, nil
}
// Read a variable by its ID.
func (s *variables) Read(ctx context.Context, workspaceID string, variableID string) (*Variable, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if !validStringID(&variableID) {
return nil, errors.New("invalid value for variable ID")
}
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
v := &Variable{}
err = s.client.do(ctx, req, v)
if err != nil {
return nil, err
}
return v, err
}
// VariableUpdateOptions represents the options for updating a variable.
type VariableUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,vars"`
// The name of the variable.
Key *string `jsonapi:"attr,key,omitempty"`
// The value of the variable.
Value *string `jsonapi:"attr,value,omitempty"`
// The description of the variable.
Description *string `jsonapi:"attr,description,omitempty"`
// Whether to evaluate the value of the variable as a string of HCL code.
HCL *bool `jsonapi:"attr,hcl,omitempty"`
// Whether the value is sensitive.
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
}
// Update values of an existing variable.
func (s *variables) Update(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if !validStringID(&variableID) {
return nil, errors.New("invalid value for variable ID")
}
// Make sure we don't send a user provided ID.
options.ID = variableID
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
v := &Variable{}
err = s.client.do(ctx, req, v)
if err != nil {
return nil, err
}
return v, nil
}
// Delete a variable by its ID.
func (s *variables) Delete(ctx context.Context, workspaceID string, variableID string) error {
if !validStringID(&workspaceID) {
return errors.New("invalid value for workspace ID")
}
if !validStringID(&variableID) {
return errors.New("invalid value for variable ID")
}
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

723
vendor/github.com/hashicorp/go-tfe/workspace.go generated vendored Normal file
View File

@ -0,0 +1,723 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Workspaces = (*workspaces)(nil)
// Workspaces describes all the workspace related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/workspaces.html
type Workspaces interface {
// List all the workspaces within an organization.
List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error)
// Create is used to create a new workspace.
Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error)
// Read a workspace by its name.
Read(ctx context.Context, organization string, workspace string) (*Workspace, error)
// ReadByID reads a workspace by its ID.
ReadByID(ctx context.Context, workspaceID string) (*Workspace, error)
// Update settings of an existing workspace.
Update(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error)
// UpdateByID updates the settings of an existing workspace.
UpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error)
// Delete a workspace by its name.
Delete(ctx context.Context, organization string, workspace string) error
// DeleteByID deletes a workspace by its ID.
DeleteByID(ctx context.Context, workspaceID string) error
// RemoveVCSConnection from a workspace.
RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error)
// RemoveVCSConnectionByID removes a VCS connection from a workspace.
RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error)
// Lock a workspace by its ID.
Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error)
// Unlock a workspace by its ID.
Unlock(ctx context.Context, workspaceID string) (*Workspace, error)
// ForceUnlock a workspace by its ID.
ForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error)
// AssignSSHKey to a workspace.
AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error)
// UnassignSSHKey from a workspace.
UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error)
}
// workspaces implements Workspaces.
type workspaces struct {
client *Client
}
// WorkspaceList represents a list of workspaces.
type WorkspaceList struct {
*Pagination
Items []*Workspace
}
// Workspace represents a Terraform Enterprise workspace.
type Workspace struct {
ID string `jsonapi:"primary,workspaces"`
Actions *WorkspaceActions `jsonapi:"attr,actions"`
AgentPoolID string `jsonapi:"attr,agent-pool-id"`
AllowDestroyPlan bool `jsonapi:"attr,allow-destroy-plan"`
AutoApply bool `jsonapi:"attr,auto-apply"`
CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Environment string `jsonapi:"attr,environment"`
ExecutionMode string `jsonapi:"attr,execution-mode"`
FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"`
Locked bool `jsonapi:"attr,locked"`
MigrationEnvironment string `jsonapi:"attr,migration-environment"`
Name string `jsonapi:"attr,name"`
Operations bool `jsonapi:"attr,operations"`
Permissions *WorkspacePermissions `jsonapi:"attr,permissions"`
QueueAllRuns bool `jsonapi:"attr,queue-all-runs"`
SpeculativeEnabled bool `jsonapi:"attr,speculative-enabled"`
TerraformVersion string `jsonapi:"attr,terraform-version"`
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"`
// Relations
AgentPool *AgentPool `jsonapi:"relation,agent-pool"`
CurrentRun *Run `jsonapi:"relation,current-run"`
Organization *Organization `jsonapi:"relation,organization"`
SSHKey *SSHKey `jsonapi:"relation,ssh-key"`
}
// VCSRepo contains the configuration of a VCS integration.
type VCSRepo struct {
Branch string `json:"branch"`
DisplayIdentifier string `json:"display-identifier"`
Identifier string `json:"identifier"`
IngressSubmodules bool `json:"ingress-submodules"`
OAuthTokenID string `json:"oauth-token-id"`
}
// WorkspaceActions represents the workspace actions.
type WorkspaceActions struct {
IsDestroyable bool `json:"is-destroyable"`
}
// WorkspacePermissions represents the workspace permissions.
type WorkspacePermissions struct {
CanDestroy bool `json:"can-destroy"`
CanForceUnlock bool `json:"can-force-unlock"`
CanLock bool `json:"can-lock"`
CanQueueApply bool `json:"can-queue-apply"`
CanQueueDestroy bool `json:"can-queue-destroy"`
CanQueueRun bool `json:"can-queue-run"`
CanReadSettings bool `json:"can-read-settings"`
CanUnlock bool `json:"can-unlock"`
CanUpdate bool `json:"can-update"`
CanUpdateVariable bool `json:"can-update-variable"`
}
// WorkspaceListOptions represents the options for listing workspaces.
type WorkspaceListOptions struct {
ListOptions
// A search string (partial workspace name) used to filter the results.
Search *string `url:"search[name],omitempty"`
// A list of relations to include. See available resources https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources
Include *string `url:"include"`
}
// List all the workspaces within an organization.
func (s *workspaces) List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
wl := &WorkspaceList{}
err = s.client.do(ctx, req, wl)
if err != nil {
return nil, err
}
return wl, nil
}
// WorkspaceCreateOptions represents the options for creating a new workspace.
type WorkspaceCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// Required when execution-mode is set to agent. The ID of the agent pool
// belonging to the workspace's organization. This value must not be specified
// if execution-mode is set to remote or local or if operations is set to true.
AgentPoolID *string `jsonapi:"attr,agent-pool-id,omitempty"`
// Whether destroy plans can be queued on the workspace.
AllowDestroyPlan *bool `jsonapi:"attr,allow-destroy-plan,omitempty"`
// Whether to automatically apply changes when a Terraform plan is successful.
AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"`
// Which execution mode to use. Valid values are remote, local, and agent.
// When set to local, the workspace will be used for state storage only.
// This value must not be specified if operations is specified.
// 'agent' execution mode is not available in Terraform Enterprise.
ExecutionMode *string `jsonapi:"attr,execution-mode,omitempty"`
// Whether to filter runs based on the changed files in a VCS push. If
// enabled, the working directory and trigger prefixes describe a set of
// paths which must contain changes for a VCS push to trigger a run. If
// disabled, any push will trigger a run.
FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"`
// The legacy TFE environment to use as the source of the migration, in the
// form organization/environment. Omit this unless you are migrating a legacy
// environment.
MigrationEnvironment *string `jsonapi:"attr,migration-environment,omitempty"`
// The name of the workspace, which can only include letters, numbers, -,
// and _. This will be used as an identifier and must be unique in the
// organization.
Name *string `jsonapi:"attr,name"`
// DEPRECATED. Whether the workspace will use remote or local execution mode.
// Use ExecutionMode instead.
Operations *bool `jsonapi:"attr,operations,omitempty"`
// Whether to queue all runs. Unless this is set to true, runs triggered by
// a webhook will not be queued until at least one run is manually queued.
QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"`
// Whether this workspace allows speculative plans. Setting this to false
// prevents Terraform Cloud or the Terraform Enterprise instance from
// running plans on pull requests, which can improve security if the VCS
// repository is public or includes untrusted contributors.
SpeculativeEnabled *bool `jsonapi:"attr,speculative-enabled,omitempty"`
// The version of Terraform to use for this workspace. Upon creating a
// workspace, the latest version is selected unless otherwise specified.
TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"`
// List of repository-root-relative paths which list all locations to be
// tracked for changes. See FileTriggersEnabled above for more details.
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"`
// Settings for the workspace's VCS repository. If omitted, the workspace is
// created without a VCS repo. If included, you must specify at least the
// oauth-token-id and identifier keys below.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
// A relative path that Terraform will execute within. This defaults to the
// root of your repository and is typically set to a subdirectory matching the
// environment when multiple environments exist within the same repository.
WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"`
}
// TODO: move this struct out. VCSRepoOptions is used by workspaces, policy sets, and registry modules
// VCSRepoOptions represents the configuration options of a VCS integration.
type VCSRepoOptions struct {
Branch *string `json:"branch,omitempty"`
Identifier *string `json:"identifier,omitempty"`
IngressSubmodules *bool `json:"ingress-submodules,omitempty"`
OAuthTokenID *string `json:"oauth-token-id,omitempty"`
}
func (o WorkspaceCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
if o.Operations != nil && o.ExecutionMode != nil {
return errors.New("operations is deprecated and cannot be specified when execution mode is used")
}
if o.AgentPoolID != nil && (o.ExecutionMode == nil || *o.ExecutionMode != "agent") {
return errors.New("specifying an agent pool ID requires 'agent' execution mode")
}
if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") {
return errors.New("'agent' execution mode requires an agent pool ID to be specified")
}
return nil
}
// Create is used to create a new workspace.
func (s *workspaces) Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// Read a workspace by its name.
func (s *workspaces) Read(ctx context.Context, organization, workspace string) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// ReadByID reads a workspace by its ID.
func (s *workspaces) ReadByID(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// WorkspaceUpdateOptions represents the options for updating a workspace.
type WorkspaceUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// Required when execution-mode is set to agent. The ID of the agent pool
// belonging to the workspace's organization. This value must not be specified
// if execution-mode is set to remote or local or if operations is set to true.
AgentPoolID *string `jsonapi:"attr,agent-pool-id,omitempty"`
// Whether destroy plans can be queued on the workspace.
AllowDestroyPlan *bool `jsonapi:"attr,allow-destroy-plan,omitempty"`
// Whether to automatically apply changes when a Terraform plan is successful.
AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"`
// A new name for the workspace, which can only include letters, numbers, -,
// and _. This will be used as an identifier and must be unique in the
// organization. Warning: Changing a workspace's name changes its URL in the
// API and UI.
Name *string `jsonapi:"attr,name,omitempty"`
// Which execution mode to use. Valid values are remote, local, and agent.
// When set to local, the workspace will be used for state storage only.
// This value must not be specified if operations is specified.
// 'agent' execution mode is not available in Terraform Enterprise.
ExecutionMode *string `jsonapi:"attr,execution-mode,omitempty"`
// Whether to filter runs based on the changed files in a VCS push. If
// enabled, the working directory and trigger prefixes describe a set of
// paths which must contain changes for a VCS push to trigger a run. If
// disabled, any push will trigger a run.
FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"`
// DEPRECATED. Whether the workspace will use remote or local execution mode.
// Use ExecutionMode instead.
Operations *bool `jsonapi:"attr,operations,omitempty"`
// Whether to queue all runs. Unless this is set to true, runs triggered by
// a webhook will not be queued until at least one run is manually queued.
QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"`
// Whether this workspace allows speculative plans. Setting this to false
// prevents Terraform Cloud or the Terraform Enterprise instance from
// running plans on pull requests, which can improve security if the VCS
// repository is public or includes untrusted contributors.
SpeculativeEnabled *bool `jsonapi:"attr,speculative-enabled,omitempty"`
// The version of Terraform to use for this workspace.
TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"`
// List of repository-root-relative paths which list all locations to be
// tracked for changes. See FileTriggersEnabled above for more details.
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"`
// To delete a workspace's existing VCS repo, specify null instead of an
// object. To modify a workspace's existing VCS repo, include whichever of
// the keys below you wish to modify. To add a new VCS repo to a workspace
// that didn't previously have one, include at least the oauth-token-id and
// identifier keys.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
// A relative path that Terraform will execute within. This defaults to the
// root of your repository and is typically set to a subdirectory matching
// the environment when multiple environments exist within the same
// repository.
WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"`
}
func (o WorkspaceUpdateOptions) valid() error {
if o.Name != nil && !validStringID(o.Name) {
return errors.New("invalid value for name")
}
if o.Operations != nil && o.ExecutionMode != nil {
return errors.New("operations is deprecated and cannot be specified when execution mode is used")
}
if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") {
return errors.New("'agent' execution mode requires an agent pool ID to be specified")
}
return nil
}
// Update settings of an existing workspace.
func (s *workspaces) Update(ctx context.Context, organization, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// UpdateByID updates the settings of an existing workspace.
func (s *workspaces) UpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// Delete a workspace by its name.
func (s *workspaces) Delete(ctx context.Context, organization, workspace string) error {
if !validStringID(&organization) {
return errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// DeleteByID deletes a workspace by its ID.
func (s *workspaces) DeleteByID(ctx context.Context, workspaceID string) error {
if !validStringID(&workspaceID) {
return errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// workspaceRemoveVCSConnectionOptions
type workspaceRemoveVCSConnectionOptions struct {
ID string `jsonapi:"primary,workspaces"`
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo"`
}
// RemoveVCSConnection from a workspace.
func (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
req, err := s.client.newRequest("PATCH", u, &workspaceRemoveVCSConnectionOptions{})
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// RemoveVCSConnectionByID removes a VCS connection from a workspace.
func (s *workspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("PATCH", u, &workspaceRemoveVCSConnectionOptions{})
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// WorkspaceLockOptions represents the options for locking a workspace.
type WorkspaceLockOptions struct {
// Specifies the reason for locking the workspace.
Reason *string `json:"reason,omitempty"`
}
// Lock a workspace by its ID.
func (s *workspaces) Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/lock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// Unlock a workspace by its ID.
func (s *workspaces) Unlock(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/unlock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// ForceUnlock a workspace by its ID.
func (s *workspaces) ForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/force-unlock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", u, nil)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to
// a workspace.
type WorkspaceAssignSSHKeyOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// The SSH key ID to assign.
SSHKeyID *string `jsonapi:"attr,id"`
}
func (o WorkspaceAssignSSHKeyOptions) valid() error {
if !validString(o.SSHKeyID) {
return errors.New("SSH key ID is required")
}
if !validStringID(o.SSHKeyID) {
return errors.New("invalid value for SSH key ID")
}
return nil
}
// AssignSSHKey to a workspace.
func (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("PATCH", u, &options)
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}
// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key
// to a workspace.
type workspaceUnassignSSHKeyOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// Must be nil to unset the currently assigned SSH key.
SSHKeyID *string `jsonapi:"attr,id"`
}
// UnassignSSHKey from a workspace.
func (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("PATCH", u, &workspaceUnassignSSHKeyOptions{})
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}

View File

@ -0,0 +1,6 @@
vault/plugins
dist/
*.test
pkg
bin
tmp

View File

@ -0,0 +1,32 @@
before:
hooks:
- go mod download
builds:
- main: ./cmd/vault-plugin-secrets-terraform
id: "vault-plugin-secrets-terraform"
binary: vault-plugin-secrets-terraform
env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
release:
prerelease: true
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

View File

@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View File

@ -0,0 +1,61 @@
GO_CMD?=go
CGO_ENABLED?=0
TOOL?=vault-plugin-secrets-terraform
TEST?=$$($(GO_CMD) list ./... | grep -v /vendor/ | grep -v /integ)
EXTERNAL_TOOLS=\
github.com/mitchellh/gox
BUILD_TAGS?=${TOOL}
GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor)
# bin generates the releaseable binaries for this plugin
bin: generate
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'"
default: dev
# dev creates binaries for testing Vault locally. These are put
# into ./bin/ as well as $GOPATH/bin, except for quickdev which
# is only put into /bin/
quickdev: generate
@CGO_ENABLED=0 go build -i -tags='$(BUILD_TAGS)' -o bin/${TOOL}
dev: generate
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' VAULT_DEV_BUILD=1 sh -c "'$(CURDIR)/scripts/build.sh'"
testcompile: generate
@for pkg in $(TEST) ; do \
go test -v -c -tags='$(BUILD_TAGS)' $$pkg -parallel=4 ; \
done
# test runs the unit tests and vets the code
test: generate
@if [ "$(TEST)" = "./..." ]; then \
echo "ERROR: Set TEST to a specific package"; \
exit 1; \
fi
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test -v -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=10m -parallel=4
# testacc runs acceptance tests
testacc:
@if [ "$(TEST)" = "./..." ]; then \
echo "ERROR: Set TEST to a specific package"; \
exit 1; \
fi
CGO_ENABLED=0 VAULT_ACC=1 VAULT_TOKEN= $(GO_CMD) test -tags='$(BUILD_TAGS)' $(TEST) -v $(TESTARGS) -timeout=10m
# generate runs `go generate` to build the dynamically generated
# source files.
generate:
@go generate $(go list ./... | grep -v /vendor/)
# bootstrap the build by downloading additional tools
bootstrap:
@for tool in $(EXTERNAL_TOOLS) ; do \
echo "Installing/Updating $$tool" ; \
go get -u $$tool; \
done
fmt:
gofmt -w $(GOFMT_FILES)
.PHONY: bin default generate test bootstrap fmt deps

View File

@ -0,0 +1,155 @@
# Vault Plugin: Terraform Cloud Secrets Backend [![HashiCorp](https://circleci.com/gh/hashicorp/vault-plugin-secrets-terraform.svg?style=svg)](https://circleci.com/gh/hashicorp/vault-plugin-secrets-terraform)
This is a standalone backend plugin for use with [Hashicorp
Vault](https://www.github.com/hashicorp/vault). This plugin generates revocable,
time-limited API tokens for [Terraform Cloud](https://www.terraform.io/cloud) users, as well as manages single API
tokens for Terraform teams and Organizations. Please see Terraform Cloud's
documentation on [API
Tokens](https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html)
for more information on the types of API tokens offered by the Terraform Cloud
API.
**Please note**: We take Vault's security and our users' trust very seriously.
If you believe you have found a security issue in Vault, _please responsibly
disclose_ by contacting us at
[security@hashicorp.com](mailto:security@hashicorp.com).
## Quick Links
- [Vault Website](https://www.vaultproject.io)
- [Terraform Cloud](https://www.terraform.io/cloud)
- [Terraform Cloud Secrets
Docs](https://www.vaultproject.io/docs/secrets/terraform/index.html)
- [Terraform Cloud API token
documentation](https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html)
- [Vault Github Project](https://www.github.com/hashicorp/vault)
## Getting Started
This is a [Vault
plugin](https://www.vaultproject.io/docs/internals/plugins.html) and is meant to
work with Vault. This guide assumes you have already installed Vault and have a
basic understanding of how Vault works.
Otherwise, first read this guide on how to [get started with
Vault](https://www.vaultproject.io/intro/getting-started/install.html).
To learn specifically about how plugins work, see documentation on [Vault
plugins](https://www.vaultproject.io/docs/internals/plugins.html).
## Usage
Please see [documentation for the
plugin](https://www.vaultproject.io/docs/secrets/terraform/index.html) on the
Vault website.
This plugin is currently built into Vault and by default is accessed at
`terraform`. To enable this in a running Vault server:
```sh
$ vault secrets enable terraform Success!
Enabled the terraform secrets engine at: terraform/
```
## Developing
If you wish to work on this plugin, you'll first need
[Go](https://www.golang.org) installed on your machine
(version 1.15.3+ is *required*).
For local dev first make sure Go is properly installed, including
setting up a [GOPATH](https://golang.org/doc/code.html#GOPATH).
Next, clone this repository into
`$GOPATH/src/github.com/hashicorp/vault-plugin-secrets-terraform`.
You can then download any required build tools by bootstrapping your
environment:
```sh
$ make bootstrap
```
To compile a development version of this plugin, run `make` or `make dev`.
This will put the plugin binary in the `bin` and `$GOPATH/bin` folders. `dev`
mode will only generate the binary for your platform and is faster:
```sh
$ make
$ make dev
```
Put the plugin binary into a location of your choice. This directory will be
specified as the
[`plugin_directory`](https://www.vaultproject.io/docs/configuration/index.html#plugin_directory)
in the Vault config used to start the server.
```json
...
plugin_directory = "path/to/plugin/directory"
...
```
Start a Vault server with this config file:
```sh
$ vault server -config=path/to/config.json ...
...
```
Once the server is started, register the plugin in the Vault server's [plugin
catalog](https://www.vaultproject.io/docs/internals/plugins.html#plugin-catalog):
```sh
$ vault write sys/plugins/catalog/terraform \
sha256=<expected SHA256 Hex value of the plugin binary> \
command="vault-plugin-secrets-terraform"
...
Success! Data written to: sys/plugins/catalog/terraform
```
Note you should generate a new sha256 checksum if you have made changes
to the plugin. Example using openssl:
```sh
openssl dgst -sha256 $GOPATH/vault-plugin-secrets-terraform
...
SHA256(.../go/bin/vault-plugin-secrets-terraform)= 896c13c0f5305daed381952a128322e02bc28a57d0c862a78cbc2ea66e8c6fa1
```
Enable the auth plugin backend using the secrets enable plugin command:
```sh
$ vault secrets enable -plugin-name='terraform' plugin
...
Successfully enabled 'plugin' at 'terraform'!
```
#### Tests
If you are developing this plugin and want to verify it is still
functioning (and you haven't broken anything else), we recommend
running the tests.
To run the tests, invoke `make test`:
```sh
$ make test
```
You can also specify a `TESTARGS` variable to filter tests like so:
```sh
$ make test TESTARGS='--run=TestTokenRole'
```
The tests assume environment variables are set to use in order to perform the
integration tests. Be sure to use appropriate values and be aware that real
tokens will be created and destroyed in the course of running the tests.
Environment variables:
- `TEST_TF_TOKEN`: (required) API token used to configure the engine and make API calls
- `TEST_TF_ADDRESS`: (optional) HTTP API address if using Terraform Enterprise.
Defaults to `https://app.terraform.io` for Terraform Cloud.
- `TEST_TF_ORGANIZATION`: The test organization to manage.
- `TEST_TF_TEAM_ID`: The *team ID* for the test Team to manage.
- `TEST_TF_USER_ID`: The *user ID* for the user to create dynamic tokens for.

View File

@ -0,0 +1,109 @@
package tfc
import (
"context"
"strings"
"sync"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
// Factory returns a new backend as logical.Backend
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
type tfBackend struct {
*framework.Backend
lock sync.RWMutex
client *client
}
func backend() *tfBackend {
b := tfBackend{}
b.Backend = &framework.Backend{
Help: strings.TrimSpace(backendHelp),
PathsSpecial: &logical.Paths{
LocalStorage: []string{
framework.WALPrefix,
},
SealWrapStorage: []string{
"config",
"role/*",
},
},
Paths: framework.PathAppend(
pathRole(&b),
[]*framework.Path{
pathConfig(&b),
pathCredentials(&b),
},
pathRotateRole(&b),
),
Secrets: []*framework.Secret{
b.terraformToken(),
},
BackendType: logical.TypeLogical,
Invalidate: b.invalidate,
}
return &b
}
func (b *tfBackend) reset() {
b.lock.Lock()
defer b.lock.Unlock()
b.client = nil
}
func (b *tfBackend) invalidate(ctx context.Context, key string) {
if key == "config" {
b.reset()
}
}
func (b *tfBackend) getClient(ctx context.Context, s logical.Storage) (*client, error) {
b.lock.RLock()
unlockFunc := b.lock.RUnlock
defer func() { unlockFunc() }()
if b.client != nil {
return b.client, nil
}
b.lock.RUnlock()
b.lock.Lock()
unlockFunc = b.lock.Unlock
config, err := getConfig(ctx, s)
if err != nil {
return nil, err
}
if b.client == nil {
if config == nil {
config = new(tfConfig)
}
}
b.client, err = newClient(config)
if err != nil {
return nil, err
}
return b.client, nil
}
const backendHelp = `
The Terraform Cloud secrets backend dynamically generates organization
and user tokens.
After mounting this backend, credentials to manage Terraform Cloud or
Enterprise tokens must be configured with the "config/" endpoints.
`

View File

@ -0,0 +1,38 @@
package tfc
import (
"errors"
"github.com/hashicorp/go-tfe"
)
type client struct {
*tfe.Client
}
type terraformToken struct {
ID string `json:"id"`
Description string `json:"description"`
Token string `json:"token"`
}
func newClient(config *tfConfig) (*client, error) {
if config == nil {
return nil, errors.New("client configuration was nil")
}
cfg := &tfe.Config{
Address: config.Address,
BasePath: config.BasePath,
Token: config.Token,
}
tfc, err := tfe.NewClient(cfg)
if err != nil {
return nil, err
}
return &client{
tfc,
}, nil
}

View File

@ -0,0 +1,12 @@
module github.com/hashicorp/vault-plugin-secrets-terraform
go 1.14
require (
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-tfe v0.12.0
github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f
github.com/hashicorp/vault/sdk v0.1.14-0.20210106220500-0ddc32f2ab8a
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/stretchr/testify v1.6.1
)

View File

@ -0,0 +1,410 @@
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro=
github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI=
github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc h1:lDK/G7OlwUnJW3O6nv/8M89bMupV6FuLK6FXmC3ueWc=
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182 h1:Caj/qGJ9KyulC1WSksyPgp7r8+DKgTGfU39lmb2C5MQ=
github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
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-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0 h1:xuTi5ZwjimfpvpL09jDE71smCBRpnF5xfo871BSX4gs=
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-tfe v0.12.0 h1:teL523WPxwYzL5Gjc2QFxExndrMfWY4BXS2/olVpULM=
github.com/hashicorp/go-tfe v0.12.0/go.mod h1:oT0AG5u/ROzWiw8JZFLDY6FLh6AZnJIG0Ahhvp10txg=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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/vault/api v1.0.5-0.20200519221902-385fac77e20f h1:PYtnlUZzFSZxPcq7mYp5oC9N+BcJ8IKYf6/EG0GHM2Y=
github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20210106220500-0ddc32f2ab8a h1:sNWA4c2EmEgJ5RhsgVKgMCmNS/RidljgHLOFYsLrClc=
github.com/hashicorp/vault/sdk v0.1.14-0.20210106220500-0ddc32f2ab8a/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
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.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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 v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
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.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,171 @@
package tfc
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
configStoragePath = "config"
)
type tfConfig struct {
Token string `json:"token"`
Address string `json:"address"`
BasePath string `json:"base_path"`
}
func pathConfig(b *tfBackend) *framework.Path {
return &framework.Path{
Pattern: "config",
Fields: map[string]*framework.FieldSchema{
"token": {
Type: framework.TypeString,
Description: "The token to access Terraform Cloud",
Required: true,
DisplayAttrs: &framework.DisplayAttributes{
Name: "Token",
Sensitive: true,
},
},
"address": {
Type: framework.TypeString,
Description: `The address to access Terraform Cloud or Enterprise.
Default is "https://app.terraform.io".`,
Default: "https://app.terraform.io",
},
"base_path": {
Type: framework.TypeString,
Description: `The base path for the Terraform Cloud or Enterprise API.
Default is "/api/v2/".`,
Default: "/api/v2/",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathConfigRead,
},
logical.CreateOperation: &framework.PathOperation{
Callback: b.pathConfigWrite,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathConfigWrite,
},
logical.DeleteOperation: &framework.PathOperation{
Callback: b.pathConfigDelete,
},
},
ExistenceCheck: b.pathConfigExistenceCheck,
HelpSynopsis: pathConfigHelpSynopsis,
HelpDescription: pathConfigHelpDescription,
}
}
func (b *tfBackend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
out, err := req.Storage.Get(ctx, req.Path)
if err != nil {
return false, fmt.Errorf("existence check failed: %w", err)
}
return out != nil, nil
}
func (b *tfBackend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
config, err := getConfig(ctx, req.Storage)
if err != nil {
return nil, err
}
return &logical.Response{
Data: map[string]interface{}{
"address": config.Address,
"base_path": config.BasePath,
},
}, nil
}
func (b *tfBackend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
config, err := getConfig(ctx, req.Storage)
if err != nil {
return nil, err
}
if config == nil {
if req.Operation == logical.UpdateOperation {
return nil, errors.New("config not found during update operation")
}
config = new(tfConfig)
}
address := data.Get("address").(string)
basePath := data.Get("base_path").(string)
config.Address = address
config.BasePath = basePath
token, ok := data.GetOk("token")
if ok {
config.Token = token.(string)
}
entry, err := logical.StorageEntryJSON(configStoragePath, config)
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
// reset the client so the next invocation will pick up the new configuration
b.reset()
return nil, nil
}
func (b *tfBackend) pathConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
err := req.Storage.Delete(ctx, configStoragePath)
if err == nil {
b.reset()
}
return nil, err
}
func getConfig(ctx context.Context, s logical.Storage) (*tfConfig, error) {
entry, err := s.Get(ctx, configStoragePath)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
config := new(tfConfig)
if err := entry.DecodeJSON(&config); err != nil {
return nil, fmt.Errorf("error reading root configuration: %w", err)
}
// return the config, we are done
return config, nil
}
const pathConfigHelpSynopsis = `Configure the Terraform Cloud / Enterprise backend.`
const pathConfigHelpDescription = `
The Terraform Cloud / Enterprise secret backend requires credentials for managing
organization and team tokens for Terraform Cloud or Enterprise. This endpoint
is used to configure those credentials and the default values for the backend in general.
You must specify a Terraform Cloud or Enterprise token with organization access
to allow Vault to create tokens.
If you are running Terraform Enterprise, you can specify the address and base path
for your instance and API endpoint.
`

View File

@ -0,0 +1,143 @@
package tfc
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathCredentials(b *tfBackend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeLowerCaseString,
Description: "Name of the role",
Required: true,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredentialsRead,
logical.UpdateOperation: b.pathCredentialsRead,
},
HelpSynopsis: pathCredentialsHelpSyn,
HelpDescription: pathCredentialsHelpDesc,
}
}
func (b *tfBackend) terraformToken() *framework.Secret {
return &framework.Secret{
Type: terraformTokenType,
Fields: map[string]*framework.FieldSchema{
"token": {
Type: framework.TypeString,
Description: "Terraform Token",
},
},
Revoke: b.terraformTokenRevoke,
Renew: b.terraformTokenRenew,
}
}
func (b *tfBackend) pathCredentialsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleName := d.Get("name").(string)
roleEntry, err := b.getRole(ctx, req.Storage, roleName)
if err != nil {
return nil, fmt.Errorf("error retrieving role: %w", err)
}
if roleEntry == nil {
return nil, errors.New("error retrieving role: role is nil")
}
if roleEntry.UserID != "" {
return b.createUserCreds(ctx, req, roleEntry)
}
resp := &logical.Response{
Data: map[string]interface{}{
"token_id": roleEntry.TokenID,
"token": roleEntry.Token,
"organization": roleEntry.Organization,
"team_id": roleEntry.TeamID,
"role": roleEntry.Name,
},
}
return resp, nil
}
func (b *tfBackend) createUserCreds(ctx context.Context, req *logical.Request, role *terraformRoleEntry) (*logical.Response, error) {
token, err := b.createToken(ctx, req.Storage, role)
if err != nil {
return nil, err
}
resp := b.Secret(terraformTokenType).Response(map[string]interface{}{
"token": token.Token,
"token_id": token.ID,
}, map[string]interface{}{
"token_id": token.ID,
"role": role.Name,
})
if role.TTL > 0 {
resp.Secret.TTL = role.TTL
}
if role.MaxTTL > 0 {
resp.Secret.MaxTTL = role.MaxTTL
}
return resp, nil
}
func (b *tfBackend) createToken(ctx context.Context, s logical.Storage, roleEntry *terraformRoleEntry) (*terraformToken, error) {
client, err := b.getClient(ctx, s)
if err != nil {
return nil, err
}
var token *terraformToken
switch {
case isOrgToken(roleEntry.Organization, roleEntry.TeamID):
token, err = createOrgToken(ctx, client, roleEntry.Organization)
case isTeamToken(roleEntry.TeamID):
token, err = createTeamToken(ctx, client, roleEntry.TeamID)
default:
token, err = createUserToken(ctx, client, roleEntry.UserID)
}
if err != nil {
return nil, fmt.Errorf("error creating Terraform token: %w", err)
}
if token == nil {
return nil, errors.New("error creating Terraform token")
}
return token, nil
}
const pathCredentialsHelpSyn = `
Generate a Terraform Cloud or Enterprise API token from a specific Vault role.
`
const pathCredentialsHelpDesc = `
This path generates Terraform Cloud or Enterprise API Organization, Team, or
User Tokens based on a particular role. A role can only represent a single type
of Token; Organization, Team, or User, and so can only contain one parameter for
organization, team_id, or user_id.
If the role has the team ID configured, this path generates a team token.
If this role only has the organization configured, this path generates an
organization token.
If this role has a user ID configured, this path generates a user token.
`

View File

@ -0,0 +1,276 @@
package tfc
import (
"context"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
// terraformRoleEntry is a Vault role construct that maps to TFC/TFE
type terraformRoleEntry struct {
Name string `json:"name"`
Organization string `json:"organization,omitempty"`
TeamID string `json:"team_id,omitempty"`
UserID string `json:"user_id,omitempty"`
TTL time.Duration `json:"ttl"`
MaxTTL time.Duration `json:"max_ttl"`
Token string `json:"token,omitempty"`
TokenID string `json:"token_id,omitempty"`
}
func (r *terraformRoleEntry) toResponseData() map[string]interface{} {
respData := map[string]interface{}{
"name": r.Name,
"ttl": r.TTL.Seconds(),
"max_ttl": r.MaxTTL.Seconds(),
}
if r.Organization != "" {
respData["organization"] = r.Organization
}
if r.TeamID != "" {
respData["team_id"] = r.TeamID
}
if r.UserID != "" {
respData["user_id"] = r.UserID
}
return respData
}
func pathRole(b *tfBackend) []*framework.Path {
return []*framework.Path{
{
Pattern: "role/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeLowerCaseString,
Description: "Name of the role",
Required: true,
},
"organization": {
Type: framework.TypeString,
Description: "Name of the Terraform Cloud or Enterprise organization",
},
"team_id": {
Type: framework.TypeString,
Description: "ID of the Terraform Cloud or Enterprise team under organization (e.g., settings/teams/team-xxxxxxxxxxxxx)",
},
"user_id": {
Type: framework.TypeString,
Description: "ID of the Terraform Cloud or Enterprise user (e.g., user-xxxxxxxxxxxxxxxx)",
},
"ttl": {
Type: framework.TypeDurationSecond,
Description: "Default lease for generated credentials. If not set or set to 0, will use system default.",
},
"max_ttl": {
Type: framework.TypeDurationSecond,
Description: "Maximum time for role. If not set or set to 0, will use system default.",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathRolesRead,
},
logical.CreateOperation: &framework.PathOperation{
Callback: b.pathRolesWrite,
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathRolesWrite,
},
logical.DeleteOperation: &framework.PathOperation{
Callback: b.pathRolesDelete,
},
},
HelpSynopsis: pathRoleHelpSynopsis,
HelpDescription: pathRoleHelpDescription,
},
{
Pattern: "role/?$",
Operations: map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: b.pathRolesList,
},
},
HelpSynopsis: pathRoleListHelpSynopsis,
HelpDescription: pathRoleListHelpDescription,
},
}
}
func (b *tfBackend) pathRolesList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
entries, err := req.Storage.List(ctx, "role/")
if err != nil {
return nil, err
}
return logical.ListResponse(entries), nil
}
func (b *tfBackend) pathRolesRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
entry, err := b.getRole(ctx, req.Storage, d.Get("name").(string))
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
return &logical.Response{
Data: entry.toResponseData(),
}, nil
}
func (b *tfBackend) pathRolesWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
if name == "" {
return logical.ErrorResponse("missing role name"), nil
}
roleEntry, err := b.getRole(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if roleEntry == nil {
roleEntry = &terraformRoleEntry{}
}
createOperation := (req.Operation == logical.CreateOperation)
roleEntry.Name = name
if organization, ok := d.GetOk("organization"); ok {
roleEntry.Organization = organization.(string)
} else if createOperation {
roleEntry.Organization = d.Get("organization").(string)
}
if teamID, ok := d.GetOk("team_id"); ok {
roleEntry.TeamID = teamID.(string)
} else if createOperation {
roleEntry.TeamID = d.Get("team_id").(string)
}
if userID, ok := d.GetOk("user_id"); ok {
roleEntry.UserID = userID.(string)
} else if createOperation {
roleEntry.UserID = d.Get("user_id").(string)
}
if roleEntry.UserID != "" && (roleEntry.Organization != "" || roleEntry.TeamID != "") {
return logical.ErrorResponse("cannot provide a user_id in combination with organization or team_id"), nil
}
if roleEntry.UserID == "" && roleEntry.Organization == "" && roleEntry.TeamID == "" {
return logical.ErrorResponse("must provide an organization name, team id, or user id"), nil
}
if ttlRaw, ok := d.GetOk("ttl"); ok {
roleEntry.TTL = time.Duration(ttlRaw.(int)) * time.Second
} else if req.Operation == logical.CreateOperation {
roleEntry.TTL = time.Duration(d.Get("ttl").(int)) * time.Second
}
if maxTTLRaw, ok := d.GetOk("max_ttl"); ok {
roleEntry.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second
} else if req.Operation == logical.CreateOperation {
roleEntry.MaxTTL = time.Duration(d.Get("max_ttl").(int)) * time.Second
}
if roleEntry.MaxTTL != 0 && roleEntry.TTL > roleEntry.MaxTTL {
return logical.ErrorResponse("ttl cannot be greater than max_ttl"), nil
}
// if we're creating a role to manage a Team or Organization, we need to
// create the token now. User tokens will be created when credentials are
// read.
if roleEntry.Organization != "" || roleEntry.TeamID != "" {
token, err := b.createToken(ctx, req.Storage, roleEntry)
if err != nil {
return nil, err
}
roleEntry.Token = token.Token
roleEntry.TokenID = token.ID
}
if err := setRole(ctx, req.Storage, name, roleEntry); err != nil {
return nil, err
}
return nil, nil
}
func (b *tfBackend) pathRolesDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
err := req.Storage.Delete(ctx, "role/"+d.Get("name").(string))
if err != nil {
return nil, fmt.Errorf("error deleting terraform role: %w", err)
}
return nil, nil
}
func setRole(ctx context.Context, s logical.Storage, name string, roleEntry *terraformRoleEntry) error {
entry, err := logical.StorageEntryJSON("role/"+name, roleEntry)
if err != nil {
return err
}
if entry == nil {
return fmt.Errorf("failed to create storage entry for role")
}
if err := s.Put(ctx, entry); err != nil {
return err
}
return nil
}
func (b *tfBackend) getRole(ctx context.Context, s logical.Storage, name string) (*terraformRoleEntry, error) {
if name == "" {
return nil, fmt.Errorf("missing role name")
}
entry, err := s.Get(ctx, "role/"+name)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var role terraformRoleEntry
if err := entry.DecodeJSON(&role); err != nil {
return nil, err
}
return &role, nil
}
const (
pathRoleHelpSynopsis = `Manages the Vault role for generating Terraform Cloud / Enterprise tokens.`
pathRoleHelpDescription = `
This path allows you to read and write roles used to generate Terraform Cloud /
Enterprise tokens. You can configure a role to manage an organization's token, a
team's token, or a user's dynamic tokens.
A Terraform Cloud/Enterprise Organization can only have one active token at a
time. To manage an Organization's token, set the organization field.
A Terraform Cloud/Enterprise Team can only have one active token at a time. To
manage a Teams's token, set the team_id field.
A Terraform Cloud/Enterprise User can have multiple API tokens. To manage a
User's token, set the user_id field.
`
pathRoleListHelpSynopsis = `List the existing roles in Terraform Cloud / Enterprise backend`
pathRoleListHelpDescription = `Roles will be listed by the role name.`
)

View File

@ -0,0 +1,75 @@
package tfc
import (
"context"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathRotateRole(b *tfBackend) []*framework.Path {
return []*framework.Path{
{
Pattern: "rotate-role/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the team or organization role",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathRotateRole,
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},
HelpSynopsis: pathRotateRoleHelpSyn,
HelpDescription: pathRotateRoleHelpDesc,
},
}
}
func (b *tfBackend) pathRotateRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
if name == "" {
return logical.ErrorResponse("missing role name"), nil
}
roleEntry, err := b.getRole(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse("missing role entry"), nil
}
if roleEntry.UserID != "" {
return logical.ErrorResponse("cannot rotate credentials for user roles"), nil
}
token, err := b.createToken(ctx, req.Storage, roleEntry)
if err != nil {
return nil, err
}
roleEntry.Token = token.Token
if err := setRole(ctx, req.Storage, name, roleEntry); err != nil {
return nil, err
}
return nil, nil
}
const pathRotateRoleHelpSyn = `
Request to rotate the credentials for a team or organization.
`
const pathRotateRoleHelpDesc = `
This path attempts to rotate the credentials for the given team or organization role.
This endpoint returns an error if attempting to rotate a user role.
`

View File

@ -0,0 +1,160 @@
package tfc
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/go-tfe"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
terraformTokenType = "terraform_token"
)
func isOrgToken(organization string, team string) bool {
return organization != "" && team == ""
}
func isTeamToken(team string) bool {
return team != ""
}
func createOrgToken(ctx context.Context, c *client, organization string) (*terraformToken, error) {
if _, err := c.Organizations.Read(ctx, organization); err != nil {
return nil, err
}
token, err := c.OrganizationTokens.Generate(ctx, organization)
if err != nil {
return nil, err
}
return &terraformToken{
ID: token.ID,
Description: token.Description,
Token: token.Token,
}, nil
}
func createTeamToken(ctx context.Context, c *client, teamID string) (*terraformToken, error) {
if _, err := c.Teams.Read(ctx, teamID); err != nil {
return nil, err
}
token, err := c.TeamTokens.Generate(ctx, teamID)
if err != nil {
return nil, err
}
return &terraformToken{
ID: token.ID,
Description: token.Description,
Token: token.Token,
}, nil
}
func createUserToken(ctx context.Context, c *client, userID string) (*terraformToken, error) {
token, err := c.UserTokens.Generate(ctx, userID, tfe.UserTokenGenerateOptions{})
if err != nil {
return nil, err
}
return &terraformToken{
ID: token.ID,
Description: token.Description,
Token: token.Token,
}, nil
}
func (b *tfBackend) terraformTokenRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
client, err := b.getClient(ctx, req.Storage)
if err != nil {
return nil, fmt.Errorf("error getting client: %w", err)
}
organization := ""
organizationRaw, ok := req.Secret.InternalData["organization"]
if ok {
organization, ok = organizationRaw.(string)
if !ok {
return nil, fmt.Errorf("invalid value for organization in secret internal data")
}
}
teamID := ""
teamIDRaw, ok := req.Secret.InternalData["team_id"]
if ok {
teamID, ok = teamIDRaw.(string)
if !ok {
return nil, fmt.Errorf("invalid value for team_id in secret internal data")
}
}
if isOrgToken(organization, teamID) {
// revoke org API token
if err := client.OrganizationTokens.Delete(ctx, organization); err != nil {
return nil, fmt.Errorf("error revoking organization token: %w", err)
}
return nil, nil
}
if isTeamToken(teamID) {
// revoke team API token
if err := client.TeamTokens.Delete(ctx, teamID); err != nil {
return nil, fmt.Errorf("error revoking team token: %w", err)
}
return nil, nil
}
// if we haven't returned yet, then the token is a user API token
tokenID := ""
tokenIDRaw, ok := req.Secret.InternalData["token_id"]
if ok {
tokenID, ok = tokenIDRaw.(string)
if !ok {
return nil, fmt.Errorf("secret is missing tokenID internal data")
}
}
if tokenID == "" {
return nil, fmt.Errorf("secret is missing tokenID internal data")
}
if err := client.UserTokens.Delete(ctx, tokenID); err != nil {
return nil, fmt.Errorf("error revoking user token: %w", err)
}
return nil, nil
}
func (b *tfBackend) terraformTokenRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleRaw, ok := req.Secret.InternalData["role"]
if !ok {
return nil, fmt.Errorf("secret is missing role internal data")
}
// get the role entry
role := roleRaw.(string)
roleEntry, err := b.getRole(ctx, req.Storage, role)
if err != nil {
return nil, fmt.Errorf("error retrieving role: %w", err)
}
if roleEntry == nil {
return nil, errors.New("error retrieving role: role is nil")
}
resp := &logical.Response{Secret: req.Secret}
if roleEntry.TTL > 0 {
resp.Secret.TTL = roleEntry.TTL
}
if roleEntry.MaxTTL > 0 {
resp.Secret.MaxTTL = roleEntry.MaxTTL
}
return resp, nil
}

1
vendor/github.com/svanharmelen/jsonapi/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/examples/examples

7
vendor/github.com/svanharmelen/jsonapi/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.8.x
- 1.9.x
- 1.10.x
- tip
script: go test ./... -v

21
vendor/github.com/svanharmelen/jsonapi/LICENSE generated vendored Normal file
View File

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

457
vendor/github.com/svanharmelen/jsonapi/README.md generated vendored Normal file
View File

@ -0,0 +1,457 @@
# jsonapi
[![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi)
[![Go Report Card](https://goreportcard.com/badge/github.com/google/jsonapi)](https://goreportcard.com/report/github.com/google/jsonapi)
[![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi)
A serializer/deserializer for JSON payloads that comply to the
[JSON API - jsonapi.org](http://jsonapi.org) spec in go.
## Installation
```
go get -u github.com/google/jsonapi
```
Or, see [Alternative Installation](#alternative-installation).
## Background
You are working in your Go web application and you have a struct that is
organized similarly to your database schema. You need to send and
receive json payloads that adhere to the JSON API spec. Once you realize that
your json needed to take on this special form, you go down the path of
creating more structs to be able to serialize and deserialize JSON API
payloads. Then there are more models required with this additional
structure. Ugh! With JSON API, you can keep your model structs as is and
use [StructTags](http://golang.org/pkg/reflect/#StructTag) to indicate
to JSON API how you want your response built or your request
deserialized. What about your relationships? JSON API supports
relationships out of the box and will even put them in your response
into an `included` side-loaded slice--that contains associated records.
## Introduction
JSON API uses [StructField](http://golang.org/pkg/reflect/#StructField)
tags to annotate the structs fields that you already have and use in
your app and then reads and writes [JSON API](http://jsonapi.org)
output based on the instructions you give the library in your JSON API
tags. Let's take an example. In your app, you most likely have structs
that look similar to these:
```go
type Blog struct {
ID int `json:"id"`
Title string `json:"title"`
Posts []*Post `json:"posts"`
CurrentPost *Post `json:"current_post"`
CurrentPostId int `json:"current_post_id"`
CreatedAt time.Time `json:"created_at"`
ViewCount int `json:"view_count"`
}
type Post struct {
ID int `json:"id"`
BlogID int `json:"blog_id"`
Title string `json:"title"`
Body string `json:"body"`
Comments []*Comment `json:"comments"`
}
type Comment struct {
Id int `json:"id"`
PostID int `json:"post_id"`
Body string `json:"body"`
Likes uint `json:"likes_count,omitempty"`
}
```
These structs may or may not resemble the layout of your database. But
these are the ones that you want to use right? You wouldn't want to use
structs like those that JSON API sends because it is difficult to get at
all of your data easily.
## Example App
[examples/app.go](https://github.com/google/jsonapi/blob/master/examples/app.go)
This program demonstrates the implementation of a create, a show,
and a list [http.Handler](http://golang.org/pkg/net/http#Handler). It
outputs some example requests and responses as well as serialized
examples of the source/target structs to json. That is to say, I show
you that the library has successfully taken your JSON API request and
turned it into your struct types.
To run,
* Make sure you have [Go installed](https://golang.org/doc/install)
* Create the following directories or similar: `~/go`
* Set `GOPATH` to `PWD` in your shell session, `export GOPATH=$PWD`
* `go get github.com/google/jsonapi`. (Append `-u` after `get` if you
are updating.)
* `cd $GOPATH/src/github.com/google/jsonapi/examples`
* `go build && ./examples`
## `jsonapi` Tag Reference
### Example
The `jsonapi` [StructTags](http://golang.org/pkg/reflect/#StructTag)
tells this library how to marshal and unmarshal your structs into
JSON API payloads and your JSON API payloads to structs, respectively.
Then Use JSON API's Marshal and Unmarshal methods to construct and read
your responses and replies. Here's an example of the structs above
using JSON API tags:
```go
type Blog struct {
ID int `jsonapi:"primary,blogs"`
Title string `jsonapi:"attr,title"`
Posts []*Post `jsonapi:"relation,posts"`
CurrentPost *Post `jsonapi:"relation,current_post"`
CurrentPostID int `jsonapi:"attr,current_post_id"`
CreatedAt time.Time `jsonapi:"attr,created_at"`
ViewCount int `jsonapi:"attr,view_count"`
}
type Post struct {
ID int `jsonapi:"primary,posts"`
BlogID int `jsonapi:"attr,blog_id"`
Title string `jsonapi:"attr,title"`
Body string `jsonapi:"attr,body"`
Comments []*Comment `jsonapi:"relation,comments"`
}
type Comment struct {
ID int `jsonapi:"primary,comments"`
PostID int `jsonapi:"attr,post_id"`
Body string `jsonapi:"attr,body"`
Likes uint `jsonapi:"attr,likes-count,omitempty"`
}
```
### Permitted Tag Values
#### `primary`
```
`jsonapi:"primary,<type field output>"`
```
This indicates this is the primary key field for this struct type.
Tag value arguments are comma separated. The first argument must be,
`primary`, and the second must be the name that should appear in the
`type`\* field for all data objects that represent this type of model.
\* According the [JSON API](http://jsonapi.org) spec, the plural record
types are shown in the examples, but not required.
#### `attr`
```
`jsonapi:"attr,<key name in attributes hash>,<optional: omitempty>"`
```
These fields' values will end up in the `attributes`hash for a record.
The first argument must be, `attr`, and the second should be the name
for the key to display in the `attributes` hash for that record. The optional
third argument is `omitempty` - if it is present the field will not be present
in the `"attributes"` if the field's value is equivalent to the field types
empty value (ie if the `count` field is of type `int`, `omitempty` will omit the
field when `count` has a value of `0`). Lastly, the spec indicates that
`attributes` key names should be dasherized for multiple word field names.
#### `relation`
```
`jsonapi:"relation,<key name in relationships hash>,<optional: omitempty>"`
```
Relations are struct fields that represent a one-to-one or one-to-many
relationship with other structs. JSON API will traverse the graph of
relationships and marshal or unmarshal records. The first argument must
be, `relation`, and the second should be the name of the relationship,
used as the key in the `relationships` hash for the record. The optional
third argument is `omitempty` - if present will prevent non existent to-one and
to-many from being serialized.
## Methods Reference
**All `Marshal` and `Unmarshal` methods expect pointers to struct
instance or slices of the same contained with the `interface{}`s**
Now you have your structs prepared to be seralized or materialized, What
about the rest?
### Create Record Example
You can Unmarshal a JSON API payload using
[jsonapi.UnmarshalPayload](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload).
It reads from an [io.Reader](https://golang.org/pkg/io/#Reader)
containing a JSON API payload for one record (but can have related
records). Then, it materializes a struct that you created and passed in
(using new or &). Again, the method supports single records only, at
the top level, in request payloads at the moment. Bulk creates and
updates are not supported yet.
After saving your record, you can use,
[MarshalOnePayload](http://godoc.org/github.com/google/jsonapi#MarshalOnePayload),
to write the JSON API response to an
[io.Writer](https://golang.org/pkg/io/#Writer).
#### `UnmarshalPayload`
```go
UnmarshalPayload(in io.Reader, model interface{})
```
Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload)
#### `MarshalPayload`
```go
MarshalPayload(w io.Writer, models interface{}) error
```
Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalPayload)
Writes a JSON API response, with related records sideloaded, into an
`included` array. This method encodes a response for either a single record or
many records.
##### Handler Example Code
```go
func CreateBlog(w http.ResponseWriter, r *http.Request) {
blog := new(Blog)
if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ...save your blog...
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
if err := jsonapi.MarshalPayload(w, blog); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
```
### Create Records Example
#### `UnmarshalManyPayload`
```go
UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)
```
Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalManyPayload)
Takes an `io.Reader` and a `reflect.Type` representing the uniform type
contained within the `"data"` JSON API member.
##### Handler Example Code
```go
func CreateBlogs(w http.ResponseWriter, r *http.Request) {
// ...create many blogs at once
blogs, err := UnmarshalManyPayload(r.Body, reflect.TypeOf(new(Blog)))
if err != nil {
t.Fatal(err)
}
for _, blog := range blogs {
b, ok := blog.(*Blog)
// ...save each of your blogs
}
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
if err := jsonapi.MarshalPayload(w, blogs); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
```
### Links
If you need to include [link objects](http://jsonapi.org/format/#document-links) along with response data, implement the `Linkable` interface for document-links, and `RelationshipLinkable` for relationship links:
```go
func (post Post) JSONAPILinks() *Links {
return &Links{
"self": "href": fmt.Sprintf("https://example.com/posts/%d", post.ID),
"comments": Link{
Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", post.ID),
Meta: map[string]interface{}{
"counts": map[string]uint{
"likes": 4,
},
},
},
}
}
// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipLinks(relation string) *Links {
if relation == "comments" {
return &Links{
"related": fmt.Sprintf("https://example.com/posts/%d/comments", post.ID),
}
}
return nil
}
```
### Meta
If you need to include [meta objects](http://jsonapi.org/format/#document-meta) along with response data, implement the `Metable` interface for document-meta, and `RelationshipMetable` for relationship meta:
```go
func (post Post) JSONAPIMeta() *Meta {
return &Meta{
"details": "sample details here",
}
}
// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
if relation == "comments" {
return &Meta{
"this": map[string]interface{}{
"can": map[string]interface{}{
"go": []interface{}{
"as",
"deep",
map[string]interface{}{
"as": "required",
},
},
},
},
}
}
return nil
}
```
### Errors
This package also implements support for JSON API compatible `errors` payloads using the following types.
#### `MarshalErrors`
```go
MarshalErrors(w io.Writer, errs []*ErrorObject) error
```
Writes a JSON API response using the given `[]error`.
#### `ErrorsPayload`
```go
type ErrorsPayload struct {
Errors []*ErrorObject `json:"errors"`
}
```
ErrorsPayload is a serializer struct for representing a valid JSON API errors payload.
#### `ErrorObject`
```go
type ErrorObject struct { ... }
// Error implements the `Error` interface.
func (e *ErrorObject) Error() string {
return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail)
}
```
ErrorObject is an `Error` implementation as well as an implementation of the JSON API error object.
The main idea behind this struct is that you can use it directly in your code as an error type and pass it directly to `MarshalErrors` to get a valid JSON API errors payload.
##### Errors Example Code
```go
// An error has come up in your code, so set an appropriate status, and serialize the error.
if err := validate(&myStructToValidate); err != nil {
context.SetStatusCode(http.StatusBadRequest) // Or however you need to set a status.
jsonapi.MarshalErrors(w, []*ErrorObject{{
Title: "Validation Error",
Detail: "Given request body was invalid.",
Status: "400",
Meta: map[string]interface{}{"field": "some_field", "error": "bad type", "expected": "string", "received": "float64"},
}})
return
}
```
## Testing
### `MarshalOnePayloadEmbedded`
```go
MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error
```
Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalOnePayloadEmbedded)
This method is not strictly meant to for use in implementation code,
although feel free. It was mainly created for use in tests; in most cases,
your request payloads for create will be embedded rather than sideloaded
for related records. This method will serialize a single struct pointer
into an embedded json response. In other words, there will be no,
`included`, array in the json; all relationships will be serialized
inline with the data.
However, in tests, you may want to construct payloads to post to create
methods that are embedded to most closely model the payloads that will
be produced by the client. This method aims to enable that.
### Example
```go
out := bytes.NewBuffer(nil)
// testModel returns a pointer to a Blog
jsonapi.MarshalOnePayloadEmbedded(out, testModel())
h := new(BlogsHandler)
w := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/blogs", out)
h.CreateBlog(w, r)
blog := new(Blog)
jsonapi.UnmarshalPayload(w.Body, blog)
// ... assert stuff about blog here ...
```
## Alternative Installation
I use git subtrees to manage dependencies rather than `go get` so that
the src is committed to my repo.
```
git subtree add --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
```
To update,
```
git subtree pull --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
```
This assumes that I have my repo structured with a `src` dir containing
a collection of packages and `GOPATH` is set to the root
folder--containing `src`.
## Contributing
Fork, Change, Pull Request *with tests*.

55
vendor/github.com/svanharmelen/jsonapi/constants.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
package jsonapi
const (
// StructTag annotation strings
annotationJSONAPI = "jsonapi"
annotationPrimary = "primary"
annotationClientID = "client-id"
annotationAttribute = "attr"
annotationRelation = "relation"
annotationOmitEmpty = "omitempty"
annotationISO8601 = "iso8601"
annotationSeperator = ","
iso8601TimeFormat = "2006-01-02T15:04:05Z"
// MediaType is the identifier for the JSON API media type
//
// see http://jsonapi.org/format/#document-structure
MediaType = "application/vnd.api+json"
// Pagination Constants
//
// http://jsonapi.org/format/#fetching-pagination
// KeyFirstPage is the key to the links object whose value contains a link to
// the first page of data
KeyFirstPage = "first"
// KeyLastPage is the key to the links object whose value contains a link to
// the last page of data
KeyLastPage = "last"
// KeyPreviousPage is the key to the links object whose value contains a link
// to the previous page of data
KeyPreviousPage = "prev"
// KeyNextPage is the key to the links object whose value contains a link to
// the next page of data
KeyNextPage = "next"
// QueryParamPageNumber is a JSON API query parameter used in a page based
// pagination strategy in conjunction with QueryParamPageSize
QueryParamPageNumber = "page[number]"
// QueryParamPageSize is a JSON API query parameter used in a page based
// pagination strategy in conjunction with QueryParamPageNumber
QueryParamPageSize = "page[size]"
// QueryParamPageOffset is a JSON API query parameter used in an offset based
// pagination strategy in conjunction with QueryParamPageLimit
QueryParamPageOffset = "page[offset]"
// QueryParamPageLimit is a JSON API query parameter used in an offset based
// pagination strategy in conjunction with QueryParamPageOffset
QueryParamPageLimit = "page[limit]"
// QueryParamPageCursor is a JSON API query parameter used with a cursor-based
// strategy
QueryParamPageCursor = "page[cursor]"
)

70
vendor/github.com/svanharmelen/jsonapi/doc.go generated vendored Normal file
View File

@ -0,0 +1,70 @@
/*
Package jsonapi provides a serializer and deserializer for jsonapi.org spec payloads.
You can keep your model structs as is and use struct field tags to indicate to jsonapi
how you want your response built or your request deserialzied. What about my relationships?
jsonapi supports relationships out of the box and will even side load them in your response
into an "included" array--that contains associated objects.
jsonapi uses StructField tags to annotate the structs fields that you already have and use
in your app and then reads and writes jsonapi.org output based on the instructions you give
the library in your jsonapi tags.
Example structs using a Blog > Post > Comment structure,
type Blog struct {
ID int `jsonapi:"primary,blogs"`
Title string `jsonapi:"attr,title"`
Posts []*Post `jsonapi:"relation,posts"`
CurrentPost *Post `jsonapi:"relation,current_post"`
CurrentPostID int `jsonapi:"attr,current_post_id"`
CreatedAt time.Time `jsonapi:"attr,created_at"`
ViewCount int `jsonapi:"attr,view_count"`
}
type Post struct {
ID int `jsonapi:"primary,posts"`
BlogID int `jsonapi:"attr,blog_id"`
Title string `jsonapi:"attr,title"`
Body string `jsonapi:"attr,body"`
Comments []*Comment `jsonapi:"relation,comments"`
}
type Comment struct {
ID int `jsonapi:"primary,comments"`
PostID int `jsonapi:"attr,post_id"`
Body string `jsonapi:"attr,body"`
}
jsonapi Tag Reference
Value, primary: "primary,<type field output>"
This indicates that this is the primary key field for this struct type. Tag
value arguments are comma separated. The first argument must be, "primary", and
the second must be the name that should appear in the "type" field for all data
objects that represent this type of model.
Value, attr: "attr,<key name in attributes hash>[,<extra arguments>]"
These fields' values should end up in the "attribute" hash for a record. The first
argument must be, "attr', and the second should be the name for the key to display in
the the "attributes" hash for that record.
The following extra arguments are also supported:
"omitempty": excludes the fields value from the "attribute" hash.
"iso8601": uses the ISO8601 timestamp format when serialising or deserialising the time.Time value.
Value, relation: "relation,<key name in relationships hash>"
Relations are struct fields that represent a one-to-one or one-to-many to other structs.
jsonapi will traverse the graph of relationships and marshal or unmarshal records. The first
argument must be, "relation", and the second should be the name of the relationship, used as
the key in the "relationships" hash for the record.
Use the methods below to Marshal and Unmarshal jsonapi.org json payloads.
Visit the readme at https://github.com/google/jsonapi
*/
package jsonapi

55
vendor/github.com/svanharmelen/jsonapi/errors.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
package jsonapi
import (
"encoding/json"
"fmt"
"io"
)
// MarshalErrors writes a JSON API response using the given `[]error`.
//
// For more information on JSON API error payloads, see the spec here:
// http://jsonapi.org/format/#document-top-level
// and here: http://jsonapi.org/format/#error-objects.
func MarshalErrors(w io.Writer, errorObjects []*ErrorObject) error {
if err := json.NewEncoder(w).Encode(&ErrorsPayload{Errors: errorObjects}); err != nil {
return err
}
return nil
}
// ErrorsPayload is a serializer struct for representing a valid JSON API errors payload.
type ErrorsPayload struct {
Errors []*ErrorObject `json:"errors"`
}
// ErrorObject is an `Error` implementation as well as an implementation of the JSON API error object.
//
// The main idea behind this struct is that you can use it directly in your code as an error type
// and pass it directly to `MarshalErrors` to get a valid JSON API errors payload.
// For more information on Golang errors, see: https://golang.org/pkg/errors/
// For more information on the JSON API spec's error objects, see: http://jsonapi.org/format/#error-objects
type ErrorObject struct {
// ID is a unique identifier for this particular occurrence of a problem.
ID string `json:"id,omitempty"`
// Title is a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
Title string `json:"title,omitempty"`
// Detail is a human-readable explanation specific to this occurrence of the problem. Like title, this fields value can be localized.
Detail string `json:"detail,omitempty"`
// Status is the HTTP status code applicable to this problem, expressed as a string value.
Status string `json:"status,omitempty"`
// Code is an application-specific error code, expressed as a string value.
Code string `json:"code,omitempty"`
// Meta is an object containing non-standard meta-information about the error.
Meta *map[string]interface{} `json:"meta,omitempty"`
}
// Error implements the `Error` interface.
func (e *ErrorObject) Error() string {
return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail)
}

121
vendor/github.com/svanharmelen/jsonapi/node.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
package jsonapi
import "fmt"
// Payloader is used to encapsulate the One and Many payload types
type Payloader interface {
clearIncluded()
}
// OnePayload is used to represent a generic JSON API payload where a single
// resource (Node) was included as an {} in the "data" key
type OnePayload struct {
Data *Node `json:"data"`
Included []*Node `json:"included,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
func (p *OnePayload) clearIncluded() {
p.Included = []*Node{}
}
// ManyPayload is used to represent a generic JSON API payload where many
// resources (Nodes) were included in an [] in the "data" key
type ManyPayload struct {
Data []*Node `json:"data"`
Included []*Node `json:"included,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
func (p *ManyPayload) clearIncluded() {
p.Included = []*Node{}
}
// Node is used to represent a generic JSON API Resource
type Node struct {
Type string `json:"type"`
ID string `json:"id,omitempty"`
ClientID string `json:"client-id,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
Relationships map[string]interface{} `json:"relationships,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
// RelationshipOneNode is used to represent a generic has one JSON API relation
type RelationshipOneNode struct {
Data *Node `json:"data"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
// RelationshipManyNode is used to represent a generic has many JSON API
// relation
type RelationshipManyNode struct {
Data []*Node `json:"data"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
// Links is used to represent a `links` object.
// http://jsonapi.org/format/#document-links
type Links map[string]interface{}
func (l *Links) validate() (err error) {
// Each member of a links object is a “link”. A link MUST be represented as
// either:
// - a string containing the links URL.
// - an object (“link object”) which can contain the following members:
// - href: a string containing the links URL.
// - meta: a meta object containing non-standard meta-information about the
// link.
for k, v := range *l {
_, isString := v.(string)
_, isLink := v.(Link)
if !(isString || isLink) {
return fmt.Errorf(
"The %s member of the links object was not a string or link object",
k,
)
}
}
return
}
// Link is used to represent a member of the `links` object.
type Link struct {
Href string `json:"href"`
Meta Meta `json:"meta,omitempty"`
}
// Linkable is used to include document links in response data
// e.g. {"self": "http://example.com/posts/1"}
type Linkable interface {
JSONAPILinks() *Links
}
// RelationshipLinkable is used to include relationship links in response data
// e.g. {"related": "http://example.com/posts/1/comments"}
type RelationshipLinkable interface {
// JSONAPIRelationshipLinks will be invoked for each relationship with the corresponding relation name (e.g. `comments`)
JSONAPIRelationshipLinks(relation string) *Links
}
// Meta is used to represent a `meta` object.
// http://jsonapi.org/format/#document-meta
type Meta map[string]interface{}
// Metable is used to include document meta in response data
// e.g. {"foo": "bar"}
type Metable interface {
JSONAPIMeta() *Meta
}
// RelationshipMetable is used to include relationship meta in response data
type RelationshipMetable interface {
// JSONRelationshipMeta will be invoked for each relationship with the corresponding relation name (e.g. `comments`)
JSONAPIRelationshipMeta(relation string) *Meta
}

680
vendor/github.com/svanharmelen/jsonapi/request.go generated vendored Normal file
View File

@ -0,0 +1,680 @@
package jsonapi
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
const (
unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s"
)
var (
// ErrInvalidTime is returned when a struct has a time.Time type field, but
// the JSON value was not a unix timestamp integer.
ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps")
// ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes
// "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string.
ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps")
// ErrUnknownFieldNumberType is returned when the JSON value was a float
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
// float, etc)
ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type")
// ErrInvalidType is returned when the given type is incompatible with the expected type.
ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation.
)
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
// the JSON value was of a different type
type ErrUnsupportedPtrType struct {
rf reflect.Value
t reflect.Type
structField reflect.StructField
}
func (eupt ErrUnsupportedPtrType) Error() string {
typeName := eupt.t.Elem().Name()
kind := eupt.t.Elem().Kind()
if kind.String() != "" && kind.String() != typeName {
typeName = fmt.Sprintf("%s (%s)", typeName, kind.String())
}
return fmt.Sprintf(
"jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`",
eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName,
)
}
func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error {
return ErrUnsupportedPtrType{rf, t, structField}
}
// UnmarshalPayload converts an io into a struct instance using jsonapi tags on
// struct fields. This method supports single request payloads only, at the
// moment. Bulk creates and updates are not supported yet.
//
// Will Unmarshal embedded and sideloaded payloads. The latter is only possible if the
// object graph is complete. That is, in the "relationships" data there are type and id,
// keys that correspond to records in the "included" array.
//
// For example you could pass it, in, req.Body and, model, a BlogPost
// struct instance to populate in an http handler,
//
// func CreateBlog(w http.ResponseWriter, r *http.Request) {
// blog := new(Blog)
//
// if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
// http.Error(w, err.Error(), 500)
// return
// }
//
// // ...do stuff with your blog...
//
// w.Header().Set("Content-Type", jsonapi.MediaType)
// w.WriteHeader(201)
//
// if err := jsonapi.MarshalPayload(w, blog); err != nil {
// http.Error(w, err.Error(), 500)
// }
// }
//
//
// Visit https://github.com/google/jsonapi#create for more info.
//
// model interface{} should be a pointer to a struct.
func UnmarshalPayload(in io.Reader, model interface{}) error {
payload := new(OnePayload)
if err := json.NewDecoder(in).Decode(payload); err != nil {
return err
}
if payload.Included != nil {
includedMap := make(map[string]*Node)
for _, included := range payload.Included {
key := fmt.Sprintf("%s,%s", included.Type, included.ID)
includedMap[key] = included
}
return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap)
}
return unmarshalNode(payload.Data, reflect.ValueOf(model), nil)
}
// UnmarshalManyPayload converts an io into a set of struct instances using
// jsonapi tags on the type's struct fields.
func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) {
payload := new(ManyPayload)
if err := json.NewDecoder(in).Decode(payload); err != nil {
return nil, err
}
models := []interface{}{} // will be populated from the "data"
includedMap := map[string]*Node{} // will be populate from the "included"
if payload.Included != nil {
for _, included := range payload.Included {
key := fmt.Sprintf("%s,%s", included.Type, included.ID)
includedMap[key] = included
}
}
for _, data := range payload.Data {
model := reflect.New(t.Elem())
err := unmarshalNode(data, model, &includedMap)
if err != nil {
return nil, err
}
models = append(models, model.Interface())
}
return models, nil
}
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type())
}
}()
modelValue := model.Elem()
modelType := model.Type().Elem()
var er error
for i := 0; i < modelValue.NumField(); i++ {
fieldType := modelType.Field(i)
tag := fieldType.Tag.Get("jsonapi")
if tag == "" {
continue
}
fieldValue := modelValue.Field(i)
args := strings.Split(tag, ",")
if len(args) < 1 {
er = ErrBadJSONAPIStructTag
break
}
annotation := args[0]
if (annotation == annotationClientID && len(args) != 1) ||
(annotation != annotationClientID && len(args) < 2) {
er = ErrBadJSONAPIStructTag
break
}
if annotation == annotationPrimary {
if data.ID == "" {
continue
}
// Check the JSON API Type
if data.Type != args[1] {
er = fmt.Errorf(
"Trying to Unmarshal an object of type %#v, but %#v does not match",
data.Type,
args[1],
)
break
}
// ID will have to be transmitted as astring per the JSON API spec
v := reflect.ValueOf(data.ID)
// Deal with PTRS
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Type.Elem().Kind()
} else {
kind = fieldType.Type.Kind()
}
// Handle String case
if kind == reflect.String {
assign(fieldValue, v)
continue
}
// Value was not a string... only other supported type was a numeric,
// which would have been sent as a float value.
floatValue, err := strconv.ParseFloat(data.ID, 64)
if err != nil {
// Could not convert the value in the "id" attr to a float
er = ErrBadJSONAPIID
break
}
// Convert the numeric float to one of the supported ID numeric types
// (int[8,16,32,64] or uint[8,16,32,64])
var idValue reflect.Value
switch kind {
case reflect.Int:
n := int(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Int8:
n := int8(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Int16:
n := int16(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Int32:
n := int32(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Int64:
n := int64(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Uint:
n := uint(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Uint8:
n := uint8(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Uint16:
n := uint16(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Uint32:
n := uint32(floatValue)
idValue = reflect.ValueOf(&n)
case reflect.Uint64:
n := uint64(floatValue)
idValue = reflect.ValueOf(&n)
default:
// We had a JSON float (numeric), but our field was not one of the
// allowed numeric types
er = ErrBadJSONAPIID
break
}
assign(fieldValue, idValue)
} else if annotation == annotationClientID {
if data.ClientID == "" {
continue
}
fieldValue.Set(reflect.ValueOf(data.ClientID))
} else if annotation == annotationAttribute {
attributes := data.Attributes
if attributes == nil || len(data.Attributes) == 0 {
continue
}
attribute := attributes[args[1]]
// continue if the attribute was not included in the request
if attribute == nil {
continue
}
structField := fieldType
value, err := unmarshalAttribute(attribute, args, structField, fieldValue)
if err != nil {
er = err
break
}
assign(fieldValue, value)
continue
} else if annotation == annotationRelation {
isSlice := fieldValue.Type().Kind() == reflect.Slice
if data.Relationships == nil || data.Relationships[args[1]] == nil {
continue
}
if isSlice {
// to-many relationship
relationship := new(RelationshipManyNode)
buf := bytes.NewBuffer(nil)
json.NewEncoder(buf).Encode(data.Relationships[args[1]])
json.NewDecoder(buf).Decode(relationship)
data := relationship.Data
models := reflect.New(fieldValue.Type()).Elem()
for _, n := range data {
m := reflect.New(fieldValue.Type().Elem().Elem())
if err := unmarshalNode(
fullNode(n, included),
m,
included,
); err != nil {
er = err
break
}
models = reflect.Append(models, m)
}
fieldValue.Set(models)
} else {
// to-one relationships
relationship := new(RelationshipOneNode)
buf := bytes.NewBuffer(nil)
json.NewEncoder(buf).Encode(
data.Relationships[args[1]],
)
json.NewDecoder(buf).Decode(relationship)
/*
http://jsonapi.org/format/#document-resource-object-relationships
http://jsonapi.org/format/#document-resource-object-linkage
relationship can have a data node set to null (e.g. to disassociate the relationship)
so unmarshal and set fieldValue only if data obj is not null
*/
if relationship.Data == nil {
continue
}
m := reflect.New(fieldValue.Type().Elem())
if err := unmarshalNode(
fullNode(relationship.Data, included),
m,
included,
); err != nil {
er = err
break
}
fieldValue.Set(m)
}
} else {
er = fmt.Errorf(unsuportedStructTagMsg, annotation)
}
}
return er
}
func fullNode(n *Node, included *map[string]*Node) *Node {
includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID)
if included != nil && (*included)[includedKey] != nil {
return (*included)[includedKey]
}
return n
}
// assign will take the value specified and assign it to the field; if
// field is expecting a ptr assign will assign a ptr.
func assign(field, value reflect.Value) {
value = reflect.Indirect(value)
if field.Kind() == reflect.Ptr {
// initialize pointer so it's value
// can be set by assignValue
field.Set(reflect.New(field.Type().Elem()))
assignValue(field.Elem(), value)
} else {
assignValue(field, value)
}
}
// assign assigns the specified value to the field,
// expecting both values not to be pointer types.
func assignValue(field, value reflect.Value) {
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
field.SetInt(value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
field.SetUint(value.Uint())
case reflect.Float32, reflect.Float64:
field.SetFloat(value.Float())
case reflect.String:
field.SetString(value.String())
case reflect.Bool:
field.SetBool(value.Bool())
default:
field.Set(value)
}
}
func unmarshalAttribute(
attribute interface{},
args []string,
structField reflect.StructField,
fieldValue reflect.Value) (value reflect.Value, err error) {
value = reflect.ValueOf(attribute)
fieldType := structField.Type
// Handle field of type []string
if fieldValue.Type() == reflect.TypeOf([]string{}) {
value, err = handleStringSlice(attribute, args, fieldType, fieldValue)
return
}
// Handle field of type time.Time
if fieldValue.Type() == reflect.TypeOf(time.Time{}) ||
fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
value, err = handleTime(attribute, args, fieldType, fieldValue)
return
}
// Handle field of type struct
if fieldValue.Type().Kind() == reflect.Struct {
value, err = handleStruct(attribute, args, fieldType, fieldValue)
return
}
// Handle field containing slice of structs
if fieldValue.Type().Kind() == reflect.Slice {
elem := reflect.TypeOf(fieldValue.Interface()).Elem()
if elem.Kind() == reflect.Ptr {
elem = elem.Elem()
}
if elem.Kind() == reflect.Struct {
value, err = handleStructSlice(attribute, args, fieldType, fieldValue)
return
}
}
// JSON value was a float (numeric)
if value.Kind() == reflect.Float64 {
value, err = handleNumeric(attribute, args, fieldType, fieldValue)
return
}
// Field was a Pointer type
if fieldValue.Kind() == reflect.Ptr {
value, err = handlePointer(attribute, args, fieldType, fieldValue, structField)
return
}
// As a final catch-all, ensure types line up to avoid a runtime panic.
if fieldValue.Kind() != value.Kind() {
err = ErrInvalidType
return
}
return
}
func handleStringSlice(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
v := reflect.ValueOf(attribute)
values := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
values[i] = v.Index(i).Interface().(string)
}
return reflect.ValueOf(values), nil
}
func handleTime(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
var isIso8601 bool
v := reflect.ValueOf(attribute)
if len(args) > 2 {
for _, arg := range args[2:] {
if arg == annotationISO8601 {
isIso8601 = true
}
}
}
if isIso8601 {
var tm string
if v.Kind() == reflect.String {
tm = v.Interface().(string)
} else {
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
}
t, err := time.Parse(iso8601TimeFormat, tm)
if err != nil {
return reflect.ValueOf(time.Now()), ErrInvalidISO8601
}
if fieldValue.Kind() == reflect.Ptr {
return reflect.ValueOf(&t), nil
}
return reflect.ValueOf(t), nil
}
var at int64
if v.Kind() == reflect.Float64 {
at = int64(v.Interface().(float64))
} else if v.Kind() == reflect.Int {
at = v.Int()
} else {
return reflect.ValueOf(time.Now()), ErrInvalidTime
}
t := time.Unix(at, 0)
return reflect.ValueOf(t), nil
}
func handleNumeric(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
v := reflect.ValueOf(attribute)
floatValue := v.Interface().(float64)
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Elem().Kind()
} else {
kind = fieldType.Kind()
}
var numericValue reflect.Value
switch kind {
case reflect.Int:
n := int(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int8:
n := int8(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int16:
n := int16(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int32:
n := int32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Int64:
n := int64(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint:
n := uint(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint8:
n := uint8(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint16:
n := uint16(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint32:
n := uint32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Uint64:
n := uint64(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Float32:
n := float32(floatValue)
numericValue = reflect.ValueOf(&n)
case reflect.Float64:
n := floatValue
numericValue = reflect.ValueOf(&n)
default:
return reflect.Value{}, ErrUnknownFieldNumberType
}
return numericValue, nil
}
func handlePointer(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value,
structField reflect.StructField) (reflect.Value, error) {
t := fieldValue.Type()
var concreteVal reflect.Value
switch cVal := attribute.(type) {
case string:
concreteVal = reflect.ValueOf(&cVal)
case bool:
concreteVal = reflect.ValueOf(&cVal)
case complex64, complex128, uintptr:
concreteVal = reflect.ValueOf(&cVal)
case map[string]interface{}:
var err error
concreteVal, err = handleStruct(attribute, args, fieldType, fieldValue)
if err != nil {
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
}
return concreteVal.Elem(), err
default:
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
}
if t != concreteVal.Type() {
return reflect.Value{}, newErrUnsupportedPtrType(
reflect.ValueOf(attribute), fieldType, structField)
}
return concreteVal, nil
}
func handleStruct(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
model := reflect.New(fieldValue.Type())
data, err := json.Marshal(attribute)
if err != nil {
return model, err
}
err = json.Unmarshal(data, model.Interface())
if err != nil {
return model, err
}
return model, err
}
func handleStructSlice(
attribute interface{},
args []string,
fieldType reflect.Type,
fieldValue reflect.Value) (reflect.Value, error) {
models := reflect.New(fieldValue.Type()).Elem()
dataMap := reflect.ValueOf(attribute).Interface().([]interface{})
for _, data := range dataMap {
model := reflect.New(fieldValue.Type().Elem()).Elem()
modelType := model.Type()
value, err := handleStruct(data, []string{}, modelType, model)
if err != nil {
continue
}
models = reflect.Append(models, reflect.Indirect(value))
}
return models, nil
}

539
vendor/github.com/svanharmelen/jsonapi/response.go generated vendored Normal file
View File

@ -0,0 +1,539 @@
package jsonapi
import (
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
var (
// ErrBadJSONAPIStructTag is returned when the Struct field's JSON API
// annotation is invalid.
ErrBadJSONAPIStructTag = errors.New("Bad jsonapi struct tag format")
// ErrBadJSONAPIID is returned when the Struct JSON API annotated "id" field
// was not a valid numeric type.
ErrBadJSONAPIID = errors.New(
"id should be either string, int(8,16,32,64) or uint(8,16,32,64)")
// ErrExpectedSlice is returned when a variable or argument was expected to
// be a slice of *Structs; MarshalMany will return this error when its
// interface{} argument is invalid.
ErrExpectedSlice = errors.New("models should be a slice of struct pointers")
// ErrUnexpectedType is returned when marshalling an interface; the interface
// had to be a pointer or a slice; otherwise this error is returned.
ErrUnexpectedType = errors.New("models should be a struct pointer or slice of struct pointers")
)
// MarshalPayload writes a jsonapi response for one or many records. The
// related records are sideloaded into the "included" array. If this method is
// given a struct pointer as an argument it will serialize in the form
// "data": {...}. If this method is given a slice of pointers, this method will
// serialize in the form "data": [...]
//
// One Example: you could pass it, w, your http.ResponseWriter, and, models, a
// ptr to a Blog to be written to the response body:
//
// func ShowBlog(w http.ResponseWriter, r *http.Request) {
// blog := &Blog{}
//
// w.Header().Set("Content-Type", jsonapi.MediaType)
// w.WriteHeader(http.StatusOK)
//
// if err := jsonapi.MarshalPayload(w, blog); err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// }
// }
//
// Many Example: you could pass it, w, your http.ResponseWriter, and, models, a
// slice of Blog struct instance pointers to be written to the response body:
//
// func ListBlogs(w http.ResponseWriter, r *http.Request) {
// blogs := []*Blog{}
//
// w.Header().Set("Content-Type", jsonapi.MediaType)
// w.WriteHeader(http.StatusOK)
//
// if err := jsonapi.MarshalPayload(w, blogs); err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// }
// }
//
func MarshalPayload(w io.Writer, models interface{}) error {
payload, err := Marshal(models)
if err != nil {
return err
}
if err := json.NewEncoder(w).Encode(payload); err != nil {
return err
}
return nil
}
// Marshal does the same as MarshalPayload except it just returns the payload
// and doesn't write out results. Useful if you use your own JSON rendering
// library.
func Marshal(models interface{}) (Payloader, error) {
switch vals := reflect.ValueOf(models); vals.Kind() {
case reflect.Slice:
m, err := convertToSliceInterface(&models)
if err != nil {
return nil, err
}
payload, err := marshalMany(m)
if err != nil {
return nil, err
}
if linkableModels, isLinkable := models.(Linkable); isLinkable {
jl := linkableModels.JSONAPILinks()
if er := jl.validate(); er != nil {
return nil, er
}
payload.Links = linkableModels.JSONAPILinks()
}
if metableModels, ok := models.(Metable); ok {
payload.Meta = metableModels.JSONAPIMeta()
}
return payload, nil
case reflect.Ptr:
// Check that the pointer was to a struct
if reflect.Indirect(vals).Kind() != reflect.Struct {
return nil, ErrUnexpectedType
}
return marshalOne(models)
default:
return nil, ErrUnexpectedType
}
}
// MarshalPayloadWithoutIncluded writes a jsonapi response with one or many
// records, without the related records sideloaded into "included" array.
// If you want to serialize the relations into the "included" array see
// MarshalPayload.
//
// models interface{} should be either a struct pointer or a slice of struct
// pointers.
func MarshalPayloadWithoutIncluded(w io.Writer, model interface{}) error {
payload, err := Marshal(model)
if err != nil {
return err
}
payload.clearIncluded()
if err := json.NewEncoder(w).Encode(payload); err != nil {
return err
}
return nil
}
// marshalOne does the same as MarshalOnePayload except it just returns the
// payload and doesn't write out results. Useful is you use your JSON rendering
// library.
func marshalOne(model interface{}) (*OnePayload, error) {
included := make(map[string]*Node)
rootNode, err := visitModelNode(model, &included, true)
if err != nil {
return nil, err
}
payload := &OnePayload{Data: rootNode}
payload.Included = nodeMapValues(&included)
return payload, nil
}
// marshalMany does the same as MarshalManyPayload except it just returns the
// payload and doesn't write out results. Useful is you use your JSON rendering
// library.
func marshalMany(models []interface{}) (*ManyPayload, error) {
payload := &ManyPayload{
Data: []*Node{},
}
included := map[string]*Node{}
for _, model := range models {
node, err := visitModelNode(model, &included, true)
if err != nil {
return nil, err
}
payload.Data = append(payload.Data, node)
}
payload.Included = nodeMapValues(&included)
return payload, nil
}
// MarshalOnePayloadEmbedded - This method not meant to for use in
// implementation code, although feel free. The purpose of this
// method is for use in tests. In most cases, your request
// payloads for create will be embedded rather than sideloaded for
// related records. This method will serialize a single struct
// pointer into an embedded json response. In other words, there
// will be no, "included", array in the json all relationships will
// be serailized inline in the data.
//
// However, in tests, you may want to construct payloads to post
// to create methods that are embedded to most closely resemble
// the payloads that will be produced by the client. This is what
// this method is intended for.
//
// model interface{} should be a pointer to a struct.
func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error {
rootNode, err := visitModelNode(model, nil, false)
if err != nil {
return err
}
payload := &OnePayload{Data: rootNode}
if err := json.NewEncoder(w).Encode(payload); err != nil {
return err
}
return nil
}
func visitModelNode(model interface{}, included *map[string]*Node,
sideload bool) (*Node, error) {
node := new(Node)
var er error
value := reflect.ValueOf(model)
if value.IsNil() {
return nil, nil
}
modelValue := value.Elem()
modelType := value.Type().Elem()
for i := 0; i < modelValue.NumField(); i++ {
structField := modelValue.Type().Field(i)
tag := structField.Tag.Get(annotationJSONAPI)
if tag == "" {
continue
}
fieldValue := modelValue.Field(i)
fieldType := modelType.Field(i)
args := strings.Split(tag, annotationSeperator)
if len(args) < 1 {
er = ErrBadJSONAPIStructTag
break
}
annotation := args[0]
if (annotation == annotationClientID && len(args) != 1) ||
(annotation != annotationClientID && len(args) < 2) {
er = ErrBadJSONAPIStructTag
break
}
if annotation == annotationPrimary {
v := fieldValue
// Deal with PTRS
var kind reflect.Kind
if fieldValue.Kind() == reflect.Ptr {
kind = fieldType.Type.Elem().Kind()
v = reflect.Indirect(fieldValue)
} else {
kind = fieldType.Type.Kind()
}
// Handle allowed types
switch kind {
case reflect.String:
node.ID = v.Interface().(string)
case reflect.Int:
node.ID = strconv.FormatInt(int64(v.Interface().(int)), 10)
case reflect.Int8:
node.ID = strconv.FormatInt(int64(v.Interface().(int8)), 10)
case reflect.Int16:
node.ID = strconv.FormatInt(int64(v.Interface().(int16)), 10)
case reflect.Int32:
node.ID = strconv.FormatInt(int64(v.Interface().(int32)), 10)
case reflect.Int64:
node.ID = strconv.FormatInt(v.Interface().(int64), 10)
case reflect.Uint:
node.ID = strconv.FormatUint(uint64(v.Interface().(uint)), 10)
case reflect.Uint8:
node.ID = strconv.FormatUint(uint64(v.Interface().(uint8)), 10)
case reflect.Uint16:
node.ID = strconv.FormatUint(uint64(v.Interface().(uint16)), 10)
case reflect.Uint32:
node.ID = strconv.FormatUint(uint64(v.Interface().(uint32)), 10)
case reflect.Uint64:
node.ID = strconv.FormatUint(v.Interface().(uint64), 10)
default:
// We had a JSON float (numeric), but our field was not one of the
// allowed numeric types
er = ErrBadJSONAPIID
break
}
node.Type = args[1]
} else if annotation == annotationClientID {
clientID := fieldValue.String()
if clientID != "" {
node.ClientID = clientID
}
} else if annotation == annotationAttribute {
var omitEmpty, iso8601 bool
if len(args) > 2 {
for _, arg := range args[2:] {
switch arg {
case annotationOmitEmpty:
omitEmpty = true
case annotationISO8601:
iso8601 = true
}
}
}
if node.Attributes == nil {
node.Attributes = make(map[string]interface{})
}
if fieldValue.Type() == reflect.TypeOf(time.Time{}) {
t := fieldValue.Interface().(time.Time)
if t.IsZero() {
continue
}
if iso8601 {
node.Attributes[args[1]] = t.UTC().Format(iso8601TimeFormat)
} else {
node.Attributes[args[1]] = t.Unix()
}
} else if fieldValue.Type() == reflect.TypeOf(new(time.Time)) {
// A time pointer may be nil
if fieldValue.IsNil() {
if omitEmpty {
continue
}
node.Attributes[args[1]] = nil
} else {
tm := fieldValue.Interface().(*time.Time)
if tm.IsZero() && omitEmpty {
continue
}
if iso8601 {
node.Attributes[args[1]] = tm.UTC().Format(iso8601TimeFormat)
} else {
node.Attributes[args[1]] = tm.Unix()
}
}
} else {
// Dealing with a fieldValue that is not a time
emptyValue := reflect.Zero(fieldValue.Type())
// See if we need to omit this field
if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) {
continue
}
strAttr, ok := fieldValue.Interface().(string)
if ok {
node.Attributes[args[1]] = strAttr
} else {
node.Attributes[args[1]] = fieldValue.Interface()
}
}
} else if annotation == annotationRelation {
var omitEmpty bool
//add support for 'omitempty' struct tag for marshaling as absent
if len(args) > 2 {
omitEmpty = args[2] == annotationOmitEmpty
}
isSlice := fieldValue.Type().Kind() == reflect.Slice
if omitEmpty &&
(isSlice && fieldValue.Len() < 1 ||
(!isSlice && fieldValue.IsNil())) {
continue
}
if node.Relationships == nil {
node.Relationships = make(map[string]interface{})
}
var relLinks *Links
if linkableModel, ok := model.(RelationshipLinkable); ok {
relLinks = linkableModel.JSONAPIRelationshipLinks(args[1])
}
var relMeta *Meta
if metableModel, ok := model.(RelationshipMetable); ok {
relMeta = metableModel.JSONAPIRelationshipMeta(args[1])
}
if isSlice {
// to-many relationship
relationship, err := visitModelNodeRelationships(
fieldValue,
included,
sideload,
)
if err != nil {
er = err
break
}
relationship.Links = relLinks
relationship.Meta = relMeta
if sideload {
shallowNodes := []*Node{}
for _, n := range relationship.Data {
appendIncluded(included, n)
shallowNodes = append(shallowNodes, toShallowNode(n))
}
node.Relationships[args[1]] = &RelationshipManyNode{
Data: shallowNodes,
Links: relationship.Links,
Meta: relationship.Meta,
}
} else {
node.Relationships[args[1]] = relationship
}
} else {
// to-one relationships
// Handle null relationship case
if fieldValue.IsNil() {
node.Relationships[args[1]] = &RelationshipOneNode{Data: nil}
continue
}
relationship, err := visitModelNode(
fieldValue.Interface(),
included,
sideload,
)
if err != nil {
er = err
break
}
if sideload {
appendIncluded(included, relationship)
node.Relationships[args[1]] = &RelationshipOneNode{
Data: toShallowNode(relationship),
Links: relLinks,
Meta: relMeta,
}
} else {
node.Relationships[args[1]] = &RelationshipOneNode{
Data: relationship,
Links: relLinks,
Meta: relMeta,
}
}
}
} else {
er = ErrBadJSONAPIStructTag
break
}
}
if er != nil {
return nil, er
}
if linkableModel, isLinkable := model.(Linkable); isLinkable {
jl := linkableModel.JSONAPILinks()
if er := jl.validate(); er != nil {
return nil, er
}
node.Links = linkableModel.JSONAPILinks()
}
if metableModel, ok := model.(Metable); ok {
node.Meta = metableModel.JSONAPIMeta()
}
return node, nil
}
func toShallowNode(node *Node) *Node {
return &Node{
ID: node.ID,
Type: node.Type,
}
}
func visitModelNodeRelationships(models reflect.Value, included *map[string]*Node,
sideload bool) (*RelationshipManyNode, error) {
nodes := []*Node{}
for i := 0; i < models.Len(); i++ {
n := models.Index(i).Interface()
node, err := visitModelNode(n, included, sideload)
if err != nil {
return nil, err
}
nodes = append(nodes, node)
}
return &RelationshipManyNode{Data: nodes}, nil
}
func appendIncluded(m *map[string]*Node, nodes ...*Node) {
included := *m
for _, n := range nodes {
k := fmt.Sprintf("%s,%s", n.Type, n.ID)
if _, hasNode := included[k]; hasNode {
continue
}
included[k] = n
}
}
func nodeMapValues(m *map[string]*Node) []*Node {
mp := *m
nodes := make([]*Node, len(mp))
i := 0
for _, n := range mp {
nodes[i] = n
i++
}
return nodes
}
func convertToSliceInterface(i *interface{}) ([]interface{}, error) {
vals := reflect.ValueOf(*i)
if vals.Kind() != reflect.Slice {
return nil, ErrExpectedSlice
}
var response []interface{}
for x := 0; x < vals.Len(); x++ {
response = append(response, vals.Index(x).Interface())
}
return response, nil
}

103
vendor/github.com/svanharmelen/jsonapi/runtime.go generated vendored Normal file
View File

@ -0,0 +1,103 @@
package jsonapi
import (
"crypto/rand"
"fmt"
"io"
"reflect"
"time"
)
type Event int
const (
UnmarshalStart Event = iota
UnmarshalStop
MarshalStart
MarshalStop
)
type Runtime struct {
ctx map[string]interface{}
}
type Events func(*Runtime, Event, string, time.Duration)
var Instrumentation Events
func NewRuntime() *Runtime { return &Runtime{make(map[string]interface{})} }
func (r *Runtime) WithValue(key string, value interface{}) *Runtime {
r.ctx[key] = value
return r
}
func (r *Runtime) Value(key string) interface{} {
return r.ctx[key]
}
func (r *Runtime) Instrument(key string) *Runtime {
return r.WithValue("instrument", key)
}
func (r *Runtime) shouldInstrument() bool {
return Instrumentation != nil
}
func (r *Runtime) UnmarshalPayload(reader io.Reader, model interface{}) error {
return r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error {
return UnmarshalPayload(reader, model)
})
}
func (r *Runtime) UnmarshalManyPayload(reader io.Reader, kind reflect.Type) (elems []interface{}, err error) {
r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error {
elems, err = UnmarshalManyPayload(reader, kind)
return err
})
return
}
func (r *Runtime) MarshalPayload(w io.Writer, model interface{}) error {
return r.instrumentCall(MarshalStart, MarshalStop, func() error {
return MarshalPayload(w, model)
})
}
func (r *Runtime) instrumentCall(start Event, stop Event, c func() error) error {
if !r.shouldInstrument() {
return c()
}
instrumentationGUID, err := newUUID()
if err != nil {
return err
}
begin := time.Now()
Instrumentation(r, start, instrumentationGUID, time.Duration(0))
if err := c(); err != nil {
return err
}
diff := time.Duration(time.Now().UnixNano() - begin.UnixNano())
Instrumentation(r, stop, instrumentationGUID, diff)
return nil
}
// citation: http://play.golang.org/p/4FkNSiUDMg
func newUUID() (string, error) {
uuid := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, uuid); err != nil {
return "", err
}
// variant bits; see section 4.1.1
uuid[8] = uuid[8]&^0xc0 | 0x80
// version 4 (pseudo-random); see section 4.1.3
uuid[6] = uuid[6]&^0xf0 | 0x40
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}

8
vendor/modules.txt vendored
View File

@ -497,11 +497,15 @@ github.com/hashicorp/go-raftchunking/types
github.com/hashicorp/go-retryablehttp
# github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-rootcerts
# github.com/hashicorp/go-slug v0.4.1
github.com/hashicorp/go-slug
# github.com/hashicorp/go-sockaddr v1.0.2
github.com/hashicorp/go-sockaddr
github.com/hashicorp/go-sockaddr/template
# github.com/hashicorp/go-syslog v1.0.0
github.com/hashicorp/go-syslog
# github.com/hashicorp/go-tfe v0.12.0
github.com/hashicorp/go-tfe
# github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-uuid
# github.com/hashicorp/go-version v1.2.1
@ -590,6 +594,8 @@ github.com/hashicorp/vault-plugin-secrets-mongodbatlas
# github.com/hashicorp/vault-plugin-secrets-openldap v0.1.6-0.20210201204049-4f0f91977798
github.com/hashicorp/vault-plugin-secrets-openldap
github.com/hashicorp/vault-plugin-secrets-openldap/client
# github.com/hashicorp/vault-plugin-secrets-terraform v0.1.0
github.com/hashicorp/vault-plugin-secrets-terraform
# github.com/hashicorp/vault/api v1.0.5-0.20201001211907-38d91b749c77 => ./api
github.com/hashicorp/vault/api
# github.com/hashicorp/vault/sdk v0.1.14-0.20210127185906-6b455835fa8c => ./sdk
@ -976,6 +982,8 @@ github.com/stretchr/objx
github.com/stretchr/testify/assert
github.com/stretchr/testify/mock
github.com/stretchr/testify/require
# github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d
github.com/svanharmelen/jsonapi
# github.com/tencentcloud/tencentcloud-sdk-go v3.0.171+incompatible
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors