├── tools ├── test.sh ├── publish.sh ├── package.sh ├── build.sh └── version.sh ├── .gitignore ├── drp ├── README.md ├── config.go ├── provider_test.go ├── resource_drp_user_test.go ├── resource_drp_template_test.go ├── resource_drp_machine_test.go ├── resource_drp_param_test.go ├── provider.go ├── resource_drp_profile_test.go ├── resource_drp_reservation_test.go ├── resource_drp_task_test.go ├── resource_drp_plugin_test.go ├── resource_drp_stage_test.go ├── resource_drp_bootenv_test.go ├── resource_drp_subnet_test.go ├── resource_drp_raw_machine_test.go ├── resource_drp_machine.go └── utils.go ├── main.go ├── go.mod ├── scripts ├── gogetcookie.sh ├── gofmtcheck.sh ├── errcheck.sh └── changelog-links.sh ├── test.tf.example ├── GNUmakefile ├── .travis.yml ├── README.md ├── LICENSE.rst └── go.sum /tools/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exit 0 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | bin 3 | terraform-provider-drp.zip 4 | terraform-provider-drp.sha256 5 | .terraform 6 | -------------------------------------------------------------------------------- /drp/README.md: -------------------------------------------------------------------------------- 1 | 2 | The following objects are not reflected: 3 | 4 | * interfaces 5 | * jobs 6 | * leases 7 | * plugin_providers 8 | * preferences 9 | 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/terraform/plugin" 5 | "github.com/rackn/terraform-provider-drp/drp" 6 | ) 7 | 8 | func main() { 9 | plugin.Serve(&plugin.ServeOpts{ProviderFunc: drp.Provider}) 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rackn/terraform-provider-drp 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/VictorLowther/jsonpatch2 v1.0.0 7 | github.com/digitalrebar/provision/v4 v4.0.10 8 | github.com/go-test/deep v1.0.2 9 | github.com/hashicorp/terraform v0.12.6 10 | github.com/pborman/uuid v1.2.0 11 | ) 12 | -------------------------------------------------------------------------------- /scripts/gogetcookie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | touch ~/.gitcookies 4 | chmod 0600 ~/.gitcookies 5 | 6 | git config --global http.cookiefile ~/.gitcookies 7 | 8 | tr , \\t <<\__END__ >>~/.gitcookies 9 | .googlesource.com,TRUE,/,TRUE,2147483647,o,git-paul.hashicorp.com=1/z7s05EYPudQ9qoe6dMVfmAVwgZopEkZBb1a2mA5QtHE 10 | __END__ 11 | -------------------------------------------------------------------------------- /scripts/gofmtcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check gofmt 4 | echo "==> Checking that code complies with gofmt requirements..." 5 | gofmt_files=$(gofmt -l `find . -name '*.go' | grep -v vendor`) 6 | if [[ -n ${gofmt_files} ]]; then 7 | echo 'gofmt needs running on the following files:' 8 | echo "${gofmt_files}" 9 | echo "You can use the command: \`make fmt\` to reformat code." 10 | exit 1 11 | fi 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /tools/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | . tools/version.sh 6 | version="$Prepart$MajorV.$MinorV.$PatchV$Extra-$GITHASH" 7 | 8 | TOKEN=R0cketSk8ts 9 | for i in terraform ; do 10 | echo "Publishing $i to cloud" 11 | CONTENT=$i 12 | 13 | arches=("amd64") 14 | oses=("linux" "darwin" "windows") 15 | for arch in "${arches[@]}"; do 16 | for os in "${oses[@]}"; do 17 | suffix="" 18 | if [[ $os == windows ]] ; then 19 | suffix=".exe" 20 | fi 21 | path="$CONTENT/$version/$arch/$os" 22 | mkdir -p "rebar-catalog/$path" 23 | cp bin/$os/$arch/terraform-provider-drp${suffix} "rebar-catalog/$path" 24 | done 25 | done 26 | done 27 | 28 | -------------------------------------------------------------------------------- /scripts/errcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check gofmt 4 | echo "==> Checking for unchecked errors..." 5 | 6 | if ! which errcheck > /dev/null; then 7 | echo "==> Installing errcheck..." 8 | go get -u github.com/kisielk/errcheck 9 | fi 10 | 11 | err_files=$(errcheck -ignoretests \ 12 | -ignore 'github.com/hashicorp/terraform/helper/schema:Set' \ 13 | -ignore 'bytes:.*' \ 14 | -ignore 'io:Close|Write' \ 15 | $(go list ./...| grep -v /vendor/)) 16 | 17 | if [[ -n ${err_files} ]]; then 18 | echo 'Unchecked errors found in the following places:' 19 | echo "${err_files}" 20 | echo "Please handle returned errors. You can check directly with \`make errcheck\`" 21 | exit 1 22 | fi 23 | 24 | exit 0 25 | -------------------------------------------------------------------------------- /tools/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | case $(uname -s) in 6 | Darwin) 7 | shasum="command shasum -a 256";; 8 | Linux) 9 | shasum="command sha256sum";; 10 | *) 11 | # Someday, support installing on Windows. Service creation could be tricky. 12 | echo "No idea how to check sha256sums" 13 | exit 1;; 14 | esac 15 | 16 | . tools/version.sh 17 | 18 | version="$Prepart$MajorV.$MinorV.$PatchV$Extra-$GITHASH" 19 | 20 | tmpdir="$(mktemp -d /tmp/rs-bundle-XXXXXXXX)" 21 | cp -a bin "$tmpdir" 22 | ( 23 | cd "$tmpdir" 24 | $shasum $(find . -type f) >sha256sums 25 | zip -p -r terraform-provider-drp.zip * 26 | ) 27 | cp "$tmpdir/terraform-provider-drp.zip" . 28 | $shasum terraform-provider-drp.zip > terraform-provider-drp.sha256 29 | rm -rf "$tmpdir" 30 | 31 | -------------------------------------------------------------------------------- /drp/config.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/digitalrebar/provision/v4/api" 7 | ) 8 | 9 | type Config struct { 10 | Token string 11 | Username string 12 | Password string 13 | Url string 14 | 15 | session *api.Client 16 | } 17 | 18 | /* 19 | * Builds a client object for this config 20 | */ 21 | func (c *Config) validateAndConnect() error { 22 | log.Println("[DEBUG] [Config.validateAndConnect] Configuring the DRP API client") 23 | 24 | if c.session != nil { 25 | return nil 26 | } 27 | var err error 28 | if c.Token != "" { 29 | c.session, err = api.TokenSession(c.Url, c.Token) 30 | } else { 31 | c.session, err = api.UserSession(c.Url, c.Username, c.Password) 32 | } 33 | if err != nil { 34 | log.Printf("[ERROR] Error creating session: %v", err) 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /scripts/changelog-links.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script rewrites [GH-nnnn]-style references in the CHANGELOG.md file to 4 | # be Markdown links to the given github issues. 5 | # 6 | # This is run during releases so that the issue references in all of the 7 | # released items are presented as clickable links, but we can just use the 8 | # easy [GH-nnnn] shorthand for quickly adding items to the "Unrelease" section 9 | # while merging things between releases. 10 | 11 | set -e 12 | 13 | if [[ ! -f CHANGELOG.md ]]; then 14 | echo "ERROR: CHANGELOG.md not found in pwd." 15 | echo "Please run this from the root of the terraform provider repository" 16 | exit 1 17 | fi 18 | 19 | if [[ `uname` == "Darwin" ]]; then 20 | echo "Using BSD sed" 21 | SED="sed -i.bak -E -e" 22 | else 23 | echo "Using GNU sed" 24 | SED="sed -i.bak -r -e" 25 | fi 26 | 27 | PROVIDER_URL="https:\/\/github.com\/terraform-providers\/terraform-provider-cobbler\/issues" 28 | 29 | $SED "s/GH-([0-9]+)/\[#\1\]\($PROVIDER_URL\/\1\)/g" -e 's/\[\[#(.+)([0-9])\)]$/(\[#\1\2))/g' CHANGELOG.md 30 | 31 | rm CHANGELOG.md.bak 32 | -------------------------------------------------------------------------------- /test.tf.example: -------------------------------------------------------------------------------- 1 | provider "drp" { 2 | api_user = "rocketskates" 3 | api_password = "r0cketsk8ts" 4 | api_url = "https://127.0.0.1:8092" 5 | } 6 | 7 | resource "drp_machine" "one_random_node" { 8 | count = 1 9 | 10 | # Settable values 11 | # Name = force the name to something. 12 | # Workflow (required!) = starting workflow 13 | # Description = force the description to something 14 | # owner = force the terraform/owner value to something 15 | 16 | Workflow = "centos-install" 17 | Description = "Deployed by Terraform" 18 | Meta { 19 | icon = "birthday" 20 | color = "purple" 21 | } 22 | userdata = "yaml cloudinit file" 23 | 24 | # SPECIAL FIELDS 25 | # List of profiles to apply to node (if not already there) 26 | add_profiles = ["mandy", "clause"] 27 | # list of parameters to set with their string value forms 28 | parameters = [ 29 | { 30 | name = "fred" 31 | value = "tuesday" 32 | }, 33 | { 34 | name = "jill" 35 | value = "thursday" 36 | }, 37 | ] 38 | # list of filters to reduce the nodes to draw from. 39 | # Name, UUID, and Address are unique forcing singletons 40 | # filters = [{ 41 | # name = "Name" 42 | # value = "greg2" 43 | # }] 44 | 45 | # DECOMMISSIONING OVERRIDES 46 | # decommission_workflow = "discover" 47 | # decommission_icon = "map outline" 48 | # decommission_color = "black" 49 | # completion_stage = "complete" 50 | } 51 | -------------------------------------------------------------------------------- /tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if ! which go &>/dev/null; then 6 | echo "Must have go installed" 7 | exit 255 8 | fi 9 | 10 | # Work out the GO version we are working with: 11 | GO_VERSION=$(go version | awk '{ print $3 }' | sed 's/go//') 12 | WANTED_VER=(1 12) 13 | if ! [[ "$GO_VERSION" =~ ([0-9]+)\.([0-9]+) ]]; then 14 | echo "Cannot figure out what version of Go is installed" 15 | exit 1 16 | elif ! (( ${BASH_REMATCH[1]} > ${WANTED_VER[0]} || ${BASH_REMATCH[2]} >= ${WANTED_VER[1]} )); then 17 | echo "Go Version needs to be 1.8 or higher: currently $GO_VERSION" 18 | exit -1 19 | fi 20 | 21 | export GO111MODULE=on 22 | 23 | . tools/version.sh 24 | 25 | echo "Version = $Prepart$MajorV.$MinorV.$PatchV$Extra-$GITHASH" 26 | 27 | VERFLAGS="-X main.RS_MAJOR_VERSION=$MajorV \ 28 | -X main.RS_MINOR_VERSION=$MinorV \ 29 | -X main.RS_PATCH_VERSION=$PatchV \ 30 | -X main.RS_EXTRA=$Extra \ 31 | -X main.RS_PREPART=$Prepart \ 32 | -X main.BuildStamp=`date -u '+%Y-%m-%d_%I:%M:%S%p'` \ 33 | -X main.GitHash=$GITHASH" 34 | 35 | arches=("amd64") 36 | oses=("linux" "darwin" "windows") 37 | for arch in "${arches[@]}"; do 38 | for os in "${oses[@]}"; do 39 | ( 40 | suffix="" 41 | if [[ $os == windows ]] ; then 42 | suffix=".exe" 43 | fi 44 | export GOOS="$os" GOARCH="$arch" 45 | echo "Building binaries for ${arch} ${os}" 46 | binpath="bin/$os/$arch" 47 | mkdir -p "$binpath" 48 | go build -ldflags "$VERFLAGS" -o "$binpath/terraform-provider-drp${suffix}" 49 | ) 50 | done 51 | done 52 | echo "To run tests, run: tools/test.sh" 53 | -------------------------------------------------------------------------------- /tools/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tag_re='([^-]+)-([^-]+)-g([^ ]+)' 4 | semver_re='v([0-9]+).([0-9]+).([0-9]+)' 5 | tip_re='^tip-' 6 | 7 | 8 | TAG=$(git describe --tags --abbrev=1000) 9 | if [[ $TAG =~ $tag_re ]]; then 10 | BASE="${BASH_REMATCH[1]}" 11 | AHEAD="${BASH_REMATCH[2]}" 12 | GITHASH="${BASH_REMATCH[3]}" 13 | fi 14 | 15 | if [[ $BASE == tip || $TAG == tip ]] ; then 16 | Extra="-tip" 17 | TAG=$(git describe --tags --abbrev=1000 tip^2) 18 | if [[ $TAG =~ $tag_re ]]; then 19 | BASE="${BASH_REMATCH[1]}" 20 | if [[ $AHEAD ]] ; then 21 | AHEAD=$(($AHEAD + ${BASH_REMATCH[2]})) 22 | Extra="$Extra-$(whoami)-dev" 23 | else 24 | AHEAD="${BASH_REMATCH[2]}" 25 | fi 26 | GITHASH="${GITHASH:-${BASH_REMATCH[3]}}" 27 | fi 28 | commit=$(git show $BASE | head -1 | awk '{ print $2 }') 29 | REAL_VER=$(git tag --points-at $commit | grep -v stable) 30 | TAG="${REAL_VER}-${AHEAD}-g${GITHASH}" 31 | fi 32 | 33 | if [[ $GITHASH == "" ]] ; then 34 | # Find ref tag 35 | commit=$(git show $TAG | head -1 | awk '{ print $2 }') 36 | REAL_VER=$(git tag --points-at $commit | grep -v stable) 37 | TAG="${TAG//stable/$REAL_VER}-0-g${commit}" 38 | fi 39 | 40 | if [[ $TAG =~ $tag_re ]]; then 41 | BASE="${BASH_REMATCH[1]}" 42 | AHEAD="${BASH_REMATCH[2]}" 43 | GITHASH="${BASH_REMATCH[3]}" 44 | fi 45 | 46 | if [[ $BASE =~ $semver_re ]] ; then 47 | MajorV=${BASH_REMATCH[1]} 48 | MinorV=${BASH_REMATCH[2]} 49 | PatchV=${BASH_REMATCH[3]} 50 | Extra="$Extra-$AHEAD" 51 | Prepart="v" 52 | else 53 | MajorV="$BASE" 54 | MinorV=$(whoami) 55 | PatchV=$AHEAD 56 | Extra="$Extra-strange" 57 | Prepart="" 58 | fi 59 | 60 | echo "Version = $Prepart$MajorV.$MinorV.$PatchV$Extra-$GITHASH" 61 | -------------------------------------------------------------------------------- /drp/provider_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/digitalrebar/provision/v4/api" 11 | "github.com/digitalrebar/provision/v4/test" 12 | "github.com/hashicorp/terraform/helper/schema" 13 | "github.com/hashicorp/terraform/terraform" 14 | ) 15 | 16 | var testAccDrpProviders map[string]terraform.ResourceProvider 17 | var testAccDrpProvider *schema.Provider 18 | 19 | func init() { 20 | testAccDrpProvider = Provider().(*schema.Provider) 21 | testAccDrpProviders = map[string]terraform.ResourceProvider{ 22 | "drp": testAccDrpProvider, 23 | } 24 | } 25 | 26 | func TestProvider(t *testing.T) { 27 | if err := Provider().(*schema.Provider).InternalValidate(); err != nil { 28 | t.Fatalf("err: %s", err) 29 | } 30 | } 31 | 32 | func TestProvider_impl(t *testing.T) { 33 | var _ terraform.ResourceProvider = Provider() 34 | } 35 | 36 | func testAccDrpPreCheck(t *testing.T) { 37 | os.Setenv("RS_KEY", "rocketskates:r0cketsk8ts") 38 | os.Setenv("RS_ENDPOINT", "https://127.0.0.1:10031") 39 | } 40 | 41 | var tmpDir string 42 | var session *api.Client 43 | 44 | func TestMain(m *testing.M) { 45 | var err error 46 | tmpDir, err = ioutil.TempDir("", "tf-") 47 | if err != nil { 48 | log.Printf("Creating temp dir for file root failed: %v", err) 49 | os.Exit(1) 50 | } 51 | 52 | if err := test.StartServer(tmpDir, 10031); err != nil { 53 | log.Printf("Error starting dr-provision: %v", err) 54 | os.RemoveAll(tmpDir) 55 | os.Exit(1) 56 | } 57 | 58 | count := 0 59 | for count < 30 { 60 | session, err = api.UserSession("https://127.0.0.1:10031", "rocketskates", "r0cketsk8ts") 61 | if err == nil { 62 | break 63 | } 64 | time.Sleep(1 * time.Second) 65 | count++ 66 | } 67 | if session == nil { 68 | log.Printf("Failed to create UserSession: %v", err) 69 | test.StopServer() 70 | os.RemoveAll(tmpDir) 71 | os.Exit(1) 72 | } 73 | if err != nil { 74 | log.Printf("Server failed to start in time allowed") 75 | test.StopServer() 76 | os.RemoveAll(tmpDir) 77 | os.Exit(1) 78 | } 79 | ret := m.Run() 80 | test.StopServer() 81 | os.RemoveAll(tmpDir) 82 | os.Exit(ret) 83 | } 84 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... |grep -v 'vendor') 2 | GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) 3 | PKG_NAME=drp 4 | WEBSITE_REPO=github.com/hashicorp/terraform-website 5 | 6 | default: build 7 | 8 | build: fmtcheck 9 | go get github.com/kardianos/govendor 10 | tools/build.sh 11 | go install 12 | 13 | test: fmtcheck 14 | go test -i $(TEST) || exit 1 15 | echo $(TEST) | \ 16 | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 17 | 18 | testacc: fmtcheck 19 | TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m 20 | 21 | vet: 22 | @echo "go vet ." 23 | @go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \ 24 | echo ""; \ 25 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 26 | echo "and fix them if necessary before submitting the code for review."; \ 27 | exit 1; \ 28 | fi 29 | 30 | fmt: 31 | gofmt -w $(GOFMT_FILES) 32 | 33 | fmtcheck: 34 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 35 | 36 | errcheck: 37 | @sh -c "'$(CURDIR)/scripts/errcheck.sh'" 38 | 39 | vendor-status: 40 | @govendor status 41 | 42 | test-compile: 43 | @if [ "$(TEST)" = "./..." ]; then \ 44 | echo "ERROR: Set TEST to a specific package. For example,"; \ 45 | echo " make test-compile TEST=./$(PKG_NAME)"; \ 46 | exit 1; \ 47 | fi 48 | go test -c $(TEST) $(TESTARGS) 49 | 50 | website: 51 | ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) 52 | echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." 53 | git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) 54 | endif 55 | @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) 56 | 57 | website-test: 58 | ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) 59 | echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." 60 | git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) 61 | endif 62 | @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider-test PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) 63 | 64 | 65 | .PHONY: build test testacc vet fmt fmtcheck errcheck vendor-status test-compile website website-test 66 | 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | addons: 3 | apt: 4 | packages: 5 | - sshpass 6 | - bsdtar 7 | - p7zip-full 8 | sudo: false 9 | go: 10 | - "1.10" 11 | git: 12 | depth: 500 13 | install: 14 | - go get github.com/kardianos/govendor 15 | - ./tools/build.sh 16 | - ./tools/package.sh 17 | - ./tools/publish.sh 18 | script: 19 | - make test 20 | - make testacc 21 | - make vendor-status 22 | - make vet 23 | after_success: 24 | - bash <(curl -s https://codecov.io/bash) -t 6259c706-5c1d-471f-b62b-4f908ea1801d 25 | notifications: 26 | slack: 27 | secure: Imuptg78FWWZlGVb8LBuwn45CeMfnHgf+pXbftR8mSGFRsg08jw38MIRtw/MAET3RJsciwDXf22AkgM8WgZ+fcUbJI4MGn2IvnM40T/imc5deqlMn1QdE5HasWrrBwfyY7hDu8XBexit+2fmYLGGrHX2fsbvUJepT4xOV8MZbZWMYueoBuL2bxNRR+lhjqVsKMq+/8q4g7hsKxYOdStEOZQIM13qI8dbIIQK4cCPQGtdNMGlNSX0a/nEFZmxDKh3WILssZcRst7SW3QldYppJO21PcTmY/zCTLJ5b7izweLczDmV77ckv61X50B8iaarhwEMFYs9Icg8U69mAkbqeU5HsWy1d2VDPDG+QW32VMTU371pVEzsmBVzArSXvldULmRzYcA+9LoYb5P6LNbq2y7tdimETOElbkMyw53hnb4ZPiypLWlwusZibCxGsJMp41MgbxxaMKxUgrBsAY9nSBg/oj7ixditveXm5IjqBJXfFyxuxfvcxssT15AiqFI3z+Mpoarq233V7E/c0AwdYc/iJKPtW8DG2uSjb0EMIKopXjEi/tz8ykVQH1UbKww5PvGfzCw2mCNF+Bc925coTs8gwaU+XOnqQesggUheAmpaQhbdPGpNYecbLgtQ3kafEBllTrgLyTqISkXlDMZybItP1C8GrEEshGWSEzBAEGU= 28 | deploy: 29 | - provider: releases 30 | overwrite: true 31 | api_key: 32 | secure: uKwH4f/tv36GkcwTvLfrO4AxwQ5CmOHZRhmi1rOLp3bABMsLumtXmsw204w4v6vLB/NA6+Dgp/oIMfnlvTK/aktaEBxYDWG5UIgeTdISvjvwGG2+lEehO7aC1JOwW4jPe9OOqkuhIDuQQeCWfzzRCd0zAtKS426cwMnsvY52AMm9cJdTGSznRbDn1guE5Fjt9YbIClyXHHJ38hlQMq2Rb/dqwjAprtJtHwW0h11Wu+8+dkyf1LjnFq9TOki1iiDBKakgoK+sxsnR57/IEdMZK/BhotkvpdlNyjwJusZbTZ/2F6orsXuqgEkoqYVSgea5Ce01BiA6Jm7VbFRBY41wMpsQDB12zY/58Lc0tfFY0hV+trl/FpPn6y8utVjzQ++rffsk/6tUiFEQnCAHVbYhO16fqY3yBmJeZNhVY5mO30cR0Hf5dUKJjaLBjwfNTxvfjV0BeHDR1qY7XMLgWZYeEtCwQXp0p4BGTd0T83t8X6qnoiA0z4Ys81aWV0dCZlmKveJVvPqaaSASzxje2o6d7u5QmgmiJOblk99NMqeqxsTiRReZZf+LtlVMHmTxa7fi1tfuL8chCVVG2QcTimNRWH3xl2RwTqdypAqTcuwNWtpynb/qtJX5WogXSQZFW6GEEdaB8rd+JF/s03pJ9GeGJah8hn7QbniPNx5B7t+NLCg= 33 | file: 34 | - terraform-provider-drp.zip 35 | - terraform-provider-drp.sha256 36 | skip_cleanup: true 37 | on: 38 | repo: rackn/terraform-provider-drp 39 | tags: true 40 | - provider: s3 41 | access_key_id: AKIAJY25W4N3CA7NRHJQ 42 | secret_access_key: 43 | secure: rnqO8erYlMIUFPYmw0QHZtCxlx5R9IODVYELb8sQ35WfCRz2nv1AfJzGb3fCdNRlTiphbRAamOo9lSVdd5/n7Qt9ZKIc9YhR8m9i4+elM3mIqqNrbLBRLMSDYakSo7ODbsZkkmcQGhRlpwgwk7ANCXhDhmDYTPkOLB/ZiT79kQdoKcVS93WBXOMurDYtjLViDTcsZSserMeErkEGQLcVBNwSb1BJPhAAwskuUtxmKI0NJ5LgiOnaAaQRud0E0IRs3iTy1dMCTfhSVD7o2uBrzcx2xfI7y3ct/tmrnlU4rpFp5LrKo05vOGHKeQkwvFrPWDXqYSXSGxmyaL8uk1V31zh+Xd6ph31e4AOMEo+6h4HR9rXSwf7Rp11fPj2whyhPPvbyipI3mo6R/ZzUpOgVbPVJxq4AHwQsQZTM4YjIc7xG3SQdVBZvERsQ5TgPuBx4CHOXgzhkjdneUKqC6bCJ1cJDZvel99HEmcxYjehBDuhWk4H6AguHEVs5QQfmCujNUnkBkwPOVZ225Tk/anZCnv9UUkAQShgZ8cRSrr9jEtPyxFoTjobJsuSWw0tdyQfyPj+2vd6B9gSDt9om6F5Hd+y8An6i2r0fO1RvdoZrV0uf8enTCqrbbD+8fob8NRufhXBi6N2cWuFy61qMFOpG8T2k6q3lpAqzRizGJDMST9Q= 44 | bucket: rebar-catalog 45 | region: us-west-2 46 | local-dir: rebar-catalog 47 | acl: public_read 48 | skip_cleanup: true 49 | on: 50 | repo: rackn/terraform-provider-drp 51 | tags: true 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform Provider 2 | ================== 3 | 4 | - Hashicorp Website: https://www.terraform.io 5 | - RackN Website: https://rackn.com 6 | - Digital Rebar Community: http://rebar.digital 7 | 8 | NOTE: For new users, you should use the release managed binaries from https://github.com/rackn/terraform-provider-drp/releases. 9 | 10 | NOT Documentation! 11 | ------------------ 12 | 13 | This page is about building, NOT about using, the provider! DRP Terraform Provider documentation is maintained with the project integrations documentation, please see https://provision.readthedocs.io/en/tip/doc/integrations/terraform.html 14 | 15 | Build Requirements 16 | ------------------ 17 | 18 | - [Terraform](https://www.terraform.io/downloads.html) 0.11.x 19 | - [Go](https://golang.org/doc/install) 1.10 (to build the provider plugin) 20 | - Digital Rebar terraform/[params] in system (can be imported from RackN content) 21 | 22 | Building The Provider 23 | --------------------- 24 | 25 | Clone repository to: `$GOPATH/src/github.com/rackn/terraform-provider-drp` 26 | 27 | ```sh 28 | $ mkdir -p $GOPATH/src/github.com/rackn; cd $GOPATH/src/github.com/rackn 29 | $ git clone git@github.com:rackn/terraform-provider-drp 30 | ``` 31 | 32 | Enter the provider directory and build the provider 33 | 34 | ```sh 35 | $ cd $GOPATH/src/github.com/rackn/terraform-provider-drp 36 | $ make build 37 | ``` 38 | 39 | Requirements for the Digital Rebar Provision (DRP) provider 40 | ----------------------------------------------------------- 41 | 42 | DRP Terraform Provider documentation is maintained with the project integrations documentation, please see https://provision.readthedocs.io/en/tip/doc/integrations/terraform.html 43 | 44 | The DRP Terraform Provider uses a pair of Machine Parameters to create an inventory pool. Only machines with these parameters will be available to the provider. 45 | 46 | The terraform/managed parameter determines the basic inventory availability. This flag must be set to true for Terraform to find machines. 47 | 48 | The terraform/allocated parameter determines when machines have been assigned to a Terraform plan. When true, the machine is now being managed by Terraform. When false, the machine is available for allocation. 49 | 50 | The terraform/pool parameter allows operators to create groups of machines that can be managed separately. It should have a default value of `default`. 51 | 52 | Using the RackN terraform-ready stage will automatically set these three parameters. 53 | 54 | The Terraform Provider can read additional fields when requesting inventory. In this way, users can request machines with specific characteristics. 55 | 56 | Developing the Provider 57 | ----------------------- 58 | 59 | If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.9+ is *required*). You'll also need to correctly setup a [GOPATH](http://golang.org/doc/code.html#GOPATH), as well as adding `$GOPATH/bin` to your `$PATH`. 60 | 61 | To compile the provider, run `make build`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 62 | 63 | ```sh 64 | $ make bin 65 | ... 66 | $ $GOPATH/bin/terraform-provider-drp 67 | ... 68 | ``` 69 | 70 | In order to test the provider, you can simply run `make test`. 71 | 72 | ```sh 73 | $ make test 74 | ``` 75 | 76 | In order to run the full suite of Acceptance tests, run `make testacc`. 77 | 78 | *Note:* Acceptance tests create real resources, and often cost money to run. 79 | *Note:* In this case, acceptances run locally without external resources. 80 | 81 | ```sh 82 | $ ulimit -n 2560 # for MACs 83 | $ make testacc 84 | ``` 85 | -------------------------------------------------------------------------------- /drp/resource_drp_user_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpUser_basic = ` 13 | resource "drp_user" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | }` 20 | 21 | func TestAccDrpUser_basic(t *testing.T) { 22 | user := models.User{Name: "foo", 23 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 24 | } 25 | user.Fill() 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccDrpPreCheck(t) }, 29 | Providers: testAccDrpProviders, 30 | CheckDestroy: testAccDrpCheckUserDestroy, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: testAccDrpUser_basic, 34 | Check: resource.ComposeTestCheckFunc( 35 | testAccDrpCheckUserExists(t, "drp_user.foo", &user), 36 | ), 37 | }, 38 | }, 39 | }) 40 | } 41 | 42 | var testAccDrpUser_change_1 = ` 43 | resource "drp_user" "foo" { 44 | Name = "foo" 45 | Secret = "I am a user" 46 | }` 47 | 48 | var testAccDrpUser_change_2 = ` 49 | resource "drp_user" "foo" { 50 | Name = "foo" 51 | Secret = "I am a user again" 52 | }` 53 | 54 | func TestAccDrpUser_change(t *testing.T) { 55 | user1 := models.User{Name: "foo", Secret: "I am a user"} 56 | user1.Fill() 57 | user2 := models.User{Name: "foo", Secret: "I am a user again"} 58 | user2.Fill() 59 | 60 | resource.Test(t, resource.TestCase{ 61 | PreCheck: func() { testAccDrpPreCheck(t) }, 62 | Providers: testAccDrpProviders, 63 | CheckDestroy: testAccDrpCheckUserDestroy, 64 | Steps: []resource.TestStep{ 65 | resource.TestStep{ 66 | Config: testAccDrpUser_change_1, 67 | Check: resource.ComposeTestCheckFunc( 68 | testAccDrpCheckUserExists(t, "drp_user.foo", &user1), 69 | ), 70 | }, 71 | resource.TestStep{ 72 | Config: testAccDrpUser_change_2, 73 | Check: resource.ComposeTestCheckFunc( 74 | testAccDrpCheckUserExists(t, "drp_user.foo", &user2), 75 | ), 76 | }, 77 | }, 78 | }) 79 | } 80 | 81 | // XXX: One day worry about setting user's passwords. 82 | 83 | func testAccDrpCheckUserDestroy(s *terraform.State) error { 84 | config := testAccDrpProvider.Meta().(*Config) 85 | 86 | for _, rs := range s.RootModule().Resources { 87 | if rs.Type != "drp_user" { 88 | continue 89 | } 90 | 91 | if _, err := config.session.GetModel("users", rs.Primary.ID); err == nil { 92 | return fmt.Errorf("User still exists") 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func testAccDrpCheckUserExists(t *testing.T, n string, user *models.User) resource.TestCheckFunc { 100 | return func(s *terraform.State) error { 101 | rs, ok := s.RootModule().Resources[n] 102 | if !ok { 103 | return fmt.Errorf("Not found: %s", n) 104 | } 105 | 106 | if rs.Primary.ID == "" { 107 | return fmt.Errorf("No ID is set") 108 | } 109 | 110 | config := testAccDrpProvider.Meta().(*Config) 111 | 112 | obj, err := config.session.GetModel("users", rs.Primary.ID) 113 | if err != nil { 114 | return err 115 | } 116 | found := obj.(*models.User) 117 | found.ClearValidation() 118 | 119 | if found.Name != rs.Primary.ID { 120 | return fmt.Errorf("User not found") 121 | } 122 | 123 | // Secret is unset, it should be set to something 124 | if user.Secret == "" { 125 | if found.Secret != "" { 126 | user.Secret = found.Secret 127 | } 128 | } 129 | 130 | if err := diffObjects(user, found, "User"); err != nil { 131 | return err 132 | } 133 | return nil 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /drp/resource_drp_template_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpTemplate_basic = ` 13 | resource "drp_template" "foo" { 14 | ID = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | }` 20 | 21 | func TestAccDrpTemplate_basic(t *testing.T) { 22 | template := models.Template{ID: "foo", 23 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 24 | } 25 | template.Fill() 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccDrpPreCheck(t) }, 29 | Providers: testAccDrpProviders, 30 | CheckDestroy: testAccDrpCheckTemplateDestroy, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: testAccDrpTemplate_basic, 34 | Check: resource.ComposeTestCheckFunc( 35 | testAccDrpCheckTemplateExists(t, "drp_template.foo", &template), 36 | ), 37 | }, 38 | }, 39 | }) 40 | } 41 | 42 | var testAccDrpTemplate_change_1 = ` 43 | resource "drp_template" "foo" { 44 | ID = "foo" 45 | Description = "I am a template" 46 | Contents = "base content" 47 | }` 48 | 49 | var testAccDrpTemplate_change_2 = ` 50 | resource "drp_template" "foo" { 51 | ID = "foo" 52 | Description = "I am a template again" 53 | Contents = "{{ .Env.OS }}" 54 | }` 55 | 56 | func TestAccDrpTemplate_change(t *testing.T) { 57 | template1 := models.Template{ID: "foo", Description: "I am a template", Contents: "base content"} 58 | template1.Fill() 59 | template2 := models.Template{ID: "foo", Description: "I am a template again", Contents: "{{ .Env.OS }}"} 60 | template2.Fill() 61 | 62 | resource.Test(t, resource.TestCase{ 63 | PreCheck: func() { testAccDrpPreCheck(t) }, 64 | Providers: testAccDrpProviders, 65 | CheckDestroy: testAccDrpCheckTemplateDestroy, 66 | Steps: []resource.TestStep{ 67 | resource.TestStep{ 68 | Config: testAccDrpTemplate_change_1, 69 | Check: resource.ComposeTestCheckFunc( 70 | testAccDrpCheckTemplateExists(t, "drp_template.foo", &template1), 71 | ), 72 | }, 73 | resource.TestStep{ 74 | Config: testAccDrpTemplate_change_2, 75 | Check: resource.ComposeTestCheckFunc( 76 | testAccDrpCheckTemplateExists(t, "drp_template.foo", &template2), 77 | ), 78 | }, 79 | }, 80 | }) 81 | } 82 | 83 | func testAccDrpCheckTemplateDestroy(s *terraform.State) error { 84 | config := testAccDrpProvider.Meta().(*Config) 85 | 86 | for _, rs := range s.RootModule().Resources { 87 | if rs.Type != "drp_template" { 88 | continue 89 | } 90 | 91 | if _, err := config.session.GetModel("templates", rs.Primary.ID); err == nil { 92 | return fmt.Errorf("Template still exists") 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func testAccDrpCheckTemplateExists(t *testing.T, n string, template *models.Template) resource.TestCheckFunc { 100 | return func(s *terraform.State) error { 101 | rs, ok := s.RootModule().Resources[n] 102 | if !ok { 103 | return fmt.Errorf("Not found: %s", n) 104 | } 105 | 106 | if rs.Primary.ID == "" { 107 | return fmt.Errorf("No ID is set") 108 | } 109 | 110 | config := testAccDrpProvider.Meta().(*Config) 111 | 112 | obj, err := config.session.GetModel("templates", rs.Primary.ID) 113 | if err != nil { 114 | return err 115 | } 116 | found := obj.(*models.Template) 117 | found.ClearValidation() 118 | 119 | if found.ID != rs.Primary.ID { 120 | return fmt.Errorf("Template not found") 121 | } 122 | 123 | if err := diffObjects(template, found, "Template"); err != nil { 124 | return err 125 | } 126 | return nil 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /drp/resource_drp_machine_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | "github.com/pborman/uuid" 11 | ) 12 | 13 | var testAccDrpMachine_basic = ` 14 | resource "drp_machine" "foo" { 15 | Name = "mach1" 16 | Stage = "local" 17 | completion_stage = "local" 18 | decommission_stage = "none" 19 | add_profiles = [ "p-test" ] 20 | Meta = { 21 | "feature-flags" = "change-stage-v2" 22 | "field1" = "value1" 23 | "field2" = "value2" 24 | } 25 | }` 26 | 27 | func TestAccDrpMachine_basic(t *testing.T) { 28 | machine := models.Machine{ 29 | Name: "mach1", 30 | Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7e"), 31 | Secret: "12", 32 | Runnable: true, 33 | BootEnv: "local", 34 | Stage: "local", 35 | Profiles: []string{"p-test"}, 36 | Params: map[string]interface{}{ 37 | "terraform/allocated": true, 38 | "terraform/managed": true, 39 | }, 40 | CurrentTask: -1, 41 | Meta: map[string]string{"feature-flags": "change-stage-v2", "field1": "value1", "field2": "value2"}, 42 | } 43 | machine.Fill() 44 | 45 | resource.Test(t, resource.TestCase{ 46 | PreCheck: func() { testAccDrpPreCheck(t) }, 47 | Providers: testAccDrpProviders, 48 | CheckDestroy: testAccDrpCheckMachineDestroy, 49 | Steps: []resource.TestStep{ 50 | resource.TestStep{ 51 | PreConfig: testAccCreateResources, 52 | Config: testAccDrpMachine_basic, 53 | Check: resource.ComposeTestCheckFunc( 54 | testAccDrpCheckMachineExists(t, "drp_machine.foo", &machine), 55 | ), 56 | }, 57 | }, 58 | }) 59 | } 60 | 61 | func testAccCreateResources() { 62 | config := testAccDrpProvider.Meta().(*Config) 63 | 64 | p := &models.Profile{Name: "p-test"} 65 | ta := &models.Param{Name: "terraform/allocated", Schema: map[string]string{"type": "boolean"}} 66 | tm := &models.Param{Name: "terraform/managed", Schema: map[string]string{"type": "boolean"}} 67 | tp := &models.Param{Name: "terraform/pool", Schema: map[string]string{"type": "string", "default": "default"}} 68 | m := &models.Machine{Name: "mach1", Secret: "12", Params: map[string]interface{}{"terraform/allocated": false, "terraform/managed": true}, Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7e")} 69 | 70 | config.session.CreateModel(p) 71 | config.session.CreateModel(ta) 72 | config.session.CreateModel(tm) 73 | config.session.CreateModel(tp) 74 | config.session.CreateModel(m) 75 | } 76 | 77 | func testAccDrpCheckMachineDestroy(s *terraform.State) error { 78 | config := testAccDrpProvider.Meta().(*Config) 79 | 80 | for _, rs := range s.RootModule().Resources { 81 | if rs.Type != "drp_machine" { 82 | continue 83 | } 84 | 85 | if _, err := config.session.GetModel("machines", rs.Primary.ID); err != nil { 86 | return fmt.Errorf("Machine does not exist") 87 | } 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func testAccDrpCheckMachineExists(t *testing.T, n string, machine *models.Machine) resource.TestCheckFunc { 94 | return func(s *terraform.State) error { 95 | rs, ok := s.RootModule().Resources[n] 96 | if !ok { 97 | return fmt.Errorf("Not found: %s", n) 98 | } 99 | 100 | if rs.Primary.ID == "" { 101 | return fmt.Errorf("No ID is set") 102 | } 103 | 104 | config := testAccDrpProvider.Meta().(*Config) 105 | 106 | obj, err := config.session.GetModel("machines", rs.Primary.ID) 107 | if err != nil { 108 | return err 109 | } 110 | found := obj.(*models.Machine) 111 | found.ClearValidation() 112 | 113 | if found.Key() != rs.Primary.ID { 114 | return fmt.Errorf("Machine not found") 115 | } 116 | 117 | if err := diffObjects(machine, found, "Machine"); err != nil { 118 | return err 119 | } 120 | return nil 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /drp/resource_drp_param_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpParam_basic = ` 13 | resource "drp_param" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | }` 20 | 21 | func TestAccDrpParam_basic(t *testing.T) { 22 | param := models.Param{Name: "foo", 23 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 24 | } 25 | param.Fill() 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccDrpPreCheck(t) }, 29 | Providers: testAccDrpProviders, 30 | CheckDestroy: testAccDrpCheckParamDestroy, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: testAccDrpParam_basic, 34 | Check: resource.ComposeTestCheckFunc( 35 | testAccDrpCheckParamExists(t, "drp_param.foo", ¶m), 36 | ), 37 | }, 38 | }, 39 | }) 40 | } 41 | 42 | var testAccDrpParam_change_1 = ` 43 | resource "drp_param" "foo" { 44 | Name = "foo" 45 | Description = "I am a param" 46 | Documentation = "here I am" 47 | Schema = "{\"type\":\"boolean\"}" 48 | }` 49 | 50 | var testAccDrpParam_change_2 = ` 51 | resource "drp_param" "foo" { 52 | Name = "foo" 53 | Description = "I am a param again" 54 | Documentation = "here am I" 55 | Schema = "{\"type\":\"integer\"}" 56 | }` 57 | 58 | func TestAccDrpParam_change(t *testing.T) { 59 | param1 := models.Param{ 60 | Name: "foo", 61 | Description: "I am a param", 62 | Documentation: "here I am", 63 | Schema: map[string]string{"type": "boolean"}, 64 | } 65 | param1.Fill() 66 | param2 := models.Param{ 67 | Name: "foo", 68 | Description: "I am a param again", 69 | Documentation: "here am I", 70 | Schema: map[string]string{"type": "integer"}, 71 | } 72 | param2.Fill() 73 | 74 | resource.Test(t, resource.TestCase{ 75 | PreCheck: func() { testAccDrpPreCheck(t) }, 76 | Providers: testAccDrpProviders, 77 | CheckDestroy: testAccDrpCheckParamDestroy, 78 | Steps: []resource.TestStep{ 79 | resource.TestStep{ 80 | Config: testAccDrpParam_change_1, 81 | Check: resource.ComposeTestCheckFunc( 82 | testAccDrpCheckParamExists(t, "drp_param.foo", ¶m1), 83 | ), 84 | }, 85 | resource.TestStep{ 86 | Config: testAccDrpParam_change_2, 87 | Check: resource.ComposeTestCheckFunc( 88 | testAccDrpCheckParamExists(t, "drp_param.foo", ¶m2), 89 | ), 90 | }, 91 | }, 92 | }) 93 | } 94 | 95 | func testAccDrpCheckParamDestroy(s *terraform.State) error { 96 | config := testAccDrpProvider.Meta().(*Config) 97 | 98 | for _, rs := range s.RootModule().Resources { 99 | if rs.Type != "drp_param" { 100 | continue 101 | } 102 | 103 | if _, err := config.session.GetModel("params", rs.Primary.ID); err == nil { 104 | return fmt.Errorf("Param still exists") 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func testAccDrpCheckParamExists(t *testing.T, n string, param *models.Param) resource.TestCheckFunc { 112 | return func(s *terraform.State) error { 113 | rs, ok := s.RootModule().Resources[n] 114 | if !ok { 115 | return fmt.Errorf("Not found: %s", n) 116 | } 117 | 118 | if rs.Primary.ID == "" { 119 | return fmt.Errorf("No ID is set") 120 | } 121 | 122 | config := testAccDrpProvider.Meta().(*Config) 123 | 124 | obj, err := config.session.GetModel("params", rs.Primary.ID) 125 | if err != nil { 126 | return err 127 | } 128 | found := obj.(*models.Param) 129 | found.ClearValidation() 130 | 131 | if found.Name != rs.Primary.ID { 132 | return fmt.Errorf("Param not found") 133 | } 134 | 135 | if err := diffObjects(param, found, "Param"); err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /drp/provider.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/digitalrebar/provision/v4/models" 10 | "github.com/hashicorp/terraform/helper/schema" 11 | "github.com/hashicorp/terraform/terraform" 12 | ) 13 | 14 | /* 15 | * Enable terraform to use DRP as a provider. Fill out the 16 | * appropriate functions and information about this plugin. 17 | */ 18 | func Provider() terraform.ResourceProvider { 19 | log.Println("[DEBUG] Initializing the DRP provider") 20 | p := &schema.Provider{ 21 | Schema: map[string]*schema.Schema{ 22 | "api_key": { 23 | Type: schema.TypeString, 24 | Optional: true, 25 | Description: "The api key for API operations", 26 | DefaultFunc: schema.EnvDefaultFunc("RS_TOKEN", nil), 27 | }, 28 | "api_user": { 29 | Type: schema.TypeString, 30 | Optional: true, 31 | Description: "The api user for API operations", 32 | DefaultFunc: envDefaultKeyFunc("RS_KEY", "username"), 33 | }, 34 | "api_password": { 35 | Type: schema.TypeString, 36 | Optional: true, 37 | Description: "The api password for API operations", 38 | DefaultFunc: envDefaultKeyFunc("RS_KEY", "password"), 39 | }, 40 | "api_url": { 41 | Type: schema.TypeString, 42 | Required: true, 43 | Description: "The DRP server URL. ie: https://1.2.3.4:8092", 44 | DefaultFunc: schema.EnvDefaultFunc("RS_ENDPOINT", nil), 45 | }, 46 | }, 47 | 48 | ResourcesMap: map[string]*schema.Resource{ 49 | "drp_machine": resourceMachine(), 50 | }, 51 | DataSourcesMap: map[string]*schema.Resource{ 52 | "drp_machine": dataSourceMachine(), 53 | }, 54 | 55 | ConfigureFunc: providerConfigure, 56 | } 57 | 58 | for _, m := range models.All() { 59 | pref := m.Prefix() 60 | // These are generally read-only. preferences is the one to come. 61 | if pref == "preferences" || pref == "plugin_providers" || 62 | pref == "interfaces" || pref == "jobs" || pref == "leases" { 63 | continue 64 | } 65 | 66 | spref := strings.TrimRight(pref, "s") 67 | if pref == "machines" { 68 | // Machine is already added, add raw_machine to manipulate raw machine objects 69 | spref = "raw_machine" 70 | } 71 | p.ResourcesMap[fmt.Sprintf("drp_%s", spref)] = resourceGeneric(pref) 72 | p.DataSourcesMap[fmt.Sprintf("drp_%s", spref)] = dataSourceGeneric(pref) 73 | } 74 | 75 | return p 76 | } 77 | 78 | func envDefaultKeyFunc(k, part string) schema.SchemaDefaultFunc { 79 | return func() (interface{}, error) { 80 | if v := os.Getenv(k); v != "" { 81 | parts := strings.SplitN(v, ":", 2) 82 | if len(parts) < 2 { 83 | return nil, fmt.Errorf("RS_KEY has not enough parts") 84 | } 85 | if part == "username" { 86 | return parts[0], nil 87 | } else if part == "password" { 88 | return parts[1], nil 89 | } 90 | return nil, fmt.Errorf("Asking for unknown part of RS_KEY: %s", part) 91 | } 92 | 93 | return nil, nil 94 | } 95 | } 96 | 97 | /* 98 | * The config method that terraform uses to pass information about configuration 99 | * to the plugin. 100 | */ 101 | func providerConfigure(d *schema.ResourceData) (interface{}, error) { 102 | log.Println("[DEBUG] Configuring the DRP provider") 103 | config := Config{ 104 | Url: d.Get("api_url").(string), 105 | } 106 | 107 | if key := d.Get("api_key"); key != nil { 108 | config.Token = key.(string) 109 | } 110 | if user := d.Get("api_user"); user != nil { 111 | config.Username = user.(string) 112 | config.Password = d.Get("api_password").(string) 113 | } 114 | 115 | if config.Token == "" && config.Username == "" { 116 | return nil, fmt.Errorf("drp provider requires either user or token ids") 117 | } 118 | if config.Username != "" && config.Password == "" { 119 | return nil, fmt.Errorf("drp provider requires a password for the specified user") 120 | } 121 | 122 | if err := config.validateAndConnect(); err != nil { 123 | return nil, err 124 | } 125 | 126 | return &config, nil 127 | } 128 | -------------------------------------------------------------------------------- /drp/resource_drp_profile_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpProfile_basic = ` 13 | resource "drp_profile" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | }` 20 | 21 | func TestAccDrpProfile_basic(t *testing.T) { 22 | profile := models.Profile{Name: "foo", 23 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 24 | } 25 | profile.Fill() 26 | 27 | resource.Test(t, resource.TestCase{ 28 | PreCheck: func() { testAccDrpPreCheck(t) }, 29 | Providers: testAccDrpProviders, 30 | CheckDestroy: testAccDrpCheckProfileDestroy, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: testAccDrpProfile_basic, 34 | Check: resource.ComposeTestCheckFunc( 35 | testAccDrpCheckProfileExists(t, "drp_profile.foo", &profile), 36 | ), 37 | }, 38 | }, 39 | }) 40 | } 41 | 42 | var testAccDrpProfile_change_1 = ` 43 | resource "drp_profile" "foo" { 44 | Name = "foo" 45 | Description = "I am a profile" 46 | }` 47 | 48 | var testAccDrpProfile_change_2 = ` 49 | resource "drp_profile" "foo" { 50 | Name = "foo" 51 | Description = "I am a profile again" 52 | }` 53 | 54 | func TestAccDrpProfile_change(t *testing.T) { 55 | profile1 := models.Profile{Name: "foo", Description: "I am a profile"} 56 | profile1.Fill() 57 | profile2 := models.Profile{Name: "foo", Description: "I am a profile again"} 58 | profile2.Fill() 59 | 60 | resource.Test(t, resource.TestCase{ 61 | PreCheck: func() { testAccDrpPreCheck(t) }, 62 | Providers: testAccDrpProviders, 63 | CheckDestroy: testAccDrpCheckProfileDestroy, 64 | Steps: []resource.TestStep{ 65 | resource.TestStep{ 66 | Config: testAccDrpProfile_change_1, 67 | Check: resource.ComposeTestCheckFunc( 68 | testAccDrpCheckProfileExists(t, "drp_profile.foo", &profile1), 69 | ), 70 | }, 71 | resource.TestStep{ 72 | Config: testAccDrpProfile_change_2, 73 | Check: resource.ComposeTestCheckFunc( 74 | testAccDrpCheckProfileExists(t, "drp_profile.foo", &profile2), 75 | ), 76 | }, 77 | }, 78 | }) 79 | } 80 | 81 | var testAccDrpProfile_withParams = ` 82 | resource "drp_profile" "foo" { 83 | Name = "foo" 84 | Description = "I am a profile again" 85 | Params = { 86 | "test/string" = "fred" 87 | "test/int" = 3 88 | "test/bool" = "true" 89 | "test/list" = "[\"one\",\"two\"]" 90 | } 91 | }` 92 | 93 | func TestAccDrpProfile_withParams(t *testing.T) { 94 | profile := models.Profile{Name: "foo", Description: "I am a profile again", 95 | Params: map[string]interface{}{ 96 | "test/string": "fred", 97 | "test/int": 3, 98 | "test/bool": true, 99 | "test/list": []string{"one", "two"}, 100 | }, 101 | } 102 | profile.Fill() 103 | 104 | resource.Test(t, resource.TestCase{ 105 | PreCheck: func() { testAccDrpPreCheck(t) }, 106 | Providers: testAccDrpProviders, 107 | CheckDestroy: testAccDrpCheckProfileDestroy, 108 | Steps: []resource.TestStep{ 109 | resource.TestStep{ 110 | Config: testAccDrpProfile_withParams, 111 | Check: resource.ComposeTestCheckFunc( 112 | testAccDrpCheckProfileExists(t, "drp_profile.foo", &profile), 113 | ), 114 | }, 115 | }, 116 | }) 117 | } 118 | 119 | func testAccDrpCheckProfileDestroy(s *terraform.State) error { 120 | config := testAccDrpProvider.Meta().(*Config) 121 | 122 | for _, rs := range s.RootModule().Resources { 123 | if rs.Type != "drp_profile" { 124 | continue 125 | } 126 | 127 | if _, err := config.session.GetModel("profiles", rs.Primary.ID); err == nil { 128 | return fmt.Errorf("Profile still exists") 129 | } 130 | } 131 | 132 | return nil 133 | } 134 | 135 | func testAccDrpCheckProfileExists(t *testing.T, n string, profile *models.Profile) resource.TestCheckFunc { 136 | return func(s *terraform.State) error { 137 | rs, ok := s.RootModule().Resources[n] 138 | if !ok { 139 | return fmt.Errorf("Not found: %s", n) 140 | } 141 | 142 | if rs.Primary.ID == "" { 143 | return fmt.Errorf("No ID is set") 144 | } 145 | 146 | config := testAccDrpProvider.Meta().(*Config) 147 | 148 | obj, err := config.session.GetModel("profiles", rs.Primary.ID) 149 | if err != nil { 150 | return err 151 | } 152 | found := obj.(*models.Profile) 153 | found.ClearValidation() 154 | 155 | if found.Name != rs.Primary.ID { 156 | return fmt.Errorf("Profile not found") 157 | } 158 | 159 | if err := diffObjects(profile, found, "Profile"); err != nil { 160 | return err 161 | } 162 | return nil 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /drp/resource_drp_reservation_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | 8 | "github.com/digitalrebar/provision/v4/models" 9 | "github.com/hashicorp/terraform/helper/resource" 10 | "github.com/hashicorp/terraform/terraform" 11 | ) 12 | 13 | var testAccDrpReservation_basic = ` 14 | resource "drp_reservation" "foo" { 15 | Addr = "1.1.1.1" 16 | Token = "aa:bb:cc:dd:ee:ff" 17 | Strategy = "MAC" 18 | Meta = { 19 | "field1" = "value1" 20 | "field2" = "value2" 21 | } 22 | }` 23 | 24 | func TestAccDrpReservation_basic(t *testing.T) { 25 | reservation := models.Reservation{ 26 | Addr: net.ParseIP("1.1.1.1"), 27 | Token: "aa:bb:cc:dd:ee:ff", 28 | Strategy: "MAC", 29 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 30 | } 31 | 32 | reservation.Fill() 33 | 34 | resource.Test(t, resource.TestCase{ 35 | PreCheck: func() { testAccDrpPreCheck(t) }, 36 | Providers: testAccDrpProviders, 37 | CheckDestroy: testAccDrpCheckReservationDestroy, 38 | Steps: []resource.TestStep{ 39 | resource.TestStep{ 40 | Config: testAccDrpReservation_basic, 41 | Check: resource.ComposeTestCheckFunc( 42 | testAccDrpCheckReservationExists(t, "drp_reservation.foo", &reservation), 43 | ), 44 | }, 45 | }, 46 | }) 47 | } 48 | 49 | var testAccDrpReservation_change_1 = ` 50 | resource "drp_reservation" "foo" { 51 | Addr = "1.1.1.1" 52 | Token = "aa:bb:cc:dd:ee:ff" 53 | Strategy = "MAC" 54 | NextServer = "1.2.3.4" 55 | Options = [ 56 | { Code = 30, Value = "fred" }, 57 | { Code = 3, Value = "1.1.1.1" } 58 | ] 59 | }` 60 | 61 | var testAccDrpReservation_change_2 = ` 62 | resource "drp_reservation" "foo" { 63 | Addr = "1.1.1.1" 64 | Token = "aa:bb:cc:dd:ee:ff" 65 | Strategy = "MAC" 66 | NextServer = "1.2.3.5" 67 | Options = [ 68 | { Code = 33, Value = "fred" }, 69 | { Code = 4, Value = "1.2.1.1" } 70 | ] 71 | }` 72 | 73 | func TestAccDrpReservation_change(t *testing.T) { 74 | reservation1 := models.Reservation{ 75 | Addr: net.ParseIP("1.1.1.1"), 76 | Token: "aa:bb:cc:dd:ee:ff", 77 | Strategy: "MAC", 78 | NextServer: net.ParseIP("1.2.3.4"), 79 | Options: []models.DhcpOption{ 80 | models.DhcpOption{Code: 30, Value: "fred"}, 81 | models.DhcpOption{Code: 3, Value: "1.1.1.1"}, 82 | }, 83 | } 84 | reservation1.Fill() 85 | reservation2 := models.Reservation{ 86 | Addr: net.ParseIP("1.1.1.1"), 87 | Token: "aa:bb:cc:dd:ee:ff", 88 | Strategy: "MAC", 89 | NextServer: net.ParseIP("1.2.3.5"), 90 | Options: []models.DhcpOption{ 91 | models.DhcpOption{Code: 33, Value: "fred"}, 92 | models.DhcpOption{Code: 4, Value: "1.2.1.1"}, 93 | }, 94 | } 95 | reservation2.Fill() 96 | 97 | resource.Test(t, resource.TestCase{ 98 | PreCheck: func() { testAccDrpPreCheck(t) }, 99 | Providers: testAccDrpProviders, 100 | CheckDestroy: testAccDrpCheckReservationDestroy, 101 | Steps: []resource.TestStep{ 102 | resource.TestStep{ 103 | Config: testAccDrpReservation_change_1, 104 | Check: resource.ComposeTestCheckFunc( 105 | testAccDrpCheckReservationExists(t, "drp_reservation.foo", &reservation1), 106 | ), 107 | }, 108 | resource.TestStep{ 109 | Config: testAccDrpReservation_change_2, 110 | Check: resource.ComposeTestCheckFunc( 111 | testAccDrpCheckReservationExists(t, "drp_reservation.foo", &reservation2), 112 | ), 113 | }, 114 | }, 115 | }) 116 | } 117 | 118 | func testAccDrpCheckReservationDestroy(s *terraform.State) error { 119 | config := testAccDrpProvider.Meta().(*Config) 120 | 121 | for _, rs := range s.RootModule().Resources { 122 | if rs.Type != "drp_reservation" { 123 | continue 124 | } 125 | 126 | if _, err := config.session.GetModel("reservations", rs.Primary.ID); err == nil { 127 | return fmt.Errorf("Reservation still exists") 128 | } 129 | } 130 | 131 | return nil 132 | } 133 | 134 | func testAccDrpCheckReservationExists(t *testing.T, n string, reservation *models.Reservation) resource.TestCheckFunc { 135 | return func(s *terraform.State) error { 136 | rs, ok := s.RootModule().Resources[n] 137 | if !ok { 138 | return fmt.Errorf("Not found: %s", n) 139 | } 140 | 141 | if rs.Primary.ID == "" { 142 | return fmt.Errorf("No ID is set") 143 | } 144 | 145 | config := testAccDrpProvider.Meta().(*Config) 146 | 147 | obj, err := config.session.GetModel("reservations", rs.Primary.ID) 148 | if err != nil { 149 | return err 150 | } 151 | found := obj.(*models.Reservation) 152 | found.ClearValidation() 153 | 154 | if found.Key() != rs.Primary.ID { 155 | return fmt.Errorf("Reservation not found: %s %s", found.Key(), rs.Primary.ID) 156 | } 157 | 158 | if err := diffObjects(reservation, found, "Reservation"); err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /drp/resource_drp_task_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpTask_basic = ` 13 | resource "drp_task" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "feature-flags" = "sane-exit-codes" 17 | "field1" = "value1" 18 | "field2" = "value2" 19 | } 20 | }` 21 | 22 | func TestAccDrpTask_basic(t *testing.T) { 23 | task := models.Task{Name: "foo", 24 | Meta: map[string]string{"field1": "value1", "field2": "value2", "feature-flags": "sane-exit-codes"}, 25 | } 26 | task.Fill() 27 | 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccDrpPreCheck(t) }, 30 | Providers: testAccDrpProviders, 31 | CheckDestroy: testAccDrpCheckTaskDestroy, 32 | Steps: []resource.TestStep{ 33 | { 34 | Config: testAccDrpTask_basic, 35 | Check: resource.ComposeTestCheckFunc( 36 | testAccDrpCheckTaskExists(t, "drp_task.foo", &task), 37 | ), 38 | }, 39 | }, 40 | }) 41 | } 42 | 43 | var testAccDrpTask_change_1 = ` 44 | resource "drp_task" "foo" { 45 | Name = "foo" 46 | Description = "I am a task" 47 | Documentation = "I am docs" 48 | RequiredParams = [ "p1", "p2" ] 49 | OptionalParams = [ "p3", "p4" ] 50 | Templates = [ 51 | { Name = "t1", Path = "fred1", Contents = "temp1"}, 52 | { Name = "t2", Path = "fred2", Contents = "actual stuff"} 53 | ] 54 | }` 55 | 56 | var testAccDrpTask_change_2 = ` 57 | resource "drp_task" "foo" { 58 | Name = "foo" 59 | Description = "I am a task again" 60 | Documentation = "I am docs more so" 61 | RequiredParams = [ "p3", "p4" ] 62 | OptionalParams = [ "p1", "p2" ] 63 | Templates = [ 64 | { Name = "t3", Path = "jill1", Contents = "temp2"}, 65 | { Name = "t4", Path = "jill2", Contents = "really actual stuff"} 66 | ] 67 | }` 68 | 69 | func TestAccDrpTask_change(t *testing.T) { 70 | task1 := models.Task{ 71 | Name: "foo", 72 | Description: "I am a task", 73 | Documentation: "I am docs", 74 | Meta: map[string]string{"feature-flags": "sane-exit-codes"}, 75 | RequiredParams: []string{"p1", "p2"}, 76 | OptionalParams: []string{"p3", "p4"}, 77 | Templates: []models.TemplateInfo{ 78 | {Name: "t1", Path: "fred1", Contents: "temp1", Meta: map[string]string{}}, 79 | {Name: "t2", Path: "fred2", Contents: "actual stuff", Meta: map[string]string{}}, 80 | }, 81 | } 82 | task1.Fill() 83 | task2 := models.Task{ 84 | Name: "foo", 85 | Description: "I am a task again", 86 | Documentation: "I am docs more so", 87 | Meta: map[string]string{"feature-flags": "sane-exit-codes"}, 88 | RequiredParams: []string{"p3", "p4"}, 89 | OptionalParams: []string{"p1", "p2"}, 90 | Templates: []models.TemplateInfo{ 91 | {Name: "t3", Path: "jill1", Contents: "temp2", Meta: map[string]string{}}, 92 | {Name: "t4", Path: "jill2", Contents: "really actual stuff", Meta: map[string]string{}}, 93 | }, 94 | } 95 | task2.Fill() 96 | 97 | resource.Test(t, resource.TestCase{ 98 | PreCheck: func() { testAccDrpPreCheck(t) }, 99 | Providers: testAccDrpProviders, 100 | CheckDestroy: testAccDrpCheckTaskDestroy, 101 | Steps: []resource.TestStep{ 102 | { 103 | Config: testAccDrpTask_change_1, 104 | Check: resource.ComposeTestCheckFunc( 105 | testAccDrpCheckTaskExists(t, "drp_task.foo", &task1), 106 | ), 107 | }, 108 | { 109 | Config: testAccDrpTask_change_2, 110 | Check: resource.ComposeTestCheckFunc( 111 | testAccDrpCheckTaskExists(t, "drp_task.foo", &task2), 112 | ), 113 | }, 114 | }, 115 | }) 116 | } 117 | 118 | func testAccDrpCheckTaskDestroy(s *terraform.State) error { 119 | config := testAccDrpProvider.Meta().(*Config) 120 | 121 | for _, rs := range s.RootModule().Resources { 122 | if rs.Type != "drp_task" { 123 | continue 124 | } 125 | 126 | if _, err := config.session.GetModel("tasks", rs.Primary.ID); err == nil { 127 | return fmt.Errorf("Task still exists") 128 | } 129 | } 130 | 131 | return nil 132 | } 133 | 134 | func testAccDrpCheckTaskExists(t *testing.T, n string, task *models.Task) resource.TestCheckFunc { 135 | return func(s *terraform.State) error { 136 | rs, ok := s.RootModule().Resources[n] 137 | if !ok { 138 | return fmt.Errorf("Not found: %s", n) 139 | } 140 | 141 | if rs.Primary.ID == "" { 142 | return fmt.Errorf("No ID is set") 143 | } 144 | 145 | config := testAccDrpProvider.Meta().(*Config) 146 | 147 | obj, err := config.session.GetModel("tasks", rs.Primary.ID) 148 | if err != nil { 149 | return err 150 | } 151 | found := obj.(*models.Task) 152 | found.ClearValidation() 153 | 154 | if found.Name != rs.Primary.ID { 155 | return fmt.Errorf("Task not found") 156 | } 157 | 158 | if err := diffObjects(task, found, "Task"); err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /drp/resource_drp_plugin_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpPlugin_basic = ` 13 | resource "drp_plugin" "foo" { 14 | Name = "foo" 15 | PluginProvider = "ipmi" 16 | Meta = { 17 | "field1" = "value1" 18 | "field2" = "value2" 19 | } 20 | }` 21 | 22 | func TestAccDrpPlugin_basic(t *testing.T) { 23 | plugin := models.Plugin{Name: "foo", 24 | Provider: "ipmi", 25 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 26 | PluginErrors: []string{"Missing Plugin Provider: ipmi"}, 27 | } 28 | plugin.Fill() 29 | 30 | resource.Test(t, resource.TestCase{ 31 | PreCheck: func() { testAccDrpPreCheck(t) }, 32 | Providers: testAccDrpProviders, 33 | CheckDestroy: testAccDrpCheckPluginDestroy, 34 | Steps: []resource.TestStep{ 35 | resource.TestStep{ 36 | Config: testAccDrpPlugin_basic, 37 | Check: resource.ComposeTestCheckFunc( 38 | testAccDrpCheckPluginExists(t, "drp_plugin.foo", &plugin), 39 | ), 40 | }, 41 | }, 42 | }) 43 | } 44 | 45 | var testAccDrpPlugin_change_1 = ` 46 | resource "drp_plugin" "foo" { 47 | Name = "foo" 48 | PluginProvider = "ipmi" 49 | Description = "I am a plugin" 50 | }` 51 | 52 | var testAccDrpPlugin_change_2 = ` 53 | resource "drp_plugin" "foo" { 54 | Name = "foo" 55 | PluginProvider = "ipmi" 56 | Description = "I am a plugin again" 57 | }` 58 | 59 | func TestAccDrpPlugin_change(t *testing.T) { 60 | plugin1 := models.Plugin{ 61 | Name: "foo", 62 | Description: "I am a plugin", 63 | Provider: "ipmi", 64 | PluginErrors: []string{"Missing Plugin Provider: ipmi"}, 65 | } 66 | plugin1.Fill() 67 | plugin2 := models.Plugin{ 68 | Name: "foo", 69 | Description: "I am a plugin again", 70 | Provider: "ipmi", 71 | PluginErrors: []string{"Missing Plugin Provider: ipmi"}, 72 | } 73 | plugin2.Fill() 74 | 75 | resource.Test(t, resource.TestCase{ 76 | PreCheck: func() { testAccDrpPreCheck(t) }, 77 | Providers: testAccDrpProviders, 78 | CheckDestroy: testAccDrpCheckPluginDestroy, 79 | Steps: []resource.TestStep{ 80 | resource.TestStep{ 81 | Config: testAccDrpPlugin_change_1, 82 | Check: resource.ComposeTestCheckFunc( 83 | testAccDrpCheckPluginExists(t, "drp_plugin.foo", &plugin1), 84 | ), 85 | }, 86 | resource.TestStep{ 87 | Config: testAccDrpPlugin_change_2, 88 | Check: resource.ComposeTestCheckFunc( 89 | testAccDrpCheckPluginExists(t, "drp_plugin.foo", &plugin2), 90 | ), 91 | }, 92 | }, 93 | }) 94 | } 95 | 96 | var testAccDrpPlugin_withParams = ` 97 | resource "drp_plugin" "foo" { 98 | Name = "foo" 99 | PluginProvider = "ipmi" 100 | Params = { 101 | "test/string" = "fred" 102 | "test/int" = 3 103 | "test/bool" = "true" 104 | "test/list" = "[\"one\",\"two\"]" 105 | } 106 | }` 107 | 108 | func TestAccDrpPlugin_withParams(t *testing.T) { 109 | plugin := models.Plugin{Name: "foo", Provider: "ipmi", 110 | Params: map[string]interface{}{ 111 | "test/string": "fred", 112 | "test/int": 3, 113 | "test/bool": true, 114 | "test/list": []string{"one", "two"}, 115 | }, 116 | PluginErrors: []string{"Missing Plugin Provider: ipmi"}, 117 | } 118 | plugin.Fill() 119 | 120 | resource.Test(t, resource.TestCase{ 121 | PreCheck: func() { testAccDrpPreCheck(t) }, 122 | Providers: testAccDrpProviders, 123 | CheckDestroy: testAccDrpCheckPluginDestroy, 124 | Steps: []resource.TestStep{ 125 | resource.TestStep{ 126 | Config: testAccDrpPlugin_withParams, 127 | Check: resource.ComposeTestCheckFunc( 128 | testAccDrpCheckPluginExists(t, "drp_plugin.foo", &plugin), 129 | ), 130 | }, 131 | }, 132 | }) 133 | } 134 | 135 | func testAccDrpCheckPluginDestroy(s *terraform.State) error { 136 | config := testAccDrpProvider.Meta().(*Config) 137 | 138 | for _, rs := range s.RootModule().Resources { 139 | if rs.Type != "drp_plugin" { 140 | continue 141 | } 142 | 143 | if _, err := config.session.GetModel("plugins", rs.Primary.ID); err == nil { 144 | return fmt.Errorf("Plugin still exists") 145 | } 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func testAccDrpCheckPluginExists(t *testing.T, n string, plugin *models.Plugin) resource.TestCheckFunc { 152 | return func(s *terraform.State) error { 153 | rs, ok := s.RootModule().Resources[n] 154 | if !ok { 155 | return fmt.Errorf("Not found: %s", n) 156 | } 157 | 158 | if rs.Primary.ID == "" { 159 | return fmt.Errorf("No ID is set") 160 | } 161 | 162 | config := testAccDrpProvider.Meta().(*Config) 163 | 164 | obj, err := config.session.GetModel("plugins", rs.Primary.ID) 165 | if err != nil { 166 | return err 167 | } 168 | found := obj.(*models.Plugin) 169 | found.ClearValidation() 170 | 171 | if found.Name != rs.Primary.ID { 172 | return fmt.Errorf("Plugin not found") 173 | } 174 | 175 | if err := diffObjects(plugin, found, "Plugin"); err != nil { 176 | return err 177 | } 178 | return nil 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /drp/resource_drp_stage_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpStage_basic = ` 13 | resource "drp_stage" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | }` 20 | 21 | func TestAccDrpStage_basic(t *testing.T) { 22 | stage := models.Stage{Name: "foo", 23 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 24 | RunnerWait: true, 25 | } 26 | stage.Fill() 27 | 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccDrpPreCheck(t) }, 30 | Providers: testAccDrpProviders, 31 | CheckDestroy: testAccDrpCheckStageDestroy, 32 | Steps: []resource.TestStep{ 33 | { 34 | Config: testAccDrpStage_basic, 35 | Check: resource.ComposeTestCheckFunc( 36 | testAccDrpCheckStageExists(t, "drp_stage.foo", &stage), 37 | ), 38 | }, 39 | }, 40 | }) 41 | } 42 | 43 | var testAccDrpStage_change_1 = ` 44 | resource "drp_stage" "foo" { 45 | Name = "foo" 46 | Description = "I am a stage" 47 | RequiredParams = [ "p1", "p2" ] 48 | OptionalParams = [ "p3", "p4" ] 49 | Templates = [ 50 | { Name = "t1", Path = "fred1", Contents = "temp1"}, 51 | { Name = "t2", Path = "fred2", Contents = "actual stuff"} 52 | ] 53 | BootEnv = "local" 54 | Tasks = [ "t1", "t2" ] 55 | Profiles = [ "p1" ] 56 | Reboot = true 57 | RunnerWait = true 58 | }` 59 | 60 | var testAccDrpStage_change_2 = ` 61 | resource "drp_stage" "foo" { 62 | Name = "foo" 63 | Description = "I am a stage again" 64 | RequiredParams = [ "p3", "p4" ] 65 | OptionalParams = [ "p1", "p2" ] 66 | Templates = [ 67 | { Name = "t3", Path = "jill1", Contents = "temp2"}, 68 | { Name = "t4", Path = "jill2", Contents = "really actual stuff"} 69 | ] 70 | Tasks = [ "t3", "t4", "t5" ] 71 | Profiles = [ "p2", "p3" ] 72 | Reboot = false 73 | RunnerWait = true 74 | }` 75 | 76 | func TestAccDrpStage_change(t *testing.T) { 77 | stage1 := models.Stage{ 78 | Name: "foo", 79 | Description: "I am a stage", 80 | RequiredParams: []string{"p1", "p2"}, 81 | OptionalParams: []string{"p3", "p4"}, 82 | Templates: []models.TemplateInfo{ 83 | {Name: "t1", Path: "fred1", Contents: "temp1", Meta: map[string]string{}}, 84 | {Name: "t2", Path: "fred2", Contents: "actual stuff", Meta: map[string]string{}}, 85 | }, 86 | BootEnv: "local", 87 | Tasks: []string{"t1", "t2"}, 88 | Profiles: []string{"p1"}, 89 | Reboot: true, 90 | RunnerWait: true, 91 | } 92 | stage1.Fill() 93 | stage2 := models.Stage{ 94 | Name: "foo", 95 | Description: "I am a stage again", 96 | RequiredParams: []string{"p3", "p4"}, 97 | OptionalParams: []string{"p1", "p2"}, 98 | Templates: []models.TemplateInfo{ 99 | {Name: "t3", Path: "jill1", Contents: "temp2", Meta: map[string]string{}}, 100 | {Name: "t4", Path: "jill2", Contents: "really actual stuff", Meta: map[string]string{}}, 101 | }, 102 | BootEnv: "local", 103 | Tasks: []string{"t3", "t4", "t5"}, 104 | Profiles: []string{"p2", "p3"}, 105 | RunnerWait: true, 106 | } 107 | stage2.Fill() 108 | 109 | resource.Test(t, resource.TestCase{ 110 | PreCheck: func() { testAccDrpPreCheck(t) }, 111 | Providers: testAccDrpProviders, 112 | CheckDestroy: testAccDrpCheckStageDestroy, 113 | Steps: []resource.TestStep{ 114 | { 115 | Config: testAccDrpStage_change_1, 116 | Check: resource.ComposeTestCheckFunc( 117 | testAccDrpCheckStageExists(t, "drp_stage.foo", &stage1), 118 | ), 119 | }, 120 | { 121 | Config: testAccDrpStage_change_2, 122 | Check: resource.ComposeTestCheckFunc( 123 | testAccDrpCheckStageExists(t, "drp_stage.foo", &stage2), 124 | ), 125 | }, 126 | }, 127 | }) 128 | } 129 | 130 | func testAccDrpCheckStageDestroy(s *terraform.State) error { 131 | config := testAccDrpProvider.Meta().(*Config) 132 | 133 | for _, rs := range s.RootModule().Resources { 134 | if rs.Type != "drp_stage" { 135 | continue 136 | } 137 | 138 | if _, err := config.session.GetModel("stages", rs.Primary.ID); err == nil { 139 | return fmt.Errorf("Stage still exists") 140 | } 141 | } 142 | 143 | return nil 144 | } 145 | 146 | func testAccDrpCheckStageExists(t *testing.T, n string, stage *models.Stage) resource.TestCheckFunc { 147 | return func(s *terraform.State) error { 148 | rs, ok := s.RootModule().Resources[n] 149 | if !ok { 150 | return fmt.Errorf("Not found: %s", n) 151 | } 152 | 153 | if rs.Primary.ID == "" { 154 | return fmt.Errorf("No ID is set") 155 | } 156 | 157 | config := testAccDrpProvider.Meta().(*Config) 158 | 159 | obj, err := config.session.GetModel("stages", rs.Primary.ID) 160 | if err != nil { 161 | return err 162 | } 163 | found := obj.(*models.Stage) 164 | found.ClearValidation() 165 | 166 | if found.Name != rs.Primary.ID { 167 | return fmt.Errorf("Stage not found") 168 | } 169 | 170 | if err := diffObjects(stage, found, "Stage"); err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /drp/resource_drp_bootenv_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/digitalrebar/provision/v4/models" 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | ) 11 | 12 | var testAccDrpBootEnv_basic = ` 13 | resource "drp_bootenv" "foo" { 14 | Name = "foo" 15 | Meta = { 16 | "field1" = "value1" 17 | "field2" = "value2" 18 | } 19 | OS = {} 20 | }` 21 | 22 | func TestAccDrpBootEnv_basic(t *testing.T) { 23 | bootenv := models.BootEnv{Name: "foo", 24 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 25 | } 26 | bootenv.Fill() 27 | 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccDrpPreCheck(t) }, 30 | Providers: testAccDrpProviders, 31 | CheckDestroy: testAccDrpCheckBootEnvDestroy, 32 | Steps: []resource.TestStep{ 33 | { 34 | Config: testAccDrpBootEnv_basic, 35 | Check: resource.ComposeTestCheckFunc( 36 | testAccDrpCheckBootEnvExists(t, "drp_bootenv.foo", &bootenv), 37 | ), 38 | }, 39 | }, 40 | }) 41 | } 42 | 43 | var testAccDrpBootEnv_change_1 = ` 44 | resource "drp_bootenv" "foo" { 45 | Name = "foo" 46 | Description = "I am a bootenv" 47 | RequiredParams = [ "p1", "p2" ] 48 | OptionalParams = [ "p3", "p4" ] 49 | Templates = [ 50 | { Name = "t1", Path = "fred1", Contents = "temp1"}, 51 | { Name = "t2", Path = "fred2", Contents = "actual stuff"} 52 | ] 53 | Kernel = "jill" 54 | Initrds = [ "joyce", "julie", "janna" ] 55 | BootParams = "kernel go" 56 | OnlyUnknown = true 57 | OS = {} 58 | }` 59 | 60 | var testAccDrpBootEnv_change_2 = ` 61 | resource "drp_bootenv" "foo" { 62 | Name = "foo" 63 | Description = "I am a bootenv again" 64 | RequiredParams = [ "p3", "p4" ] 65 | OptionalParams = [ "p1", "p2" ] 66 | Templates = [ 67 | { Name = "t3", Path = "jill1", Contents = "temp2"}, 68 | { Name = "t4", Path = "jill2", Contents = "really actual stuff"} 69 | ] 70 | Kernel = "~jill" 71 | Initrds = [ "~joyce", "~julie", "~janna" ] 72 | BootParams = "kernel nogo" 73 | OnlyUnknown = false 74 | OS = {} 75 | }` 76 | 77 | func TestAccDrpBootEnv_change(t *testing.T) { 78 | bootenv1 := models.BootEnv{ 79 | Name: "foo", 80 | Description: "I am a bootenv", 81 | RequiredParams: []string{"p1", "p2"}, 82 | OptionalParams: []string{"p3", "p4"}, 83 | Templates: []models.TemplateInfo{ 84 | {Name: "t1", Path: "fred1", Contents: "temp1", Meta: map[string]string{}}, 85 | {Name: "t2", Path: "fred2", Contents: "actual stuff", Meta: map[string]string{}}, 86 | }, 87 | Kernel: "jill", 88 | Initrds: []string{"joyce", "julie", "janna"}, 89 | BootParams: "kernel go", 90 | OnlyUnknown: true, 91 | } 92 | bootenv1.Fill() 93 | bootenv2 := models.BootEnv{ 94 | Name: "foo", 95 | Description: "I am a bootenv again", 96 | RequiredParams: []string{"p3", "p4"}, 97 | OptionalParams: []string{"p1", "p2"}, 98 | Templates: []models.TemplateInfo{ 99 | {Name: "t3", Path: "jill1", Contents: "temp2", Meta: map[string]string{}}, 100 | {Name: "t4", Path: "jill2", Contents: "really actual stuff", Meta: map[string]string{}}, 101 | }, 102 | Kernel: "~jill", 103 | Initrds: []string{"~joyce", "~julie", "~janna"}, 104 | BootParams: "kernel nogo", 105 | } 106 | bootenv2.Fill() 107 | 108 | resource.Test(t, resource.TestCase{ 109 | PreCheck: func() { testAccDrpPreCheck(t) }, 110 | Providers: testAccDrpProviders, 111 | CheckDestroy: testAccDrpCheckBootEnvDestroy, 112 | Steps: []resource.TestStep{ 113 | { 114 | Config: testAccDrpBootEnv_change_1, 115 | Check: resource.ComposeTestCheckFunc( 116 | testAccDrpCheckBootEnvExists(t, "drp_bootenv.foo", &bootenv1), 117 | ), 118 | }, 119 | { 120 | Config: testAccDrpBootEnv_change_2, 121 | Check: resource.ComposeTestCheckFunc( 122 | testAccDrpCheckBootEnvExists(t, "drp_bootenv.foo", &bootenv2), 123 | ), 124 | }, 125 | }, 126 | }) 127 | } 128 | 129 | func testAccDrpCheckBootEnvDestroy(s *terraform.State) error { 130 | config := testAccDrpProvider.Meta().(*Config) 131 | 132 | for _, rs := range s.RootModule().Resources { 133 | if rs.Type != "drp_bootenv" { 134 | continue 135 | } 136 | 137 | if _, err := config.session.GetModel("bootenvs", rs.Primary.ID); err == nil { 138 | return fmt.Errorf("BootEnv still exists") 139 | } 140 | } 141 | 142 | return nil 143 | } 144 | 145 | func testAccDrpCheckBootEnvExists(t *testing.T, n string, bootenv *models.BootEnv) resource.TestCheckFunc { 146 | return func(s *terraform.State) error { 147 | rs, ok := s.RootModule().Resources[n] 148 | if !ok { 149 | return fmt.Errorf("Not found: %s", n) 150 | } 151 | 152 | if rs.Primary.ID == "" { 153 | return fmt.Errorf("No ID is set") 154 | } 155 | 156 | config := testAccDrpProvider.Meta().(*Config) 157 | 158 | obj, err := config.session.GetModel("bootenvs", rs.Primary.ID) 159 | if err != nil { 160 | return err 161 | } 162 | found := obj.(*models.BootEnv) 163 | found.ClearValidation() 164 | 165 | if found.Name != rs.Primary.ID { 166 | return fmt.Errorf("BootEnv not found") 167 | } 168 | 169 | if err := diffObjects(bootenv, found, "BootEnv"); err != nil { 170 | return err 171 | } 172 | return nil 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /drp/resource_drp_subnet_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | 8 | "github.com/digitalrebar/provision/v4/models" 9 | "github.com/hashicorp/terraform/helper/resource" 10 | "github.com/hashicorp/terraform/terraform" 11 | ) 12 | 13 | var testAccDrpSubnet_basic = ` 14 | resource "drp_subnet" "foo" { 15 | Name = "foo" 16 | Subnet = "1.1.1.0/24" 17 | Strategy = "MAC" 18 | ActiveStart = "1.1.1.4" 19 | ActiveEnd = "1.1.1.9" 20 | ActiveLeaseTime = 120 21 | ReservedLeaseTime = 14400 22 | Options = [] 23 | Meta = { 24 | "field1" = "value1" 25 | "field2" = "value2" 26 | } 27 | }` 28 | 29 | func TestAccDrpSubnet_basic(t *testing.T) { 30 | subnet := models.Subnet{Name: "foo", 31 | ActiveLeaseTime: 120, 32 | ReservedLeaseTime: 14400, 33 | Subnet: "1.1.1.0/24", 34 | ActiveStart: net.ParseIP("1.1.1.4"), 35 | ActiveEnd: net.ParseIP("1.1.1.9"), 36 | Strategy: "MAC", 37 | Meta: map[string]string{"field1": "value1", "field2": "value2"}, 38 | Pickers: []string{"hint", "nextFree", "mostExpired"}, 39 | Options: []models.DhcpOption{ 40 | models.DhcpOption{Code: 1, Value: "255.255.255.0"}, 41 | models.DhcpOption{Code: 28, Value: "1.1.1.255"}, 42 | }, 43 | } 44 | 45 | subnet.Fill() 46 | 47 | resource.Test(t, resource.TestCase{ 48 | PreCheck: func() { testAccDrpPreCheck(t) }, 49 | Providers: testAccDrpProviders, 50 | CheckDestroy: testAccDrpCheckSubnetDestroy, 51 | Steps: []resource.TestStep{ 52 | resource.TestStep{ 53 | Config: testAccDrpSubnet_basic, 54 | Check: resource.ComposeTestCheckFunc( 55 | testAccDrpCheckSubnetExists(t, "drp_subnet.foo", &subnet), 56 | ), 57 | }, 58 | }, 59 | }) 60 | } 61 | 62 | var testAccDrpSubnet_change_1 = ` 63 | resource "drp_subnet" "foo" { 64 | Name = "foo" 65 | Enabled = true 66 | Proxy = true 67 | Subnet = "1.1.1.0/24" 68 | NextServer = "1.1.1.1" 69 | ActiveStart = "1.1.1.4" 70 | ActiveEnd = "1.1.1.9" 71 | ActiveLeaseTime = 120 72 | ReservedLeaseTime = 14400 73 | OnlyReservations = true 74 | Strategy = "MAC" 75 | Options = [ 76 | { Code = 30, Value = "fred" }, 77 | { Code = 3, Value = "1.1.1.1" } 78 | ] 79 | Pickers = [ "none" ] 80 | }` 81 | 82 | var testAccDrpSubnet_change_2 = ` 83 | resource "drp_subnet" "foo" { 84 | Name = "foo" 85 | Enabled = false 86 | Proxy = false 87 | Subnet = "1.1.1.0/24" 88 | NextServer = "1.1.1.2" 89 | ActiveStart = "1.1.1.5" 90 | ActiveEnd = "1.1.1.10" 91 | ActiveLeaseTime = 121 92 | ReservedLeaseTime = 14401 93 | OnlyReservations = false 94 | Strategy = "MAC" 95 | Options = [ 96 | { Code = 33, Value = "fred" }, 97 | { Code = 4, Value = "1.2.1.1" } 98 | ] 99 | Pickers = [ "hint", "none" ] 100 | }` 101 | 102 | func TestAccDrpSubnet_change(t *testing.T) { 103 | subnet1 := models.Subnet{Name: "foo", 104 | ActiveLeaseTime: 120, 105 | Enabled: true, 106 | Proxy: true, 107 | OnlyReservations: true, 108 | ReservedLeaseTime: 14400, 109 | Subnet: "1.1.1.0/24", 110 | NextServer: net.ParseIP("1.1.1.1"), 111 | ActiveStart: net.ParseIP("1.1.1.4"), 112 | ActiveEnd: net.ParseIP("1.1.1.9"), 113 | Strategy: "MAC", 114 | Pickers: []string{"none"}, 115 | Options: []models.DhcpOption{ 116 | models.DhcpOption{Code: 30, Value: "fred"}, 117 | models.DhcpOption{Code: 3, Value: "1.1.1.1"}, 118 | models.DhcpOption{Code: 1, Value: "255.255.255.0"}, 119 | models.DhcpOption{Code: 28, Value: "1.1.1.255"}, 120 | }, 121 | } 122 | subnet1.Fill() 123 | subnet2 := models.Subnet{Name: "foo", 124 | ActiveLeaseTime: 121, 125 | ReservedLeaseTime: 14401, 126 | Subnet: "1.1.1.0/24", 127 | NextServer: net.ParseIP("1.1.1.2"), 128 | ActiveStart: net.ParseIP("1.1.1.5"), 129 | ActiveEnd: net.ParseIP("1.1.1.10"), 130 | Strategy: "MAC", 131 | Pickers: []string{"hint", "none"}, 132 | Options: []models.DhcpOption{ 133 | models.DhcpOption{Code: 33, Value: "fred"}, 134 | models.DhcpOption{Code: 4, Value: "1.2.1.1"}, 135 | models.DhcpOption{Code: 1, Value: "255.255.255.0"}, 136 | models.DhcpOption{Code: 28, Value: "1.1.1.255"}, 137 | }, 138 | } 139 | subnet2.Fill() 140 | 141 | resource.Test(t, resource.TestCase{ 142 | PreCheck: func() { testAccDrpPreCheck(t) }, 143 | Providers: testAccDrpProviders, 144 | CheckDestroy: testAccDrpCheckSubnetDestroy, 145 | Steps: []resource.TestStep{ 146 | resource.TestStep{ 147 | Config: testAccDrpSubnet_change_1, 148 | Check: resource.ComposeTestCheckFunc( 149 | testAccDrpCheckSubnetExists(t, "drp_subnet.foo", &subnet1), 150 | ), 151 | }, 152 | resource.TestStep{ 153 | Config: testAccDrpSubnet_change_2, 154 | Check: resource.ComposeTestCheckFunc( 155 | testAccDrpCheckSubnetExists(t, "drp_subnet.foo", &subnet2), 156 | ), 157 | }, 158 | }, 159 | }) 160 | } 161 | 162 | func testAccDrpCheckSubnetDestroy(s *terraform.State) error { 163 | config := testAccDrpProvider.Meta().(*Config) 164 | 165 | for _, rs := range s.RootModule().Resources { 166 | if rs.Type != "drp_subnet" { 167 | continue 168 | } 169 | 170 | if _, err := config.session.GetModel("subnets", rs.Primary.ID); err == nil { 171 | return fmt.Errorf("Subnet still exists") 172 | } 173 | } 174 | 175 | return nil 176 | } 177 | 178 | func testAccDrpCheckSubnetExists(t *testing.T, n string, subnet *models.Subnet) resource.TestCheckFunc { 179 | return func(s *terraform.State) error { 180 | rs, ok := s.RootModule().Resources[n] 181 | if !ok { 182 | return fmt.Errorf("Not found: %s", n) 183 | } 184 | 185 | if rs.Primary.ID == "" { 186 | return fmt.Errorf("No ID is set") 187 | } 188 | 189 | config := testAccDrpProvider.Meta().(*Config) 190 | 191 | obj, err := config.session.GetModel("subnets", rs.Primary.ID) 192 | if err != nil { 193 | return err 194 | } 195 | found := obj.(*models.Subnet) 196 | found.ClearValidation() 197 | 198 | if found.Name != rs.Primary.ID { 199 | return fmt.Errorf("Subnet not found") 200 | } 201 | 202 | if err := diffObjects(subnet, found, "Subnet"); err != nil { 203 | return err 204 | } 205 | return nil 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /drp/resource_drp_raw_machine_test.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | 8 | "github.com/digitalrebar/provision/v4/models" 9 | "github.com/hashicorp/terraform/helper/resource" 10 | "github.com/hashicorp/terraform/terraform" 11 | "github.com/pborman/uuid" 12 | ) 13 | 14 | var testAccDrpRawMachine_basic = ` 15 | resource "drp_raw_machine" "foo" { 16 | Name = "mach11" 17 | Uuid = "3945838b-be8c-4b35-8b1c-b538ddc71f7c" 18 | Secret = "12" 19 | Meta = { 20 | "field1" = "value1" 21 | "field2" = "value2" 22 | "feature-flags" = "change-stage-v2" 23 | } 24 | }` 25 | 26 | func TestAccDrpRawMachine_basic(t *testing.T) { 27 | raw_machine := models.Machine{Name: "mach11", 28 | Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7c"), 29 | Secret: "12", 30 | Stage: "none", 31 | BootEnv: "local", 32 | Runnable: true, 33 | CurrentTask: -1, 34 | Meta: map[string]string{"feature-flags": "change-stage-v2", "field1": "value1", "field2": "value2"}, 35 | } 36 | raw_machine.Fill() 37 | 38 | resource.Test(t, resource.TestCase{ 39 | PreCheck: func() { testAccDrpPreCheck(t) }, 40 | Providers: testAccDrpProviders, 41 | CheckDestroy: testAccDrpCheckRawMachineDestroy, 42 | Steps: []resource.TestStep{ 43 | resource.TestStep{ 44 | Config: testAccDrpRawMachine_basic, 45 | Check: resource.ComposeTestCheckFunc( 46 | testAccDrpCheckRawMachineExists(t, "drp_raw_machine.foo", &raw_machine), 47 | ), 48 | }, 49 | }, 50 | }) 51 | } 52 | 53 | var testAccDrpRawMachine_change_1 = ` 54 | resource "drp_profile" "p1" { 55 | Name = "p1" 56 | } 57 | resource "drp_profile" "p2" { 58 | Name = "p2" 59 | } 60 | resource "drp_task" "t1" { 61 | Name = "t1" 62 | } 63 | resource "drp_task" "t2" { 64 | Name = "t2" 65 | } 66 | resource "drp_raw_machine" "foo" { 67 | depends_on = ["drp_profile.p1", "drp_profile.p2", "drp_task.t1", "drp_task.t2"] 68 | Name = "mach11" 69 | Uuid = "3945838b-be8c-4b35-8b1c-b538ddc71f7c" 70 | Secret = "12" 71 | Description = "I am a raw_machine" 72 | CurrentJob = "3945838b-be8c-4b35-8b1c-b538ddc71f7f" 73 | Address = "1.1.1.1" 74 | Stage = "none" 75 | BootEnv = "local" 76 | Profiles = [ "p1", "p2" ] 77 | Tasks = [ "t1", "t2" ] 78 | Runnable = true 79 | OS = "fred" 80 | }` 81 | 82 | var testAccDrpRawMachine_change_2 = ` 83 | resource "drp_profile" "p1" { 84 | Name = "p1" 85 | } 86 | resource "drp_profile" "p2" { 87 | Name = "p2" 88 | } 89 | resource "drp_task" "t1" { 90 | Name = "t1" 91 | } 92 | resource "drp_task" "t2" { 93 | Name = "t2" 94 | } 95 | resource "drp_profile" "p3" { 96 | Name = "p3" 97 | } 98 | resource "drp_profile" "p4" { 99 | Name = "p4" 100 | } 101 | resource "drp_task" "t3" { 102 | Name = "t3" 103 | } 104 | resource "drp_task" "t4" { 105 | Name = "t4" 106 | } 107 | resource "drp_raw_machine" "foo" { 108 | depends_on = ["drp_profile.p3", "drp_profile.p4", "drp_task.t3", "drp_task.t4", "drp_profile.p1", "drp_profile.p2", "drp_task.t1", "drp_task.t2"] 109 | Name = "mach11" 110 | Uuid = "3945838b-be8c-4b35-8b1c-b538ddc71f7c" 111 | Secret = "12" 112 | Description = "I am a raw_machine again" 113 | CurrentJob = "3945838b-be8c-4b35-8b1c-b538ddc71f7a" 114 | Address = "1.1.1.2" 115 | Stage = "none" 116 | BootEnv = "local" 117 | Profiles = [ "p3", "p4" ] 118 | Tasks = [ "t3", "t4" ] 119 | Runnable = false 120 | OS = "greg" 121 | }` 122 | 123 | func TestAccDrpRawMachine_change(t *testing.T) { 124 | raw_machine1 := models.Machine{ 125 | Name: "mach11", 126 | Address: net.ParseIP("1.1.1.1"), 127 | Description: "I am a raw_machine", 128 | Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7c"), 129 | CurrentJob: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7f"), 130 | CurrentTask: -1, 131 | Secret: "12", 132 | Stage: "none", 133 | BootEnv: "local", 134 | Runnable: true, 135 | Profiles: []string{"p1", "p2"}, 136 | Tasks: []string{"t1", "t2"}, 137 | OS: "fred", 138 | Meta: map[string]string{"feature-flags": "change-stage-v2"}, 139 | } 140 | raw_machine1.Fill() 141 | raw_machine2 := models.Machine{ 142 | Name: "mach11", 143 | Address: net.ParseIP("1.1.1.2"), 144 | Description: "I am a raw_machine again", 145 | Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7c"), 146 | CurrentJob: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7a"), 147 | CurrentTask: -1, 148 | Secret: "12", 149 | Stage: "none", 150 | BootEnv: "local", 151 | Profiles: []string{"p3", "p4"}, 152 | Tasks: []string{"t3", "t4"}, 153 | Runnable: false, 154 | OS: "greg", 155 | Meta: map[string]string{"feature-flags": "change-stage-v2"}, 156 | } 157 | raw_machine2.Fill() 158 | 159 | resource.Test(t, resource.TestCase{ 160 | PreCheck: func() { testAccDrpPreCheck(t) }, 161 | Providers: testAccDrpProviders, 162 | CheckDestroy: testAccDrpCheckRawMachineDestroy, 163 | Steps: []resource.TestStep{ 164 | resource.TestStep{ 165 | Config: testAccDrpRawMachine_change_1, 166 | Check: resource.ComposeTestCheckFunc( 167 | testAccDrpCheckRawMachineExists(t, "drp_raw_machine.foo", &raw_machine1), 168 | ), 169 | }, 170 | resource.TestStep{ 171 | Config: testAccDrpRawMachine_change_2, 172 | Check: resource.ComposeTestCheckFunc( 173 | testAccDrpCheckRawMachineExists(t, "drp_raw_machine.foo", &raw_machine2), 174 | ), 175 | }, 176 | }, 177 | }) 178 | } 179 | 180 | var testAccDrpRawMachine_withParams = ` 181 | resource "drp_raw_machine" "foo" { 182 | Name = "mach11" 183 | Uuid = "3945838b-be8c-4b35-8b1c-b538ddc71f7c" 184 | Secret = "12" 185 | Params = { 186 | "test/string" = "fred" 187 | "test/int" = "3" 188 | "test/bool" = "true" 189 | "test/list" = "[\"one\",\"two\"]" 190 | } 191 | }` 192 | 193 | func TestAccDrpRawMachine_withParams(t *testing.T) { 194 | raw_machine := models.Machine{ 195 | Name: "mach11", 196 | Uuid: uuid.Parse("3945838b-be8c-4b35-8b1c-b538ddc71f7c"), 197 | Secret: "12", 198 | Stage: "none", 199 | BootEnv: "local", 200 | Runnable: true, 201 | CurrentTask: -1, 202 | Meta: map[string]string{"feature-flags": "change-stage-v2"}, 203 | Params: map[string]interface{}{ 204 | "test/string": "fred", 205 | "test/int": 3, 206 | "test/bool": true, 207 | "test/list": []string{"one", "two"}, 208 | }, 209 | } 210 | raw_machine.Fill() 211 | 212 | resource.Test(t, resource.TestCase{ 213 | PreCheck: func() { testAccDrpPreCheck(t) }, 214 | Providers: testAccDrpProviders, 215 | CheckDestroy: testAccDrpCheckRawMachineDestroy, 216 | Steps: []resource.TestStep{ 217 | resource.TestStep{ 218 | Config: testAccDrpRawMachine_withParams, 219 | Check: resource.ComposeTestCheckFunc( 220 | testAccDrpCheckRawMachineExists(t, "drp_raw_machine.foo", &raw_machine), 221 | ), 222 | }, 223 | }, 224 | }) 225 | } 226 | 227 | func testAccDrpCheckRawMachineDestroy(s *terraform.State) error { 228 | config := testAccDrpProvider.Meta().(*Config) 229 | 230 | for _, rs := range s.RootModule().Resources { 231 | if rs.Type != "drp_raw_machine" { 232 | continue 233 | } 234 | 235 | if _, err := config.session.GetModel("raw_machines", rs.Primary.ID); err == nil { 236 | return fmt.Errorf("RawMachine still exists") 237 | } 238 | } 239 | 240 | return nil 241 | } 242 | 243 | func testAccDrpCheckRawMachineExists(t *testing.T, n string, raw_machine *models.Machine) resource.TestCheckFunc { 244 | return func(s *terraform.State) error { 245 | rs, ok := s.RootModule().Resources[n] 246 | if !ok { 247 | return fmt.Errorf("Not found: %s", n) 248 | } 249 | 250 | if rs.Primary.ID == "" { 251 | return fmt.Errorf("No ID is set") 252 | } 253 | 254 | config := testAccDrpProvider.Meta().(*Config) 255 | 256 | obj, err := config.session.GetModel("machines", rs.Primary.ID) 257 | if err != nil { 258 | return err 259 | } 260 | found := obj.(*models.Machine) 261 | found.ClearValidation() 262 | 263 | if found.Uuid.String() != rs.Primary.ID { 264 | return fmt.Errorf("RawMachine not found") 265 | } 266 | 267 | if err := diffObjects(raw_machine, found, "RawMachine"); err != nil { 268 | return err 269 | } 270 | return nil 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | RACKN LIMITED USE SOFTWARE END USER LICENSE AGREEMENT 2 | ===================================================== 3 | 4 | *Note: This license applies to extensions, related components and software supporting the Apache 2 licensed Digital Rebar project (https://github.com/digitalrebar/digitalrebar/blob/master/LICENSE.md). It does not supersede the Digital Rebar license.* 5 | 6 | *This UX provides a wrapper for the Digital Rebar API (http://rebar.digital). There is no requirement to use the UX to access Digital Rebar functionality. It is possible to take all actions exposed in the UX via the API or CLI.* 7 | 8 | BY CLICKING ON THE “I AGREE”, “I ACCEPT”, “CLONE” OR SUCH SIMILAR BUTTON OR 9 | LINK AS MAY BE DESIGNATED FOR PURPOSES OF INITIATING THE DOWNLOAD FROM THE 10 | INTERNET OF THE RACKN ENTERPRISE SOFTWARE PRODUCT (THE "APPLICATION") AND 11 | USING THE APPLICATION YOU AGREE TO BE LEGALLY BOUND BY THE INCLUDED LICENSE 12 | TERMS AND CONDITIONS. 13 | 14 | RACKN® reserves the right to MONITORS USE OF THIS SOFTWARE. 15 | REGISTRATION OR PERSONAL INFORMATION IS REQUIRED FOR USE. 16 | 17 | All rights not expressly granted hereunder are expressly reserved by RACKN® 18 | 19 | .. contents:: Table Contents :depth: 1 20 | 21 | OWNERSHIP 22 | --------- 23 | 24 | You acknowledge and agree that you are receiving a copy of the Application by 25 | license only and not by sale and that the "first sale" doctrine of 17 U.S.C. 26 | §109 does not apply to your receipt or use of the Application. You acknowledge 27 | that the Application, including all code, content, protocols, software, 28 | documentation and all other conceivable intellectual property rights related 29 | to and in conjunction with the Application are provided to you by RACKN and 30 | are RACKN property or the property of RACKN licensors, and are protected by 31 | U.S. and international copyright, trademark, patent and proprietary rights and 32 | international treaty provisions and all applicable law, such as the Lanham 33 | Act, relating to Intellectual Property Rights. "Intellectual Property Rights" 34 | means, collectively, rights under patent, trademark, copyright and trade 35 | secret laws, and any other intellectual property or proprietary rights 36 | recognized in any country or jurisdiction worldwide, including, without 37 | limitation, moral or similar rights. You may not delete, alter, or remove any 38 | copyright, trademark, or other proprietary rights notice RACKN has placed on 39 | the Application. 40 | 41 | LICENSE 42 | ------- 43 | 44 | RackN and its licensors provide the Application to you and grants you a non- 45 | exclusive, nontransferable license to where the Application is to be used at 46 | home or business, you may install and use the Application on any computer or 47 | computers of the type identified on the website. 48 | 49 | RackN reserves the right to add additional features or functions to the 50 | existing Application. You acknowledge that RackN may require your review and 51 | acceptance of RackN then-current privacy policy and/or end user license 52 | agreement before you will be permitted a limited license for any subsequent 53 | versions of the Application. You acknowledge and agree that RackN has no 54 | obligation to make available to you any subsequent versions of the 55 | Application. 56 | 57 | It is understood and agreed that while the Application may or may not be “copy 58 | protected,” you, as the Licensee, shall not copy the Application into any 59 | machine-readable or printed form except for archival or for backup purposes, 60 | nor shall you modify the Application and/or merge it into another computer 61 | program. 62 | 63 | It is further understood that the Application is primarily for evaluation and 64 | non-commercial use only. RackN defines "evaluation" use as the using the Application 65 | for managing more than any combination of physical and virtual nodes less than or equalling 100 nodes. 66 | If RackN learns of usage above this threashold, it will ask the user to engage in a software support agreement. 67 | The use of the Application, for purpose of this agreement, includes uses on, or in conjunction with: personal and corporate 68 | products, professional services and development work in which revenue is 69 | realized. The Application may not be offered for sale, used in conjunction 70 | with goods/services being offered for sale, or used in advertising without the 71 | express written consent of RackN. Where depiction of images created using the 72 | Application occurs not on a Web site and where express consent is granted by 73 | RackN in a written agreement separate and distinct in form and purpose from 74 | this Limited Use End User License Agreement, the Application may be used at or 75 | by a business for commercial purposes in accordance with the separate and 76 | distinct express written agreement. You agree not to modify, copy, distribute, 77 | transmit, display, perform, reproduce, publish, license, create derivative 78 | works from, transfer, or sell any content, including, without limitation, 79 | copyright works, information, software, products or services. 80 | 81 | By accepting this agreement, you agree to: a. not use the Application to disparage 82 | RackN, its suppliers, products, partners, services and investors for 83 | promotional goods or for products which, in RackN sole judgment, may diminish 84 | or otherwise damage RackN goodwill in the Application including but not 85 | limited to uses which could be deemed under applicable law to a. not use the 86 | Application in any manner that may disparage the Application or impair the 87 | validity, scope, title or goodwill of RackN in the Application; b. acknowledge 88 | that the Application and any related Trademarks shall not be modified to 89 | infringe the copyright, trademark or common law rights of any person or 90 | entity, and; d. acknowledge that nothing contained in any material produced by 91 | you that incorporates the Application or any related Trademarks will 92 | constitute a libel or slander against, or violate or infringe upon any right, 93 | common law or otherwise, of any kind or nature whatsoever, of any person or 94 | entity, including, without limitation, any right of privacy or publicity. 95 | 96 | 97 | REVERSE ENGINEERING 98 | ------------------- 99 | 100 | RackN retains all right, title, and interest in and to the Application, and 101 | any rights not granted to you herein are reserved by RackN. You may not 102 | reverse engineer, disassemble, decompile, or translate the Application, or 103 | otherwise attempt to derive the source code of the Application, except to the 104 | extent allowed under any applicable law. If applicable law permits such 105 | activities, any information so discovered must be promptly disclosed to RackN 106 | and shall be deemed to be the confidential proprietary information of RackN. 107 | 108 | TERM 109 | ---- 110 | 111 | This license is effective until terminated. You may terminate it at any time 112 | by destroying the Application together with all copies in any form. This 113 | license will also terminate upon conditions set forth elsewhere in this 114 | agreement or if you fail to comply with any term or condition of this 115 | agreement. You agree that upon such termination you will destroy the 116 | Application together with all copies in any form. 117 | 118 | JURISDICTION 119 | ------------ 120 | 121 | The following are terms of a legal agreement between you and RackN. By 122 | accessing, downloading from the Internet, uploading from any portable media 123 | storage device or installing the Application, you acknowledge that you have 124 | read, understood, and agree to be bound by these terms and to comply with all 125 | applicable laws and regulations, including U.S. export laws and regulations 126 | including those which apply to export of data. If you do not agree to these 127 | terms, do not use the Application. The material provided in the Application is 128 | protected by law, including, but not limited to, United States Copyright Law 129 | and international treaties. Those who choose to use the Application do so on 130 | their own initiative and are responsible for compliance with applicable laws. 131 | Any claim relating to, and the use of, the Application and the materials 132 | contained therein is governed by the laws of the State of New York. 133 | 134 | INDEMNIFICATION 135 | --------------- 136 | 137 | You hereby agree to indemnify, defendingnd and hold RackN, its affiliates, 138 | subsidiaries, parents, shareholders, directors, officers, employees, agents, 139 | contractors, licensors, and representatives harmless from and against any and 140 | all claims, loss, damage, tax, liability and/or expense that may be incurred 141 | by RackN, its affiliates, subsidiaries, parents, shareholders, directors, 142 | officers, employees, agents, contractors, licensors, and representatives 143 | arising out of or in connection with the performance of its duties as 144 | described in this Agreement including the legal costs, fees and expenses of 145 | defending itself against any claim by any or all of the parties to any RackN 146 | transaction and/or by any other person and/or as a result of your taking any 147 | action or refraining from taking any action or instituting or defending any 148 | action or legal proceeding. 149 | 150 | You further agree to indemnify and hold RackN, its affiliates, subsidiaries, 151 | parents, shareholders, directors, officers, employees, agents, contractors, 152 | licensors, and representatives harmless from any claim or demand, including 153 | reasonable attorneys' fees, made by any third party due to or arising out of 154 | your use of the Application, your violation of the terms and conditions of 155 | this Licensing Agreement, or the infringement by you, or other user(s) of the 156 | Application using your computer, of any intellectual property or other right 157 | of any person or entity. 158 | 159 | DISCLAIMER AND LIMITATIONS OF REMEDIES 160 | -------------------------------------- 161 | 162 | RACKN, THE MAKER OF THE APPLICATION, MAKES NO WARRANTIES THAT THE IMAGES 163 | CONTAINED HEREIN ARE FREE FROM INFRINGEMENT OF COPYRIGHT, OR ANY OTHER FORM 164 | OF INTELLECTUAL PROPERTY. THE USER ASSUMES ALL LEGAL RISKS RELATED TO 165 | DOWNLOADED/UPLOADED IMAGES. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE 166 | LAW, TUBEHEAD AND ITS SUPPLIERS PROVIDE TO YOU THE APPLICATION, AND ANY (IF 167 | ANY) SUPPORT SERVICES RELATED TO THE APPLICATION ("SUPPORT SERVICES") AS IS 168 | AND WITH ALL FAULTS; AND TUBEHEAD AND ITS SUPPLIERS HEREBY DISCLAIM WITH 169 | RESPECT TO THE APPLICATION AND SUPPORT SERVICES ALL WARRANTIES AND 170 | CONDITIONS, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED 171 | TO, ANY (IF ANY) WARRANTIES OR CONDITIONS OF OR RELATED TO: TITLE, NON- 172 | INFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, LACK OF 173 | VIRUSES, ACCURACY OR COMPLETENESS OF RESPONSES, ACCURACY OR COMPLETENESS OF 174 | FACTUAL INFORMATION, FITNESS FOR ANY SPECIFIC CURRICULUM OR AGE GROUP, 175 | RESULTS, LACK OF NEGLIGENCE OR LACK OF WORKMANLIKE EFFORT, QUIET ENJOYMENT, 176 | QUIET POSSESSION, AND CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING 177 | OUT OF USE OR PERFORMANCE OF THE APPLICATION, COMPONENTS AND ANY SUPPORT 178 | SERVICES REMAINS WITH YOU. 179 | 180 | EXCLUSION OF INCIDENTAL, CONSEQUENTIAL, AND CERTAIN OTHER DAMAGES. TO THE 181 | MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL TUBEHEAD OR ITS 182 | SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL 183 | DAMAGES WHATSOEVER (INCLUDING, BUT NOT LIMITED TO, DAMAGES FOR: LOSS OF 184 | PROFITS, LOSS OF CONFIDENTIAL OR OTHER INFORMATION, BUSINESS INTERRUPTION, 185 | PERSONAL INJURY, EMOTIONAL DISTRESS, LOSS OF PRIVACY, FAILURE TO MEET ANY 186 | DUTY (INCLUDING OF GOOD FAITH OR OF REASONABLE CARE), NEGLIGENCE, AND ANY 187 | OTHER PECUNIARY OR OTHER LOSS WHATSOEVER) ARISING OUT OF OR IN ANY WAY RELATED 188 | TO THE USE OF OR INABILITY TO USE THE APPLICATION OR THE SUPPORT SERVICES, OR 189 | THE PROVISION OF OR FAILURE TO PROVIDE SUPPORT SERVICES, OR OTHERWISE UNDER 190 | OR IN CONNECTION WITH ANY PROVISION OF THIS SUPPLEMENTAL END USER LICENSE 191 | AGREEMENT (EULA), EVEN IF TUBEHEAD OR ANY SUPPLIER HAS BEEN ADVISED OF THE 192 | POSSIBILITY OF SUCH DAMAGES. 193 | 194 | LIMITATION OF LIABILITY AND REMEDIES. NOT WITHSTANDING ANY DAMAGES THAT YOU 195 | MIGHT INCUR FOR ANY REASON WHATSOEVER (INCLUDING, WITHOUT LIMITATION, ALL 196 | DAMAGES REFERENCED ABOVE AND ALL DIRECT OR GENERAL DAMAGES), THE ENTIRE 197 | LIABILITY OF TUBEHEAD AND ANY OF ITS SUPPLIERS UNDER ANY PROVISION OF THIS 198 | SUPPLEMENTAL EULA AND YOUR EXCLUSIVE REMEDY FOR ALL OF THE FOREGOING SHALL BE 199 | LIMITED TO THE REPLACEMENT OF THE APPLICATION. THE FOREGOING LIMITATIONS, 200 | EXCLUSIONS, AND DISCLAIMERS SHALL APPLY TO THE MAXIMUM EXTENT PERMITTED BY 201 | APPLICABLE LAW, EVEN IF ANY REMEDY FAILS ITS ESSENTIAL PURPOSE. 202 | 203 | If, for any reason, any part of this Agreement is deemed legally improper, 204 | inapplicable or inoperative, the remainder of the parts comprising the 205 | entirety of the Agreement shall remain legally proper, applicable and 206 | operable. This Agreement grants permission only for the allowances described 207 | above and does not grant any additional rights for any copyright(s), 208 | trademark(s), patent(s), trade secret(s), or other forms of intellectual 209 | property/proprietary rights belonging to RackN Inc., the maker of RackN 210 | Enterprise. 211 | 212 | -------------------------------------------------------------------------------- /drp/resource_drp_machine.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/digitalrebar/provision/v4/models" 10 | "github.com/hashicorp/terraform/helper/resource" 11 | "github.com/hashicorp/terraform/helper/schema" 12 | ) 13 | 14 | func dataSourceMachine() *schema.Resource { 15 | log.Println("[DEBUG] [dataSourceMachine] Initializing data structure") 16 | 17 | m, _ := models.New("machine") 18 | r := buildSchema(m, false) 19 | r.Create = nil 20 | r.Update = nil 21 | r.Delete = nil 22 | r.Importer = nil 23 | r.Exists = nil 24 | 25 | // Machines also have filters 26 | r.Schema["filters"] = &schema.Schema{ 27 | Type: schema.TypeList, 28 | Optional: true, 29 | ForceNew: true, 30 | Elem: &schema.Resource{ 31 | Schema: map[string]*schema.Schema{ 32 | "name": { 33 | Type: schema.TypeString, 34 | Optional: true, 35 | }, 36 | "jsonvalue": { 37 | Type: schema.TypeString, 38 | Optional: true, 39 | }, 40 | }, 41 | }, 42 | } 43 | return r 44 | } 45 | 46 | func resourceMachine() *schema.Resource { 47 | log.Println("[DEBUG] [resourceMachine] Initializing data structure") 48 | 49 | m, _ := models.New("machine") 50 | r := buildSchema(m, true) 51 | 52 | r.Create = resourceMachineCreate 53 | r.Update = resourceMachineUpdate 54 | r.Delete = resourceMachineDelete 55 | 56 | r.Timeouts = &schema.ResourceTimeout{ 57 | Create: schema.DefaultTimeout(25 * time.Minute), 58 | Update: schema.DefaultTimeout(10 * time.Minute), 59 | Delete: schema.DefaultTimeout(10 * time.Minute), 60 | } 61 | 62 | // Define what the machines completion stage. 63 | r.Schema["completion_stage"] = &schema.Schema{ 64 | Type: schema.TypeString, 65 | Optional: true, 66 | } 67 | 68 | // Define what the machines decommision workflow 69 | r.Schema["decommission_workflow"] = &schema.Schema{ 70 | Type: schema.TypeString, 71 | Optional: true, 72 | } 73 | 74 | // Define what the machines decommision workflow 75 | r.Schema["decommission_icon"] = &schema.Schema{ 76 | Type: schema.TypeString, 77 | Optional: true, 78 | } 79 | 80 | // Define what the machines decommision workflow 81 | r.Schema["decommission_color"] = &schema.Schema{ 82 | Type: schema.TypeString, 83 | Optional: true, 84 | } 85 | 86 | // Define what the machines decommision stage 87 | r.Schema["decommission_stage"] = &schema.Schema{ 88 | Type: schema.TypeString, 89 | Optional: true, 90 | } 91 | 92 | // Define what profiles to add and remove at destroy 93 | r.Schema["add_profiles"] = &schema.Schema{ 94 | Type: schema.TypeList, 95 | Optional: true, 96 | Elem: &schema.Schema{ 97 | Type: schema.TypeString, 98 | }, 99 | } 100 | 101 | // Define what the machines decommision stage 102 | r.Schema["pool"] = &schema.Schema{ 103 | Type: schema.TypeString, 104 | Optional: true, 105 | } 106 | 107 | // Machines also have filters 108 | r.Schema["filters"] = &schema.Schema{ 109 | Type: schema.TypeList, 110 | Optional: true, 111 | ForceNew: true, 112 | Elem: &schema.Resource{ 113 | Schema: map[string]*schema.Schema{ 114 | "name": { 115 | Type: schema.TypeString, 116 | Optional: true, 117 | }, 118 | "jsonvalue": { 119 | Type: schema.TypeString, 120 | Optional: true, 121 | }, 122 | }, 123 | }, 124 | } 125 | return r 126 | } 127 | 128 | func allocateMachine(cc *Config, filters []string) (*models.Machine, error) { 129 | for { 130 | machines, err := cc.session.ListModel("machine", filters...) 131 | if err != nil { 132 | return nil, err 133 | } 134 | if len(machines) == 0 { 135 | return nil, fmt.Errorf("No machines available") 136 | } 137 | 138 | machine := machines[0] 139 | merged := models.Clone(machine).(*models.Machine) 140 | merged.Params["terraform/allocated"] = true 141 | 142 | ret, err := cc.session.PatchTo(machine, merged) 143 | if err != nil { 144 | berr, ok := err.(*models.Error) 145 | if ok { 146 | // If we get a patch error, the machine was allocated while we were 147 | // waiting. Try again. 148 | if berr.Type == "PATCH" && (berr.Code == 406 || berr.Code == 409) { 149 | continue 150 | } 151 | } 152 | return nil, err 153 | } 154 | return ret.(*models.Machine), nil 155 | } 156 | } 157 | 158 | func releaseMachine(cc *Config, uuid string, tfManaged bool) error { 159 | for { 160 | machine, err := cc.session.GetModel("machines", uuid) 161 | if err != nil { 162 | return nil 163 | } 164 | 165 | merged := models.Clone(machine).(*models.Machine) 166 | merged.Params["terraform/allocated"] = false 167 | merged.Params["terraform/managed"] = tfManaged 168 | 169 | _, err = cc.session.PatchTo(machine, merged) 170 | if err != nil { 171 | berr, ok := err.(*models.Error) 172 | if ok { 173 | // If we get a patch error, the machine was allocated while we were 174 | // waiting. Try again. 175 | if berr.Type == "PATCH" && (berr.Code == 406 || berr.Code == 409) { 176 | continue 177 | } 178 | } 179 | return err 180 | } 181 | return nil 182 | } 183 | } 184 | 185 | func getMachineStatus(cc *Config, uuid string, stages []string) resource.StateRefreshFunc { 186 | log.Printf("[DEBUG] [getMachineStatus] Getting status of machine: %s", uuid) 187 | return func() (interface{}, string, error) { 188 | mo, err := cc.session.GetModel("machines", uuid) 189 | if err != nil { 190 | log.Printf("[ERROR] [getMachineStatus] Unable to get machine: %s\n", uuid) 191 | return nil, "", err 192 | } 193 | machineObject := mo.(*models.Machine) 194 | 195 | // 6 == done 9 == pending 196 | machineStatus := "6" 197 | if machineObject.Stage != "" { 198 | found := false 199 | for _, s := range stages { 200 | if s == machineObject.Stage { 201 | found = true 202 | break 203 | } 204 | } 205 | if !found { 206 | machineStatus = "9" 207 | } 208 | } else { 209 | if machineObject.BootEnv != "local" { 210 | machineStatus = "9" 211 | } 212 | } 213 | 214 | var statusRetVal bytes.Buffer 215 | statusRetVal.WriteString(machineStatus) 216 | statusRetVal.WriteString(":") 217 | 218 | return machineObject, statusRetVal.String(), nil 219 | } 220 | } 221 | 222 | func machineDo(cc *Config, uuid, action string) error { 223 | log.Printf("[DEBUG] [machineDo] uuid: %s, action: %s", uuid, action) 224 | 225 | actionParams := map[string]interface{}{} 226 | var resp interface{} 227 | err := cc.session.Req().Post(actionParams).UrlFor("machines", uuid, "actions", action).Do(resp) 228 | if err != nil { 229 | log.Printf("[DEBUG] [machineDo] call %s:%s error = %v\n", uuid, action, err) 230 | return err 231 | } 232 | return nil 233 | } 234 | 235 | func updateMachine(cc *Config, machineObj *models.Machine, d *schema.ResourceData) (*models.Machine, error) { 236 | obj, e := buildModel(machineObj, d) 237 | if e != nil { 238 | log.Printf("[ERROR] [updateMachine] Unable to build model: %v\n", e) 239 | return nil, e 240 | } 241 | m := obj.(*models.Machine) 242 | 243 | // Make sure the add profiles are on the machine 244 | if ol, ok := d.GetOk("add_profiles"); ok { 245 | l := ol.([]interface{}) 246 | for _, s := range l { 247 | prof := s.(string) 248 | 249 | found := false 250 | for _, t := range m.Profiles { 251 | if t == prof { 252 | found = true 253 | break 254 | } 255 | } 256 | 257 | if !found { 258 | m.Profiles = append(m.Profiles, prof) 259 | } 260 | } 261 | } 262 | 263 | cBootEnv := machineObj.BootEnv 264 | 265 | err := cc.session.Req().PatchTo(machineObj, m).Params("force", "true").Do(&m) 266 | if err != nil { 267 | log.Printf("[ERROR] [updateMachine] Unable to initialize machine: %v\n", err) 268 | return nil, err 269 | } 270 | machineObj = m 271 | 272 | if err := machineDo(cc, machineObj.UUID(), "nextbootpxe"); err != nil { 273 | log.Printf("[WARN] [updateMachine] Unable to mark the machine for pxe next boot: %s\n", machineObj.UUID()) 274 | } 275 | 276 | // Power on and then cycle, if needed 277 | if err := machineDo(cc, machineObj.UUID(), "poweron"); err != nil { 278 | log.Printf("[WARN] [updateMachine] Unable to power on machine: %s\n", machineObj.UUID()) 279 | } 280 | 281 | obj, err = cc.session.GetModel(machineObj.Prefix(), machineObj.Key()) 282 | if err != nil { 283 | log.Printf("[ERROR] [updateMachine] Unable to re-get machine: %v\n", err) 284 | return nil, err 285 | } 286 | machineObj = obj.(*models.Machine) 287 | 288 | if machineObj.BootEnv != cBootEnv { 289 | if err := machineDo(cc, machineObj.Key(), "powercycle"); err != nil { 290 | log.Printf("[WARN] [updateMachine] Unable to power cycleup machine: %s\n", machineObj.UUID()) 291 | } 292 | } 293 | 294 | return machineObj, nil 295 | } 296 | 297 | // This function doesn't really *create* a new machine but, 298 | // consume an already registered machine. 299 | func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { 300 | log.Println("[DEBUG] [resourceMachineCreate] Launching new drp_machine") 301 | cc := meta.(*Config) 302 | 303 | filters := []string{} 304 | if pval, set := d.GetOk("filters"); set { 305 | for _, o := range pval.([]interface{}) { 306 | v := o.(map[string]interface{}) 307 | filters = append(filters, v["name"].(string), v["jsonvalue"].(string)) 308 | } 309 | } 310 | if fval, set := d.GetOk("pool"); set { 311 | filters = append(filters, "terraform/pool", fval.(string)) 312 | } else { 313 | filters = append(filters, "terraform/pool", "default") 314 | } 315 | filters = append(filters, "terraform/allocated", "false") 316 | filters = append(filters, "terraform/managed", "true") 317 | 318 | machineObj, err := allocateMachine(cc, filters) 319 | if err != nil { 320 | log.Printf("[ERROR] [resourceMachineCreate] Unable to allocate machine: %v\n", err) 321 | return err 322 | } 323 | 324 | uuid := machineObj.UUID() 325 | machineObj, err = updateMachine(cc, machineObj, d) 326 | if err != nil { 327 | log.Printf("[ERROR] [resourceMachineCreate] Unable to update machine: %v\n", err) 328 | if err2 := releaseMachine(cc, uuid, true); err2 != nil { 329 | log.Printf("[ERROR] [resourceMachineCreate] Unable to release machine: %v\n", err2) 330 | } 331 | return err 332 | } 333 | 334 | log.Printf("[DEBUG] [resourceMachineCreate] Waiting for machine (%s) to become active\n", machineObj.UUID()) 335 | 336 | stages := []string{"complete", "complete-nowait"} 337 | if ns, ok := d.GetOk("completion_stage"); ok { 338 | stages = []string{ns.(string)} 339 | } 340 | 341 | stateConf := &resource.StateChangeConf{ 342 | Pending: []string{"9:"}, 343 | Target: []string{"6:"}, 344 | Refresh: getMachineStatus(cc, machineObj.UUID(), stages), 345 | Timeout: d.Timeout(schema.TimeoutCreate), 346 | Delay: 10 * time.Second, 347 | MinTimeout: 3 * time.Second, 348 | } 349 | 350 | if _, err := stateConf.WaitForState(); err != nil { 351 | if err2 := releaseMachine(cc, machineObj.UUID(), true); err2 != nil { 352 | log.Printf("[ERROR] [resourceMachineCreate] Unable to release machine: %v\n", err2) 353 | } 354 | return fmt.Errorf( 355 | "[ERROR] [resourceMachineCreate] Error waiting for machine (%s) to become deployed: %s", 356 | machineObj.UUID(), err) 357 | } 358 | 359 | d.SetId(machineObj.UUID()) 360 | 361 | answer, err := cc.session.GetModel(machineObj.Prefix(), d.Id()) 362 | if err != nil { 363 | return err 364 | } 365 | return updateResourceData(answer, d) 366 | } 367 | 368 | func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { 369 | cc := meta.(*Config) 370 | log.Printf("[DEBUG] [resourceMachineUpdate] Modifying machine %s\n", d.Id()) 371 | 372 | obj, err := cc.session.GetModel("machines", d.Id()) 373 | if err != nil { 374 | log.Printf("[ERROR] [resourceMachineUpdate] Unable to get machine: %v\n", err) 375 | return err 376 | } 377 | machineObj := obj.(*models.Machine) 378 | 379 | machineObj, err = updateMachine(cc, machineObj, d) 380 | if err != nil { 381 | log.Printf("[ERROR] [resourceMachineCreate] Unable to update machine: %v\n", err) 382 | return err 383 | } 384 | 385 | log.Printf("[DEBUG] Done Modifying machine %s\n", d.Id()) 386 | return updateResourceData(machineObj, d) 387 | } 388 | 389 | // This function doesn't really *delete* a drp managed machine but releases (read, turns off) the machine. 390 | func resourceMachineDelete(d *schema.ResourceData, meta interface{}) error { 391 | cc := meta.(*Config) 392 | log.Printf("[DEBUG] Deleting machine %s\n", d.Id()) 393 | 394 | obj, err := cc.session.GetModel("machines", d.Id()) 395 | if err != nil { 396 | log.Printf("[ERROR] [resourceMachineDelete] Failed to get machine: %v\n", err) 397 | return err 398 | } 399 | machineObj := obj.(*models.Machine) 400 | newObj := models.Clone(machineObj).(*models.Machine) 401 | 402 | if nc, ok := d.GetOk("decommission_color"); ok { 403 | newObj.Meta["color"] = nc.(string) 404 | } else { 405 | newObj.Meta["color"] = "black" 406 | } 407 | 408 | if ni, ok := d.GetOk("decommission_icon"); ok { 409 | newObj.Meta["icon"] = ni.(string) 410 | } else { 411 | newObj.Meta["icon"] = "map outline" 412 | } 413 | 414 | if nw, ok := d.GetOk("decommission_workflow"); ok { 415 | newObj.Workflow = nw.(string) 416 | } else { 417 | if ns, ok := d.GetOk("decommission_stage"); ok { 418 | newObj.Stage = ns.(string) 419 | } else { 420 | if machineObj.Workflow != "" { 421 | newObj.Workflow = "discover" 422 | } else { 423 | if machineObj.Stage != "" { 424 | newObj.Stage = "discover" 425 | } else { 426 | newObj.BootEnv = "sledgehammer" 427 | } 428 | } 429 | } 430 | // Runnable should only be set to false if we aren't using workflows. 431 | newObj.Runnable = false 432 | } 433 | 434 | // Remove the profiles 435 | if ol, ok := d.GetOk("add_profiles"); ok { 436 | l := ol.([]interface{}) 437 | newList := []string{} 438 | 439 | for _, ts := range newObj.Profiles { 440 | found := false 441 | for _, s := range l { 442 | prof := s.(string) 443 | if prof == ts { 444 | found = true 445 | break 446 | } 447 | } 448 | if !found { 449 | newList = append(newList, ts) 450 | } 451 | } 452 | 453 | newObj.Profiles = newList 454 | } 455 | 456 | // Update the machine to request position 457 | err = cc.session.Req().PatchTo(machineObj, newObj).Params("force", "true").Do(&newObj) 458 | if err != nil { 459 | log.Printf("[ERROR] [resourceMachineDelete] Unable to reset machine: %v\n", err) 460 | return err 461 | } 462 | 463 | if err := releaseMachine(cc, d.Id(), false); err != nil { 464 | return err 465 | } 466 | 467 | if err := machineDo(cc, machineObj.UUID(), "nextbootpxe"); err != nil { 468 | log.Printf("[ERROR] [resourceMachineRelease] Unable to mark the machine for pxe next boot: %s\n", machineObj.UUID()) 469 | } 470 | 471 | if err := machineDo(cc, machineObj.UUID(), "powercycle"); err != nil { 472 | log.Printf("[ERROR] [resourceMachineRelease] Unable to power cycle machine: %s\n", machineObj.UUID()) 473 | } 474 | 475 | log.Printf("[DEBUG] [resourceMachineDelete] Machine (%s) released", d.Id()) 476 | 477 | d.SetId("") 478 | 479 | return nil 480 | } 481 | -------------------------------------------------------------------------------- /drp/utils.go: -------------------------------------------------------------------------------- 1 | package drp 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net" 8 | "reflect" 9 | "strings" 10 | "time" 11 | 12 | "github.com/VictorLowther/jsonpatch2/utils" 13 | "github.com/digitalrebar/provision/v4/models" 14 | "github.com/go-test/deep" 15 | "github.com/hashicorp/terraform/helper/schema" 16 | "github.com/pborman/uuid" 17 | ) 18 | 19 | func buildSchemaListFromObject(m interface{}, computed bool) *schema.Schema { 20 | r := &schema.Resource{ 21 | Schema: buildSchemaFromObject(m, computed), 22 | } 23 | return &schema.Schema{ 24 | Type: schema.TypeList, 25 | Elem: r, 26 | Optional: true, 27 | Computed: computed, 28 | } 29 | } 30 | 31 | func buildSchemaFromObject(m interface{}, computed bool) map[string]*schema.Schema { 32 | sm := map[string]*schema.Schema{} 33 | 34 | val := reflect.ValueOf(m).Elem() 35 | 36 | for i := 0; i < val.NumField(); i++ { 37 | typeField := val.Type().Field(i) 38 | tag := typeField.Tag 39 | 40 | // Skip non-exported fields 41 | if typeField.PkgPath != "" { 42 | continue 43 | } 44 | 45 | // Skip the access and validation fields 46 | if typeField.Name == "Access" || typeField.Name == "Validation" { 47 | continue 48 | } 49 | 50 | // Skip the Profile - deprecated fields 51 | if typeField.Name == "Profile" { 52 | continue 53 | } 54 | 55 | fieldName := typeField.Name 56 | // Provider is reserved Terraform name 57 | if fieldName == "Provider" { 58 | fieldName = "PluginProvider" 59 | } 60 | 61 | // Meta is a constant map of strings (but shows up as a type of Meta - fix it) 62 | if fieldName == "Meta" { 63 | sm["Meta"] = &schema.Schema{ 64 | Type: schema.TypeMap, 65 | Elem: &schema.Schema{ 66 | Type: schema.TypeString, 67 | }, 68 | Optional: true, 69 | Computed: computed, 70 | } 71 | 72 | continue 73 | } 74 | 75 | // We don't handle maps generically 76 | // Members is map[string][]string 77 | if fieldName == "Members" { 78 | sm["Members"] = &schema.Schema{ 79 | Type: schema.TypeMap, 80 | Elem: &schema.Schema{ 81 | Type: schema.TypeList, 82 | Elem: &schema.Schema{ 83 | Type: schema.TypeString, 84 | }, 85 | Optional: true, 86 | Computed: computed, 87 | }, 88 | Optional: true, 89 | Computed: computed, 90 | } 91 | 92 | continue 93 | 94 | } 95 | 96 | // 97 | // This is a cluster. Terraform doesn't do generic interface{} 98 | // basically, interface{} and map[string]interface{} 99 | // 100 | // Will try some things. 101 | // 102 | if fieldName == "Params" { 103 | sm["Params"] = &schema.Schema{ 104 | Type: schema.TypeMap, 105 | Elem: &schema.Schema{ 106 | Type: schema.TypeString, 107 | }, 108 | Optional: true, 109 | Computed: computed, 110 | } 111 | continue 112 | } 113 | if fieldName == "Schema" { 114 | sm["Schema"] = &schema.Schema{ 115 | Type: schema.TypeString, 116 | Optional: true, 117 | Computed: computed, 118 | } 119 | continue 120 | } 121 | if fieldName == "SupportedArchitectures" { 122 | sm[fieldName] = &schema.Schema{ 123 | Type: schema.TypeMap, 124 | Elem: buildSchemaFromObject(&models.ArchInfo{}, computed), 125 | Optional: true, 126 | Computed: computed, 127 | } 128 | continue 129 | 130 | } 131 | 132 | if strings.HasPrefix(typeField.Type.String(), "[]") { 133 | listType := typeField.Type.String()[2:] 134 | 135 | switch listType { 136 | case "string": 137 | sm[fieldName] = &schema.Schema{ 138 | Type: schema.TypeList, 139 | Elem: &schema.Schema{ 140 | Type: schema.TypeString, 141 | }, 142 | Optional: true, 143 | Computed: computed, 144 | } 145 | case "models.DhcpOption", "*models.DhcpOption": 146 | sm[fieldName] = buildSchemaListFromObject(&models.DhcpOption{}, computed) 147 | 148 | case "models.TemplateInfo": 149 | sm[fieldName] = buildSchemaListFromObject(&models.TemplateInfo{}, computed) 150 | case "*models.Claim": 151 | sm[fieldName] = buildSchemaListFromObject(&models.Claim{}, computed) 152 | case "uint8": 153 | sm[fieldName] = &schema.Schema{ 154 | Type: schema.TypeString, 155 | Optional: true, 156 | Computed: computed, 157 | } 158 | default: 159 | fmt.Printf("[DEBUG] UNKNOWN List Field Name: %s (%s),\t Tag Value: %s\n", 160 | fieldName, typeField.Type, 161 | tag.Get("tag_name")) 162 | } 163 | continue 164 | } 165 | 166 | switch typeField.Type.String() { 167 | case "models.Owned", "models.Bundled": 168 | // Nothing to do here. 169 | case "models.OsInfo": 170 | // Singleton struct - encode as a list for now. 171 | sm[fieldName] = buildSchemaListFromObject(&models.OsInfo{}, computed) 172 | case "string", "net.IP", "uuid.UUID", "time.Time": 173 | sm[fieldName] = &schema.Schema{ 174 | Type: schema.TypeString, 175 | Optional: true, 176 | Computed: computed, 177 | } 178 | case "bool": 179 | sm[fieldName] = &schema.Schema{ 180 | Type: schema.TypeBool, 181 | Optional: true, 182 | Computed: computed, 183 | } 184 | case "int", "int32", "uint8": 185 | sm[fieldName] = &schema.Schema{ 186 | Type: schema.TypeInt, 187 | Optional: true, 188 | Computed: computed, 189 | } 190 | default: 191 | fmt.Printf("[DEBUG] UNKNOWN Base Field Name: %s (%s),\t Tag Value: %s\n", 192 | fieldName, typeField.Type, 193 | tag.Get("tag_name")) 194 | } 195 | } 196 | 197 | return sm 198 | } 199 | 200 | func dataSourceGeneric(pref string) *schema.Resource { 201 | log.Printf("[DEBUG] [dataSourceGeneric] Initializing data structure: %s\n", pref) 202 | m, _ := models.New(pref) 203 | r := buildSchema(m, false) 204 | r.Read = createDefaultDataSourceReadFunction(m) 205 | r.Create = nil 206 | r.Update = nil 207 | r.Delete = nil 208 | r.Importer = nil 209 | r.Exists = nil 210 | return r 211 | } 212 | 213 | func resourceGeneric(pref string) *schema.Resource { 214 | log.Printf("[DEBUG] [resourceGeneric] Initializing data structure: %s\n", pref) 215 | m, _ := models.New(pref) 216 | return buildSchema(m, true) 217 | } 218 | 219 | func buildSchema(m models.Model, computed bool) *schema.Resource { 220 | r := &schema.Resource{ 221 | Create: createDefaultCreateFunction(m), 222 | Read: createDefaultReadFunction(m), 223 | Update: createDefaultUpdateFunction(m), 224 | Delete: createDefaultDeleteFunction(m), 225 | Exists: createDefaultExistsFunction(m), 226 | Importer: &schema.ResourceImporter{ 227 | State: schema.ImportStatePassthrough, 228 | }, 229 | Schema: buildSchemaFromObject(m, computed), 230 | } 231 | 232 | return r 233 | } 234 | 235 | func updateResourceData(m models.Model, d *schema.ResourceData) error { 236 | val := reflect.ValueOf(m).Elem() 237 | 238 | for i := 0; i < val.NumField(); i++ { 239 | valueField := val.Field(i) 240 | typeField := val.Type().Field(i) 241 | tag := typeField.Tag 242 | 243 | // Skip the access and validation fields 244 | if typeField.Name == "Access" || typeField.Name == "Validation" { 245 | continue 246 | } 247 | 248 | // Skip the Profile - deprecated fields 249 | if typeField.Name == "Profile" { 250 | continue 251 | } 252 | 253 | fieldName := typeField.Name 254 | // Provider is reserved Terraform name 255 | if fieldName == "Provider" { 256 | fieldName = "PluginProvider" 257 | } 258 | 259 | // Meta is a constant map of strings (but shows up as a type of Meta - fix it) 260 | if fieldName == "Meta" { 261 | d.Set("Meta", valueField.Interface()) 262 | continue 263 | } 264 | 265 | // 266 | // This is a cluster. Terraform doesn't generic interface{} 267 | // basically, interface{} and map[string]interface{} 268 | // 269 | // Will try some things. 270 | // 271 | if fieldName == "Params" { 272 | answer := map[string]string{} 273 | 274 | drpAnswer := valueField.Interface().(map[string]interface{}) 275 | for k, v := range drpAnswer { 276 | b, e := json.Marshal(v) 277 | if e != nil { 278 | return e 279 | } 280 | if s, ok := v.(string); ok { 281 | answer[k] = s 282 | } else { 283 | answer[k] = string(b) 284 | } 285 | } 286 | d.Set("Params", answer) 287 | continue 288 | } 289 | if fieldName == "Schema" { 290 | b, e := json.Marshal(valueField.Interface()) 291 | if e != nil { 292 | return e 293 | } 294 | d.Set("Schema", string(b)) 295 | continue 296 | } 297 | if fieldName == "SupportedArchitectures" { 298 | answer := map[string]string{} 299 | 300 | drpAnswer := valueField.Interface().(map[string]interface{}) 301 | for k, v := range drpAnswer { 302 | b, e := json.Marshal(v) 303 | if e != nil { 304 | return e 305 | } 306 | if s, ok := v.(string); ok { 307 | answer[k] = s 308 | } else { 309 | answer[k] = string(b) 310 | } 311 | } 312 | d.Set("SupportedArchitectures", answer) 313 | continue 314 | } 315 | 316 | if strings.HasPrefix(typeField.Type.String(), "[]") { 317 | listType := typeField.Type.String()[2:] 318 | 319 | switch listType { 320 | case "string", "*models.DhcpOption", "models.DhcpOption", "models.TemplateInfo", "*models.Claim": 321 | d.Set(fieldName, valueField.Interface()) 322 | case "uint8": 323 | d.Set(fieldName, fmt.Sprintf("%s", valueField.Interface())) 324 | default: 325 | log.Printf("[DEBUG] UNKNOWN Field Name: %s (%s),\t Field Value: %v,\t Tag Value: %s\n", 326 | fieldName, typeField.Type, 327 | valueField.Interface(), tag.Get("tag_name")) 328 | } 329 | continue 330 | } 331 | 332 | switch typeField.Type.String() { 333 | case "models.OsInfo": 334 | d.Set(fieldName, []models.OsInfo{valueField.Interface().(models.OsInfo)}) 335 | case "string", "net.IP", "uuid.UUID", "time.Time": 336 | d.Set(fieldName, fmt.Sprintf("%s", valueField.Interface())) 337 | case "bool": 338 | d.Set(fieldName, valueField.Interface()) 339 | case "int", "int32", "uint8": 340 | d.Set(fieldName, valueField.Interface()) 341 | default: 342 | log.Printf("[DEBUG] UNKNOWN Field Name: %s (%s),\t Field Value: %v,\t Tag Value: %s\n", 343 | fieldName, typeField.Type, 344 | valueField.Interface(), tag.Get("tag_name")) 345 | } 346 | } 347 | return nil 348 | } 349 | 350 | func buildModel(m models.Model, d *schema.ResourceData) (models.Model, error) { 351 | new := models.Clone(m) 352 | 353 | val := reflect.ValueOf(new).Elem() 354 | for i := 0; i < val.NumField(); i++ { 355 | valueField := val.Field(i) 356 | typeField := val.Type().Field(i) 357 | tag := typeField.Tag 358 | 359 | // Skip the access and validation fields 360 | if typeField.Name == "Access" || typeField.Name == "Validation" { 361 | continue 362 | } 363 | 364 | // Skip the Profile - deprecated fields 365 | if typeField.Name == "Profile" { 366 | continue 367 | } 368 | 369 | fieldName := typeField.Name 370 | // Provider is reserved Terraform name 371 | if fieldName == "Provider" { 372 | fieldName = "PluginProvider" 373 | } 374 | 375 | if !d.HasChange(fieldName) { 376 | continue 377 | } 378 | 379 | // Meta is a constant map of strings (but shows up as a type of Meta - fix it) 380 | if fieldName == "Meta" { 381 | valueField.Set(reflect.MakeMap(typeField.Type)) 382 | ms := d.Get("Meta").(map[string]interface{}) 383 | for k, v := range ms { 384 | valueField.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v)) 385 | } 386 | continue 387 | } 388 | 389 | // 390 | // This is a cluster. Terraform doesn't generic interface{} 391 | // basically, interface{} and map[string]interface{} 392 | // 393 | // Will try some things. 394 | // 395 | if fieldName == "Params" { 396 | answer := d.Get("Params").(map[string]interface{}) 397 | 398 | valueField.Set(reflect.MakeMap(typeField.Type)) 399 | 400 | for k, v := range answer { 401 | s := v.(string) 402 | 403 | var i interface{} 404 | if e := json.Unmarshal([]byte(s), &i); e != nil { 405 | i = s 406 | } 407 | 408 | valueField.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(i)) 409 | } 410 | continue 411 | } 412 | if fieldName == "Schema" { 413 | s := d.Get("Schema").(string) 414 | var i interface{} 415 | if e := json.Unmarshal([]byte(s), &i); e != nil { 416 | return nil, e 417 | } 418 | valueField.Set(reflect.ValueOf(i)) 419 | continue 420 | } 421 | if fieldName == "SupportedArchitectures" { 422 | answer := d.Get("SupportedArchitectures").(map[string]interface{}) 423 | 424 | valueField.Set(reflect.MakeMap(typeField.Type)) 425 | 426 | for k, v := range answer { 427 | s := v.(string) 428 | 429 | var i interface{} 430 | if e := json.Unmarshal([]byte(s), &i); e != nil { 431 | i = s 432 | } 433 | 434 | valueField.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(i)) 435 | } 436 | continue 437 | } 438 | 439 | if strings.HasPrefix(typeField.Type.String(), "[]") { 440 | listType := typeField.Type.String()[2:] 441 | subType := typeField.Type.Elem() 442 | 443 | switch listType { 444 | case "string", "models.TemplateInfo", "*models.Claim", 445 | "models.DhcpOption", "*models.DhcpOption": 446 | 447 | data := d.Get(fieldName).([]interface{}) 448 | v := reflect.MakeSlice(typeField.Type, 0, len(data)) 449 | for _, s := range data { 450 | no := reflect.New(subType).Interface() 451 | if err := utils.Remarshal(s, no); err != nil { 452 | return nil, err 453 | } 454 | v = reflect.Append(v, reflect.Indirect(reflect.ValueOf(no))) 455 | } 456 | valueField.Set(v) 457 | 458 | case "uint8": 459 | fmt.Printf("[DEBUG] list of %s not support for push to DRP\n", listType) 460 | default: 461 | fmt.Printf("[DEBUG] UNKNOWN Field Name: %s (%s),\t Field Value: %v,\t Tag Value: %s\n", 462 | fieldName, typeField.Type, 463 | valueField.Interface(), tag.Get("tag_name")) 464 | } 465 | continue 466 | } 467 | 468 | switch typeField.Type.String() { 469 | case "models.OsInfo": 470 | data := d.Get(fieldName).([]interface{}) 471 | for _, s := range data { 472 | no := models.OsInfo{} 473 | if err := utils.Remarshal(s, &no); err != nil { 474 | return nil, err 475 | } 476 | valueField.Set(reflect.ValueOf(no)) 477 | break 478 | } 479 | case "string": 480 | valueField.SetString(d.Get(fieldName).(string)) 481 | case "net.IP": 482 | ip := net.ParseIP(d.Get(fieldName).(string)) 483 | valueField.Set(reflect.ValueOf(ip)) 484 | case "uuid.UUID": 485 | uu := uuid.Parse(d.Get(fieldName).(string)) 486 | valueField.Set(reflect.ValueOf(uu)) 487 | case "time.Time": 488 | if t, e := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", 489 | d.Get(fieldName).(string)); e != nil { 490 | return nil, e 491 | } else { 492 | valueField.Set(reflect.ValueOf(t)) 493 | } 494 | case "bool": 495 | valueField.SetBool(d.Get(fieldName).(bool)) 496 | case "int", "int32", "uint8": 497 | valueField.SetInt(int64(d.Get(fieldName).(int))) 498 | default: 499 | fmt.Printf("[DEBUG] UNKNOWN Field Name: %s (%s),\t Field Value: %v,\t Tag Value: %s\n", 500 | fieldName, typeField.Type, 501 | valueField.Interface(), tag.Get("tag_name")) 502 | } 503 | } 504 | return new, nil 505 | } 506 | 507 | func createDefaultCreateFunction(m models.Model) func(*schema.ResourceData, interface{}) error { 508 | return func(d *schema.ResourceData, meta interface{}) error { 509 | cc := meta.(*Config) 510 | log.Printf("[DEBUG] [resource%sCreate] creating\n", m.Prefix()) 511 | 512 | new, err := buildModel(m, d) 513 | if err != nil { 514 | return err 515 | } 516 | 517 | answer, err := cc.session.GetModel(new.Prefix(), new.Key()) 518 | if err == nil { 519 | d.SetId(answer.Key()) 520 | ro, ok := answer.(models.Accessor) 521 | if !ok || ro.IsReadOnly() { 522 | return updateResourceData(answer, d) 523 | } 524 | return createDefaultUpdateFunction(m)(d, meta) 525 | } 526 | 527 | err = cc.session.CreateModel(new) 528 | if err != nil { 529 | return err 530 | } 531 | 532 | d.SetId(new.Key()) 533 | 534 | return createDefaultReadFunction(m)(d, meta) 535 | } 536 | } 537 | 538 | func createDefaultDataSourceReadFunction(m models.Model) func(*schema.ResourceData, interface{}) error { 539 | return func(d *schema.ResourceData, meta interface{}) error { 540 | cc := meta.(*Config) 541 | 542 | id := d.Get(m.KeyName()).(string) 543 | d.SetId(id) 544 | 545 | log.Printf("[DEBUG] [dataSource%sRead] reading %s\n", m.Prefix(), id) 546 | 547 | answer, err := cc.session.GetModel(m.Prefix(), id) 548 | if err != nil { 549 | return err 550 | } 551 | 552 | return updateResourceData(answer, d) 553 | } 554 | } 555 | func createDefaultReadFunction(m models.Model) func(*schema.ResourceData, interface{}) error { 556 | return func(d *schema.ResourceData, meta interface{}) error { 557 | cc := meta.(*Config) 558 | log.Printf("[DEBUG] [resource%sRead] reading %s\n", m.Prefix(), d.Id()) 559 | 560 | answer, err := cc.session.GetModel(m.Prefix(), d.Id()) 561 | if err != nil { 562 | return err 563 | } 564 | 565 | return updateResourceData(answer, d) 566 | } 567 | } 568 | 569 | func createDefaultUpdateFunction(m models.Model) func(*schema.ResourceData, interface{}) error { 570 | return func(d *schema.ResourceData, meta interface{}) error { 571 | cc := meta.(*Config) 572 | log.Printf("[DEBUG] [resource%sUpdate] updating %s\n", m.Prefix(), d.Id()) 573 | 574 | base, err := cc.session.GetModel(m.Prefix(), d.Id()) 575 | if err != nil { 576 | return err 577 | } 578 | 579 | mods, err := buildModel(base, d) 580 | if err != nil { 581 | return err 582 | } 583 | 584 | err = cc.session.Req().PatchTo(base, mods).Params("force", "true").Do(&mods) 585 | if err != nil { 586 | return err 587 | } 588 | return updateResourceData(mods, d) 589 | } 590 | } 591 | 592 | func createDefaultDeleteFunction(m models.Model) func(*schema.ResourceData, interface{}) error { 593 | return func(d *schema.ResourceData, meta interface{}) error { 594 | cc := meta.(*Config) 595 | log.Printf("[DEBUG] [resource%sDelete] deleting %s\n", m.Prefix(), d.Id()) 596 | _, err := cc.session.DeleteModel(m.Prefix(), d.Id()) 597 | return err 598 | } 599 | } 600 | 601 | func createDefaultExistsFunction(m models.Model) func(*schema.ResourceData, interface{}) (bool, error) { 602 | return func(d *schema.ResourceData, meta interface{}) (bool, error) { 603 | cc := meta.(*Config) 604 | log.Printf("[DEBUG] [resource%sExists] testing %s\n", m.Prefix(), d.Id()) 605 | return cc.session.ExistsModel(m.Prefix(), d.Id()) 606 | } 607 | } 608 | 609 | func diffObjects(exp, fnd interface{}, t string) error { 610 | b1, _ := json.MarshalIndent(exp, "", " ") 611 | b2, _ := json.MarshalIndent(fnd, "", " ") 612 | if string(b1) != string(b2) { 613 | return fmt.Errorf("json diff: %s: %v\n%v\n", t, string(b1), string(b2)) 614 | 615 | } 616 | if diff := deep.Equal(exp, fnd); diff != nil { 617 | return fmt.Errorf("%s doesn't match: %v", t, diff) 618 | } 619 | return nil 620 | } 621 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8= 5 | cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40= 6 | dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= 7 | dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= 8 | dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= 9 | dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= 10 | git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 11 | github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 12 | github.com/Azure/go-autorest v10.15.4+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 13 | github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= 16 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 17 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 18 | github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= 19 | github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 20 | github.com/Masterminds/sprig v2.20.0+incompatible h1:dJTKKuUkYW3RMFdQFXPU/s6hg10RgctmTjRcbZ98Ap8= 21 | github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 22 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 23 | github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no= 24 | github.com/VictorLowther/godmi v0.0.0-20190712193004-d244e3465e37/go.mod h1:JiYtLLi0v25VZLHRInc/p4CtOCFXUPKb9Q7vNQ6rqyk= 25 | github.com/VictorLowther/jsonpatch2 v1.0.0 h1:gQxztbUxxBK0EccdhRO44TgDy7jSFFIZAGO7vdQA2xA= 26 | github.com/VictorLowther/jsonpatch2 v1.0.0/go.mod h1:MagdKGtUJ6bwyDgLk502ME/LDMKy9Rqh9iOf41iG5ds= 27 | github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= 28 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 29 | github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= 30 | github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 31 | github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= 32 | github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= 33 | github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= 34 | github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= 35 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 36 | github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= 37 | github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= 38 | github.com/apparentlymart/go-cidr v1.0.0 h1:lGDvXx8Lv9QHjrAVP7jyzleG4F9+FkRhJcEsDFxeb8w= 39 | github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= 40 | github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 41 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= 42 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 43 | github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= 44 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 45 | github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 46 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 47 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 48 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 49 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 50 | github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= 51 | github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 52 | github.com/aws/aws-sdk-go v1.21.7 h1:ml+k7szyVaq4YD+3LhqOGl9tgMTqgMbpnuUSkB6UJvQ= 53 | github.com/aws/aws-sdk-go v1.21.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 54 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= 55 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 56 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= 57 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= 58 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 59 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 60 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 61 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 62 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 63 | github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= 64 | github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= 65 | github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= 66 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 67 | github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 68 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 69 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 70 | github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 71 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 72 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 73 | github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 74 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 75 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 76 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 77 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 78 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 79 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 80 | github.com/digitalrebar/logger v0.3.0 h1:7/XP3BQiWfl1Lqmm7tvkvdwjPilDUUPC/iWbeWO9G6g= 81 | github.com/digitalrebar/logger v0.3.0/go.mod h1:zH2t4FBhzVxZJTg+eaPzNCRL50nlcRLxWurfTsl5jWE= 82 | github.com/digitalrebar/provision/v4 v4.0.10 h1:dQnnvkirJ/JY5vY5GTN6U9W5xbPoxsEwlIRzmv+3Ai4= 83 | github.com/digitalrebar/provision/v4 v4.0.10/go.mod h1:AWhC3b3eBPL2c9hC8FnHwfL7X2EiEascOYvwHW3tkIs= 84 | github.com/digitalrebar/tftp v2.1.0+incompatible/go.mod h1:k1tcsyQwHE72oTU5sfXPJdOCHFiZUt3M2BdYdHsfw6c= 85 | github.com/digitalrebar/tftp/v3 v3.0.0/go.mod h1:OEQX1RPK9eiehWKXeZMbYiRLJ3JJLaB68vj7IR7LvOM= 86 | github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 87 | github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 88 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 89 | github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= 90 | github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= 91 | github.com/elithrar/simple-scrypt v1.3.0 h1:KIlOlxdoQf9JWKl5lMAJ28SY2URB0XTRDn2TckyzAZg= 92 | github.com/elithrar/simple-scrypt v1.3.0/go.mod h1:U2XQRI95XHY0St410VE3UjT7vuKb1qPwrl/EJwEqnZo= 93 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 94 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 95 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 96 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 97 | github.com/ghodss/yaml v0.0.0-20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= 98 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 99 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 100 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 101 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 102 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 103 | github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= 104 | github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 105 | github.com/gofunky/semver v3.5.2+incompatible h1:bLtS5NNx0gLpaUJHGRtePWV6vs5Q2cNhatKFqKny5J8= 106 | github.com/gofunky/semver v3.5.2+incompatible/go.mod h1:7MXgDdC47tqmTJhxqX5CCuJaIx+c5igi9n0xPbKjG0U= 107 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 108 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 109 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 110 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 111 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 112 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 113 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 114 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 115 | github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= 116 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 117 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 118 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 119 | github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= 120 | github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 121 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 122 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 123 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 124 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 125 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 126 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 127 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 128 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 129 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 130 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 131 | github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 132 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 133 | github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= 134 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 135 | github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc= 136 | github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= 137 | github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 138 | github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= 139 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 140 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 141 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 142 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 143 | github.com/groob/plist v0.0.0-20190114192801-a99fbe489d03/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc= 144 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 145 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 146 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 147 | github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 148 | github.com/hashicorp/aws-sdk-go-base v0.2.0/go.mod h1:ZIWACGGi0N7a4DZbf15yuE1JQORmWLtBcVM6F5SXNFU= 149 | github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= 150 | github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 151 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 152 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 153 | github.com/hashicorp/go-azure-helpers v0.0.0-20190129193224-166dfd221bb2/go.mod h1:lu62V//auUow6k0IykxLK2DCNW8qTmpm8KqhYVWattA= 154 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 155 | github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= 156 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 157 | github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e h1:6krcdHPiS+aIP9XKzJzSahfjD7jG7Z+4+opm0z39V1M= 158 | github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ= 159 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 160 | github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f h1:Yv9YzBlAETjy6AOX9eLBZ3nshNVRREgerT/3nvxlGho= 161 | github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 162 | github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw= 163 | github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 164 | github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= 165 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 166 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 167 | github.com/hashicorp/go-plugin v1.0.1-0.20190610192547-a1bc61569a26 h1:hRho44SAoNu1CBtn5r8Q9J3rCs4ZverWZ4R+UeeNuWM= 168 | github.com/hashicorp/go-plugin v1.0.1-0.20190610192547-a1bc61569a26/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 169 | github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 170 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 171 | github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= 172 | github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= 173 | github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= 174 | github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 175 | github.com/hashicorp/go-tfe v0.3.16/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM= 176 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 177 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 178 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 179 | github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= 180 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 181 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 182 | github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws= 183 | github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= 184 | github.com/hashicorp/hcl2 v0.0.0-20181208003705-670926858200/go.mod h1:ShfpTh661oAaxo7VcNxg0zcZW6jvMa7Moy2oFx7e5dE= 185 | github.com/hashicorp/hcl2 v0.0.0-20190725010614-0c3fe388e450 h1:wpa0vOXOnSEuwZ++eVk1gQNm3Jy2+Envn0cQRgsl8K8= 186 | github.com/hashicorp/hcl2 v0.0.0-20190725010614-0c3fe388e450/go.mod h1:FSQTwDi9qesxGBsII2VqhIzKQ4r0bHvBkOczWfD7llg= 187 | github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI= 188 | github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= 189 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 190 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 191 | github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= 192 | github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= 193 | github.com/hashicorp/terraform v0.12.6 h1:mWItQdLZQ7f3kBYBu2Kgdg+E5iZb1KtCq73V10Hmu48= 194 | github.com/hashicorp/terraform v0.12.6/go.mod h1:udmq5rU8CO9pEIh/A/Xrs3zb3yYU/W9ce1pp8K1ysHA= 195 | github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70 h1:oZm5nE11yhzsTRz/YrUyDMSvixePqjoZihwn8ipuOYI= 196 | github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70/go.mod h1:ItvqtvbC3K23FFET62ZwnkwtpbKZm8t8eMcWjmVVjD8= 197 | github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= 198 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= 199 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 200 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 201 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= 202 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 203 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 204 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 205 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 206 | github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 207 | github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= 208 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 209 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 210 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 211 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 212 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 213 | github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= 214 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 215 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 216 | github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 217 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 218 | github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= 219 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 220 | github.com/klauspost/compress v1.7.4/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 221 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 222 | github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 223 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 224 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 225 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 226 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 227 | github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 228 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 229 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 230 | github.com/krolaw/dhcp4 v0.0.0-20190531080455-7b64900047ae h1:eJIlhimWuFB4RmBV8UWt2CJhEPGunO0obfXHzzUxrOU= 231 | github.com/krolaw/dhcp4 v0.0.0-20190531080455-7b64900047ae/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= 232 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= 233 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 234 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 235 | github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84= 236 | github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= 237 | github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= 238 | github.com/masterzen/winrm v0.0.0-20190223112901-5e5c9a7fe54b/go.mod h1:wr1VqkwW0AB5JS0QLy5GpVMS9E3VtRoSYXUYyVk46KY= 239 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 240 | github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= 241 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 242 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 243 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 244 | github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= 245 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 246 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 247 | github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= 248 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 249 | github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 250 | github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 251 | github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= 252 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 253 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 254 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 255 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 256 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 257 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 258 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 259 | github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= 260 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 261 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 262 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 263 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 264 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 265 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 266 | github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= 267 | github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= 268 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 269 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 270 | github.com/mitchellh/panicwrap v0.0.0-20190213213626-17011010aaa4/go.mod h1:YYMf4xtQnR8LRC0vKi3afvQ5QwRPQ17zjcpkBCufb+I= 271 | github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= 272 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 273 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 274 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 275 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 276 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= 277 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= 278 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= 279 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 280 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 281 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 282 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 283 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 284 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 285 | github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= 286 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 287 | github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 288 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 289 | github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 290 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 291 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 292 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 293 | github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= 294 | github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= 295 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 296 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 297 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 298 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 299 | github.com/rackn/gohai v0.0.0-20190619164647-92918a701117/go.mod h1:1NhfVxJ6qD4S5ondz2CobWFiYlODF8ex4pNgQrKYCxc= 300 | github.com/rackn/gohai v0.3.0/go.mod h1:JGFSlNYmSPglk0ogBwtBandVf2mQPzVuFqjIbGDOZME= 301 | github.com/rackn/netwrangler v0.7.0/go.mod h1:1ZvFAprvzdbIZ/kMYh90tLMGsK4U31YqrAsKNIUEqls= 302 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 303 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 304 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 305 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 306 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 307 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 308 | github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 309 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= 310 | github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= 311 | github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= 312 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= 313 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 314 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 315 | github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= 316 | github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= 317 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= 318 | github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= 319 | github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= 320 | github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= 321 | github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= 322 | github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 323 | github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= 324 | github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= 325 | github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= 326 | github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= 327 | github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= 328 | github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= 329 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 330 | github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= 331 | github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= 332 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 333 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 334 | github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 335 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 336 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 337 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 338 | github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= 339 | github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 340 | github.com/spf13/cobra v0.0.4-0.20180722215644-7c4570c3ebeb/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 341 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 342 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 343 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 344 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 345 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 346 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 347 | github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= 348 | github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= 349 | github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew= 350 | github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 351 | github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 352 | github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= 353 | github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 354 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 355 | github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= 356 | github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 357 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= 358 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= 359 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 360 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 361 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 362 | github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= 363 | github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= 364 | github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 365 | github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 366 | github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 367 | github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 368 | github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f h1:sq2p8SN6ji66CFEQFIWLlD/gFmGtr5hBrOzv5nLlGfA= 369 | github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 370 | github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8= 371 | github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= 372 | go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= 373 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 374 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 375 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 376 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 377 | go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= 378 | golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= 379 | golang.org/x/crypto v0.0.0-20180816225734-aabede6cba87/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 380 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 381 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 382 | golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 383 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 384 | golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 385 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 386 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 387 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 388 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 389 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 390 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 391 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 392 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 393 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 394 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 395 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 396 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 397 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 398 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 399 | golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 400 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 401 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 402 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 403 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 404 | golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 405 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= 406 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 407 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 408 | golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 409 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 410 | golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9 h1:pfyU+l9dEu0vZzDDMsdAKa1gZbJYEn6urYXj/+Xkz7s= 411 | golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 412 | golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 413 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 414 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 415 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 416 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 417 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 418 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 419 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 420 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 421 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 422 | golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 423 | golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 424 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 425 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 426 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 427 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 428 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 430 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0= 431 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 432 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 433 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 434 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 435 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 436 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 437 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 438 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 439 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 440 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 441 | golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 442 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 443 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 444 | google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 445 | google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI= 446 | google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= 447 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 448 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 449 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 450 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 451 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 452 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 453 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 454 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 455 | google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= 456 | google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8= 457 | google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= 458 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 459 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 460 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 461 | google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= 462 | google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 463 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 464 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 465 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 466 | gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 467 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 468 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 469 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 470 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 471 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 472 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 473 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 474 | grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= 475 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 476 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 477 | howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 478 | sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= 479 | sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= 480 | --------------------------------------------------------------------------------