├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── SUPPORT.md └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── .travis.yml ├── GNUmakefile ├── LICENSE ├── README.md ├── docs ├── index.md └── resources │ ├── file.md │ ├── folder.md │ ├── group.md │ └── user.md ├── go.mod ├── go.sum ├── linux ├── config.go ├── provider.go ├── provider_test.go ├── resource_file.go ├── resource_file_test.go ├── resource_file_validators.go ├── resource_file_validators_test.go ├── resource_folder.go ├── resource_folder_test.go ├── resource_group.go ├── resource_group_test.go ├── resource_user.go ├── resource_user_test.go └── utils.go ├── main.go └── scripts ├── changelog-links.sh ├── errcheck.sh ├── gofmtcheck.sh ├── gogetcookie.sh ├── tests_cleanup.sh ├── tests_setup.sh └── testsacc_run.sh /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | HashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code. 4 | 5 | Please read the full text at https://www.hashicorp.com/community-guidelines 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi there, 2 | 3 | Thank you for opening an issue. Please note that we try to keep the Terraform issue tracker reserved for bug reports and feature requests. For general usage questions, please see: https://www.terraform.io/community.html. 4 | 5 | ### Terraform Version 6 | Run `terraform -v` to show the version. If you are not running the latest version of Terraform, please upgrade because your issue may have already been fixed. 7 | 8 | ### Affected Resource(s) 9 | Please list the resources as a list, for example: 10 | - opc_instance 11 | - opc_storage_volume 12 | 13 | If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this. 14 | 15 | ### Terraform Configuration Files 16 | ```hcl 17 | # Copy-paste your Terraform configurations here - for large Terraform configs, 18 | # please use a service like Dropbox and share a link to the ZIP file. For 19 | # security, you can also encrypt the files using our GPG public key. 20 | ``` 21 | 22 | ### Debug Output 23 | Please provider a link to a GitHub Gist containing the complete debug output: https://www.terraform.io/docs/internals/debugging.html. Please do NOT paste the debug output in the issue; just paste a link to the Gist. 24 | 25 | ### Panic Output 26 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 27 | 28 | ### Expected Behavior 29 | What should have happened? 30 | 31 | ### Actual Behavior 32 | What actually happened? 33 | 34 | ### Steps to Reproduce 35 | Please list the steps required to reproduce the issue, for example: 36 | 1. `terraform apply` 37 | 38 | ### Important Factoids 39 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 40 | 41 | ### References 42 | Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example: 43 | - GH-1234 44 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Terraform is a mature project with a growing community. There are active, dedicated people willing to help you through various mediums. 4 | 5 | Take a look at those mediums listed at https://www.terraform.io/community.html 6 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (paultyng/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - 'v*' 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - 22 | name: Checkout 23 | uses: actions/checkout@v2 24 | - 25 | name: Unshallow 26 | run: git fetch --prune --unshallow 27 | - 28 | name: Set up Go 29 | uses: actions/setup-go@v2 30 | with: 31 | go-version: 1.14 32 | - 33 | name: Import GPG key 34 | id: import_gpg 35 | uses: paultyng/ghaction-import-gpg@v2.1.0 36 | env: 37 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 38 | PASSPHRASE: ${{ secrets.PASSPHRASE }} 39 | - 40 | name: Run GoReleaser 41 | uses: goreleaser/goreleaser-action@v2 42 | with: 43 | version: latest 44 | args: release --rm-dist 45 | env: 46 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | terraform.tfplan 6 | terraform.tfstate 7 | bin/ 8 | modules-dev/ 9 | /pkg/ 10 | website/.vagrant 11 | website/.bundle 12 | website/build 13 | website/node_modules 14 | .vagrant/ 15 | *.backup 16 | ./*.tfstate 17 | .terraform/ 18 | *.log 19 | *.bak 20 | *~ 21 | .*.swp 22 | .idea 23 | *.iml 24 | *.test 25 | *.iml 26 | 27 | website/vendor 28 | 29 | # Test exclusions 30 | !command/test-fixtures/**/*.tfstate 31 | !command/test-fixtures/**/.terraform/ 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | 2 | # Visit https://goreleaser.com for documentation on how to customize this 3 | # behavior. 4 | before: 5 | hooks: 6 | # this is just an example and not a requirement for provider building/publishing 7 | - go mod tidy 8 | builds: 9 | - env: 10 | # goreleaser does not work with CGO, it could also complicate 11 | # usage by users in CI/CD systems like Terraform Cloud where 12 | # they are unable to install libraries. 13 | - CGO_ENABLED=0 14 | mod_timestamp: '{{ .CommitTimestamp }}' 15 | flags: 16 | - -trimpath 17 | ldflags: 18 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 19 | goos: 20 | - freebsd 21 | - windows 22 | - linux 23 | - darwin 24 | goarch: 25 | - amd64 26 | - '386' 27 | - arm 28 | - arm64 29 | ignore: 30 | - goos: darwin 31 | goarch: '386' 32 | binary: '{{ .ProjectName }}_v{{ .Version }}' 33 | archives: 34 | - format: zip 35 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 36 | checksum: 37 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 38 | algorithm: sha256 39 | signs: 40 | - artifacts: checksum 41 | args: 42 | # if you are using this is a GitHub action or some other automated pipeline, you 43 | # need to pass the batch flag to indicate its not interactive. 44 | - "--batch" 45 | - "--local-user" 46 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 47 | - "--output" 48 | - "${signature}" 49 | - "--detach-sign" 50 | - "${artifact}" 51 | release: 52 | # If you want to manually examine the release before its live, uncomment this line: 53 | # draft: true 54 | changelog: 55 | skip: true 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | services: 4 | - docker 5 | language: go 6 | go: 7 | - 1.12.x 8 | env: 9 | GO111MODULE=on 10 | 11 | install: 12 | # This script is used by the Travis build to install a cookie for 13 | # go.googlesource.com so rate limits are higher when using `go get` to fetch 14 | # packages that live there. 15 | # See: https://github.com/golang/go/issues/12933 16 | - bash scripts/gogetcookie.sh 17 | - go mod download 18 | 19 | script: 20 | - make test 21 | - make testacc 22 | - make vet 23 | 24 | branches: 25 | only: 26 | - master 27 | matrix: 28 | fast_finish: true 29 | allow_failures: 30 | - go: tip -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... |grep -v 'vendor') 2 | GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) 3 | PKG_NAME=linux 4 | 5 | default: build 6 | 7 | build: fmtcheck 8 | go install 9 | 10 | test: fmtcheck 11 | go test -i $(TEST) || exit 1 12 | echo $(TEST) | \ 13 | xargs -t -n4 go test $(TESTARGS) -v -timeout=30s -parallel=4 14 | 15 | testacc: fmtcheck 16 | @sh -c "'$(CURDIR)/scripts/testsacc_run.sh'" 17 | 18 | vet: 19 | @echo "go vet ." 20 | @go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \ 21 | echo ""; \ 22 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 23 | echo "and fix them if necessary before submitting the code for review."; \ 24 | exit 1; \ 25 | fi 26 | 27 | fmt: 28 | gofmt -w $(GOFMT_FILES) 29 | 30 | fmtcheck: 31 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 32 | 33 | errcheck: 34 | @sh -c "'$(CURDIR)/scripts/errcheck.sh'" 35 | 36 | test-compile: 37 | @if [ "$(TEST)" = "./..." ]; then \ 38 | echo "ERROR: Set TEST to a specific package. For example,"; \ 39 | echo " make test-compile TEST=./$(PKG_NAME)"; \ 40 | exit 1; \ 41 | fi 42 | go test -c $(TEST) $(TESTARGS) 43 | 44 | .PHONY: build test testacc vet fmt fmtcheck errcheck test-compile 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform Linux Provider [![Build Status](https://travis-ci.org/mavidser/terraform-provider-linux.svg?branch=master)](https://travis-ci.org/mavidser/terraform-provider-linux) 2 | ======================== 3 | 4 | *I realize that the name 'Linux Provider' is misleading, but I needed this provider and couldn't find a more suitable name.* 5 | 6 | - [Documentation](https://github.com/mavidser/terraform-provider-linux/tree/master/docs) 7 | 8 | 9 | 10 | 11 | Requirements 12 | ------------ 13 | 14 | - [Terraform](https://www.terraform.io/downloads.html) 0.12.x 15 | - [Go](https://golang.org/doc/install) 1.12 (to build the provider plugin) 16 | 17 | Usage 18 | --------------------- 19 | 20 | ```terraform 21 | provider "linux" { 22 | host = "192.168.1.2" 23 | user = "user" 24 | } 25 | ``` 26 | 27 | Building The Provider 28 | --------------------- 29 | 30 | Clone repository to: `$GOPATH/src/github.com/mavidser/terraform-provider-linux` 31 | 32 | ```sh 33 | $ mkdir -p $GOPATH/src/github.com/terraform-providers; cd $GOPATH/src/github.com/terraform-providers 34 | $ git clone git@github.com:mavidser/terraform-provider-linux 35 | ``` 36 | 37 | Enter the provider directory and build the provider 38 | 39 | ```sh 40 | $ cd $GOPATH/src/github.com/mavidser/terraform-provider-linux 41 | $ make build 42 | ``` 43 | 44 | Using the provider 45 | ---------------------- 46 | 47 | Sample configuration for creating a few users/groups: 48 | 49 | ```terraform 50 | resource "linux_group" "testgroup" { 51 | name = "testgroup" 52 | system = false 53 | } 54 | 55 | resource "linux_user" "testuser1" { 56 | name = "testuser1" 57 | gid = linux_group.testgroup.gid 58 | system = false 59 | } 60 | 61 | resource "linux_user" "testuser2" { 62 | name = "testuser2" 63 | gid = linux_group.testgroup.gid 64 | system = false 65 | } 66 | ``` 67 | 68 | Developing the Provider 69 | --------------------------- 70 | 71 | If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.11+ 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`. 72 | 73 | To compile the provider, run `make build`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 74 | 75 | ```sh 76 | $ make build 77 | ... 78 | $ $GOPATH/bin/terraform-provider-linux 79 | ... 80 | ``` 81 | 82 | In order to test the provider, you can simply run `make test`. 83 | Note: These tests will require docker installed to spin up a container with ssh access. 84 | 85 | ```sh 86 | $ make test 87 | ``` 88 | 89 | In order to run the full suite of Acceptance tests, run `make testacc`. 90 | 91 | ```sh 92 | $ make testacc 93 | ``` 94 | 95 | In order to run only single Acceptance tests, execute the following steps: 96 | 97 | ```sh 98 | # setup the testing environment 99 | $ source ./scripts/tests_setup.sh 100 | 101 | # run single tests 102 | TF_LOG=INFO TF_ACC=1 go test -v ./linux -run TestAccUserCreation -timeout 360s 103 | 104 | # cleanup the local testing resources 105 | $ source ./scripts/tests_cleanup.sh 106 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Linux Provider 2 | 3 | This providor is used to manage parts of a typical linux system. Eg - users, groups, files, etc. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "linux" { 9 | host = "192.168.1.128" 10 | user = "root" 11 | } 12 | 13 | resource "linux_user" "testuser" { 14 | name = "testuser" 15 | uid = 1024 16 | } 17 | 18 | resource "linux_file" "testfile" { 19 | path = "/etc/testfile" 20 | content = "testcontent" 21 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 22 | permissions = 777 23 | } 24 | ``` 25 | 26 | ## Argument Reference 27 | 28 | The following arguments are supported: 29 | 30 | - `host` - (Required) The host to ssh into. 31 | - `port` - (Optional) The ssh port. Defaults to "22". 32 | - `user` - (Required) The username to ssh with. 33 | - `private_key` - (Optional) The location of the private key, if used for authentication. Defaults to `$HOME/.ssh/id_rsa`. 34 | - `password` - (Optional) The password, if used for authentication. 35 | - `use_sudo` - (Optional) Do certain commands need to be prefixed with sudo? Defaults to true if user is "root", else false. 36 | 37 | -> For encrypted private keys, use `ssh-agent` to allow connection. -------------------------------------------------------------------------------- /docs/resources/file.md: -------------------------------------------------------------------------------- 1 | # linux_file 2 | 3 | Manages files and their attributes. 4 | 5 | -> Make sure that the user has permissions to the files being created. 6 | 7 | -> If using the provider with a non-sudoer user, allow NOPASSWD sudo access to these commands - `chown` and `chmod`. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "linux_user" "testuser" { 13 | name = "testuser" 14 | uid = 1024 15 | } 16 | 17 | resource "linux_file" "testfile" { 18 | path = "/etc/testfile" 19 | content = "testcontent" 20 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 21 | permissions = 777 22 | } 23 | ``` 24 | 25 | ## Argument Reference 26 | 27 | The following arguments are supported: 28 | 29 | - `path` - (Required, string) Absolute path of the file. 30 | - `owner` - (Optional, string) Owners of the file, in `user:group` format. 31 | - `permissions` - (Optional, int) Octal permissions of the file. 32 | - `content` - (Optional, string) Content of the file. 33 | -------------------------------------------------------------------------------- /docs/resources/folder.md: -------------------------------------------------------------------------------- 1 | # linux_folder 2 | 3 | Manages folders and their attributes. 4 | 5 | -> Make sure that the user has permissions to the folders being created. 6 | 7 | -> If using the provider with a non-sudoer user, allow NOPASSWD sudo access to these commands - `chown` and `chmod`. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "linux_user" "testuser" { 13 | name = "testuser" 14 | uid = 1024 15 | } 16 | 17 | resource "linux_folder" "testfolder" { 18 | path = "/etc/testfolder" 19 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 20 | permissions = 777 21 | } 22 | ``` 23 | 24 | ## Argument Reference 25 | 26 | The following arguments are supported: 27 | 28 | - `path` - (Required, string) Absolute path of the folder. 29 | - `owner` - (Optional, string) Owners of the folder, in `user:group` format. 30 | - `permissions` - (Optional, int) Octal permissions of the folder. 31 | -------------------------------------------------------------------------------- /docs/resources/group.md: -------------------------------------------------------------------------------- 1 | # linux_group 2 | 3 | Manages groups and their attributes. 4 | 5 | -> If using the provider with a non-sudoer user, allow NOPASSWD sudo access to these commands - `groupadd`, `groupmod`, and `groupdel`. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "linux_group" "testgroup" { 11 | name = "testgroup" 12 | gid = 1048 13 | } 14 | 15 | resource "linux_user" "testuser" { 16 | name = "testuser" 17 | uid = 1024 18 | gid = linux_group.testgroup.gid 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments are supported: 25 | 26 | - `name` - (Required, string) Name of the group. 27 | - `gid` - (Optional, int) gid to set of the group. 28 | - `system` - (Optional, bool) If GID is not supplied, this attribute is factored in while generating the GID. Defaults to false. 29 | 30 | ## Attribute Reference 31 | 32 | The following attributes are exported: 33 | 34 | - `gid` - If not supplied, the generated GID. -------------------------------------------------------------------------------- /docs/resources/user.md: -------------------------------------------------------------------------------- 1 | # linux_user 2 | 3 | Manages users and their attributes. 4 | 5 | -> If using the provider with a non-sudoer user, allow NOPASSWD sudo access to these commands - `useradd`, `usermod`, and `userdel`. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "linux_group" "testgroup" { 11 | name = "testgroup" 12 | gid = 1048 13 | } 14 | 15 | resource "linux_user" "testuser" { 16 | name = "testuser" 17 | uid = 1024 18 | gid = linux_group.testgroup.gid 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments are supported: 25 | 26 | - `name` - (Required, string) Name of the user. 27 | - `uid` - (Optional, int) UID to set of the user. 28 | - `gid` - (Optional, int) GID to set of the user. 29 | - `system` - (Optional, bool) If UID is not supplied, this attribute is factored in while generating the GID. Defaults to false. 30 | 31 | ## Attribute Reference 32 | 33 | The following attributes are exported: 34 | 35 | - `uid` - If not supplied, the generated uid. 36 | - `gid` - If not supplied, the generated uid. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mavidser/terraform-provider-linux 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/hashicorp/terraform v0.12.6 7 | github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 8 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf 9 | golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 // indirect 10 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 11 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /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/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no= 17 | github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= 18 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 19 | github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= 20 | github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 21 | github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= 22 | github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= 23 | github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= 24 | github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= 25 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 26 | github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= 27 | github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= 28 | github.com/apparentlymart/go-cidr v1.0.0 h1:lGDvXx8Lv9QHjrAVP7jyzleG4F9+FkRhJcEsDFxeb8w= 29 | github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= 30 | github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 31 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= 32 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 33 | github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= 34 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 35 | github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 36 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 37 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 38 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 39 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 40 | github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= 41 | github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 42 | github.com/aws/aws-sdk-go v1.21.7 h1:ml+k7szyVaq4YD+3LhqOGl9tgMTqgMbpnuUSkB6UJvQ= 43 | github.com/aws/aws-sdk-go v1.21.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 44 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= 45 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 46 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= 47 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= 48 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 49 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 50 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 51 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 52 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 53 | github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= 54 | github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= 55 | github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= 56 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 57 | github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 58 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 59 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 60 | github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 61 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 62 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 63 | github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 64 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 65 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 67 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 68 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 69 | github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 70 | github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 71 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 72 | github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= 73 | github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= 74 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 75 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 76 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 77 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 78 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 79 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 80 | github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= 81 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 82 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 83 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 84 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 85 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 86 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 87 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 88 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 89 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 90 | github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= 91 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 92 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 93 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 94 | github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= 95 | github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 96 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 97 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 98 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 99 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 100 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 101 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 102 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 103 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 104 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 105 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 106 | github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= 107 | github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 108 | github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc= 109 | github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= 110 | github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 111 | github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= 112 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 113 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 114 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 115 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 116 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 117 | github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 118 | github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 119 | github.com/hashicorp/aws-sdk-go-base v0.2.0/go.mod h1:ZIWACGGi0N7a4DZbf15yuE1JQORmWLtBcVM6F5SXNFU= 120 | github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= 121 | github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 122 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 123 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 124 | github.com/hashicorp/go-azure-helpers v0.0.0-20190129193224-166dfd221bb2/go.mod h1:lu62V//auUow6k0IykxLK2DCNW8qTmpm8KqhYVWattA= 125 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 126 | github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= 127 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 128 | github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e h1:6krcdHPiS+aIP9XKzJzSahfjD7jG7Z+4+opm0z39V1M= 129 | github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ= 130 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 131 | github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f h1:Yv9YzBlAETjy6AOX9eLBZ3nshNVRREgerT/3nvxlGho= 132 | github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 133 | github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw= 134 | github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 135 | github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= 136 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 137 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 138 | github.com/hashicorp/go-plugin v1.0.1-0.20190610192547-a1bc61569a26 h1:hRho44SAoNu1CBtn5r8Q9J3rCs4ZverWZ4R+UeeNuWM= 139 | github.com/hashicorp/go-plugin v1.0.1-0.20190610192547-a1bc61569a26/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 140 | github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 141 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 142 | github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= 143 | github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= 144 | github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= 145 | github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 146 | github.com/hashicorp/go-tfe v0.3.16/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM= 147 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 148 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 149 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 150 | github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= 151 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 152 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 153 | github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws= 154 | github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= 155 | github.com/hashicorp/hcl2 v0.0.0-20181208003705-670926858200/go.mod h1:ShfpTh661oAaxo7VcNxg0zcZW6jvMa7Moy2oFx7e5dE= 156 | github.com/hashicorp/hcl2 v0.0.0-20190725010614-0c3fe388e450 h1:wpa0vOXOnSEuwZ++eVk1gQNm3Jy2+Envn0cQRgsl8K8= 157 | github.com/hashicorp/hcl2 v0.0.0-20190725010614-0c3fe388e450/go.mod h1:FSQTwDi9qesxGBsII2VqhIzKQ4r0bHvBkOczWfD7llg= 158 | github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI= 159 | github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= 160 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 161 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 162 | github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= 163 | github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= 164 | github.com/hashicorp/terraform v0.12.6 h1:mWItQdLZQ7f3kBYBu2Kgdg+E5iZb1KtCq73V10Hmu48= 165 | github.com/hashicorp/terraform v0.12.6/go.mod h1:udmq5rU8CO9pEIh/A/Xrs3zb3yYU/W9ce1pp8K1ysHA= 166 | github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70 h1:oZm5nE11yhzsTRz/YrUyDMSvixePqjoZihwn8ipuOYI= 167 | github.com/hashicorp/terraform-config-inspect v0.0.0-20190327195015-8022a2663a70/go.mod h1:ItvqtvbC3K23FFET62ZwnkwtpbKZm8t8eMcWjmVVjD8= 168 | github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= 169 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= 170 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 171 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 172 | github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 173 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 174 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 175 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 176 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 177 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 178 | github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= 179 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 180 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 181 | github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 182 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 183 | github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= 184 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 185 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 186 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 187 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 188 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 189 | github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 190 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 191 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 192 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= 193 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 194 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 195 | github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84= 196 | github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= 197 | github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= 198 | github.com/masterzen/winrm v0.0.0-20190223112901-5e5c9a7fe54b/go.mod h1:wr1VqkwW0AB5JS0QLy5GpVMS9E3VtRoSYXUYyVk46KY= 199 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 200 | github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= 201 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 202 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 203 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 204 | github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= 205 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 206 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 207 | github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= 208 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 209 | github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 210 | github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 211 | github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= 212 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 213 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 214 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 215 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 216 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 217 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 218 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 219 | github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= 220 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 221 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 222 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 223 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 224 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 225 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 226 | github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= 227 | github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= 228 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 229 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 230 | github.com/mitchellh/panicwrap v0.0.0-20190213213626-17011010aaa4/go.mod h1:YYMf4xtQnR8LRC0vKi3afvQ5QwRPQ17zjcpkBCufb+I= 231 | github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= 232 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 233 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 234 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 235 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 236 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= 237 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= 238 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= 239 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 240 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 241 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 242 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 243 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 244 | github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 245 | github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= 246 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 247 | github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo= 248 | github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 249 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 250 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 251 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 252 | github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= 253 | github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= 254 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 255 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 256 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 257 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 258 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 259 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 260 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 261 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 262 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 263 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 264 | github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= 265 | github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= 266 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= 267 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 268 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 269 | github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= 270 | github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= 271 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= 272 | github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= 273 | github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= 274 | github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= 275 | github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= 276 | github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 277 | github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= 278 | github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= 279 | github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= 280 | github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= 281 | github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= 282 | github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= 283 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 284 | github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= 285 | github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= 286 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 287 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 288 | github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 289 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 290 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 291 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 292 | github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= 293 | github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 294 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 295 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 296 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 297 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 298 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 299 | github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= 300 | github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= 301 | github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew= 302 | github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 303 | github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 304 | github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= 305 | github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 306 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 307 | github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= 308 | github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 309 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= 310 | github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 311 | github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 312 | github.com/zclconf/go-cty v0.0.0-20181129180422-88fbe721e0f8/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 313 | github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 314 | github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f h1:sq2p8SN6ji66CFEQFIWLlD/gFmGtr5hBrOzv5nLlGfA= 315 | github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 316 | github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8= 317 | github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= 318 | go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= 319 | go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 320 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 321 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 322 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 323 | go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= 324 | golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= 325 | golang.org/x/crypto v0.0.0-20180816225734-aabede6cba87/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 326 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 327 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 328 | golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 329 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 330 | golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 331 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 332 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 333 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 334 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= 335 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 336 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 337 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 338 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 339 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 340 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 341 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 342 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 343 | golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 344 | golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 345 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 346 | golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 347 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 348 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 349 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 350 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 351 | golang.org/x/net v0.0.0-20190502183928-7f726cade0ab h1:9RfW3ktsOZxgo9YNbBAjq1FWzc/igwEcUzZz8IXgSbk= 352 | golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 353 | golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= 354 | golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 355 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 356 | golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 357 | golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 358 | golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9 h1:pfyU+l9dEu0vZzDDMsdAKa1gZbJYEn6urYXj/+Xkz7s= 359 | golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 360 | golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 361 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 362 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 363 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 364 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 365 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 366 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 367 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 368 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 369 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 370 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 371 | golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 372 | golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 373 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 374 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 375 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 376 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 377 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 378 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w= 379 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 380 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE= 381 | golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 382 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 383 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 384 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 385 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 386 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 387 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 388 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 389 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 390 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 391 | golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 392 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 393 | google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 394 | google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 395 | google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI= 396 | google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= 397 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 398 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 399 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 400 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 401 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 402 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 403 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 404 | google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 405 | google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= 406 | google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8= 407 | google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= 408 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 409 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 410 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 411 | google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= 412 | google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 413 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 414 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 415 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 416 | gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 417 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 418 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 419 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 420 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 421 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 422 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 423 | grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= 424 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 425 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 426 | howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 427 | sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= 428 | sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= 429 | -------------------------------------------------------------------------------- /linux/config.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "os" 9 | 10 | "golang.org/x/crypto/ssh" 11 | "golang.org/x/crypto/ssh/agent" 12 | ) 13 | 14 | type Config struct { 15 | Host string 16 | Port int 17 | User string 18 | Password string 19 | PrivateKey string 20 | UseSudo bool 21 | } 22 | 23 | type Client struct { 24 | connection *ssh.Client 25 | useSudo bool 26 | } 27 | 28 | func (c *Config) Client() (*Client, error) { 29 | var auths []ssh.AuthMethod 30 | var sshAgent net.Conn 31 | keys := []ssh.Signer{} 32 | 33 | if c.Password != "" { 34 | auths = append(auths, ssh.Password(c.Password)) 35 | } else { 36 | key, err := ioutil.ReadFile(c.PrivateKey) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if sshAgent, err = net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { 42 | signers, err := agent.NewClient(sshAgent).Signers() 43 | if err == nil { 44 | keys = append(keys, signers...) 45 | } 46 | } 47 | 48 | signer, err := ssh.ParsePrivateKey(key) 49 | if err == nil { 50 | keys = append(keys, signer) 51 | } 52 | 53 | auths = append(auths, ssh.PublicKeys(keys...)) 54 | } 55 | 56 | sshConfig := &ssh.ClientConfig{ 57 | User: c.User, 58 | Auth: auths, 59 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 60 | } 61 | 62 | connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port), sshConfig) 63 | if err != nil { 64 | return nil, fmt.Errorf("Failed to dial: %s", err) 65 | } 66 | 67 | log.Printf("SSH client configured") 68 | 69 | return &Client{ 70 | connection: connection, 71 | useSudo: c.UseSudo, 72 | }, nil 73 | } 74 | -------------------------------------------------------------------------------- /linux/provider.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/hashicorp/terraform/helper/schema" 8 | ) 9 | 10 | func Provider() *schema.Provider { 11 | return &schema.Provider{ 12 | Schema: map[string]*schema.Schema{ 13 | "user": { 14 | Type: schema.TypeString, 15 | Required: true, 16 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_SSH_USER", ""), 17 | Description: "The username to ssh with", 18 | }, 19 | "use_sudo": { 20 | Type: schema.TypeString, 21 | Optional: true, 22 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_USE_SUDO", ""), 23 | Description: "Do certain commands need to be prefixed with sudo?", 24 | }, 25 | "host": { 26 | Type: schema.TypeString, 27 | Required: true, 28 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_SSH_HOST", ""), 29 | Description: "The host to ssh into", 30 | }, 31 | "port": { 32 | Type: schema.TypeInt, 33 | Required: true, 34 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_SSH_PORT", 22), 35 | Description: "The ssh port", 36 | }, 37 | "password": { 38 | Type: schema.TypeString, 39 | Required: true, 40 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_SSH_PASSWORD", ""), 41 | Description: "The password, if used for authentication", 42 | }, 43 | "private_key": { 44 | Type: schema.TypeString, 45 | Required: true, 46 | DefaultFunc: schema.EnvDefaultFunc("TF_LINUX_SSH_PRIVATE_KEY", "$HOME/.ssh/id_rsa"), 47 | Description: "The location of the private key, if used for authentication", 48 | }, 49 | }, 50 | ResourcesMap: map[string]*schema.Resource{ 51 | "linux_group": groupResource(), 52 | "linux_user": userResource(), 53 | "linux_file": fileResource(), 54 | "linux_folder": folderResource(), 55 | }, 56 | ConfigureFunc: providerConfigure, 57 | } 58 | } 59 | 60 | func providerConfigure(d *schema.ResourceData) (interface{}, error) { 61 | user := d.Get("user").(string) 62 | 63 | var useSudoBool bool 64 | useSudo, ok := d.GetOk("use_sudo") 65 | if !ok { 66 | if user == "root" { 67 | useSudoBool = false 68 | } else { 69 | useSudoBool = true 70 | } 71 | } else { 72 | useSudoBool = useSudo.(bool) 73 | } 74 | config := Config{ 75 | Host: d.Get("host").(string), 76 | Port: d.Get("port").(int), 77 | User: user, 78 | Password: d.Get("password").(string), 79 | PrivateKey: os.ExpandEnv(d.Get("private_key").(string)), 80 | UseSudo: useSudoBool, 81 | } 82 | 83 | log.Println("Initializing SSH client") 84 | return config.Client() 85 | } 86 | -------------------------------------------------------------------------------- /linux/provider_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform/helper/schema" 7 | "github.com/hashicorp/terraform/terraform" 8 | ) 9 | 10 | var testAccProviders map[string]terraform.ResourceProvider 11 | var testAccProvider *schema.Provider 12 | 13 | func init() { 14 | testAccProvider = Provider() 15 | testAccProviders = map[string]terraform.ResourceProvider{ 16 | "linux": testAccProvider, 17 | } 18 | } 19 | 20 | func TestProvider(t *testing.T) { 21 | if err := testAccProvider.InternalValidate(); err != nil { 22 | t.Fatalf("err: %s", err) 23 | } 24 | } 25 | 26 | func testAccPreCheck(t *testing.T) { 27 | err := testAccProvider.Configure(terraform.NewResourceConfig(nil)) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /linux/resource_file.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/hashicorp/terraform/helper/schema" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | func fileResource() *schema.Resource { 12 | return &schema.Resource{ 13 | Create: fileResourceCreateWrapper(false), 14 | Read: fileResourceReadWrapper(false), 15 | Update: fileResourceUpdateWrapper(false), 16 | Delete: fileResourceDelete, 17 | 18 | Schema: map[string]*schema.Schema{ 19 | "path": { 20 | Type: schema.TypeString, 21 | Required: true, 22 | ValidateFunc: validatePath, 23 | }, 24 | "owner": { 25 | Type: schema.TypeString, 26 | Optional: true, 27 | Computed: true, 28 | ValidateFunc: validateOwner, 29 | }, 30 | "permissions": { 31 | Type: schema.TypeInt, 32 | Optional: true, 33 | Computed: true, 34 | }, 35 | "content": { 36 | Type: schema.TypeString, 37 | Optional: true, 38 | Default: "", 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | func createFile(client *Client, path string, isFolder bool) error { 45 | var command string 46 | if isFolder { 47 | command = "mkdir -p" 48 | } else { 49 | command = "touch" 50 | } 51 | command = fmt.Sprintf("%s %s", command, path) 52 | _, _, err := runCommand(client, false, command, "") 53 | if err != nil { 54 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 55 | } 56 | return nil 57 | } 58 | 59 | func applyOwner(client *Client, path string, owner string) error { 60 | command := fmt.Sprintf("chown %s %s", owner, path) 61 | _, _, err := runCommand(client, true, command, "") 62 | if err != nil { 63 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 64 | } 65 | return nil 66 | } 67 | 68 | func applyPermissions(client *Client, path string, permissions int) error { 69 | command := fmt.Sprintf("chmod %d %s", permissions, path) 70 | _, _, err := runCommand(client, true, command, "") 71 | if err != nil { 72 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 73 | } 74 | return nil 75 | } 76 | 77 | func writeContent(client *Client, path string, content string) error { 78 | command := fmt.Sprintf("cat > %s", path) 79 | _, _, err := runCommand(client, false, command, content) 80 | if err != nil { 81 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 82 | } 83 | return nil 84 | } 85 | 86 | func rollback(client *Client, err error, errMsg string, path string) error { 87 | err2 := errors.Wrap(err, errMsg) 88 | if err3 := deleteFile(client, path); err3 != nil { 89 | err3 = errors.Wrap(err2, err3.Error()) 90 | return errors.Wrap(err3, "Couldn't delete file.") 91 | } 92 | return err2 93 | } 94 | 95 | func parsePermissionString(perms string) int { 96 | permMap := map[string]int{ 97 | "---": 0, 98 | "--x": 1, 99 | "-w-": 2, 100 | "-wx": 3, 101 | "r--": 4, 102 | "r-x": 5, 103 | "rw-": 6, 104 | "rwx": 7, 105 | } 106 | return (permMap[perms[1:4]] * 100) + 107 | (permMap[perms[4:7]] * 10) + 108 | (permMap[perms[7:10]] * 1) 109 | } 110 | 111 | func fileResourceCreateWrapper(isFolder bool) func(*schema.ResourceData, interface{}) error { 112 | return func(d *schema.ResourceData, m interface{}) error { 113 | client := m.(*Client) 114 | path := d.Get("path").(string) 115 | owner := d.Get("owner").(string) 116 | permissions := d.Get("permissions").(int) 117 | 118 | if err := createFile(client, path, isFolder); err != nil { 119 | return errors.Wrap(err, "Couldn't create file") 120 | } 121 | 122 | if owner != "" { 123 | if err := applyOwner(client, path, owner); err != nil { 124 | return rollback(client, err, "Couldn't apply owner, rolling back file creation", path) 125 | } 126 | } 127 | 128 | if permissions != 0 { 129 | if err := applyPermissions(client, path, permissions); err != nil { 130 | return rollback(client, err, "Couldn't apply permissions, rolling back file creation", path) 131 | } 132 | } 133 | 134 | if !isFolder { 135 | content := d.Get("content").(string) 136 | if content != "" { 137 | if err := writeContent(client, path, content); err != nil { 138 | return rollback(client, err, "Couldn't write content, rolling back file creation", path) 139 | } 140 | } 141 | } 142 | 143 | d.SetId(path) 144 | return fileResourceReadWrapper(isFolder)(d, m) 145 | } 146 | } 147 | 148 | func getDetails(client *Client, path string) (string, int, error) { 149 | command := fmt.Sprintf("ls -ld %s", path) 150 | stdout, _, err := runCommand(client, false, command, "") 151 | if err != nil { 152 | return "", 0, errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 153 | } 154 | if stdout == "" { 155 | return "", 0, fmt.Errorf("File not found with path %v", path) 156 | } 157 | fields := strings.Fields(stdout) 158 | permissions, user, group := parsePermissionString(fields[0]), fields[2], fields[3] 159 | if err != nil { 160 | return "", 0, errors.Wrap(err, fmt.Sprintf("Unable to parse permission string from %s", command)) 161 | } 162 | return fmt.Sprintf("%s:%s", user, group), permissions, nil 163 | } 164 | 165 | func readFile(client *Client, path string) (string, error) { 166 | command := fmt.Sprintf("cat %s", path) 167 | stdout, _, err := runCommand(client, false, command, "") 168 | if err != nil { 169 | return "", errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 170 | } 171 | return stdout, nil 172 | } 173 | 174 | func fileResourceReadWrapper(isFolder bool) func(*schema.ResourceData, interface{}) error { 175 | return func(d *schema.ResourceData, m interface{}) error { 176 | client := m.(*Client) 177 | id := d.Id() 178 | 179 | owner, permissions, err := getDetails(client, id) 180 | if err != nil { 181 | if strings.Contains(err.Error(), "File not found with path") { 182 | d.SetId("") 183 | return nil 184 | } 185 | return errors.Wrap(err, "Unable to ls the file") 186 | } 187 | 188 | if !isFolder { 189 | content, err := readFile(client, id) 190 | if err != nil { 191 | return errors.Wrap(err, "Unable to read the file") 192 | } 193 | d.Set("content", content) 194 | } 195 | 196 | d.Set("owner", owner) 197 | d.Set("permissions", permissions) 198 | return nil 199 | } 200 | } 201 | 202 | func moveFile(client *Client, oldPath string, newPath string) error { 203 | command := fmt.Sprintf("mv %s %s", oldPath, newPath) 204 | _, _, err := runCommand(client, false, command, "") 205 | if err != nil { 206 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 207 | } 208 | return nil 209 | } 210 | 211 | func fileResourceUpdateWrapper(isFolder bool) func(*schema.ResourceData, interface{}) error { 212 | return func(d *schema.ResourceData, m interface{}) error { 213 | client := m.(*Client) 214 | 215 | path := d.Get("path").(string) 216 | owner := d.Get("owner").(string) 217 | permissions := d.Get("permissions").(int) 218 | 219 | oldPath := d.Id() 220 | oldOwner, oldPermissions, err := getDetails(client, oldPath) 221 | if err != nil { 222 | return errors.Wrap(err, "Unable to ls the file") 223 | } 224 | 225 | if !isFolder { 226 | content := d.Get("content").(string) 227 | oldContent, err := readFile(client, oldPath) 228 | if err != nil { 229 | return errors.Wrap(err, "Unable to read the file") 230 | } 231 | if oldContent != content { 232 | if err := writeContent(client, oldPath, content); err != nil { 233 | return errors.Wrap(err, "Couldn't rewrite content") 234 | } 235 | } 236 | } 237 | 238 | if oldPath != path { 239 | if err := moveFile(client, oldPath, path); err != nil { 240 | return errors.Wrap(err, "Couldn't mv file") 241 | } 242 | d.SetId(path) 243 | } 244 | 245 | if oldOwner != owner { 246 | if err := applyOwner(client, path, owner); err != nil { 247 | return errors.Wrap(err, "Couldn't apply owner") 248 | } 249 | } 250 | 251 | if oldPermissions != permissions { 252 | if err := applyPermissions(client, path, permissions); err != nil { 253 | return errors.Wrap(err, "Couldn't apply permissions") 254 | } 255 | } 256 | 257 | return fileResourceReadWrapper(isFolder)(d, m) 258 | } 259 | } 260 | 261 | func deleteFile(client *Client, path string) error { 262 | command := fmt.Sprintf("rm -rf %s", path) 263 | _, _, err := runCommand(client, false, command, "") 264 | if err != nil { 265 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 266 | } 267 | return nil 268 | } 269 | 270 | func fileResourceDelete(d *schema.ResourceData, m interface{}) error { 271 | client := m.(*Client) 272 | id := d.Id() 273 | 274 | return deleteFile(client, id) 275 | } 276 | -------------------------------------------------------------------------------- /linux/resource_file_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform/helper/resource" 7 | ) 8 | 9 | func TestAccFileCreation(t *testing.T) { 10 | resource.Test(t, resource.TestCase{ 11 | PreCheck: func() { testAccPreCheck(t) }, 12 | Providers: testAccProviders, 13 | Steps: []resource.TestStep{ 14 | resource.TestStep{ 15 | Config: fileCreationConfig, 16 | Check: resource.ComposeTestCheckFunc( 17 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 18 | resource.TestCheckResourceAttr("linux_file.testfile", "content", ""), 19 | ), 20 | }, 21 | }, 22 | }) 23 | } 24 | 25 | func TestAccFileWithContentCreation(t *testing.T) { 26 | resource.Test(t, resource.TestCase{ 27 | PreCheck: func() { testAccPreCheck(t) }, 28 | Providers: testAccProviders, 29 | Steps: []resource.TestStep{ 30 | resource.TestStep{ 31 | Config: fileWithContentCreationConfig, 32 | Check: resource.ComposeTestCheckFunc( 33 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 34 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent"), 35 | ), 36 | }, 37 | }, 38 | }) 39 | } 40 | 41 | func TestAccFileWithOwnerCreation(t *testing.T) { 42 | resource.Test(t, resource.TestCase{ 43 | PreCheck: func() { testAccPreCheck(t) }, 44 | Providers: testAccProviders, 45 | Steps: []resource.TestStep{ 46 | resource.TestStep{ 47 | Config: fileWithOwnerCreationConfig, 48 | Check: resource.ComposeTestCheckFunc( 49 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 50 | resource.TestCheckResourceAttr("linux_file.testfile", "content", ""), 51 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser:testuser"), 52 | ), 53 | }, 54 | }, 55 | }) 56 | } 57 | 58 | func TestAccFileWithPermissionsCreation(t *testing.T) { 59 | resource.Test(t, resource.TestCase{ 60 | PreCheck: func() { testAccPreCheck(t) }, 61 | Providers: testAccProviders, 62 | Steps: []resource.TestStep{ 63 | resource.TestStep{ 64 | Config: fileWithPermissionsCreationConfig, 65 | Check: resource.ComposeTestCheckFunc( 66 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 67 | resource.TestCheckResourceAttr("linux_file.testfile", "content", ""), 68 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "777"), 69 | ), 70 | }, 71 | }, 72 | }) 73 | } 74 | 75 | func TestAccFileWithAllAttrsCreation(t *testing.T) { 76 | resource.Test(t, resource.TestCase{ 77 | PreCheck: func() { testAccPreCheck(t) }, 78 | Providers: testAccProviders, 79 | Steps: []resource.TestStep{ 80 | resource.TestStep{ 81 | Config: fileWithAllAttrsCreationConfig, 82 | Check: resource.ComposeTestCheckFunc( 83 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 84 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent"), 85 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser:testuser"), 86 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "777"), 87 | ), 88 | }, 89 | }, 90 | }) 91 | } 92 | 93 | func TestAccFileUpdation(t *testing.T) { 94 | resource.Test(t, resource.TestCase{ 95 | PreCheck: func() { testAccPreCheck(t) }, 96 | Providers: testAccProviders, 97 | Steps: []resource.TestStep{ 98 | resource.TestStep{ 99 | Config: fileWithAllAttrsCreationConfig, 100 | }, 101 | resource.TestStep{ 102 | Config: fileWithOwnerUpdatedConfig, 103 | Check: resource.ComposeTestCheckFunc( 104 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 105 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent"), 106 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser_alt:testuser_alt"), 107 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "777"), 108 | ), 109 | }, 110 | resource.TestStep{ 111 | Config: fileWithOwnerPermissionsUpdatedConfig, 112 | Check: resource.ComposeTestCheckFunc( 113 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 114 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent"), 115 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser_alt:testuser_alt"), 116 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "666"), 117 | ), 118 | }, 119 | resource.TestStep{ 120 | Config: fileWithOwnerPermissionsContentUpdatedConfig, 121 | Check: resource.ComposeTestCheckFunc( 122 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile"), 123 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent2"), 124 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser_alt:testuser_alt"), 125 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "666"), 126 | ), 127 | }, 128 | resource.TestStep{ 129 | Config: fileWithOwnerPermissionsContentPathUpdatedConfig, 130 | Check: resource.ComposeTestCheckFunc( 131 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile2"), 132 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent2"), 133 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser_alt:testuser_alt"), 134 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "666"), 135 | ), 136 | }, 137 | resource.TestStep{ 138 | Config: fileWithOwnerPermissionsContentPathUpdatedAltConfig, 139 | Check: resource.ComposeTestCheckFunc( 140 | resource.TestCheckResourceAttr("linux_file.testfile", "path", "/etc/testfile3"), 141 | resource.TestCheckResourceAttr("linux_file.testfile", "content", "testcontent3"), 142 | resource.TestCheckResourceAttr("linux_file.testfile", "owner", "testuser_alt_alt:testuser_alt_alt"), 143 | resource.TestCheckResourceAttr("linux_file.testfile", "permissions", "766"), 144 | ), 145 | }, 146 | }, 147 | }) 148 | } 149 | 150 | const fileCreationConfig = ` 151 | resource "linux_file" "testfile" { 152 | path = "/etc/testfile" 153 | } 154 | ` 155 | const fileWithContentCreationConfig = ` 156 | resource "linux_file" "testfile" { 157 | path = "/etc/testfile" 158 | content = "testcontent" 159 | } 160 | ` 161 | const fileWithOwnerCreationConfig = ` 162 | resource "linux_user" "testuser" { 163 | name = "testuser" 164 | uid = 1024 165 | } 166 | resource "linux_file" "testfile" { 167 | path = "/etc/testfile" 168 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 169 | } 170 | ` 171 | const fileWithPermissionsCreationConfig = ` 172 | resource "linux_file" "testfile" { 173 | path = "/etc/testfile" 174 | permissions = 777 175 | } 176 | ` 177 | const fileWithAllAttrsCreationConfig = ` 178 | resource "linux_user" "testuser" { 179 | name = "testuser" 180 | uid = 1024 181 | } 182 | resource "linux_file" "testfile" { 183 | path = "/etc/testfile" 184 | content = "testcontent" 185 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 186 | permissions = 777 187 | } 188 | ` 189 | const fileWithOwnerUpdatedConfig = ` 190 | resource "linux_user" "testuser" { 191 | name = "testuser_alt" 192 | uid = 1025 193 | } 194 | resource "linux_file" "testfile" { 195 | path = "/etc/testfile" 196 | content = "testcontent" 197 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 198 | permissions = 777 199 | } 200 | ` 201 | const fileWithOwnerPermissionsUpdatedConfig = ` 202 | resource "linux_user" "testuser" { 203 | name = "testuser_alt" 204 | uid = 1025 205 | } 206 | resource "linux_file" "testfile" { 207 | path = "/etc/testfile" 208 | content = "testcontent" 209 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 210 | permissions = 666 211 | } 212 | ` 213 | const fileWithOwnerPermissionsContentUpdatedConfig = ` 214 | resource "linux_user" "testuser" { 215 | name = "testuser_alt" 216 | uid = 1025 217 | } 218 | resource "linux_file" "testfile" { 219 | path = "/etc/testfile" 220 | content = "testcontent2" 221 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 222 | permissions = 666 223 | } 224 | ` 225 | const fileWithOwnerPermissionsContentPathUpdatedConfig = ` 226 | resource "linux_user" "testuser" { 227 | name = "testuser_alt" 228 | uid = 1025 229 | } 230 | resource "linux_file" "testfile" { 231 | path = "/etc/testfile2" 232 | content = "testcontent2" 233 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 234 | permissions = 666 235 | } 236 | ` 237 | const fileWithOwnerPermissionsContentPathUpdatedAltConfig = ` 238 | resource "linux_user" "testuser" { 239 | name = "testuser_alt_alt" 240 | uid = 1026 241 | } 242 | resource "linux_file" "testfile" { 243 | path = "/etc/testfile3" 244 | content = "testcontent3" 245 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 246 | permissions = 766 247 | } 248 | ` 249 | -------------------------------------------------------------------------------- /linux/resource_file_validators.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func validatePath(vi interface{}, k string) (ws []string, errors []error) { 9 | v, err := vi.(string) 10 | if !err { 11 | errors = append(errors, fmt.Errorf("path should be a string")) 12 | } 13 | if !strings.HasPrefix(v, "/") { 14 | errors = append(errors, fmt.Errorf("path should be an absolute path")) 15 | } 16 | return 17 | } 18 | 19 | func validateOwner(vi interface{}, k string) (ws []string, errors []error) { 20 | v, err := vi.(string) 21 | if !err { 22 | errors = append(errors, fmt.Errorf("owner should be a string")) 23 | } 24 | if i := strings.Index(v, ":"); i <= 0 || i >= len(v)-1 { 25 | errors = append(errors, fmt.Errorf("owner should be of the form user:group")) 26 | } 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /linux/resource_file_validators_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInvalidPath(t *testing.T) { 8 | _, err := validatePath("some/path", "") 9 | if err == nil { 10 | t.Errorf("Non-absolute path should be invalid: %v", err) 11 | } 12 | } 13 | 14 | func TestValidPath(t *testing.T) { 15 | _, err := validatePath("/some/path", "") 16 | if err != nil { 17 | t.Errorf("Absolute path should valid: %v", err) 18 | } 19 | } 20 | 21 | func TestValidOwner(t *testing.T) { 22 | _, err := validateOwner("owner:group", "") 23 | if err != nil { 24 | t.Errorf("owner:group is a valid definition: %v", err) 25 | } 26 | 27 | _, err = validateOwner("123:123", "") 28 | if err != nil { 29 | t.Errorf("123:123 is a valid definition: %v", err) 30 | } 31 | } 32 | 33 | func TestInvalidOwner(t *testing.T) { 34 | _, err := validateOwner("owner", "") 35 | if err == nil { 36 | t.Errorf("Owners should be formatted in user:group form: %v", err) 37 | } 38 | _, err = validateOwner(123, "") 39 | if err == nil { 40 | t.Errorf("Owners should be formatted in user:group form: %v", err) 41 | } 42 | _, err = validateOwner(":group", "") 43 | if err == nil { 44 | t.Errorf("Owners should be formatted in user:group form: %v", err) 45 | } 46 | _, err = validateOwner("user:", "") 47 | if err == nil { 48 | t.Errorf("Owners should be formatted in user:group form: %v", err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /linux/resource_folder.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "github.com/hashicorp/terraform/helper/schema" 5 | ) 6 | 7 | func folderResource() *schema.Resource { 8 | return &schema.Resource{ 9 | Create: fileResourceCreateWrapper(true), 10 | Read: fileResourceReadWrapper(true), 11 | Update: fileResourceUpdateWrapper(true), 12 | Delete: fileResourceDelete, 13 | 14 | Schema: map[string]*schema.Schema{ 15 | "path": { 16 | Type: schema.TypeString, 17 | Required: true, 18 | ValidateFunc: validatePath, 19 | }, 20 | "owner": { 21 | Type: schema.TypeString, 22 | Optional: true, 23 | Computed: true, 24 | ValidateFunc: validateOwner, 25 | }, 26 | "permissions": { 27 | Type: schema.TypeInt, 28 | Optional: true, 29 | Computed: true, 30 | }, 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /linux/resource_folder_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform/helper/resource" 7 | ) 8 | 9 | func TestAccFolderCreation(t *testing.T) { 10 | resource.Test(t, resource.TestCase{ 11 | PreCheck: func() { testAccPreCheck(t) }, 12 | Providers: testAccProviders, 13 | Steps: []resource.TestStep{ 14 | resource.TestStep{ 15 | Config: folderCreationConfig, 16 | Check: resource.ComposeTestCheckFunc( 17 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 18 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 19 | ), 20 | }, 21 | }, 22 | }) 23 | } 24 | 25 | func TestAccFolderWithOwnerCreation(t *testing.T) { 26 | resource.Test(t, resource.TestCase{ 27 | PreCheck: func() { testAccPreCheck(t) }, 28 | Providers: testAccProviders, 29 | Steps: []resource.TestStep{ 30 | resource.TestStep{ 31 | Config: folderWithOwnerCreationConfig, 32 | Check: resource.ComposeTestCheckFunc( 33 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 34 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 35 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser:testuser"), 36 | ), 37 | }, 38 | }, 39 | }) 40 | } 41 | 42 | func TestAccFolderWithPermissionsCreation(t *testing.T) { 43 | resource.Test(t, resource.TestCase{ 44 | PreCheck: func() { testAccPreCheck(t) }, 45 | Providers: testAccProviders, 46 | Steps: []resource.TestStep{ 47 | resource.TestStep{ 48 | Config: folderWithPermissionsCreationConfig, 49 | Check: resource.ComposeTestCheckFunc( 50 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 51 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 52 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "777"), 53 | ), 54 | }, 55 | }, 56 | }) 57 | } 58 | 59 | func TestAccFolderWithAllAttrsCreation(t *testing.T) { 60 | resource.Test(t, resource.TestCase{ 61 | PreCheck: func() { testAccPreCheck(t) }, 62 | Providers: testAccProviders, 63 | Steps: []resource.TestStep{ 64 | resource.TestStep{ 65 | Config: folderWithAllAttrsCreationConfig, 66 | Check: resource.ComposeTestCheckFunc( 67 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 68 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 69 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser:testuser"), 70 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "777"), 71 | ), 72 | }, 73 | }, 74 | }) 75 | } 76 | 77 | func TestAccFolderUpdation(t *testing.T) { 78 | resource.Test(t, resource.TestCase{ 79 | PreCheck: func() { testAccPreCheck(t) }, 80 | Providers: testAccProviders, 81 | Steps: []resource.TestStep{ 82 | resource.TestStep{ 83 | Config: folderWithAllAttrsCreationConfig, 84 | }, 85 | resource.TestStep{ 86 | Config: folderWithOwnerUpdatedConfig, 87 | Check: resource.ComposeTestCheckFunc( 88 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 89 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 90 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser_alt:testuser_alt"), 91 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "777"), 92 | ), 93 | }, 94 | resource.TestStep{ 95 | Config: folderWithOwnerPermissionsUpdatedConfig, 96 | Check: resource.ComposeTestCheckFunc( 97 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder"), 98 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 99 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser_alt:testuser_alt"), 100 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "666"), 101 | ), 102 | }, 103 | resource.TestStep{ 104 | Config: folderWithOwnerPermissionsPathUpdatedConfig, 105 | Check: resource.ComposeTestCheckFunc( 106 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder2"), 107 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 108 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser_alt:testuser_alt"), 109 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "666"), 110 | ), 111 | }, 112 | resource.TestStep{ 113 | Config: folderWithOwnerPermissionsPathUpdatedAltConfig, 114 | Check: resource.ComposeTestCheckFunc( 115 | resource.TestCheckResourceAttr("linux_folder.testfolder", "path", "/etc/testfolder3"), 116 | resource.TestCheckNoResourceAttr("linux_folder.testfolder", "content"), 117 | resource.TestCheckResourceAttr("linux_folder.testfolder", "owner", "testuser_alt_alt:testuser_alt_alt"), 118 | resource.TestCheckResourceAttr("linux_folder.testfolder", "permissions", "766"), 119 | ), 120 | }, 121 | }, 122 | }) 123 | } 124 | 125 | const folderCreationConfig = ` 126 | resource "linux_folder" "testfolder" { 127 | path = "/etc/testfolder" 128 | } 129 | ` 130 | const folderWithOwnerCreationConfig = ` 131 | resource "linux_user" "testuser" { 132 | name = "testuser" 133 | uid = 1024 134 | } 135 | resource "linux_folder" "testfolder" { 136 | path = "/etc/testfolder" 137 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 138 | } 139 | ` 140 | const folderWithPermissionsCreationConfig = ` 141 | resource "linux_folder" "testfolder" { 142 | path = "/etc/testfolder" 143 | permissions = 777 144 | } 145 | ` 146 | const folderWithAllAttrsCreationConfig = ` 147 | resource "linux_user" "testuser" { 148 | name = "testuser" 149 | uid = 1024 150 | } 151 | resource "linux_folder" "testfolder" { 152 | path = "/etc/testfolder" 153 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 154 | permissions = 777 155 | } 156 | ` 157 | const folderWithOwnerUpdatedConfig = ` 158 | resource "linux_user" "testuser" { 159 | name = "testuser_alt" 160 | uid = 1025 161 | } 162 | resource "linux_folder" "testfolder" { 163 | path = "/etc/testfolder" 164 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 165 | permissions = 777 166 | } 167 | ` 168 | const folderWithOwnerPermissionsUpdatedConfig = ` 169 | resource "linux_user" "testuser" { 170 | name = "testuser_alt" 171 | uid = 1025 172 | } 173 | resource "linux_folder" "testfolder" { 174 | path = "/etc/testfolder" 175 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 176 | permissions = 666 177 | } 178 | ` 179 | const folderWithOwnerPermissionsPathUpdatedConfig = ` 180 | resource "linux_user" "testuser" { 181 | name = "testuser_alt" 182 | uid = 1025 183 | } 184 | resource "linux_folder" "testfolder" { 185 | path = "/etc/testfolder2" 186 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 187 | permissions = 666 188 | } 189 | ` 190 | const folderWithOwnerPermissionsPathUpdatedAltConfig = ` 191 | resource "linux_user" "testuser" { 192 | name = "testuser_alt_alt" 193 | uid = 1026 194 | } 195 | resource "linux_folder" "testfolder" { 196 | path = "/etc/testfolder3" 197 | owner = "${linux_user.testuser.name}:${linux_user.testuser.name}" 198 | permissions = 766 199 | } 200 | ` 201 | -------------------------------------------------------------------------------- /linux/resource_group.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/hashicorp/terraform/helper/schema" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func groupResource() *schema.Resource { 14 | return &schema.Resource{ 15 | Create: groupResourceCreate, 16 | Read: groupResourceRead, 17 | Update: groupResourceUpdate, 18 | Delete: groupResourceDelete, 19 | 20 | Schema: map[string]*schema.Schema{ 21 | "name": { 22 | Type: schema.TypeString, 23 | Required: true, 24 | }, 25 | "gid": { 26 | Type: schema.TypeInt, 27 | Optional: true, 28 | Computed: true, 29 | ForceNew: true, 30 | }, 31 | "system": { 32 | Type: schema.TypeBool, 33 | Optional: true, 34 | Default: false, 35 | }, 36 | }, 37 | } 38 | } 39 | 40 | func groupResourceCreate(d *schema.ResourceData, m interface{}) error { 41 | client := m.(*Client) 42 | name := d.Get("name").(string) 43 | gid := d.Get("gid").(int) 44 | system := d.Get("system").(bool) 45 | 46 | err := createGroup(client, name, gid, system) 47 | if err != nil { 48 | return errors.Wrap(err, "Couldn't create group") 49 | } 50 | 51 | gid, err = getGroupId(client, name) 52 | if err != nil { 53 | return errors.Wrap(err, "Couldn't get gid") 54 | } 55 | 56 | d.Set("gid", gid) 57 | 58 | d.SetId(fmt.Sprintf("%v", gid)) 59 | return groupResourceRead(d, m) 60 | } 61 | 62 | func createGroup(client *Client, name string, gid int, system bool) error { 63 | command := "/usr/sbin/groupadd" 64 | 65 | if gid > 0 { 66 | command = fmt.Sprintf("%s --gid %d", command, gid) 67 | } 68 | if system { 69 | command = fmt.Sprintf("%s --system", command) 70 | } 71 | command = fmt.Sprintf("%s %s", command, name) 72 | _, _, err := runCommand(client, true, command, "") 73 | if err != nil { 74 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 75 | } 76 | return nil 77 | } 78 | 79 | func getGroupId(client *Client, name string) (int, error) { 80 | command := fmt.Sprintf("getent group %s", name) 81 | stdout, _, err := runCommand(client, false, command, "") 82 | if err != nil { 83 | return 0, errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 84 | } 85 | if stdout == "" { 86 | return 0, fmt.Errorf("Group not found with name %v", name) 87 | } 88 | gid, err := strconv.Atoi(strings.Split(stdout, ":")[2]) 89 | if err != nil { 90 | return 0, err 91 | } 92 | return gid, nil 93 | } 94 | 95 | func getGroupName(client *Client, gid int) (string, error) { 96 | command := fmt.Sprintf("getent group %d", gid) 97 | stdout, _, err := runCommand(client, false, command, "") 98 | if err != nil { 99 | return "", errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 100 | } 101 | if stdout == "" { 102 | return "", fmt.Errorf("Group not found with id %v", gid) 103 | } 104 | name := strings.Split(stdout, ":")[0] 105 | return name, nil 106 | } 107 | 108 | func groupResourceRead(d *schema.ResourceData, m interface{}) error { 109 | client := m.(*Client) 110 | gid, err := strconv.Atoi(d.Id()) 111 | if err != nil { 112 | return errors.Wrap(err, "ID stored is not int") 113 | } 114 | name, err := getGroupName(client, gid) 115 | if err != nil { 116 | log.Printf("%v", err) 117 | log.Printf("Error getting group name, will recreate it") 118 | d.SetId("") 119 | return nil 120 | } 121 | d.Set("name", name) 122 | return nil 123 | } 124 | 125 | func groupResourceUpdate(d *schema.ResourceData, m interface{}) error { 126 | client := m.(*Client) 127 | gid, err := strconv.Atoi(d.Id()) 128 | if err != nil { 129 | return errors.Wrap(err, "ID stored is not int") 130 | } 131 | name := d.Get("name").(string) 132 | oldname, err := getGroupName(client, gid) 133 | if err != nil { 134 | return errors.Wrap(err, "Failed to get group name") 135 | } 136 | 137 | if oldname != name { 138 | command := fmt.Sprintf("/usr/sbin/groupmod %s -n %s", oldname, name) 139 | _, _, err = runCommand(client, true, command, "") 140 | if err != nil { 141 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 142 | } 143 | } 144 | return groupResourceRead(d, m) 145 | } 146 | 147 | func deleteGroup(client *Client, name string) error { 148 | command := fmt.Sprintf("/usr/sbin/groupdel %s", name) 149 | _, _, err := runCommand(client, true, command, "") 150 | if err != nil { 151 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 152 | } 153 | return nil 154 | } 155 | 156 | func groupResourceDelete(d *schema.ResourceData, m interface{}) error { 157 | client := m.(*Client) 158 | gid, err := strconv.Atoi(d.Id()) 159 | if err != nil { 160 | return errors.Wrap(err, "ID stored is not int") 161 | } 162 | name, err := getGroupName(client, gid) 163 | if err != nil { 164 | return errors.Wrap(err, "Failed to get group name") 165 | } 166 | 167 | return deleteGroup(client, name) 168 | } 169 | -------------------------------------------------------------------------------- /linux/resource_group_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/resource" 8 | "github.com/hashicorp/terraform/terraform" 9 | ) 10 | 11 | func TestAccGroupCreation(t *testing.T) { 12 | resource.Test(t, resource.TestCase{ 13 | PreCheck: func() { testAccPreCheck(t) }, 14 | Providers: testAccProviders, 15 | Steps: []resource.TestStep{ 16 | resource.TestStep{ 17 | Config: groupConfig, 18 | Check: resource.ComposeTestCheckFunc( 19 | resource.TestCheckResourceAttr("linux_group.testgroup", "name", "testgroup"), 20 | testAccCheckGID("testgroup", func(gid int) error { return nil }), 21 | ), 22 | }, 23 | }, 24 | }) 25 | } 26 | 27 | func TestAccSystemGroupCreation(t *testing.T) { 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccPreCheck(t) }, 30 | Providers: testAccProviders, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: systemGroupConfig, 34 | Check: resource.ComposeTestCheckFunc( 35 | resource.TestCheckResourceAttr("linux_group.testgroup", "name", "testgroup"), 36 | resource.TestCheckResourceAttr("linux_group.testgroup", "system", "true"), 37 | testAccCheckGID("testgroup", func(gid int) error { 38 | if gid > 1000 { 39 | return fmt.Errorf("System group gid should be less than 1000") 40 | } 41 | return nil 42 | }), 43 | ), 44 | }, 45 | }, 46 | }) 47 | } 48 | 49 | func TestAccGroupWithGIDCreation(t *testing.T) { 50 | resource.Test(t, resource.TestCase{ 51 | PreCheck: func() { testAccPreCheck(t) }, 52 | Providers: testAccProviders, 53 | Steps: []resource.TestStep{ 54 | resource.TestStep{ 55 | Config: groupWithGIDConfig, 56 | Check: resource.ComposeTestCheckFunc( 57 | resource.TestCheckResourceAttr("linux_group.testgroup", "name", "testgroup"), 58 | resource.TestCheckResourceAttr("linux_group.testgroup", "gid", "1024"), 59 | testAccCheckGID("testgroup", func(gid int) error { 60 | if gid != 1024 { 61 | return fmt.Errorf("GID should be 1024") 62 | } 63 | return nil 64 | }), 65 | ), 66 | }, 67 | }, 68 | }) 69 | } 70 | 71 | func testAccCheckGID(groupname string, check func(int) error) resource.TestCheckFunc { 72 | return func(s *terraform.State) error { 73 | client := testAccProvider.Meta().(*Client) 74 | gid, err := getGroupId(client, groupname) 75 | if err != nil { 76 | return err 77 | } 78 | return check(gid) 79 | } 80 | } 81 | 82 | func TestAccGroupUpdation(t *testing.T) { 83 | resource.Test(t, resource.TestCase{ 84 | PreCheck: func() { testAccPreCheck(t) }, 85 | Providers: testAccProviders, 86 | Steps: []resource.TestStep{ 87 | resource.TestStep{ 88 | Config: groupWithGIDConfig, 89 | }, 90 | resource.TestStep{ 91 | Config: groupWithNameUpdatedConfig, 92 | Check: resource.ComposeTestCheckFunc( 93 | resource.TestCheckResourceAttr("linux_group.testgroup", "name", "testgroup_alt"), 94 | resource.TestCheckResourceAttr("linux_group.testgroup", "gid", "1024"), 95 | testAccCheckGID("testgroup_alt", func(gid int) error { 96 | if gid != 1024 { 97 | return fmt.Errorf("GID should be 1024") 98 | } 99 | return nil 100 | }), 101 | ), 102 | }, 103 | resource.TestStep{ 104 | Config: groupWithNameGIDUpdatedConfig, 105 | Check: resource.ComposeTestCheckFunc( 106 | resource.TestCheckResourceAttr("linux_group.testgroup", "name", "testgroup_alt"), 107 | resource.TestCheckResourceAttr("linux_group.testgroup", "gid", "1025"), 108 | ), 109 | }, 110 | }, 111 | }) 112 | } 113 | 114 | const groupConfig = ` 115 | resource "linux_group" "testgroup" { 116 | name = "testgroup" 117 | } 118 | ` 119 | const systemGroupConfig = ` 120 | resource "linux_group" "testgroup" { 121 | name = "testgroup" 122 | system = true 123 | } 124 | ` 125 | const groupWithGIDConfig = ` 126 | resource "linux_group" "testgroup" { 127 | name = "testgroup" 128 | gid = 1024 129 | } 130 | ` 131 | const groupWithNameUpdatedConfig = ` 132 | resource "linux_group" "testgroup" { 133 | name = "testgroup_alt" 134 | gid = 1024 135 | } 136 | ` 137 | const groupWithNameGIDUpdatedConfig = ` 138 | resource "linux_group" "testgroup" { 139 | name = "testgroup_alt" 140 | gid = 1025 141 | } 142 | ` 143 | -------------------------------------------------------------------------------- /linux/resource_user.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/hashicorp/terraform/helper/schema" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func userResource() *schema.Resource { 13 | return &schema.Resource{ 14 | Create: userResourceCreate, 15 | Read: userResourceRead, 16 | Update: userResourceUpdate, 17 | Delete: userResourceDelete, 18 | 19 | Schema: map[string]*schema.Schema{ 20 | "name": &schema.Schema{ 21 | Type: schema.TypeString, 22 | Required: true, 23 | }, 24 | "uid": { 25 | Type: schema.TypeInt, 26 | Optional: true, 27 | Computed: true, 28 | ForceNew: true, 29 | }, 30 | "gid": { 31 | Type: schema.TypeInt, 32 | Optional: true, 33 | Computed: true, 34 | }, 35 | "system": &schema.Schema{ 36 | Type: schema.TypeBool, 37 | Optional: true, 38 | Default: false, 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | func userResourceCreate(d *schema.ResourceData, m interface{}) error { 45 | client := m.(*Client) 46 | name := d.Get("name").(string) 47 | uid := d.Get("uid").(int) 48 | gid := d.Get("gid").(int) 49 | system := d.Get("system").(bool) 50 | 51 | err := createUser(client, name, uid, gid, system) 52 | if err != nil { 53 | return errors.Wrap(err, "Couldn't create user") 54 | } 55 | 56 | uid, err = getUserId(client, name) 57 | if err != nil { 58 | return errors.Wrap(err, "Couldn't get uid") 59 | } 60 | 61 | d.Set("uid", uid) 62 | 63 | d.SetId(fmt.Sprintf("%v", uid)) 64 | return userResourceRead(d, m) 65 | } 66 | 67 | func createUser(client *Client, name string, uid int, gid int, system bool) error { 68 | command := "/usr/sbin/useradd" 69 | 70 | if uid > 0 { 71 | command = fmt.Sprintf("%s --uid %d", command, uid) 72 | } 73 | if gid > 0 { 74 | command = fmt.Sprintf("%s --gid %d", command, gid) 75 | } 76 | if system { 77 | command = fmt.Sprintf("%s --system", command) 78 | } 79 | command = fmt.Sprintf("%s %s", command, name) 80 | _, _, err := runCommand(client, true, command, "") 81 | if err != nil { 82 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 83 | } 84 | return nil 85 | } 86 | 87 | func getUserId(client *Client, name string) (int, error) { 88 | command := fmt.Sprintf("id --user %s", name) 89 | stdout, _, err := runCommand(client, false, command, "") 90 | if err != nil { 91 | return 0, errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 92 | } 93 | if stdout == "" { 94 | return 0, fmt.Errorf("User not found with name %v", name) 95 | } 96 | uid, err := strconv.Atoi(strings.TrimSpace(stdout)) 97 | if err != nil { 98 | return 0, err 99 | } 100 | return uid, nil 101 | } 102 | 103 | func getUserName(client *Client, uid int) (string, error) { 104 | command := fmt.Sprintf("getent passwd %d", uid) 105 | stdout, _, err := runCommand(client, false, command, "") 106 | if err != nil { 107 | return "", errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 108 | } 109 | if stdout == "" { 110 | return "", fmt.Errorf("User not found with id %v", uid) 111 | } 112 | name := strings.Split(stdout, ":")[0] 113 | return name, nil 114 | } 115 | 116 | func getGroupIdForUser(client *Client, name string) (int, error) { 117 | command := fmt.Sprintf("getent passwd %s", name) 118 | stdout, _, err := runCommand(client, false, command, "") 119 | if err != nil { 120 | return 0, errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 121 | } 122 | if stdout == "" { 123 | return 0, fmt.Errorf("Group not found for user %v", name) 124 | } 125 | uid, err := strconv.Atoi(strings.TrimSpace(strings.Split(stdout, ":")[3])) 126 | if err != nil { 127 | return 0, err 128 | } 129 | return uid, nil 130 | } 131 | 132 | func userResourceRead(d *schema.ResourceData, m interface{}) error { 133 | client := m.(*Client) 134 | uid, err := strconv.Atoi(d.Id()) 135 | if err != nil { 136 | return errors.Wrap(err, "ID stored is not int") 137 | } 138 | name, err := getUserName(client, uid) 139 | if err != nil { 140 | d.SetId("") 141 | return nil 142 | } 143 | d.Set("name", name) 144 | gid, err := getGroupIdForUser(client, name) 145 | if err != nil { 146 | return errors.Wrap(err, "Couldn't find group for user") 147 | } 148 | d.Set("gid", gid) 149 | return nil 150 | } 151 | 152 | func userResourceUpdate(d *schema.ResourceData, m interface{}) error { 153 | client := m.(*Client) 154 | uid, err := strconv.Atoi(d.Id()) 155 | if err != nil { 156 | return errors.Wrap(err, "ID stored is not int") 157 | } 158 | name := d.Get("name").(string) 159 | gid := d.Get("gid").(int) 160 | oldname, err := getUserName(client, uid) 161 | if err != nil { 162 | return errors.Wrap(err, "Failed to get user name") 163 | } 164 | oldgid, err := getGroupIdForUser(client, oldname) 165 | if err != nil { 166 | return errors.Wrap(err, "Failed to get user gid") 167 | } 168 | 169 | if oldname != name { 170 | command := fmt.Sprintf("/usr/sbin/usermod %s -l %s", oldname, name) 171 | _, _, err = runCommand(client, true, command, "") 172 | if err != nil { 173 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 174 | } 175 | } 176 | 177 | if oldgid != gid { 178 | command := fmt.Sprintf("/usr/sbin/usermod %s -g %d", name, gid) 179 | _, _, err = runCommand(client, true, command, "") 180 | if err != nil { 181 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 182 | } 183 | } 184 | return userResourceRead(d, m) 185 | } 186 | 187 | func userResourceDelete(d *schema.ResourceData, m interface{}) error { 188 | client := m.(*Client) 189 | uid, err := strconv.Atoi(d.Id()) 190 | if err != nil { 191 | return errors.Wrap(err, "ID stored is not int") 192 | } 193 | name, err := getUserName(client, uid) 194 | if err != nil { 195 | return errors.Wrap(err, "Failed to get user name") 196 | } 197 | 198 | command := fmt.Sprintf("/usr/sbin/userdel %s", name) 199 | _, _, err = runCommand(client, true, command, "") 200 | if err != nil { 201 | return errors.Wrap(err, fmt.Sprintf("Command failed: %s", command)) 202 | } 203 | return nil 204 | } 205 | -------------------------------------------------------------------------------- /linux/resource_user_test.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/resource" 8 | "github.com/hashicorp/terraform/terraform" 9 | ) 10 | 11 | func TestAccUserCreation(t *testing.T) { 12 | resource.Test(t, resource.TestCase{ 13 | PreCheck: func() { testAccPreCheck(t) }, 14 | Providers: testAccProviders, 15 | Steps: []resource.TestStep{ 16 | resource.TestStep{ 17 | Config: userConfig, 18 | Check: resource.ComposeTestCheckFunc( 19 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser"), 20 | testAccCheckUID("testuser", func(uid int) error { return nil }), 21 | ), 22 | }, 23 | }, 24 | }) 25 | } 26 | 27 | func TestAccSystemUserCreation(t *testing.T) { 28 | resource.Test(t, resource.TestCase{ 29 | PreCheck: func() { testAccPreCheck(t) }, 30 | Providers: testAccProviders, 31 | Steps: []resource.TestStep{ 32 | resource.TestStep{ 33 | Config: systemUserConfig, 34 | Check: resource.ComposeTestCheckFunc( 35 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser"), 36 | resource.TestCheckResourceAttr("linux_user.testuser", "system", "true"), 37 | testAccCheckUID("testuser", func(uid int) error { 38 | if uid > 1000 { 39 | return fmt.Errorf("System user uid should be less than 1000") 40 | } 41 | return nil 42 | }), 43 | ), 44 | }, 45 | }, 46 | }) 47 | } 48 | 49 | func TestAccUserWithUIDCreation(t *testing.T) { 50 | resource.Test(t, resource.TestCase{ 51 | PreCheck: func() { testAccPreCheck(t) }, 52 | Providers: testAccProviders, 53 | Steps: []resource.TestStep{ 54 | resource.TestStep{ 55 | Config: userWithUIDConfig, 56 | Check: resource.ComposeTestCheckFunc( 57 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser"), 58 | resource.TestCheckResourceAttr("linux_user.testuser", "uid", "1024"), 59 | resource.TestCheckResourceAttr("linux_user.testuser", "gid", "1024"), 60 | testAccCheckUID("testuser", func(uid int) error { 61 | if uid != 1024 { 62 | return fmt.Errorf("UID should be 1024") 63 | } 64 | return nil 65 | }), 66 | ), 67 | }, 68 | }, 69 | }) 70 | } 71 | 72 | func TestAccUserWithGroupCreation(t *testing.T) { 73 | resource.Test(t, resource.TestCase{ 74 | PreCheck: func() { testAccPreCheck(t) }, 75 | Providers: testAccProviders, 76 | Steps: []resource.TestStep{ 77 | resource.TestStep{ 78 | Config: userWithGroupConfig, 79 | Check: resource.ComposeTestCheckFunc( 80 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser"), 81 | resource.TestCheckResourceAttr("linux_user.testuser", "uid", "1024"), 82 | resource.TestCheckResourceAttr("linux_user.testuser", "gid", "1048"), 83 | testAccCheckUID("testuser", func(uid int) error { 84 | if uid != 1024 { 85 | return fmt.Errorf("UID should be 1024") 86 | } 87 | return nil 88 | }), 89 | testAccCheckGID("testgroup", func(gid int) error { 90 | if gid != 1048 { 91 | return fmt.Errorf("GID should be 1048") 92 | } 93 | return nil 94 | }), 95 | testAccCheckGIDForUser("testuser", func(gid int) error { 96 | if gid != 1048 { 97 | return fmt.Errorf("GID should be 1048") 98 | } 99 | return nil 100 | }), 101 | ), 102 | }, 103 | }, 104 | }) 105 | } 106 | 107 | func TestAccUserUpdation(t *testing.T) { 108 | resource.Test(t, resource.TestCase{ 109 | PreCheck: func() { testAccPreCheck(t) }, 110 | Providers: testAccProviders, 111 | CheckDestroy: func(*terraform.State) error { 112 | client := testAccProvider.Meta().(*Client) 113 | return deleteGroup(client, "testuser") // changing testuser's name leaves this group dangling 114 | }, 115 | Steps: []resource.TestStep{ 116 | resource.TestStep{ 117 | Config: userWithUIDConfig, 118 | }, 119 | resource.TestStep{ 120 | Config: userWithGroupConfig, 121 | Check: resource.ComposeTestCheckFunc( 122 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser"), 123 | resource.TestCheckResourceAttr("linux_user.testuser", "uid", "1024"), 124 | resource.TestCheckResourceAttr("linux_user.testuser", "gid", "1048"), 125 | testAccCheckUID("testuser", func(uid int) error { 126 | if uid != 1024 { 127 | return fmt.Errorf("UID should be 1024") 128 | } 129 | return nil 130 | }), 131 | testAccCheckGID("testgroup", func(gid int) error { 132 | if gid != 1048 { 133 | return fmt.Errorf("GID should be 1048") 134 | } 135 | return nil 136 | }), 137 | testAccCheckGIDForUser("testuser", func(gid int) error { 138 | if gid != 1048 { 139 | return fmt.Errorf("GID should be 1048") 140 | } 141 | return nil 142 | }), 143 | ), 144 | }, 145 | resource.TestStep{ 146 | Config: userWithGroupNameUpdatedConfig, 147 | Check: resource.ComposeTestCheckFunc( 148 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser_alt"), 149 | resource.TestCheckResourceAttr("linux_user.testuser", "uid", "1024"), 150 | resource.TestCheckResourceAttr("linux_user.testuser", "gid", "1048"), 151 | ), 152 | }, 153 | resource.TestStep{ 154 | Config: userWithGroupNameUIDUpdatedConfig, 155 | Check: resource.ComposeTestCheckFunc( 156 | resource.TestCheckResourceAttr("linux_user.testuser", "name", "testuser_alt"), 157 | resource.TestCheckResourceAttr("linux_user.testuser", "uid", "1025"), 158 | resource.TestCheckResourceAttr("linux_user.testuser", "gid", "1048"), 159 | ), 160 | }, 161 | }, 162 | }) 163 | } 164 | 165 | func testAccCheckUID(username string, check func(int) error) resource.TestCheckFunc { 166 | return func(s *terraform.State) error { 167 | client := testAccProvider.Meta().(*Client) 168 | uid, err := getUserId(client, username) 169 | if err != nil { 170 | return err 171 | } 172 | return check(uid) 173 | } 174 | } 175 | 176 | func testAccCheckGIDForUser(username string, check func(int) error) resource.TestCheckFunc { 177 | return func(s *terraform.State) error { 178 | client := testAccProvider.Meta().(*Client) 179 | gid, err := getGroupIdForUser(client, username) 180 | if err != nil { 181 | return err 182 | } 183 | return check(gid) 184 | } 185 | } 186 | 187 | const userConfig = ` 188 | resource "linux_user" "testuser" { 189 | name = "testuser" 190 | } 191 | ` 192 | const systemUserConfig = ` 193 | resource "linux_user" "testuser" { 194 | name = "testuser" 195 | system = true 196 | } 197 | ` 198 | const userWithUIDConfig = ` 199 | resource "linux_user" "testuser" { 200 | name = "testuser" 201 | uid = 1024 202 | } 203 | ` 204 | const userWithGroupConfig = ` 205 | resource "linux_group" "testgroup" { 206 | name = "testgroup" 207 | gid = 1048 208 | } 209 | resource "linux_user" "testuser" { 210 | name = "testuser" 211 | uid = 1024 212 | gid = linux_group.testgroup.gid 213 | } 214 | ` 215 | const userWithGroupNameUpdatedConfig = ` 216 | resource "linux_group" "testgroup" { 217 | name = "testgroup" 218 | gid = 1048 219 | } 220 | resource "linux_user" "testuser" { 221 | name = "testuser_alt" 222 | uid = 1024 223 | gid = linux_group.testgroup.gid 224 | } 225 | ` 226 | const userWithGroupNameUIDUpdatedConfig = ` 227 | resource "linux_group" "testgroup" { 228 | name = "testgroup" 229 | gid = 1048 230 | } 231 | resource "linux_user" "testuser" { 232 | name = "testuser_alt" 233 | uid = 1025 234 | gid = linux_group.testgroup.gid 235 | } 236 | ` 237 | -------------------------------------------------------------------------------- /linux/utils.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func runCommand(client *Client, sudo bool, command string, stdinContent string) (string, string, error) { 13 | if sudo && client.useSudo { 14 | command = fmt.Sprintf("sudo %s", command) 15 | } 16 | session, err := client.connection.NewSession() 17 | if err != nil { 18 | return "", "", errors.Wrap(err, "Failed to create session") 19 | } 20 | stdin, err := session.StdinPipe() 21 | defer stdin.Close() 22 | if err != nil { 23 | return "", "", errors.Wrap(err, "Unable to setup stdin for session") 24 | } 25 | 26 | stderr, err := session.StderrPipe() 27 | if err != nil { 28 | return "", "", errors.Wrap(err, "Unable to setup stderr for session") 29 | } 30 | stdout, err := session.StdoutPipe() 31 | if err != nil { 32 | return "", "", errors.Wrap(err, "Unable to setup stdout for session") 33 | } 34 | 35 | log.Printf("Running command %s", command) 36 | 37 | var stdoutOutput, stderrOutput []byte 38 | err = session.Start(command) 39 | if err != nil { 40 | return "", "", errors.Wrap(err, fmt.Sprintf("Unable to start command %s", command)) 41 | } 42 | if stdinContent != "" { 43 | stdin.Write([]byte(stdinContent)) 44 | stdin.Close() 45 | } 46 | err = session.Wait() 47 | 48 | if err != nil { 49 | stderrOutput, err2 := ioutil.ReadAll(stderr) 50 | if err2 != nil { 51 | log.Printf("Unable to read stderr for command: %v", err) 52 | } 53 | log.Printf("Stderr output: %s", strings.TrimSpace(string(stderrOutput))) 54 | 55 | return string(stdoutOutput), string(stderrOutput), errors.Wrap(err, fmt.Sprintf("Error running command %s", command)) 56 | } 57 | stdoutOutput, err = ioutil.ReadAll(stdout) 58 | if err != nil { 59 | return string(stdoutOutput), string(stderrOutput), errors.Wrap(err, fmt.Sprintf("Unable to read stdout for command %s", command)) 60 | } 61 | 62 | return string(stdoutOutput), string(stderrOutput), nil 63 | } 64 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mavidser/terraform-provider-linux/linux" 5 | 6 | "github.com/hashicorp/terraform/plugin" 7 | "github.com/hashicorp/terraform/terraform" 8 | ) 9 | 10 | func main() { 11 | plugin.Serve(&plugin.ServeOpts{ 12 | ProviderFunc: func() terraform.ResourceProvider { 13 | return linux.Provider() 14 | }, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /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\/mavidser\/terraform-provider-linux\/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | go.googlesource.com,FALSE,/,TRUE,2147483647,o,git-admin.hashicorptest.com=1/F-KiU2h0C3CsGR-W37nUzB2LOSfI24YXa71rjfd4qUI 10 | __END__ 11 | -------------------------------------------------------------------------------- /scripts/tests_cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | docker rm -f tf_linux_test_sshd -------------------------------------------------------------------------------- /scripts/tests_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | docker run -d -p 5001:22 --name tf_linux_test_sshd rastasheep/ubuntu-sshd:18.04 5 | export TF_LINUX_SSH_USER=root 6 | export TF_LINUX_SSH_HOST=127.0.0.1 7 | export TF_LINUX_SSH_PORT=5001 8 | export TF_LINUX_SSH_PASSWORD=root -------------------------------------------------------------------------------- /scripts/testsacc_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | log() { 5 | echo "####################" 6 | echo "## -> $1 " 7 | echo "####################" 8 | } 9 | 10 | setup() { 11 | source "$(pwd)/scripts/tests_setup.sh" 12 | } 13 | 14 | run() { 15 | go clean -testcache 16 | TF_ACC=1 go test ./linux -v -timeout 120m 17 | 18 | # keep the return value for the scripts to fail and clean properly 19 | return $? 20 | } 21 | 22 | cleanup() { 23 | source "$(pwd)/scripts/tests_cleanup.sh" 24 | } 25 | 26 | ## main 27 | log "setup" && setup 28 | log "run" && run || (log "cleanup" && cleanup && exit 1) 29 | log "cleanup" && cleanup 30 | --------------------------------------------------------------------------------