vendor: catch up vendoring

We've missed a few recent additions to go.mod in the vendor directory. We
need to keep this updated for the moment until all of the surrounding
tooling is ready to go all-in with Go 1.11 modules.
This commit is contained in:
Martin Atkins 2018-11-08 13:13:13 -08:00
parent 1e45d30036
commit 6806d7cba5
43 changed files with 8061 additions and 0 deletions

27
vendor/github.com/google/go-querystring/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2013 Google. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

320
vendor/github.com/google/go-querystring/query/encode.go generated vendored Normal file
View File

@ -0,0 +1,320 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package query implements encoding of structs into URL query parameters.
//
// As a simple example:
//
// type Options struct {
// Query string `url:"q"`
// ShowAll bool `url:"all"`
// Page int `url:"page"`
// }
//
// opt := Options{ "foo", true, 2 }
// v, _ := query.Values(opt)
// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
//
// The exact mapping between Go values and url.Values is described in the
// documentation for the Values() function.
package query
import (
"bytes"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
var timeType = reflect.TypeOf(time.Time{})
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
// Encoder is an interface implemented by any type that wishes to encode
// itself into URL values in a non-standard way.
type Encoder interface {
EncodeValues(key string, v *url.Values) error
}
// Values returns the url.Values encoding of v.
//
// Values expects to be passed a struct, and traverses it recursively using the
// following encoding rules.
//
// Each exported struct field is encoded as a URL parameter unless
//
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option
//
// The empty values are false, 0, any nil pointer or interface value, any array
// slice, map, or string of length zero, and any time.Time that returns true
// for IsZero().
//
// The URL parameter name defaults to the struct field name but can be
// specified in the struct field's tag value. The "url" key in the struct
// field's tag value is the key name, followed by an optional comma and
// options. For example:
//
// // Field is ignored by this package.
// Field int `url:"-"`
//
// // Field appears as URL parameter "myName".
// Field int `url:"myName"`
//
// // Field appears as URL parameter "myName" and the field is omitted if
// // its value is empty
// Field int `url:"myName,omitempty"`
//
// // Field appears as URL parameter "Field" (the default), but the field
// // is skipped if empty. Note the leading comma.
// Field int `url:",omitempty"`
//
// For encoding individual field values, the following type-dependent rules
// apply:
//
// Boolean values default to encoding as the strings "true" or "false".
// Including the "int" option signals that the field should be encoded as the
// strings "1" or "0".
//
// time.Time values default to encoding as RFC3339 timestamps. Including the
// "unix" option signals that the field should be encoded as a Unix time (see
// time.Unix())
//
// Slice and Array values default to encoding as multiple URL values of the
// same name. Including the "comma" option signals that the field should be
// encoded as a single comma-delimited value. Including the "space" option
// similarly encodes the value as a single space-delimited string. Including
// the "semicolon" option will encode the value as a semicolon-delimited string.
// Including the "brackets" option signals that the multiple URL values should
// have "[]" appended to the value name. "numbered" will append a number to
// the end of each incidence of the value name, example:
// name0=value0&name1=value1, etc.
//
// Anonymous struct fields are usually encoded as if their inner exported
// fields were fields in the outer struct, subject to the standard Go
// visibility rules. An anonymous struct field with a name given in its URL
// tag is treated as having that name, rather than being anonymous.
//
// Non-nil pointer values are encoded as the value pointed to.
//
// Nested structs are encoded including parent fields in value names for
// scoping. e.g:
//
// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
//
// All other values are encoded using their default string representation.
//
// Multiple fields that encode to the same URL parameter name will be included
// as multiple URL values of the same name.
func Values(v interface{}) (url.Values, error) {
values := make(url.Values)
val := reflect.ValueOf(v)
for val.Kind() == reflect.Ptr {
if val.IsNil() {
return values, nil
}
val = val.Elem()
}
if v == nil {
return values, nil
}
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
}
err := reflectValue(values, val, "")
return values, err
}
// reflectValue populates the values parameter from the struct fields in val.
// Embedded structs are followed recursively (using the rules defined in the
// Values function documentation) breadth-first.
func reflectValue(values url.Values, val reflect.Value, scope string) error {
var embedded []reflect.Value
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
sf := typ.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
sv := val.Field(i)
tag := sf.Tag.Get("url")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if name == "" {
if sf.Anonymous && sv.Kind() == reflect.Struct {
// save embedded struct for later processing
embedded = append(embedded, sv)
continue
}
name = sf.Name
}
if scope != "" {
name = scope + "[" + name + "]"
}
if opts.Contains("omitempty") && isEmptyValue(sv) {
continue
}
if sv.Type().Implements(encoderType) {
if !reflect.Indirect(sv).IsValid() {
sv = reflect.New(sv.Type().Elem())
}
m := sv.Interface().(Encoder)
if err := m.EncodeValues(name, &values); err != nil {
return err
}
continue
}
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
var del byte
if opts.Contains("comma") {
del = ','
} else if opts.Contains("space") {
del = ' '
} else if opts.Contains("semicolon") {
del = ';'
} else if opts.Contains("brackets") {
name = name + "[]"
}
if del != 0 {
s := new(bytes.Buffer)
first := true
for i := 0; i < sv.Len(); i++ {
if first {
first = false
} else {
s.WriteByte(del)
}
s.WriteString(valueString(sv.Index(i), opts))
}
values.Add(name, s.String())
} else {
for i := 0; i < sv.Len(); i++ {
k := name
if opts.Contains("numbered") {
k = fmt.Sprintf("%s%d", name, i)
}
values.Add(k, valueString(sv.Index(i), opts))
}
}
continue
}
for sv.Kind() == reflect.Ptr {
if sv.IsNil() {
break
}
sv = sv.Elem()
}
if sv.Type() == timeType {
values.Add(name, valueString(sv, opts))
continue
}
if sv.Kind() == reflect.Struct {
reflectValue(values, sv, name)
continue
}
values.Add(name, valueString(sv, opts))
}
for _, f := range embedded {
if err := reflectValue(values, f, scope); err != nil {
return err
}
}
return nil
}
// valueString returns the string representation of a value.
func valueString(v reflect.Value, opts tagOptions) string {
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return ""
}
v = v.Elem()
}
if v.Kind() == reflect.Bool && opts.Contains("int") {
if v.Bool() {
return "1"
}
return "0"
}
if v.Type() == timeType {
t := v.Interface().(time.Time)
if opts.Contains("unix") {
return strconv.FormatInt(t.Unix(), 10)
}
return t.Format(time.RFC3339)
}
return fmt.Sprint(v.Interface())
}
// isEmptyValue checks if a value should be considered empty for the purposes
// of omitting fields with the "omitempty" option.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
if v.Type() == timeType {
return v.Interface().(time.Time).IsZero()
}
return false
}
// tagOptions is the string following a comma in a struct field's "url" tag, or
// the empty string. It does not include the leading comma.
type tagOptions []string
// parseTag splits a struct field's url tag into its name and comma-separated
// options.
func parseTag(tag string) (string, tagOptions) {
s := strings.Split(tag, ",")
return s[0], s[1:]
}
// Contains checks whether the tagOptions contains the specified option.
func (o tagOptions) Contains(option string) bool {
for _, s := range o {
if s == option {
return true
}
}
return false
}

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"
"ioutil"
"log"
"os"
slug "github.com/hashicorp/go-slug"
)
func main() {
// First create a buffer for storing the slug.
slug := 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 := Pack("test-fixtures/archive-dir", slug); 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 := Unpack(slug, 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.

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

@ -0,0 +1,215 @@
package slug
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
)
// 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 directory src, and writes the new
// slug to w. Returns metadata about the slug and any error.
func Pack(src string, w io.Writer) (*Meta, error) {
// Gzip compress all the output data
gzipW := gzip.NewWriter(w)
// Tar the file contents
tarW := tar.NewWriter(gzipW)
// Track the metadata details as we go.
meta := &Meta{}
// Walk the tree of files
err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check the file type and if we need to write the body
keepFile, writeBody := checkFileMode(info.Mode())
if !keepFile {
return nil
}
// Get the relative path from the unpack 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
}
// Read the symlink target. We don't track the error because
// it doesn't matter if there is an error.
target, _ := os.Readlink(path)
// Build the file header for the tar entry
header, err := tar.FileInfoHeader(info, target)
if err != nil {
return fmt.Errorf("Failed creating archive header for file %q: %v", path, err)
}
// Modify the header to properly be the full subpath
header.Name = subpath
if info.IsDir() {
header.Name += "/"
}
// 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
}
// Add the size since we are going to write the body.
meta.Size += info.Size()
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("Failed opening file %q for archiving: %v", path, err)
}
defer f.Close()
if _, err = io.Copy(tarW, f); err != nil {
return fmt.Errorf("Failed copying file %q to archive: %v", path, err)
}
return nil
})
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
}
// Unpack is used to read and extract the contents of a slug to
// directory dst. Returns any error.
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.IsRegular():
return true, true
case m.IsDir():
return true, false
case m&os.ModeSymlink != 0:
return true, false
}
return false, false
}

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.

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

@ -0,0 +1,131 @@
Terraform Enterprise Go Client
==============================
[![Build Status](https://travis-ci.org/hashicorp/go-tfe.svg?branch=master)](https://travis-ci.org/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)
This is an API client for [Terraform Enterprise](https://www.hashicorp.com/products/terraform).
## NOTE
The Terraform Enterprise API endpoints are in beta and are subject to change!
So that means this API client is also in beta and is also subject to change. 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.
## Coverage
Currently the following endpoints are supported:
- [x] [Accounts](https://www.terraform.io/docs/enterprise/api/account.html)
- [x] [Configuration Versions](https://www.terraform.io/docs/enterprise/api/configuration-versions.html)
- [x] [OAuth Clients](https://www.terraform.io/docs/enterprise/api/oauth-clients.html)
- [x] [OAuth Tokens](https://www.terraform.io/docs/enterprise/api/oauth-tokens.html)
- [x] [Organizations](https://www.terraform.io/docs/enterprise/api/organizations.html)
- [x] [Organization Tokens](https://www.terraform.io/docs/enterprise/api/organization-tokens.html)
- [x] [Policies](https://www.terraform.io/docs/enterprise/api/policies.html)
- [x] [Policy Checks](https://www.terraform.io/docs/enterprise/api/policy-checks.html)
- [ ] [Registry Modules](https://www.terraform.io/docs/enterprise/api/modules.html)
- [x] [Runs](https://www.terraform.io/docs/enterprise/api/run.html)
- [x] [SSH Keys](https://www.terraform.io/docs/enterprise/api/ssh-keys.html)
- [x] [State Versions](https://www.terraform.io/docs/enterprise/api/state-versions.html)
- [x] [Team Access](https://www.terraform.io/docs/enterprise/api/team-access.html)
- [x] [Team Memberships](https://www.terraform.io/docs/enterprise/api/team-members.html)
- [x] [Team Tokens](https://www.terraform.io/docs/enterprise/api/team-tokens.html)
- [x] [Teams](https://www.terraform.io/docs/enterprise/api/teams.html)
- [x] [Variables](https://www.terraform.io/docs/enterprise/api/variables.html)
- [x] [Workspaces](https://www.terraform.io/docs/enterprise/api/workspaces.html)
- [ ] [Admin](https://www.terraform.io/docs/enterprise/api/admin/index.html)
## Installation
Installation can be done with a normal `go get`:
```
go get -u github.com/hashicorp/go-tfe
```
## Documentation
For complete usage of the API client, see the full [package docs](https://godoc.org/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(), OrganizationListOptions{})
if err != nil {
log.Fatal(err)
}
```
## Examples
The [examples](https://github.com/hashicorp/go-tfe/tree/master/examples) directory
contains a couple of examples. One of which is listed here as well:
```go
package main
import (
"log"
tfe "github.com/hashicorp/go-tfe"
)
func main() {
config := &tfe.Config{
Token: "insert-your-token-here",
}
client, err := tfe.NewClient(config)
if err != nil {
log.Fatal(err)
}
// Create a context
ctx := context.Background()
// Create a new organization
options := tfe.OrganizationCreateOptions{
Name: tfe.String("example"),
Email: tfe.String("info@example.com"),
}
org, err := client.Organizations.Create(ctx, options)
if err != nil {
log.Fatal(err)
}
// Delete an organization
err = client.Organizations.Delete(ctx, org.Name)
if 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.

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,199 @@
package tfe
import (
"bytes"
"context"
"errors"
"fmt"
"net/url"
"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 {
body := bytes.NewBuffer(nil)
_, err := slug.Pack(path, body)
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)
}

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

@ -0,0 +1,138 @@
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)
// Retrieve the next chunk.
resp, err := r.client.http.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
}

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

@ -0,0 +1,199 @@
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 (
ServiceProviderBitbucket ServiceProviderType = "bitbucket_hosted"
ServiceProviderBitbucketServer ServiceProviderType = "bitbucket_server"
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"`
// The VCS provider being connected with.
ServiceProvider *ServiceProviderType `jsonapi:"attr,service-provider"`
}
func (o OAuthClientCreateOptions) valid() error {
if !validString(o.APIURL) {
return errors.New("APIURL is required")
}
if !validString(o.HTTPURL) {
return errors.New("HTTPURL is required")
}
if !validString(o.OAuthToken) {
return errors.New("OAuthToken is required")
}
if o.ServiceProvider == nil {
return errors.New("ServiceProvider is required")
}
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)
}

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

@ -0,0 +1,310 @@
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)
// 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"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Email string `jsonapi:"attr,email"`
EnterprisePlan EnterprisePlanType `jsonapi:"attr,enterprise-plan"`
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"`
}
// 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"`
}
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"`
}
// 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
}
// 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,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)
}

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

@ -0,0 +1,133 @@
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"`
}
// 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
}

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

@ -0,0 +1,282 @@
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"`
Enforce []*Enforcement `jsonapi:"attr,enforce"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
}
// 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
}
// 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"`
// 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"`
// The enforcements of the policy.
Enforce []*EnforcementOptions `jsonapi:"attr,enforce"`
}
func (o PolicyUpdateOptions) valid() error {
if o.Enforce == nil {
return errors.New("Enforce is required")
}
return nil
}
// 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")
}
if err := options.valid(); err != nil {
return nil, err
}
// 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
}

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

@ -0,0 +1,220 @@
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 (
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"`
}
// 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
}
}

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

@ -0,0 +1,311 @@
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)
// 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"
RunApplying RunStatus = "applying"
RunCanceled RunStatus = "canceled"
RunConfirmed RunStatus = "confirmed"
RunDiscarded RunStatus = "discarded"
RunErrored RunStatus = "errored"
RunPending RunStatus = "pending"
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"`
// Relations
Apply *Apply `jsonapi:"relation,apply"`
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
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"`
}
// RunListOptions represents the options for listing runs.
type RunListOptions struct {
ListOptions
}
// 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"`
}
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) {
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, nil)
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)
}
// RunCancelOptions 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)
}

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)
}

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

@ -0,0 +1,216 @@
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)
// Current reads the latest available state from the given workspace.
Current(ctx context.Context, workspaceID string) (*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"`
}
// 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"`
// 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
}
// Read a state version by its ID.
func (s *stateVersions) Read(ctx context.Context, svID string) (*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, nil)
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) {
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, nil)
if err != nil {
return nil, err
}
sv := &StateVersion{}
err = s.client.do(ctx, req, sv)
if err != nil {
return nil, err
}
return sv, 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
}

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

@ -0,0 +1,165 @@
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)
// 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"`
Permissions *TeamPermissions `jsonapi:"attr,permissions"`
UserCount int `jsonapi:"attr,users-count"`
// Relations
Users []*User `jsonapi:"relation,users"`
}
// TeamPermissions represents the team permissions.
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
}
// 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"`
}
func (o TeamCreateOptions) 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 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
}
// 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)
}

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

@ -0,0 +1,184 @@
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)
// 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
// List all available team access types.
const (
AccessAdmin AccessType = "admin"
AccessRead AccessType = "read"
AccessWrite AccessType = "write"
)
// 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"`
// 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"`
// 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
}
// 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)
}

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

@ -0,0 +1,139 @@
package tfe
import (
"context"
"errors"
"fmt"
"net/url"
)
// 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 all members of a team.
List(ctx context.Context, teamID string) ([]*User, 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 teamMember struct {
Username string `jsonapi:"primary,users"`
}
// List all members of a team.
func (s *teamMembers) List(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
}
// TeamMemberAddOptions represents the options for adding team members.
type TeamMemberAddOptions struct {
Usernames []string
}
func (o *TeamMemberAddOptions) valid() error {
if o.Usernames == nil {
return errors.New("Usernames is required")
}
if len(o.Usernames) == 0 {
return errors.New("Invalid value for usernames")
}
return nil
}
// 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
}
var tms []*teamMember
for _, name := range options.Usernames {
tms = append(tms, &teamMember{Username: name})
}
u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID))
req, err := s.client.newRequest("POST", u, tms)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}
// TeamMemberRemoveOptions represents the options for deleting team members.
type TeamMemberRemoveOptions struct {
Usernames []string
}
func (o *TeamMemberRemoveOptions) valid() error {
if o.Usernames == nil {
return errors.New("Usernames is required")
}
if len(o.Usernames) == 0 {
return errors.New("Invalid value for usernames")
}
return nil
}
// 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
}
var tms []*teamMember
for _, name := range options.Usernames {
tms = append(tms, &teamMember{Username: name})
}
u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID))
req, err := s.client.newRequest("DELETE", u, tms)
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)
}

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

@ -0,0 +1,420 @@
package tfe
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"github.com/google/go-querystring/query"
"github.com/hashicorp/go-cleanhttp"
"github.com/svanharmelen/jsonapi"
)
const (
// DefaultAddress of Terraform Enterprise.
DefaultAddress = "https://app.terraform.io"
// DefaultBasePath on which the API is served.
DefaultBasePath = "/api/v2/"
)
const (
userAgent = "go-tfe"
)
var (
// 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")
)
// 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
}
// 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 *http.Client
Applies Applies
ConfigurationVersions ConfigurationVersions
OAuthClients OAuthClients
OAuthTokens OAuthTokens
Organizations Organizations
OrganizationTokens OrganizationTokens
Plans Plans
Policies Policies
PolicyChecks PolicyChecks
Runs Runs
SSHKeys SSHKeys
StateVersions StateVersions
Teams Teams
TeamAccess TeamAccesses
TeamMembers TeamMembers
TeamTokens TeamTokens
Users Users
Variables Variables
Workspaces Workspaces
}
// 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
}
}
// 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,
http: config.HTTPClient,
}
// Create the services.
client.Applies = &applies{client: client}
client.ConfigurationVersions = &configurationVersions{client: client}
client.OAuthClients = &oAuthClients{client: client}
client.OAuthTokens = &oAuthTokens{client: client}
client.Organizations = &organizations{client: client}
client.OrganizationTokens = &organizationTokens{client: client}
client.Plans = &plans{client: client}
client.Policies = &policies{client: client}
client.PolicyChecks = &policyChecks{client: client}
client.Runs = &runs{client: client}
client.SSHKeys = &sshKeys{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.Variables = &variables{client: client}
client.Workspaces = &workspaces{client: client}
return client, 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"`
}
// 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{}) (*http.Request, error) {
u, err := c.baseURL.Parse(path)
if err != nil {
return nil, err
}
req := &http.Request{
Method: method,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
// Set default headers.
for k, v := range c.headers {
req.Header[k] = v
}
switch method {
case "GET":
req.Header.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":
req.Header.Set("Accept", "application/vnd.api+json")
req.Header.Set("Content-Type", "application/vnd.api+json")
if v != nil {
var body bytes.Buffer
if err := jsonapi.MarshalPayloadWithoutIncluded(&body, v); err != nil {
return nil, err
}
req.Body = ioutil.NopCloser(&body)
req.ContentLength = int64(body.Len())
}
case "PUT":
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/octet-stream")
if v != nil {
switch v := v.(type) {
case *bytes.Buffer:
req.Body = ioutil.NopCloser(v)
req.ContentLength = int64(v.Len())
case []byte:
req.Body = ioutil.NopCloser(bytes.NewReader(v))
req.ContentLength = int64(len(v))
default:
return nil, fmt.Errorf("Unexpected type: %T", v)
}
}
}
// Set the authorization header.
req.Header.Set("Authorization", "Bearer "+c.token)
return req, 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 *http.Request, v interface{}) error {
// 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
}
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
}
// 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 %s", e.Title, e.Detail))
}
}
return fmt.Errorf(strings.Join(errs, "\n"))
}

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

@ -0,0 +1,46 @@
package tfe
// Access returns a pointer to the given team access type.
func Access(v AccessType) *AccessType {
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
}
// 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
}

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)
}

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

@ -0,0 +1,243 @@
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, options VariableListOptions) (*VariableList, error)
// Create is used to create a new variable.
Create(ctx context.Context, options VariableCreateOptions) (*Variable, error)
// Read a variable by its ID.
Read(ctx context.Context, variableID string) (*Variable, error)
// Update values of an existing variable.
Update(ctx context.Context, variableID string, options VariableUpdateOptions) (*Variable, error)
// Delete a variable by its ID.
Delete(ctx context.Context, 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"
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"`
Category CategoryType `jsonapi:"attr,category"`
HCL bool `jsonapi:"attr,hcl"`
Sensitive bool `jsonapi:"attr,sensitive"`
// Relations
Workspace *Workspace `jsonapi:"relation,workspace"`
}
// VariableListOptions represents the options for listing variables.
type VariableListOptions struct {
ListOptions
Organization *string `url:"filter[organization][name]"`
Workspace *string `url:"filter[workspace][name]"`
}
func (o VariableListOptions) 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 variables associated with the given workspace.
func (s *variables) List(ctx context.Context, options VariableListOptions) (*VariableList, error) {
if err := options.valid(); err != nil {
return nil, err
}
req, err := s.client.newRequest("GET", "vars", &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"`
// 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"`
// The workspace that owns the variable.
Workspace *Workspace `jsonapi:"relation,workspace"`
}
func (o VariableCreateOptions) valid() error {
if !validString(o.Key) {
return errors.New("Key is required")
}
if !validString(o.Value) {
return errors.New("Value is required")
}
if o.Category == nil {
return errors.New("Category is required")
}
if o.Workspace == nil {
return errors.New("Workspace is required")
}
return nil
}
// Create is used to create a new variable.
func (s *variables) Create(ctx context.Context, options VariableCreateOptions) (*Variable, 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", "vars", &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, variableID string) (*Variable, error) {
if !validStringID(&variableID) {
return nil, errors.New("Invalid value for variable ID")
}
u := fmt.Sprintf("vars/%s", 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"`
// 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, variableID string, options VariableUpdateOptions) (*Variable, error) {
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("vars/%s", 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, variableID string) error {
if !validStringID(&variableID) {
return errors.New("Invalid value for variable ID")
}
u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID))
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
return s.client.do(ctx, req, nil)
}

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

@ -0,0 +1,447 @@
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)
// Update settings of an existing workspace.
Update(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error)
// Delete a workspace by its name.
Delete(ctx context.Context, organization string, workspace string) 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)
// 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"`
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"`
Locked bool `jsonapi:"attr,locked"`
MigrationEnvironment string `jsonapi:"attr,migration-environment"`
Name string `jsonapi:"attr,name"`
Permissions *WorkspacePermissions `jsonapi:"attr,permissions"`
TerraformVersion string `jsonapi:"attr,terraform-version"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"`
// Relations
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"`
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"`
CanLock bool `json:"can-lock"`
CanQueueDestroy bool `json:"can-queue-destroy"`
CanQueueRun bool `json:"can-queue-run"`
CanReadSettings bool `json:"can-read-settings"`
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"`
}
// 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"`
// Whether to automatically apply changes when a Terraform plan is successful.
AutoApply *bool `jsonapi:"attr,auto-apply,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"`
// 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"`
// 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"`
}
// 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")
}
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
}
// WorkspaceUpdateOptions represents the options for updating a workspace.
type WorkspaceUpdateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// 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"`
// The version of Terraform to use for this workspace.
TerraformVersion *string `jsonapi:"attr,terraform-version,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 *VCSRepo `jsonapi:"relation,vcs-repo,om-tempty"`
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"`
}
// 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")
}
// 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
}
// 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)
}
// 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
}
// 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
}

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

@ -231,6 +231,8 @@ github.com/google/go-cmp/cmp/cmpopts
github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/function
github.com/google/go-cmp/cmp/internal/value
# github.com/google/go-querystring v1.0.0
github.com/google/go-querystring/query
# github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e
github.com/googleapis/gax-go
# github.com/gophercloud/gophercloud v0.0.0-20170524130959-3027adb1ce72
@ -311,6 +313,10 @@ github.com/hashicorp/go-retryablehttp
github.com/hashicorp/go-rootcerts
# github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc
github.com/hashicorp/go-safetemp
# github.com/hashicorp/go-slug v0.1.0
github.com/hashicorp/go-slug
# github.com/hashicorp/go-tfe v0.2.6
github.com/hashicorp/go-tfe
# github.com/hashicorp/go-uuid v1.0.0
github.com/hashicorp/go-uuid
# github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
@ -435,6 +441,8 @@ github.com/satori/uuid
# github.com/spf13/afero v1.0.2
github.com/spf13/afero
github.com/spf13/afero/mem
# github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d
github.com/svanharmelen/jsonapi
# github.com/terraform-providers/terraform-provider-aws v1.41.0
github.com/terraform-providers/terraform-provider-aws/aws
# github.com/terraform-providers/terraform-provider-openstack v0.0.0-20170616075611-4080a521c6ea