├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md └── tfh ├── bin └── tfh ├── lib └── tfh │ ├── cmd │ ├── tfh_curl_config.sh │ ├── tfh_pullvars.sh │ ├── tfh_pushconfig.sh │ ├── tfh_pushvars.sh │ ├── tfh_run.sh │ ├── tfh_run_cancel.sh │ ├── tfh_run_discard.sh │ ├── tfh_run_list.sh │ ├── tfh_run_show.sh │ ├── tfh_ssh.sh │ ├── tfh_ssh_assign.sh │ ├── tfh_ssh_delete.sh │ ├── tfh_ssh_list.sh │ ├── tfh_ssh_new.sh │ ├── tfh_ssh_show.sh │ ├── tfh_ssh_unassign.sh │ ├── tfh_ssh_update.sh │ ├── tfh_workspace.sh │ ├── tfh_workspace_delete.sh │ ├── tfh_workspace_list.sh │ ├── tfh_workspace_new.sh │ ├── tfh_workspace_select.sh │ ├── tfh_workspace_show.sh │ └── tfh_workspace_update.sh │ └── junonia ├── libexec └── tfh │ └── tests │ ├── runner │ ├── tests │ ├── 0000_test_help │ ├── 0010_test_workspace │ ├── 0020.tfvars │ ├── 0020_config │ │ └── vars.tf │ └── 0020_test_vars │ └── utils └── usr └── share └── doc └── tfh ├── tfh.md ├── tfh_curl_config.md ├── tfh_pullvars.md ├── tfh_pushconfig.md ├── tfh_pushvars.md ├── tfh_run.md ├── tfh_run_cancel.md ├── tfh_run_discard.md ├── tfh_run_list.md ├── tfh_run_show.md ├── tfh_ssh.md ├── tfh_ssh_assign.md ├── tfh_ssh_delete.md ├── tfh_ssh_list.md ├── tfh_ssh_new.md ├── tfh_ssh_show.md ├── tfh_ssh_unassign.md ├── tfh_ssh_update.md ├── tfh_workspace.md ├── tfh_workspace_delete.md ├── tfh_workspace_list.md ├── tfh_workspace_new.md ├── tfh_workspace_select.md ├── tfh_workspace_show.md └── tfh_workspace_update.md /.gitignore: -------------------------------------------------------------------------------- 1 | # https://github.com/github/gitignore 2 | 3 | # 4 | # vim 5 | # 6 | 7 | # Swap 8 | [._]*.s[a-v][a-z] 9 | [._]*.sw[a-p] 10 | [._]s[a-v][a-z] 11 | [._]sw[a-p] 12 | 13 | # Session 14 | Session.vim 15 | 16 | # Temporary 17 | .netrwhist 18 | *~ 19 | # Auto-generated tag files 20 | tags 21 | # Persistent undo 22 | [._]*.un~ 23 | 24 | # 25 | # macOS 26 | # 27 | 28 | # General 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Icon must end with two \r 34 | Icon 35 | 36 | 37 | # Thumbnails 38 | ._* 39 | 40 | # Files that might appear in the root of a volume 41 | .DocumentRevisions-V100 42 | .fseventsd 43 | .Spotlight-V100 44 | .TemporaryItems 45 | .Trashes 46 | .VolumeIcon.icns 47 | .com.apple.timemachine.donotpresent 48 | 49 | # Directories potentially created on remote AFP share 50 | .AppleDB 51 | .AppleDesktop 52 | Network Trash Folder 53 | Temporary Items 54 | .apdisk 55 | 56 | # 57 | # Linux 58 | # 59 | 60 | # temporary files which can be created if a process still has a handle open of a deleted file 61 | .fuse_hidden* 62 | 63 | # KDE directory preferences 64 | .directory 65 | 66 | # Linux trash folder which might appear on any partition or disk 67 | .Trash-* 68 | 69 | # .nfs files are created when an open file is removed but is still being accessed 70 | .nfs* 71 | 72 | # 73 | # Windows 74 | # 75 | 76 | # Windows thumbnail cache files 77 | Thumbs.db 78 | ehthumbs.db 79 | ehthumbs_vista.db 80 | 81 | # Dump file 82 | *.stackdump 83 | 84 | # Folder config file 85 | [Dd]esktop.ini 86 | 87 | # Recycle Bin used on file shares 88 | $RECYCLE.BIN/ 89 | 90 | # Windows Installer files 91 | *.cab 92 | *.msi 93 | *.msix 94 | *.msm 95 | *.msp 96 | 97 | # Windows shortcuts 98 | *.lnk 99 | 100 | # Terraform 101 | **/.terraform/* 102 | *.tfstate 103 | *.tfstate.* 104 | crash.log 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.9 (November 24, 2020) 2 | 3 | BUG FIXES: 4 | 5 | * VCS JSON payload missing a colon 6 | * Add check for explicit JSON `null` in variable value response 7 | * Correctly supply `is-destroy` in `pushconfig`(TylerPinson) 8 | 9 | ENHANCEMENTS: 10 | 11 | * If a workspace is set to auto-apply, then a pushconfig will follow the apply when polling or streaming 12 | 13 | 14 | ## 0.2.8 (January 7, 2020) 15 | 16 | BUG FIXES: 17 | 18 | * Set `vcs_submodules` to `false` by default (flaviotorres) 19 | * Numerous `pushvars` fixes (sumitmaggo) 20 | * Fix config pushing logic in `pushconfig` so `-destroy` and `-current-config` work 21 | * Bump junonia to 1.0.8, fixing a regex problem for a particular version of `gawk` 22 | 23 | ENHANCEMENTS: 24 | 25 | * Support parsing integer values for `pushvars` and other `pushvars` fixes (sumitmaggo) 26 | * Support trimming whitespace around variables and values in `pushvars` 27 | * If `~/.terraformrc` only has one entry, `curl-config` will use that one even if `-hostname` is not specified in some manner, making it more user friendly. 28 | 29 | ## 0.2.7 (June 11, 2019) 30 | 31 | BUG FIXES: 32 | 33 | * Small doc fix to `tfh workspace list` and hostname setting env vars 34 | * Bump junonia to 1.0.7 35 | - Fixes config file values being appended to defaults instead of overwriting them 36 | 37 | ## 0.2.6 (June 10, 2019) 38 | 39 | BUG FIXES: 40 | 41 | * Replace last occurrence of `random_enough` with `junonia_randomish_int` 42 | * Bump junonia to 1.0.6 43 | - Fixes AWK `substr` usage to be compatible with `mawk` 44 | 45 | ## 0.2.5 (June 9, 2019) 46 | 47 | BUG FIXES: 48 | 49 | * Fix `curl-config` parameter check [#2](https://github.com/hashicorp-community/tf-helper/pull/2) 50 | * Bump junonia to 1.0.5 51 | - Fixes `dash` on Ubuntu issue whereby a bug in that shell believes junonia contains an arithmatic expansion where there is none [#3](https://github.com/hashicorp-community/tf-helper/issues/3) 52 | - Fixes `tfh` reported as an unknown parameter issue [#4](https://github.com/hashicorp-community/tf-helper/issues/4) 53 | - Fixes subcommand alias issues 54 | - Fixes spec cache repeatedly adding metacommands 55 | - Correctly skips attempting to set readonly environment variables when resolving arguments 56 | 57 | ## 0.2.4 (May 17, 2019) 58 | 59 | ENHANCEMENTS: 60 | 61 | * Always try to capture and show JSON-API errors for improved error reporting. 62 | * Run commands - cancel, discard, list, show 63 | * Cache is done by version, so each upgrade will not collide. 64 | * New `tfh` options: 65 | - `-curlrc` to specify the curl config file to use 66 | - `-vv` for more verbosity 67 | - `-vvv` for even more verbosity, including outputting the `curl` commands used for API calls. 68 | * Implemented `pushconfig -stream` for streaming back plan logs. It works but could use some improvement. 69 | * Bump junonia to 1.0.3. 70 | 71 | BUG FIXES: 72 | 73 | * Token sources were not being properly handled as intended. Fixed that and documented the precendence. 74 | * Metacommands (`config`, `cache`...) were not being included in the program argument spec some times. 75 | * Subcommand aliases are always properly respected. 76 | * The mentioned fixes had positive effects on help generation, spec generation, and argument parsing. 77 | 78 | ## 0.2.3 (May 8, 2019) 79 | 80 | BUG FIXES: 81 | 82 | * Fix for One True AWK limitation by bumping junonia to 1.0.2, where it could not accept a multi-line string as a variable via `-v`. Instead, the value is passed as an argument, then read and deleted from the ARGV as is done in several other places. 83 | 84 | ## 0.2.2 (May 8, 2019 85 | 86 | NOTES: 87 | 88 | * Re-enabled caching, forgotten as part of the release process. 89 | 90 | ## 0.2.1 (May 8, 2019) 91 | 92 | BUG FIXES 93 | 94 | * Update junonia to 1.0.1 to fix help output. 95 | 96 | ## 0.2.0 (May 7, 2019) 97 | 98 | NOTES 99 | 100 | * Port of `tfe-cli` to [junonia](https://github.com/fprimex/junonia). Starting with a clean slate. 101 | * Please read the new documentation! 102 | * The `-name` parameter is now just the workspace name. Use `-org` with `-name`. 103 | * All positional parameters must come before options. This moves the configuration directories to after the `pushvars` and `pushconfig` commands. 104 | * It's now possible to use a curlrc configuration file to authenticate. 105 | * See the new `curl-config`, `config`, and `cache` commands as they are new. 106 | 107 | -------------------------------------------------------------------------------- /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 | # The Terraform Helper 2 | 3 | The `tfh` program in this repository provides commands for performing 4 | operations relating to [HashiCorp Terraform](https://www.terraform.io/). The 5 | operations include interacting with Terraform Enterprise (TFE) and also 6 | reporting on and manipulating other Terraform artifacts. 7 | 8 | Terraform Enterprise API: https://www.terraform.io/docs/enterprise/api/index.html 9 | 10 | In particular, the `tfh pushconfig` and `tfh pushvars` commands replace and 11 | extend the functionality of the deprecated `terraform push` command. You can 12 | use it to upload configurations, start runs, and change and retrieve variables 13 | using the new Terraform Enterprise API. These scripts are not necessary to use 14 | Terraform Enterprise's core workflows, but they offer a convenient interface 15 | for manual actions on the command line. 16 | 17 | The `tfh` commands are written in POSIX Bourne shell and so should be used on 18 | UNIX, Linux, and MacOS systems. Use on Windows requires a POSIX compatible 19 | environment such as the [Windows Subsystem for Linux 20 | (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) or 21 | [Cygwin](https://www.cygwin.com/). 22 | 23 | Extensive debugging output is provided by setting the `TF_LOG` environment 24 | variable to any non-empty value, such as `TF_LOG=1`. 25 | 26 | ## Installation 27 | 28 | Ensure that you have the `curl`, `jq`, and `readlink` commands installed. Clone 29 | this repository to a convenient place on your local machine and do one of the 30 | following: 31 | 32 | - Add the `tfh/bin` directory to your `PATH` environment variable. For example: 33 | 34 | ``` 35 | git clone git@github.com:hashicorp-community/tf-helper.git 36 | cd tfh-helper/tfh/bin 37 | echo "export PATH=$PWD:\$PATH" >> ~/.bash_profile 38 | ``` 39 | - Symlink the `tfh/bin/tfh` executable into a directory that's already included 40 | in your `PATH`. For example: 41 | 42 | ``` 43 | git clone git@github.com:hashicorp-community/tf-helper.git 44 | cd tf-helper/tfh/bin 45 | ln -s $PWD/tfh /usr/local/bin/tfh 46 | ``` 47 | - Run the `tfh` command with its full path. (For example: 48 | `/usr/local/src/tf-helper/tfh/bin/tfh pushconfig`) 49 | 50 | The default branch of this repository is `release`. Development occurs in the 51 | non-default branch `master`. When a release is made, `release` will be updated 52 | to the stable release, and a pull on the default branch will upgrade from 53 | stable release to stable release. 54 | 55 | ## Getting started 56 | 57 | The `tfh` program has some subcommands that can be used without any configuration, however most of the subcommands of interest require configuring access to Terraform Enterprise. There are four ways to configure `tfh` for TFE use. 58 | 59 | * Use `-token TOKEN` with each `tfh` run. 60 | * Export the environment varible `TFH_token`: `export TFH_token=TOKEN` 61 | * Put the line `TFH_token=TOKEN` in the configuration file located at `~/.tfh/tfhrc` 62 | * Create a `curlrc` in the configuration directory located at `~/tfh/curlrc` 63 | 64 | Precedence is: 65 | * `-curlrc` argument on command line 66 | * `-token` argument on command line 67 | * Any valid `curlrc` source - environment variable, config file, default 68 | * Any valid `token` source - environment variable, config file 69 | 70 | The recommended method for setting the TFE token is to use a `curlrc` file. This way the token value will not be exposed in the process list. The `curlrc` file can be generated from the `~/.terraformrc` file using the command `tfh curl-config -tfrc`, or from a token value with `tfh curl-config -curltoken TOKEN`. If configuring access to a private TFE instance, then when generating the `curlrc` file from a `.terraformrc` file the `-hostname` common option needs to be set to the hostname of the private isntance. A file can also be manually created with the contents: 71 | 72 | ``` 73 | --header "Authorization: Bearer TOKEN_GOES_HERE" 74 | ``` 75 | 76 | If, instead, the configuration file option is desired, then either the line can be inserted manually into `~/.tfh/tfhrc`, or the `tfh config` command can be used to manipulate the configuration file. To set the token with `tfh config`, run `tfh config -token TOKEN`. 77 | 78 | Similar options are available for setting the TFE organization and workspace. For the organization: 79 | 80 | * Configuration file entry of `TFH_org=org_name` in `~/.tfh/tfhrc` 81 | * Environment variable `export TFH_org=org_name` 82 | * Command line option `-org org_name` 83 | 84 | For the TFE workspace: 85 | 86 | * Configuration file entry of `TFH_name=ws_name` in `~/.tfh/tfhrc` 87 | * Environment variable `export TFH_name=ws_name` 88 | * Command line option `-name ws_name` 89 | 90 | **NOTE** that `-name` has a different meaning than in previous implementations. This naming is in line with terminology that is in use going forward. This terminology is used with Terraform 0.12 and the `remote` backend: `organization` (shortened to `org`), `name` for _just the workspace portion_, and there is also an optional `-prefix` option to mirror the `remote` backend's prefix setting. 91 | 92 | Finally, if `tfh` is being configured to connect to a private TFE instance, then the hostname for the instance also needs to be configured: 93 | 94 | * Configuration file entry of `TFH_hostname=host` in `~/.tfh/tfhrc` 95 | * Environment variable `export TFH_hostname=host` 96 | * Command line option `-hostname host` 97 | 98 | Note that the hostname should _not_ start with `https://`. That will be prepended and an `address` shell variable will be created in the code. 99 | 100 | ## Subcommands 101 | 102 | Each subcommand is documented individually. See the [documentation 103 | directory](https://github.com/hashicorp-community/tf-helper/tree/master/tfh/usr/share/doc/tfh) 104 | for each subcommand's full description. Some subcommands are documented with 105 | extended descriptions and examples that are not included in the help output at 106 | the command line. 107 | 108 | Use the `tfh help` subcommand to list syntax and options for each subcommand at 109 | the command line. 110 | 111 | A sample of commands that are available: 112 | 113 | - `tfh pushconfig` — Upload a Terraform configuration to a TFE workspace and 114 | begin a run. 115 | - `tfh pushvars` — Set variables in a TFE workspace. 116 | - `tfh pullvars` — Get variables from a TFE workspace and write them to stdout. 117 | - `tfh workspace` — Create, list, show, delete, and update workspaces 118 | - `tfh ssh` — Manage ssh keys for a TFE organization. 119 | 120 | As of version 0.2.5, the list of available commands are: 121 | 122 | ``` 123 | tfh 124 | help 125 | config 126 | cache 127 | version 128 | curl-config 129 | pullvars 130 | pushconfig 131 | pushvars 132 | run 133 | cancel 134 | discard 135 | list 136 | show, get 137 | ssh 138 | assign 139 | delete 140 | list 141 | new, create 142 | show, get 143 | unassign 144 | update 145 | workspace, ws 146 | delete 147 | list 148 | new, create 149 | select 150 | show, get 151 | update 152 | ``` 153 | 154 | ## Caching 155 | 156 | Output such as `tfh help` and other internally used artifacts are cached. Sometimes it may be necessary to view or clear the cache. To do this, use the `tfh cache` and `tfh cache -clear` commands. 157 | 158 | ## Plugins 159 | 160 | Plugin support is still under development, however at least simple plugins are functional and can allow for extending `tfh` locally, without the need to have subcommands merged into the main repository. To develop a plugin named `my_plugin`: 161 | 162 | * Create the directory `~/.tfh/plugins/my_plugin` 163 | * Create the Markdown file for the command `~/.tfh/plugins/tfh_my_plugin.md` 164 | * Create the shell file for the command implementation `~/.tfh/plugins/tfh_my_plugin.sh` 165 | 166 | In order for the files to be found, they must currently be named with the same name as the program: `tfh_`. The plugin directory name does not need to start with the program name. 167 | 168 | In the Markdown file, put a `tfh subcommand` style documentation: 169 | 170 | ``` 171 | ## `tfh my_plugin` 172 | 173 | Short description 174 | 175 | ### Description 176 | 177 | Long description 178 | 179 | ### Positional parameters 180 | 181 | * `FOO` 182 | 183 | Documentation for positonal parameter `FOO`. 184 | 185 | * `-bar BAR` 186 | 187 | Documentation for option `-bar`. 188 | 189 | * `-baz=1` 190 | 191 | Documentation for boolean flag `-baz`, with default `1`, or true. 192 | 193 | ``` 194 | 195 | The shell implementation file should contain a function with the subcommand name, `tfh_my_plugin`. The command line options are passed to the function in the order they were defined in the Markdown file. 196 | 197 | ``` 198 | tfh_my_plugin () { 199 | foo="$1" 200 | bar="$2" 201 | baz="$3" 202 | 203 | # common options have been extracted by the tfh argument filter function 204 | echo "org: $org" 205 | echo "name: $name" 206 | echo "token: $token" 207 | echo "hostname: $hostname" 208 | echo "address: $address" 209 | echo 210 | echo "foo: $foo" 211 | echo "bar: $bar" 212 | echo "baz: $baz" 213 | } 214 | ``` 215 | 216 | The `tfh` command uses [junonia](https://github.com/fprimex/junonia), and subcommands can use any of the configuration items described there - positional parameters, options, booleans, and multi-options. 217 | 218 | The following now works: 219 | 220 | ``` 221 | $ tfh help 222 | NAME 223 | tfh -- Perform operations relating to HashiCorp Terraform 224 | <--- snip ---> 225 | SUBCOMMANDS 226 | <--- snip ---> 227 | my_plugin Short description 228 | 229 | $ tfh my_plugin help 230 | NAME 231 | tfh my_plugin -- Short description 232 | 233 | DESCRIPTION 234 | Long description 235 | 236 | PARAMETERS 237 | FOO Documentation for positonal parameter `FOO`. 238 | 239 | OPTIONS 240 | -bar BAR Documentation for option `-bar`. 241 | 242 | -baz=1 Documentation for boolean flag `-baz`, with default `1`, or 243 | true. 244 | 245 | $ tfh my_plugin 246 | org: tfh_demo 247 | name: tfh_demo 248 | token: 249 | hostname: app.terraform.io 250 | address: https://app.terraform.io 251 | 252 | foo: 253 | bar: 254 | baz: 1 255 | ``` 256 | -------------------------------------------------------------------------------- /tfh/bin/tfh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## tfh: Use the Terraform Enterprise API to perform operations in TFE 6 | ## 7 | ## ------------------------------------------------------------------- 8 | ## 9 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 10 | ## 11 | ## This file is provided to you under the Mozilla Public License 12 | ## Version 2.0 (the "License"); you may not use this file 13 | ## except in compliance with the License. You may obtain 14 | ## a copy of the License at 15 | ## 16 | ## https://www.mozilla.org/en-US/MPL/2.0/ 17 | ## 18 | ## Unless required by applicable law or agreed to in writing, 19 | ## software distributed under the License is distributed on an 20 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | ## KIND, either express or implied. See the License for the 22 | ## specific language governing permissions and limitations 23 | ## under the License. 24 | ## 25 | ## ------------------------------------------------------------------- 26 | 27 | ## 28 | ## Utility function declarations 29 | ## 30 | 31 | # Return (print) "$1\n$2\n." if $1 is not empty, "$2\n." if $1 is empty. 32 | # These "lists" always have an end-of-list marker, ".", if they are not empty. 33 | # This is to help track empty list values that would otherwise be \n 34 | # special characters that will get stripped by command substitution. 35 | 36 | # Remove specified files 37 | cleanup () { 38 | if [ -n "$1" ]; then 39 | echo "$1" | while read f; do 40 | echodebug "cleaning up $f" 41 | rm "$f" 2>&3 42 | if [ 0 -ne $? ]; then 43 | echoerr "Error cleaning up $f" 44 | echoerr "$f" 45 | fi 46 | done 47 | else 48 | echodebug "No file to clean up" 49 | fi 50 | } 51 | 52 | 53 | # Replace quotes and newlines with escape characters to prepare the 54 | # value for insertion into JSON. 55 | escape_value () { 56 | printf '%s' "$1" | awk ' 57 | { 58 | gsub(/"/,"\\\"") 59 | gsub(/\\n/,"\\\\n") 60 | } 61 | NR == 1 { 62 | value_line = $0 63 | } 64 | NR != 1 { 65 | value_line = value_line "\\n" $0 66 | } 67 | END { 68 | printf "%s", value_line 69 | }' 70 | } 71 | 72 | tfh_api_call () { 73 | # $1: optional integer parameter for number of next-pages to retrieve 74 | # $1 or $2 to $#: arguments to provide to curl 75 | 76 | echodebug "tfh_api_call args: $@" 77 | 78 | if ! [ "$1" -eq "$1" ] >/dev/null 2>&1; then 79 | npages="10000" 80 | else 81 | npages="$1" 82 | shift 83 | fi 84 | 85 | echodebug "npages: $npages" 86 | echodebug "curl args: $@" 87 | 88 | if [ "$npages" -lt 1 ]; then 89 | return 0 90 | fi 91 | 92 | case $curl_token_src in 93 | curlrc) 94 | echovvv "curl --header \"Content-Type: application/vnd.api+json\"" >&2 95 | echovvv " --config \"$curlrc\"" >&2 96 | echovvv " $*" >&2 97 | 98 | resp="$(curl $curl_silent -w '\nhttp_code: %{http_code}\n' \ 99 | --header "Content-Type: application/vnd.api+json" \ 100 | --config "$curlrc" \ 101 | $@)" 102 | ;; 103 | token) 104 | echovvv "curl --header \"Content-Type: application/vnd.api+json\"" >&2 105 | echovvv " --header \"Authorization: Bearer \$TFH_token\"" >&2 106 | echovvv " $*" >&2 107 | 108 | resp="$(curl $curl_silent -w '\nhttp_code: %{http_code}\n' \ 109 | --header "Content-Type: application/vnd.api+json" \ 110 | --header "Authorization: Bearer $token" \ 111 | $@)" 112 | ;; 113 | esac 114 | 115 | resp_body="$(printf '%s' "$resp" | awk '!/^http_code/; /^http_code/{next}')" 116 | resp_code="$(printf '%s' "$resp" | awk '!/^http_code/{next} /^http_code/{print $2}')" 117 | 118 | echodebug "API request http code: $resp_code. Response:" 119 | echodebug_raw "$resp_body" 120 | 121 | case "$resp_code" in 122 | 2*) 123 | printf "%s" "$resp_body" 124 | 125 | next_page="$(printf "%s" "$resp_body" | \ 126 | jq -r '.meta.pagination."next-page"' 2>&3)" 127 | 128 | if [ -n "$next_page" ] && [ "$next_page" != null ] && 129 | ! [ "$npages" -le 1 ]; then 130 | echodebug "next page: $next_page" 131 | echodebug "npages: $npages" 132 | next_link="$(printf "%s" "$resp_body" | jq -r '.links.next')" 133 | echodebug "next link: $next_link" 134 | tfh_api_call $((--npages)) "$next_link" 135 | fi 136 | ;; 137 | 4*|5*) 138 | echoerr "API request failed." 139 | echoerr_raw "HTTP status code: $resp_code" 140 | if jsonapi_err="$(echo "$resp_body" | jq -r ' 141 | def leaf_print(o): 142 | o.indent as $i | 143 | $i + " " as $ni | 144 | o.errors as $e | 145 | $e | keys[] as $k | 146 | (select(($e[$k] | type) != "array" and ($e[$k] | type) != "object") | 147 | "\($k): \($e[$k])"), 148 | (select(($e[$k] | type) == "object") | 149 | "\($k):", 150 | "\(leaf_print({"errors": $e[$k], "indent": $ni}))"), 151 | (select(($e[$k] | type) == "array") | 152 | "\($k):", 153 | "\(leaf_print({"errors": $e[$k], "indent": $ni}))"); 154 | 155 | leaf_print({"errors": .errors[], "indent": " "})')"; then 156 | echoerr_raw "JSON-API details:" 157 | echoerr_raw "$jsonapi_err" 158 | else 159 | echoerr "Response:" 160 | echoerr_raw "$resp_body" 161 | fi 162 | 163 | return 1 164 | ;; 165 | *) 166 | echoerr "Unable to complete API request." 167 | echoerr "HTTP status code: $resp_code." 168 | echoerr "Response:" 169 | echoerr "$resp_body" 170 | return 1 171 | ;; 172 | esac 173 | } 174 | 175 | # Check the version of terraform in use. Commands can use this 176 | # to verify that their use of Terraform will go as expected. 177 | tf_version_required () { 178 | if [ $# -ne 3 ]; then 179 | echoerr "Unable to check the version of Terraform. This is a bug." 180 | exit 1 181 | fi 182 | 183 | # Verify the terraform command is present 184 | if [ -z "$(command -v terraform)" ]; then 185 | echoerr "The terraform command must be installed" 186 | exit 1 187 | fi 188 | 189 | # Test the version numbers provided in the output with the three arguments 190 | if ! terraform -version | awk -v major=$1 -v minor=$2 -v micro=$3 -F. ' 191 | NR==1 { 192 | sub(/[^[0-9]*/, "", $1) 193 | if($1 > major) exit 0 194 | if($1 == major && $2 > minor) exit 0 195 | if($1 == major && $2 == minor && $3 >= micro) exit 0 196 | exit 1 197 | }' 198 | then 199 | echoerr "This operation requires at least Terraform $1.$2.$3" 200 | exit 1 201 | fi 202 | } 203 | 204 | check_required () { 205 | if [ 0 -eq $# ]; then 206 | check_for="org ws token address" 207 | else 208 | check_for="$*" 209 | fi 210 | 211 | missing=0 212 | for i in $check_for; do 213 | case "$i" in 214 | org) 215 | if [ -z "$org" ]; then 216 | missing=1 217 | echoerr 'TFE organization required.' 218 | echoerr 'Set with $TFH_org or use -org' 219 | echoerr 220 | fi 221 | ;; 222 | ws) 223 | if [ -z "$ws" ]; then 224 | missing=1 225 | echoerr 'TFE workspace name required.' 226 | echoerr 'Set with $TFH_name or use -name, and optionally -prefix' 227 | echoerr 228 | fi 229 | ;; 230 | token) 231 | if [ "$curl_token_src" = none ]; then 232 | missing=1 233 | echoerr 'TFE API token required.' 234 | echoerr 'Set with `tfh curl-config`, $TFH_token, or -token' 235 | echoerr 236 | fi 237 | ;; 238 | address) 239 | # This really shouldn't happen. Someone would have to 240 | # explicitly pass in an empty string to the command line 241 | # argument. 242 | if [ -z "$address" ]; then 243 | missing=1 244 | echoerr 'TFE hostname required.' 245 | echoerr 'Set with -hostname or $TFH_hostname' 246 | echoerr 247 | fi 248 | ;; 249 | esac 250 | done 251 | return $missing 252 | } 253 | 254 | tfh_junonia_filter () { 255 | readonly TFH_DEFAULT_CURLRC="$JUNONIA_CONFIGDIR/curlrc" 256 | 257 | readonly org="$1" 258 | readonly name="$2" 259 | readonly prefix="$3" 260 | readonly token="$4" 261 | readonly curlrc="${5:-"$TFH_DEFAULT_CURLRC"}" 262 | readonly hostname="$6" 263 | 264 | # Waterfall verbosity levels down 265 | readonly vvverbose="$9" 266 | readonly vverbose="${8:-$vvverbose}" 267 | readonly verbose="${7:-$vverbose}" 268 | 269 | readonly address="https://$hostname" 270 | readonly ws="$prefix$name" 271 | 272 | echov "org: $org" 273 | echov "prefix: $prefix" 274 | echov "workspace: $name" 275 | echov "hostname: $hostname" 276 | echov "address: $address" 277 | echov "verbose: $verbose" 278 | echov "vverbose: $vverbose" 279 | echov "vvverbose: $vvverbose" 280 | 281 | curl_token_src= 282 | 283 | # curlrc argument at the command line takes highest precedence 284 | if echo "$TFH_CMDLINE" | grep -qE -- '-curlrc'; then 285 | echodebug "explicit -curlrc" 286 | if [ -f "$curlrc" ]; then 287 | curl_token_src=curlrc 288 | else 289 | curl_token_src=curlrc_not_found 290 | fi 291 | fi 292 | 293 | # token at the command line takes second highest precedence 294 | if [ -z "$curl_token_src" ] && [ -n "$token" ] && 295 | echo "$TFH_CMDLINE" | grep -qE -- '-token'; then 296 | echodebug "explicit -token" 297 | curl_token_src=token 298 | fi 299 | 300 | # curlrc from any source (default included) comes third 301 | if [ -z "$curl_token_src" ] && [ -f "$curlrc" ]; then 302 | echodebug "curlrc from env and config file" 303 | curl_token_src=curlrc 304 | fi 305 | 306 | # token from the config file or environment var comes last 307 | if [ -z "$curl_token_src" ] && [ -n "$token" ]; then 308 | echodebug "token from env and config file" 309 | curl_token_src=token 310 | fi 311 | 312 | if [ -z "$curl_token_src" ]; then 313 | curl_token_src=none 314 | fi 315 | 316 | if [ -z "$token" ]; then 317 | token_status="empty" 318 | else 319 | token_status="not empty" 320 | fi 321 | 322 | case $curl_token_src in 323 | curlrc) 324 | echov "token: $token_status, unused" 325 | echov "curlrc: $curlrc" 326 | ;; 327 | token) 328 | echov "token: $token_status" 329 | echov "curlrc: $curlrc, unused" 330 | ;; 331 | curlrc_not_found) 332 | echov "token: $token_status, unused" 333 | echov "curlrc: $curlrc specified but not found" 334 | ;; 335 | none) 336 | echov "token: empty" 337 | echov "curlrc: $curlrc not found" 338 | ;; 339 | esac 340 | 341 | return 9 342 | } 343 | 344 | # This is a compact version of junonia_bootstrap for easy copyhing into user 345 | # scripts. For a fully commented, documented version of this script see 346 | # https://github.com/fprimex/junonia/blob/master/junonia.sh 347 | junonia_bootstrap () { 348 | JUNONIA_TARGET="$0" 349 | while [ -h "$JUNONIA_TARGET" ]; do 350 | JUNONIA_PATH=$(readlink "$JUNONIA_TARGET") 351 | if [ "$(echo "$JUNONIA_PATH" | cut -c -1)" = "/" ]; then 352 | JUNONIA_TARGET="$JUNONIA_PATH" 353 | else 354 | JUNONIA_TARGET="$(dirname $JUNONIA_TARGET)" 355 | JUNONIA_TARGET="$JUNONIA_TARGET/$JUNONIA_PATH" 356 | fi 357 | done 358 | JUNONIA_PATH="$(cd "$(dirname "$JUNONIA_TARGET")" && pwd -P)" 359 | JUNONIA_TARGET="$JUNONIA_PATH/$(basename $JUNONIA_TARGET)" 360 | if [ "$(basename "$JUNONIA_PATH")" = bin ]; then 361 | JUNONIA_PATH="$(dirname "$JUNONIA_PATH")" 362 | fi 363 | } 364 | 365 | # Top level invocation of the command. 366 | tfh () { 367 | $0 help 368 | return 1 369 | } 370 | 371 | tfh_version () { 372 | echo "0.2.9" 373 | } 374 | 375 | ## 376 | ## Program begins here 377 | ## 378 | 379 | # JUNONIA_DEBUG # Turn on debugging of the junonia library 380 | # JUNONIA_CONFIG # Path to script rc file 381 | # JUNONIA_CONFIGDIR # Path to config directory 382 | # JUNONIA_CACHEDIR # Path to cache directory 383 | # JUNONIA_CACHE # Flag to optionally disable (0) caching 384 | # JUNONIA_WRAP # Width of two column output (option help listings) 385 | # JUNONIA_COL1 # Width of column one 386 | # JUNONIA_COL2 # Width of column two 387 | # TMPDIR # Temporary directory 388 | 389 | # Save the whole command line 390 | readonly TFH_CMDLINE="$0 $*" 391 | 392 | if [ -n "$TF_LOG" ]; then 393 | # Debugging. Print command errors and make them verbose 394 | tar_verbose=v 395 | curl_silent= 396 | export JUNONIA_DEBUG=1 397 | export TFH_vvverbose=1 398 | exec 3>&2 399 | else 400 | # Not debugging. Shut everyting up. 401 | tar_verbose= 402 | curl_silent="-s" 403 | exec 3>/dev/null 404 | fi 405 | 406 | junonia_bootstrap 407 | 408 | . "$JUNONIA_PATH"/lib/tfh/junonia 409 | 410 | echodebug "$PWD" 411 | echodebug "$0 $*" 412 | 413 | junonia_run "$@" 414 | exit $? 415 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_curl_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | # Write a given token value to a curl config file at the given path. 24 | # $1 file path 25 | # $2 token 26 | make_curlrc () { 27 | curlrc_dir="$(dirname "$1")" 28 | 29 | if [ ! -d "$curlrc_dir" ]; then 30 | if ! mkdir -p "$curlrc_dir"; then 31 | echoerr "unable to create configuration directory:" 32 | echoerr "$curlrc_dir" 33 | return 1 34 | fi 35 | fi 36 | 37 | if ! echo > "$1"; then 38 | echoerr "Error: cannot write to curl config file:" 39 | echoerr "$1" 40 | return 1 41 | fi 42 | 43 | if ! chmod 600 "$1"; then 44 | echoerr "WARNING: unable to set permissions on curl config file:" 45 | echoerr "chmod 600 $1" 46 | fi 47 | 48 | if ! echo "--header \"Authorization: Bearer $2\"" > "$1"; then 49 | echoerr "Error: cannot generate curl config file:" 50 | echoerr "$1" 51 | return 1 52 | fi 53 | 54 | echodebug "Created $1" 55 | } 56 | 57 | tfh_curl_config () { 58 | tfrc="$1" 59 | curltoken="$2" 60 | 61 | if [ -n "$curltoken" ] && [ -n "$tfrc" ]; then 62 | echoerr "only one of -curltoken or -tfrc can be specified" 63 | return 1 64 | fi 65 | 66 | if [ -n "$curltoken" ]; then 67 | # (Re)create / overwrite the curlrc 68 | make_curlrc "$curlrc" "$curltoken" 69 | echo "wrote $curlrc" 70 | return 0 71 | fi 72 | 73 | tf_config_token= 74 | tf_config="${TERRAFORM_CONFIG:-"$HOME/.terraformrc"}" 75 | if [ -f "$tf_config" ]; then 76 | # This is simplified. It depends on the token keyword and value being 77 | # on the same line in the .terraformrc. 78 | tf_config_token="$(awk -v host="$hostname" ' 79 | # Skip commented lines 80 | /^ *#/ { 81 | next 82 | } 83 | 84 | # Get the host for this credentials entry 85 | /credentials *"/ { 86 | cred_host = $2 87 | gsub(/"/, "", cred_host) 88 | } 89 | 90 | # Extract the token and note if it matches the specified host 91 | /token *= *"[A-Za-z0-9\.]+"/ { 92 | tokens++ 93 | match($0, /"[A-Za-z0-9\.]+"/) 94 | token = substr($0, RSTART+1, RLENGTH-2) 95 | 96 | if(cred_host == host) { 97 | host_token = token 98 | } 99 | } 100 | 101 | END { 102 | # There was only one token, use that regardless as to the host 103 | if(tokens == 1) { 104 | print token 105 | } 106 | 107 | # More than one token, use the specified host 108 | if(tokens > 1 && host_token) { 109 | print host_token 110 | } 111 | 112 | # Either did not find any tokens or found tokens, but did not find the 113 | # token for the specified host. To avoid being ambiguous, do not output 114 | # any tokens. 115 | }' "$tf_config")" 116 | fi 117 | 118 | if [ $tfrc ]; then 119 | if [ -n "$tf_config_token" ]; then 120 | if ! make_curlrc "$curlrc" "$tf_config_token"; then 121 | echoerr "failed to create curlrc with terraformrc token" 122 | echoerr "source: $tf_config" 123 | echoerr "destination: $curlrc" 124 | return 1 125 | fi 126 | echo "$curlrc generated from $tf_config" 127 | return 0 128 | else 129 | echoerr "unable to extract token from terraformrc:" 130 | echoerr "$tf_config" 131 | return 1 132 | fi 133 | fi 134 | 135 | if [ -f "$curlrc" ]; then 136 | echo "$curlrc" 137 | echov "$(cat "$curlrc")" 138 | 139 | if [ -f "$tf_config" ] && [ -z "$TFH_NO_CURLRC_DIFF" ] ; then 140 | # Got a .terraformrc token and the current token is from a tfh curl 141 | # config. Compare the tokens to see if they're the same. 142 | curlrc_token="$(awk ' 143 | /Bearer [A-Za-z0-9\.][A-Za-z0-9\.]*/ { 144 | match($0, /Bearer [A-Za-z0-9\.][A-Za-z0-9\.]*/) 145 | print substr($0, RSTART+7, RLENGTH-7) 146 | }' "$curlrc")" 147 | 148 | if [ "$curlrc_token" != "$tf_config_token" ]; then 149 | echo 150 | echo "WARNING tokens do not match in files:" 151 | echo "$tf_config" 152 | echo "$curlrc" 153 | echo 154 | echo "tfh will use: $curlrc" 155 | echo 156 | echo "to use $tf_config, run \`tfh curl-config -tfrc\`" 157 | echo 158 | echo "suppress this message by setting TFH_NO_CURLRC_DIFF=1" 159 | echo 160 | 161 | echov "curlrc : $curlrc_token" 162 | echov "terraformrc: $tf_config_token" 163 | fi 164 | fi 165 | else 166 | echo "no curlrc file at $curlrc" 167 | fi 168 | } 169 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_pullvars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_pullvars () ( 24 | vars="$1" 25 | env_vars="$2" 26 | env="$3" 27 | 28 | # Check for required standard options 29 | if ! check_required; then 30 | return 1 31 | fi 32 | 33 | # Handle conflicting options 34 | if [ $env ] && ( [ -n "$vars" ] || [ -n "$env_vars" ] ); then 35 | echoerr "-env true cannot be specified along with -var and/or -env-var" 36 | return 1 37 | fi 38 | 39 | # request template 40 | url="$address/api/v2/vars?filter%5Borganization%5D%5Bname%5D=$org&filter%5Bworkspace%5D%5Bname%5D=$ws" 41 | 42 | echodebug "API list variables URL:" 43 | echodebug "$url" 44 | 45 | # API call to get all of the variables 46 | echodebug "API request for variable list:" 47 | if ! var_get_resp="$(tfh_api_call $url)"; then 48 | echoerr "Error listing variables" 49 | return 1 50 | fi 51 | 52 | vars_retval=0 53 | tfevar= 54 | 55 | IFS="$JUNONIA_US" 56 | if [ -n "$vars" ]; then 57 | for v in $vars; do 58 | # Get this tf var out of the variable list with jq 59 | tfevar="$(printf "%s" "$var_get_resp" | jq -r --arg var "$v" '.data[] 60 | | select(.attributes.category == "terraform") 61 | | select(.attributes.key == $var) 62 | | [ 63 | .attributes.key + " = ", 64 | (if .attributes.hcl == false or .attributes.sensitive == true then "\"" else empty end), 65 | .attributes.value, 66 | (if .attributes.hcl == false or .attributes.sensitive == true then "\"" else empty end) 67 | ] 68 | | join("")')" 69 | 70 | if [ -z "$tfevar" ]; then 71 | echoerr "Variable $v not found" 72 | vars_retval=1 73 | else 74 | echo "$tfevar" 75 | fi 76 | done 77 | fi 78 | 79 | if [ -n "$env_vars" ]; then 80 | for v in $env_vars; do 81 | # Get this env var out of the variable list with jq 82 | tfevar="$(printf "%s" "$var_get_resp" | jq -r --arg var "$v" '.data[] 83 | | select(.attributes.category == "env") 84 | | select(.attributes.key == $var) 85 | | .attributes.key + "=\"" + .attributes.value + "\""')" 86 | 87 | if [ -z "$tfevar" ]; then 88 | echoerr "Variable $v not found" 89 | vars_retval=1 90 | else 91 | echo "$tfevar" 92 | fi 93 | done 94 | fi 95 | unset IFS 96 | 97 | if [ -n "$vars" ] || [ -n "$env_vars" ]; then 98 | return $vars_retval 99 | fi 100 | 101 | # Didn't retrieve a specific list of vars so 102 | # either list all tf or all env vars 103 | terraform_tfvars="$(printf "%s" "$var_get_resp" | jq -r '.data[] 104 | | select(.attributes.category == "terraform") 105 | | select(.attributes.sensitive == false) 106 | | [ 107 | .attributes.key + " = ", 108 | (if .attributes.hcl == false then 109 | if (.attributes.value != null) and (.attributes.value | contains("\n")) then 110 | "</dev/null | grep -E -v '^\.$|.git|.terraform' 29 | } 30 | 31 | list_modules () { 32 | find -L .terraform/modules -type f 2>/dev/null | grep -E -v '^\.$|\.git' 33 | } 34 | 35 | list_hardlinked () { 36 | find -L . -type f -links +1 2>/dev/null | grep -E -v '^\.$|.git|.terraform' | cut -c 3- 37 | } 38 | 39 | list_not_hardlinked () { 40 | find -L . -type f -links 1 2>/dev/null | grep -E -v '^\.$|.git|.terraform' | cut -c 3- 41 | } 42 | 43 | list_hardlinked_modules () { 44 | find -L .terraform/modules -type f -links +1 2>/dev/null | grep -E -v '^\.$|\.git' 45 | } 46 | 47 | list_not_hardlinked_modules () { 48 | find -L .terraform/modules -type f -links 1 2>/dev/null | grep -E -v '^\.$|\.git' 49 | } 50 | 51 | # Use GNU tar to archive the current directory 52 | gnutar () { 53 | echodebug "Using GNU tar" 54 | 55 | if [ $vcs ]; then 56 | list_vcs > "$tarlist" 57 | else 58 | list_files > "$tarlist" 59 | fi 60 | 61 | if [ $upload_modules ]; then 62 | list_modules >> "$tarlist" 63 | fi 64 | 65 | tar ${tfh_tar_verbose}zcfh \ 66 | "$1" -T "$tarlist" --hard-dereference 67 | } 68 | 69 | # Use BSD tar to archive the current directory 70 | bsdtar () { 71 | echodebug "Using BSD tar" 72 | 73 | vcslist="$TMPDIR/vcslist-$(junonia_randomish_int)" 74 | hardlinklist="$TMPDIR/hardlinklist-$(junonia_randomish_int)" 75 | 76 | hardlinks="$(list_hardlinked | sort)" 77 | echo "$hardlinks" > "$hardlinklist" 78 | 79 | if [ $vcs ]; then 80 | list_vcs | sort > "$vcslist" 81 | hardlinks="$(comm -12 "$vcslist" "$hardlinklist")" 82 | fi 83 | 84 | if [ -z "$hardlinks" ]; then 85 | # If there are no hardlinks then the job is easy and 86 | # the same as GNU tar without --hard-dereference 87 | echodebug "No hardlinks to manually resolve" 88 | if [ $vcs ]; then 89 | list_vcs > "$tarlist" 90 | else 91 | list_files > "$tarlist" 92 | fi 93 | 94 | if [ $upload_modules ]; then 95 | list_modules >> "$tarlist" 96 | fi 97 | 98 | tar ${tfh_tar_verbose}zcfh \ 99 | "$1" -T "$tarlist" 100 | else 101 | # If there are hardlinks they have to be added separately 102 | # to the archive one by one 103 | echodebug "Resolving hardlinks manually" 104 | 105 | if [ $vcs ]; then 106 | # Tracked hardlinks are in the hard link list already. 107 | # Need tracked non-hardlinks to tar. 108 | comm -23 "$vcslist" "$hardlinklist" > "$tarlist" 109 | else 110 | # Need all of the hardlinks, including ones that might be 111 | # in the modules dir if uploading modules 112 | list_hardlinked > "$hardlinklist" 113 | 114 | # Need the non-hardlinks to tar 115 | list_not_hardlinked > "$tarlist" 116 | fi 117 | 118 | if [ $upload_modules ]; then 119 | list_hardlinked_modules >> "$hardlinklist" 120 | list_not_hardlinked_modules >> "$tarlist" 121 | fi 122 | 123 | # Start by creating an uncompressed tar of the non-hardlinks 124 | echodebug "Creating initial tar" 125 | tar ${tfh_tar_verbose}cfh "${1%.gz}" -T "$tarlist" 126 | 127 | # Add each hardlink to the archive individually 128 | echodebug "Adding each hardlink" 129 | cat "$hardlinklist" | while read -r hl; do 130 | tar ${tfh_tar_verbose}rf "${1%.gz}" "$hl" 131 | done 132 | 133 | # Compress the completed archive 134 | echodebug "Compressing ${1%.gz}" 135 | gzip "${1%.gz}" 136 | fi 137 | } 138 | 139 | make_run_payload () { 140 | cat > "$run_payload" <> "$run_payload" <> "$run_payload" </dev/null 2>&1; then 207 | tarcmd=gnutar 208 | else 209 | tarcmd=bsdtar 210 | 211 | # Using bsdtar might result in a two step process where first the 212 | # tar is created and then compressed, which requires the separate 213 | # gzip command. It may not be strictly required under all 214 | # circumstances but it's probably better to error earlier rather 215 | # than later, considering how common the gzip command is. 216 | if ! junonia_require_cmds gzip; then 217 | return 1 218 | fi 219 | fi 220 | 221 | # Check for required standard options 222 | if ! check_required; then 223 | return 1 224 | fi 225 | 226 | if [ $destroy ] && [ $current_config ]; then 227 | echoerr "Options -destroy and -current-config conflict" 228 | return 1 229 | fi 230 | 231 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_workspace.sh" 232 | if ! workspace_id="$(_fetch_ws_id "$org" "$ws")"; then 233 | return 1 234 | fi 235 | 236 | if [ ! $destroy ] && [ ! $current_config ]; then 237 | # Check for additional required commands. 238 | if [ $vcs ] && ! junonia_require_cmds git; then 239 | return 1 240 | fi 241 | 242 | # Creates a tar.gz of the VCS or directory with the configuration 243 | 244 | echodebug "Creating file for upload" 245 | 246 | echodebug "Entering dir $config_dir" 247 | if ! cd "$config_dir"; then 248 | echoerr "Unable to change to configuration directory $config_dir" 249 | return 1 250 | fi 251 | 252 | if [ $vcs ]; then 253 | # VCS detection was requested so it must actually be present 254 | if ! git status >/dev/null 2>&1; then 255 | echoerr "VCS not present in $config_dir" 256 | echoerr "Disable VCS detection with -vcs 0" 257 | return 1 258 | fi 259 | echodebug "tar: Uploading vcs tracked files" 260 | echodebug "excluding any plugins" 261 | else 262 | echodebug "tar: Uploading all of $PWD" 263 | echodebug "excluding VCS files and any plugins" 264 | fi 265 | 266 | # If there are modules we might want to upload them. 267 | has_modules="$([ -d .terraform/modules ] && echo 1 || echo )" 268 | echodebug "Has .terraform/modules: $has_modules" 269 | 270 | $tarcmd "$config_payload" 271 | if [ $? != 0 ]; then 272 | echoerr "Error creating config archive payload" 273 | return 1 274 | fi 275 | 276 | echo "Uploading Terraform config..." 277 | 278 | echodebug "Creating a new config version for $ws" 279 | 280 | # The JSON Payload used to create the new configuration version 281 | config_ver_payload='{"data":{"type":"configuration-versions","attributes":{"auto-queue-runs":false}}}' 282 | 283 | echodebug "Creating config version in workspace $workspace_id" 284 | 285 | # Creates the configuration version and extractes the upload-url 286 | url=$address/api/v2/workspaces/$workspace_id/configuration-versions 287 | 288 | echodebug "URL: $url" 289 | 290 | echodebug "API request for config upload:" 291 | if ! upload_url_resp="$(tfh_api_call -d "$config_ver_payload" $url)"; then 292 | echoerr "Error creating config version" 293 | cleanup "$config_payload" "$tarlist" 294 | return 1 295 | fi 296 | 297 | if ! config_id="$(printf "%s" "$upload_url_resp" | jq -r '.data.id')"; then 298 | echoerr "Error parsing API response for config ID" 299 | cleanup "$config_payload" "$tarlist" 300 | return 1 301 | fi 302 | echodebug "Config ID: $config_id" 303 | 304 | # Perform the upload of the config archive to the upload URL 305 | if ! ( 306 | set -e 307 | url="$(printf "%s" "$upload_url_resp" | jq -r '.data.attributes."upload-url"')" 308 | echodebug "Upload URL: $url" 309 | echodebug "Uploading content to upload URL" 310 | 311 | upload_config_resp="$(curl -f $tfh_curl_silent -X PUT --data-binary "@$config_payload" ${url})" 312 | 313 | echodebug "Upload config response:" 314 | echodebug "$upload_config_resp" 315 | ) 2>&3; then 316 | echoerr "Error uploading config archive" 317 | cleanup "$config_payload" "$tarlist" 318 | return 1 319 | fi 320 | cleanup "$config_payload" "$tarlist" 321 | 322 | # Submission of the config version and upload of the archive does not mean 323 | # that the config version is ready for use. It has a status, and it's 324 | # necessary to poll on that status to make sure that the upload is 325 | # "uploaded", even if the upload is complete on the client side. 326 | url="$address/api/v2/configuration-versions/$config_id" 327 | config_status= 328 | retries=0 329 | while [ "$config_status" != "uploaded" ] && [ $retries -lt 3 ]; do 330 | # Initially don't sleep, and then back off linearly 331 | sleep $retries 332 | if config_get_resp="$(tfh_api_call $url)"; then 333 | config_status="$(printf "%s" "$config_get_resp" | jq -r '.data.attributes.status')" 334 | fi 335 | echodebug "Config status: $config_status" 336 | retries=$(( $retries + 1 )) 337 | done 338 | 339 | if [ "$config_status" != "uploaded" ]; then 340 | echoerr "Error creating run. Config status is $config_status after $retries tries" 341 | return 1 342 | fi 343 | fi 344 | 345 | if [ $destroy ]; then 346 | destroy_bool="true" 347 | else 348 | destroy_bool="false" 349 | fi 350 | make_run_payload "$destroy_bool" "$message" "$config_id" "$workspace_id" 351 | echodebug "Run payload contents:" 352 | echodebug "$(cat "$run_payload")" 353 | 354 | url=$address/api/v2/runs 355 | if ! run_create_resp="$(tfh_api_call -d @"$run_payload" $url)"; then 356 | echoerr "Error creating run" 357 | if [ $destroy ]; then 358 | echoerr "Note: -destroy requires CONFIRM_DESTROY=1 in the workspace" 359 | fi 360 | cleanup "$run_payload" 361 | return 1 362 | fi 363 | echodebug "Run create response:" 364 | echodebug "$run_create_resp" 365 | 366 | run_id="$(printf "%s" "$run_create_resp" | jq -r '.data.id')" 367 | 368 | cleanup "$run_payload" 369 | 370 | echodebug "Run ID: $run_id" 371 | 372 | if [ -z "$run_id" ]; then 373 | echoerr "Error obtaining run ID" 374 | return 1 375 | fi 376 | 377 | printf "Run $run_id submitted to $org/$ws" 378 | if [ -n "$config_id" ]; then 379 | printf " using config $config_id" 380 | fi 381 | printf "\n" 382 | 383 | # A literal, exact 0 means no polling, so a string comparison is sufficient. 384 | if [ 0 = "$poll" ]; then 385 | return 0 386 | fi 387 | 388 | plan_id="$(printf "%s" "$run_create_resp" | \ 389 | jq -r '.data.relationships.plan.data.id')" 390 | plan_url="$address/api/v2/plans/$plan_id" 391 | run_url="$address/api/v2/runs/$run_id" 392 | 393 | # Determine if the workspace will also perform an auto-apply 394 | echodebug "API Request for workspace info $org/$ws" 395 | url="$address/api/v2/organizations/$org/workspaces/$ws" 396 | ws_info_resp="$(tfh_api_call "$url")" 397 | ws_auto_apply="$(printf "%s" "$ws_info_resp" | jq -r '.data.attributes."auto-apply"')" 398 | 399 | # Repeatedly poll the system every N seconds specified with -poll to get 400 | # the run status until it reaches a non-active status. By default -poll is 401 | # 0 and there is no polling. 402 | run_status=pending 403 | poll_url="$plan_url" 404 | apply_started= 405 | lock_id= 406 | log_offset=0 407 | err=0 408 | stx=0 409 | etx=0 410 | 411 | # Poll until an end state is encountered 412 | while [ "$run_status" != applied ] && 413 | [ "$run_status" != canceled ] && 414 | [ "$run_status" != discarded ] && 415 | [ "$run_status" != errored ] && 416 | ( [ "$run_status" != planned ] || [ "$ws_auto_apply" = true ] ) && 417 | ( [ "$run_status" != planned_and_finished ] || [ "$ws_auto_apply" = true ] ); do 418 | # if the workspace was determined to be locked in the previous 419 | # poll, don't delay getting the final status and exiting. 420 | if [ true != "$ws_locked" ]; then 421 | sleep $poll 422 | fi 423 | 424 | echodebug "API request for run info:" 425 | run_resp="$(tfh_api_call "$run_url")" 426 | err=$(( $err + $? )) 427 | run_status="$(printf "%s" "$run_resp" | jq -r '.data.attributes.status')" 428 | err=$(( $err + $? )) 429 | echodebug "$run_status" 430 | 431 | echodebug "API request to poll the plan or apply:" 432 | poll_resp="$(tfh_api_call "$poll_url")" 433 | err=$(( $err + $? )) 434 | 435 | if [ $stream ]; then 436 | log_read_url="$(printf "%s" "$poll_resp" | jq -r '.data.attributes."log-read-url"')" 437 | err=$(( $err + $? )) 438 | 439 | echodebug "Log read URL:" 440 | echodebug "$log_read_url" 441 | 442 | if [ -z "$log_read_url" ] || [ "$log_read_url" = null ]; then 443 | # Loop and sleep 444 | continue 445 | fi 446 | 447 | log_resp="$(tfh_api_call "$log_read_url""?limit=1000&offset=$log_offset")" 448 | err=$(( $err + $? )) 449 | log_offset=$(( $log_offset + ${#log_resp} )) 450 | 451 | # Start of text, STX:  452 | # End of text, ETX:  453 | # Don't print until STX, print until ETX 454 | logline="$(printf "%s" "$log_resp" | awk -v stx="$stx" ' 455 | BEGIN { ret = 0 } 456 | // { sub(//, ""); ret = 2 } 457 | // { sub(//, ""); ret = 3 } 458 | stx { print } 459 | END { exit ret }')" 460 | case $? in 461 | 1) echoerr "could not process log for printing"; return 1 ;; 462 | 2) stx=1 ;; 463 | 3) etx=1 ;; 464 | esac 465 | printf "%s" "$logline" 466 | else 467 | last_run_status="$run_status" 468 | echo "$run_status" 469 | fi 470 | 471 | # See if, on the last iteration, it was detected that the run went from 472 | # plan to apply. If so, this last log read above should have finished out 473 | # the plan log. Now switch to the apply and continue looping. 474 | if [ "$apply_started" = 1 ]; then 475 | echo 476 | poll_url="$address/api/v2/applies/$apply_id" 477 | 478 | log_offset=0 479 | stx=0 480 | etx=0 481 | apply_started=2 # just needs to not be 0 or 1. we're done with it. 482 | continue 483 | fi 484 | 485 | echodebug "API Request for workspace info $org/$ws" 486 | url="$address/api/v2/organizations/$org/workspaces/$ws" 487 | ws_info_resp="$(tfh_api_call "$url")" 488 | err=$(( $err + $? )) 489 | 490 | ws_locked="$(printf "%s" "$ws_info_resp" | jq -r '.data.attributes.locked')" 491 | err=$(( $err + $? )) 492 | 493 | if [ true = "$ws_locked" ]; then 494 | lock_id="$(printf "%s" "$ws_info_resp" | jq -r '.data.relationships."locked-by".data.id')" 495 | if [ "$lock_id" != "$run_id" ]; then 496 | echo "locked by $lock_id" 497 | return 0 498 | fi 499 | fi 500 | 501 | if [ "$apply_started" != 1 ] && 502 | [ "$ws_auto_apply" = true ] && 503 | [ "$run_status" = "applying" ]; then 504 | if apply_id="$(printf "%s" "$run_resp" | jq -r '.data.relationships.apply.data.id' 2>/dev/null)"; then 505 | # Flag that the run has gone from plan to apply. The next iteration 506 | # should finish reading and printing the remaining parts of the plan 507 | # log, then switch to the apply url and log, then continue looping. 508 | apply_started=1 509 | fi 510 | fi 511 | 512 | if [ "$err" -gt 0 ]; then 513 | echoerr "failed to poll run" 514 | return 1 515 | fi 516 | done 517 | 518 | if [ $stream ]; then 519 | echo 520 | elif [ "$run_status" != "$last_run_status" ]; then 521 | # Don't report, e.g., planned twice 522 | echo "$run_status" 523 | fi 524 | } 525 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_pushvars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_var_create_payload () { 28 | cat > "$payload" < "$payload" </dev/null 2>&1; then 128 | echodebug "skipping $v due to later delete" 129 | continue 130 | fi 131 | else 132 | if echo "$env_deletes" | grep -E "$JUNONIA_US$v$JUNONIA_US" >/dev/null 2>&1; then 133 | echodebug "skipping $v due to later delete" 134 | continue 135 | fi 136 | fi 137 | fi 138 | 139 | var_id="$(printf "%s" "$var_get_resp" | \ 140 | jq -r --arg var "$v" --arg type "$2" '.data[] 141 | | select(.attributes.category == $type) 142 | | select(.attributes.key == $var) 143 | | .id')" 144 | if [ -z "$var_id" ]; then 145 | echodebug "$v not in variable listing" 146 | 147 | if [ "$3" = "delete" ]; then 148 | echoerr "Variable $v specified for deletion but doesn't exist" 149 | continue 150 | fi 151 | 152 | if [ $hide_sensitive ] && [ "$4" = true ]; then 153 | output_val=REDACTED 154 | else 155 | output_val="$val" 156 | fi 157 | 158 | printf "Creating %s type:%s hcl:%s sensitive:%s value:%s\n" "$v" "$2" "$3" "$4" "$output_val" 159 | 160 | if [ ! $dry_run ]; then 161 | url="$address/api/v2/vars" 162 | if ! make_var_create_payload "$v" "$val" $2 "$3" "$4"; then 163 | echoerr "Error generating payload file for $v" 164 | continue 165 | fi 166 | 167 | echodebug "API request for variable create:" 168 | if ! var_create_resp="$(tfh_api_call -d @"$payload" "$url")"; then 169 | echoerr "error creating variable $v" 170 | fi 171 | 172 | cleanup "$payload" 173 | fi 174 | else 175 | if [ "$3" = delete ]; then 176 | h="$(printf "%s" "$var_get_resp" | \ 177 | jq -r --arg var "$v" --arg type "$2" '.data[] 178 | | select(.attributes.category == $type) 179 | | select(.attributes.key == $var) 180 | | .attributes.hcl')" 181 | 182 | s="$(printf "%s" "$var_get_resp" | \ 183 | jq -r --arg var "$v" --arg type "$2" '.data[] 184 | | select(.attributes.category == $type) 185 | | select(.attributes.key == $var) 186 | | .attributes.sensitive')" 187 | 188 | o="$(printf "%s" "$var_get_resp" | \ 189 | jq -r --arg var "$v" --arg type "$2" '.data[] 190 | | select(.attributes.category == $type) 191 | | select(.attributes.key == $var) 192 | | .attributes.value')" 193 | 194 | printf "Deleting %s type:%s hcl:%s sensitive:%s value:%s\n" "$v" "$2" "$h" "$s" "$o" 195 | 196 | if [ ! $dry_run ]; then 197 | url="$address/api/v2/vars/$var_id" 198 | if ! tfh_api_call --request DELETE "$url"; then 199 | echoerr "Error deleting variable $v" 200 | fi 201 | fi 202 | else 203 | # This existing variable should only be overwritten if it was specified 204 | # in the correct overwrite list or if -overwrite-all is true. The 205 | # overwrites variable is bookended with separators to make the grep 206 | # simpler. 207 | if [ $overwrite_all ] || 208 | ( [ "$2" = terraform ] && echo "$JUNONIA_US$overwrites$JUNONIA_US" \ 209 | | grep -Eq "$JUNONIA_US[ \t\n]*${v}[ \t\n]*$JUNONIA_US" ) || 210 | ( [ "$2" = env ] && echo "$JUNONIA_US$envvar_overwrites$JUNONIA_US" \ 211 | | grep -Eq "$JUNONIA_US[ \t\n]*$v[ \t\n]*$JUNONIA_US" ); then 212 | 213 | if [ $hide_sensitive ] && [ "$4" = true ]; then 214 | output_val=REDACTED 215 | else 216 | output_val="$val" 217 | fi 218 | 219 | printf "Updating %s type:%s hcl:%s sensitive:%s value:%s\n" "$v" "$2" "$3" "$4" "$output_val" 220 | 221 | if [ ! $dry_run ]; then 222 | url="$address/api/v2/vars/$var_id" 223 | if ! make_var_update_payload "$var_id" "$v" "$val" "$2" "$3" "$4"; then 224 | echoerr "Error generating payload file for $v" 225 | continue 226 | fi 227 | 228 | echodebug "API request for variable update:" 229 | if ! var_update_resp="$(tfh_api_call --request PATCH -d @"$payload" "$url")"; then 230 | echoerr "Error updating variable $v" 231 | fi 232 | 233 | cleanup "$payload" 234 | fi 235 | else 236 | echodebug "Existing variable $v not specified to be overwritten" 237 | fi 238 | fi 239 | fi 240 | IFS=$JUNONIA_US 241 | done 242 | unset IFS 243 | } 244 | 245 | tfh_pushvars () { 246 | config_dir="$1" 247 | shift 248 | dry_run="$1" 249 | shift 250 | vars="$1" 251 | shift 252 | svars="$1" 253 | shift 254 | hclvars="$1" 255 | shift 256 | shclvars="$1" 257 | shift 258 | envvars="$1" 259 | shift 260 | senvvars="$1" 261 | shift 262 | deletes="$1" 263 | shift 264 | env_deletes="$1" 265 | shift 266 | overwrites="$1" 267 | shift 268 | envvar_overwrites="$1" 269 | shift 270 | overwrite_all="$1" 271 | shift 272 | var_file="$1" 273 | shift 274 | hide_sensitive="$1" 275 | 276 | working_dir="$PWD" 277 | 278 | defaultvars= 279 | defaultvars_values= 280 | defaulthclvars= 281 | defaulthclvars_values= 282 | var_file_arg= 283 | 284 | if [ -n "$var_file" ]; then 285 | # Create an input argument for 'terraform console' 286 | var_file_arg="-var-file=$var_file" 287 | fi 288 | 289 | payload="$TMPDIR/tfe-push-vars-payload-$(junonia_randomish_int)" 290 | 291 | # Check for required standard options 292 | if ! check_required; then 293 | return 1 294 | fi 295 | 296 | if [ $overwrite_all ] && [ ! $dry_run ] && 297 | ! echo "$TFH_CMDLINE" | grep -Eq -- '-dry-run (0|false)'; then 298 | echoerr "Option -overwrite-all requires -dry-run to be explicitly" 299 | echoerr "specified as false. Running with -dry-run true to preview operations." 300 | overwrite_all=1 301 | dry_run=1 302 | fi 303 | 304 | if [ -n "$config_dir" ] || [ -n "$var_file" ]; then 305 | tf_version_required 0 11 6 306 | fi 307 | 308 | # Get the variable listing for the workspace 309 | url="$address/api/v2/vars?filter%5Borganization%5D%5Bname%5D=$org&filter%5Bworkspace%5D%5Bname%5D=$ws" 310 | 311 | echodebug "API list variables URL:" 312 | echodebug "$url" 313 | 314 | echodebug "API request for variable list:" 315 | if ! var_get_resp="$(tfh_api_call $url)"; then 316 | echoerr "error listing variables" 317 | return 1 318 | fi 319 | 320 | # If a config directory was given, then variables with defaults in 321 | # the configuration need to be discovered. 322 | if [ -n "$config_dir" ]; then 323 | cd "$config_dir" 324 | if [ 0 -ne $? ]; then 325 | echoerr "Error entering the config directory:" 326 | echoerr "$config_dir" 327 | return 1 328 | fi 329 | 330 | # Get all of the variables from all of the .tf files 331 | # Outputs: 332 | # TYPE NAME 333 | # e.g. 334 | # hcl my_var 335 | all_vars="$(awk ' 336 | BEGIN { 337 | in_var = 0 338 | in_value = 0 339 | in_default = 0 340 | in_comment = 0 341 | in_heredoc = 0 342 | heredoc_id = "" 343 | in_map = 0 344 | in_default_block = 0 345 | seen_variable = 0 346 | } 347 | /^[ \t\r\n\v\f]*#/ { 348 | # line starts with a comment 349 | next 350 | } 351 | /\/\*/ { 352 | # entering a comment block 353 | in_comment = 1 354 | } 355 | in_comment == 1 && /\*\// { 356 | # exiting a comment block 357 | in_comment = 0 358 | next 359 | } 360 | in_comment == 1 { 361 | # still in a comment block 362 | next 363 | } 364 | in_map == 1 && $0 !~ /}/ { 365 | # in a map and do not see a "}" to close it 366 | next 367 | } 368 | in_map == 1 && /}/ { 369 | # in a map and see a "}" to close it. 370 | # assuming that a variable identifier is not going to follow 371 | # the "}" on the same line, which could be a bad assumption 372 | # but also really awful formatting 373 | in_map = 0 374 | next 375 | } 376 | /variable/ && seen_variable == 0 { 377 | # the variable keyword 378 | seen_variable = 1 379 | } 380 | 381 | $0 ~ /"[-_a-zA-Z0-9]*"/ && seen_variable == 1 { 382 | # get the variable name 383 | in_var = 1 384 | match($0, /"[a-zA-Z0-9]/) 385 | s = RSTART 386 | match($0, /[-_a-zA-Z0-9]"/) 387 | name = substr($0, s + 1, RSTART - s) 388 | seen_variable = 0 389 | } 390 | in_heredoc == 1 && $0 !~ "^" heredoc_id "$" { 391 | # in a heredoc and have not seen the id to end it 392 | next 393 | } 394 | in_heredoc == 1 && $0 ~ "^" heredoc_id "$" { 395 | # exiting a heredoc 396 | in_heredoc = 0 397 | heredoc_id = "" 398 | next 399 | } 400 | in_var == 1 && /{/ { 401 | # entered the variable block to look for default 402 | in_var_block = 1 403 | } 404 | in_var_block == 1 && /default/ { 405 | # this variable has a default 406 | in_default = 1 407 | } 408 | in_var_block == 1 && in_default == 0 && in_value == 0 && /}/ { 409 | # Variable block with no default. Its value may come from 410 | # a tfvars file loaded later. 411 | print name 412 | in_var = 0 413 | in_var_block = 0 414 | in_default = 0 415 | in_default_block = 0 416 | in_value = 0 417 | next 418 | } 419 | in_default == 1 && /=/{ 420 | # entering the RHS of default = 421 | in_value = 1 422 | 423 | # strip everything up to = and whitespace to make 424 | # detection of unquoted true/false easier when = and 425 | # true/false are on the same line. 426 | sub(/[^=]*[ \t\r\n\v\f]*=[ \t\r\n\v\f]*/, "") 427 | } 428 | in_var == 1 && in_default == 1 && in_value == 1 && /["{<\[tf0-9]/ { 429 | # inside the RHS and found something that looks like a value. 430 | # determine the variable type (hcl, non-hcl). 431 | # match all the things that are not Terraform variable values 432 | m = match($0, /[^"{<\[tf0-9]*/) 433 | if(m != 0) { 434 | # Get the first character after all of the things that are not 435 | # Terraform variable values 436 | value_char = substr($0, RLENGTH + 1, 1) 437 | 438 | if(value_char == "{") { 439 | # this is a map. if it is not all on one line then 440 | # we are in a map 441 | if(match($0, /}/) == 0){ 442 | in_map = 1 443 | } 444 | } 445 | if(value_char == "<") { 446 | # entering a heredoc. valid anchors should be directly 447 | # next to the brackets 448 | in_heredoc = 1 449 | match($0, /<<[-_a-zA-Z0-9]+/) 450 | heredoc_id = substr($0, RSTART+2, RLENGTH-2) 451 | } 452 | if(value_char == "t") { 453 | # Check to ensure the value is unquoted true 454 | true_chars = substr($0, RLENGTH + 1, 4) 455 | if(true_chars != "true") { 456 | # If not then start the search over 457 | in_var = 0 458 | in_value = 0 459 | in_var_block = 0 460 | in_default = 0 461 | in_default_block = 0 462 | next 463 | } 464 | } 465 | if(value_char == "f") { 466 | # Check to ensure the value is false 467 | false_chars = substr($0, RLENGTH + 1, 5) 468 | if(false_chars != "false") { 469 | # If not then start the search over 470 | in_var = 0 471 | in_value = 0 472 | in_var_block = 0 473 | in_default = 0 474 | in_default_block = 0 475 | next 476 | } 477 | } 478 | 479 | # not in a map, so this is a variable name 480 | print name 481 | in_var = 0 482 | in_value = 0 483 | in_var_block = 0 484 | in_default = 0 485 | in_default_block = 0 486 | } 487 | } ' *.tf)" 488 | cd "$working_dir" 489 | elif [ -n "$var_file" ]; then 490 | # Going to locate all of the variables from a tfvars file. 491 | # Will get the values by using the tfvars file and creating a 492 | # temporary config with just variable names in it for use with 493 | # the terraform console command. 494 | tfvar_dir="$TMPDIR/tfe-push-vars-$(junonia_randomish_int)" 495 | if ! mkdir "$tfvar_dir"; then 496 | echoerr "error creating temporary directory for tfvars." 497 | return 1 498 | fi 499 | 500 | echodebug "Temporary tfvars dir:" 501 | echodebug "$tfvar_dir" 502 | 503 | if ! cp "$var_file" "$tfvar_dir"; then 504 | echoerr "Error copying variable file to temporary path." 505 | return 1 506 | fi 507 | 508 | var_file_arg="-var-file=$(basename "$var_file")" 509 | 510 | if ! cd "$tfvar_dir"; then 511 | echoerr "Error entering variable file temporary path." 512 | return 1 513 | fi 514 | 515 | # This is not a great "parser" but it hopefully overreaches on finding 516 | # variable names, then we can silently ignore errors from terraform 517 | # console (output when TF_LOG=1). 518 | 519 | # Outputs: 520 | # TYPE NAME 521 | # e.g. 522 | # hcl my_var 523 | all_vars="$(awk ' 524 | BEGIN { 525 | in_var = 0 526 | in_value = 0 527 | in_comment = 0 528 | in_heredoc = 0 529 | heredoc_id = "" 530 | in_map = 0 531 | } 532 | /^[ \t\r\n\v\f]*#/ { 533 | # line starts with a comment 534 | next 535 | } 536 | /\/\*/ { 537 | # entering a comment block 538 | in_comment = 1 539 | next 540 | } 541 | in_comment == 1 && /\*\// { 542 | # exiting a comment block 543 | in_comment = 0 544 | next 545 | } 546 | in_comment == 1 { 547 | # still in a comment block 548 | next 549 | } 550 | in_map == 1 && $0 !~ /}/ { 551 | # in a map and do not see a "}" to close it 552 | next 553 | } 554 | in_map == 1 && /}/ { 555 | # in a map and see a "}" to close it. 556 | # assuming that a variable identifier is not going to follow 557 | # the "}" on the same line, which could be a bad assumption 558 | # but also really awful formatting 559 | in_map = 0 560 | next 561 | } 562 | in_heredoc == 1 && $0 !~ "^" heredoc_id "$" { 563 | # in a heredoc and have not seen the id to end it 564 | next 565 | } 566 | in_heredoc == 1 && $0 ~ "^" heredoc_id "$" { 567 | # exiting a heredoc 568 | in_heredoc = 0 569 | heredoc_id = "" 570 | next 571 | } 572 | /^[ \t\r\n\v\f]*[a-zA-Z0-9]/ && in_var == 0 && $0 !~ /,$/ { 573 | # token text, not in a variable already, does not end in ",". 574 | # this looks like a variable name 575 | in_var = 1 576 | match($0, /[-_a-zA-Z0-9]+/) 577 | name = substr($0, RSTART, RLENGTH) 578 | 579 | # remove the potential variable name from $0 so a search 580 | # for "=" can continue on this line as well as subsequent lines 581 | sub(name, "") 582 | 583 | # remove whitespace so that the next character either is or is 584 | # not "=" 585 | sub(/^[ \t\r\n\v\f]+/, "") 586 | } 587 | in_var == 1 && /^[^=]/ { 588 | # have a potential variable name but the next thing seen 589 | # is not "=", so this is not a variable name. 590 | in_var = 0 591 | next 592 | } 593 | in_var == 1 && /^=/ { 594 | # have a variable name and the next thing seen is = 595 | in_value = 1 596 | 597 | # strip everything up to = and whitespace to make 598 | # detection of unquoted true/false easier when = and 599 | # true/false are on the same line. 600 | sub(/[^=]*[ \t\r\n\v\f]*=[ \t\r\n\v\f]*/, "") 601 | } 602 | in_var == 1 && in_value == 1 && /["{<\[tf0-9]/ { 603 | # see if we are entering a map or heredoc so we can skip those 604 | # sections. 605 | # match all the things that are not Terraform variable values. 606 | m = match($0, /[^"{<\[tf0-9]*/) 607 | if(m != 0) { 608 | # Get the first character after all of the things that are not 609 | # Terraform variable values 610 | value_char = substr($0, RLENGTH + 1, 1) 611 | 612 | if(value_char == "{") { 613 | # this is a map. if it is not all on one line then 614 | # we are in a map 615 | if(match($0, /}/) == 0){ 616 | in_map = 1 617 | } 618 | } 619 | if(value_char == "<") { 620 | # entering a heredoc. valid anchors should be directly 621 | # next to the brackets 622 | in_heredoc = 1 623 | match($0, /<<[-_a-zA-Z0-9]+/) 624 | heredoc_id = substr($0, RSTART+2, RLENGTH-2) 625 | } 626 | if(value_char == "t") { 627 | # Check to ensure the value is unquoted true 628 | true_chars = substr($0, RLENGTH + 1, 4) 629 | if(true_chars != "true") { 630 | # If not then start the search over 631 | in_var = 0 632 | in_value = 0 633 | next 634 | } 635 | } 636 | if(value_char == "f") { 637 | # Check to ensure the value is false 638 | false_chars = substr($0, RLENGTH + 1, 5) 639 | if(false_chars != "false") { 640 | # If not then start the search over 641 | in_var = 0 642 | in_value = 0 643 | next 644 | } 645 | } 646 | 647 | # not in a map, so this is a variable name 648 | print name 649 | printf "variable \"%s\" {}\n", name >> "vars.tf" 650 | in_var = 0 651 | in_value = 0 652 | } 653 | }' "$(basename "$var_file")")" 654 | 655 | echodebug "Temporary directory contents:" 656 | echodebug "$(ls)" 657 | 658 | echodebug "Temporary tf file contents:" 659 | echodebug "$(cat vars.tf)" 660 | fi 661 | 662 | echodebug "All variables:" 663 | echodebug "$all_vars" 664 | 665 | # All of the 'parsed' variable names are just candidates, as they may 666 | # not have defaults or may not be variable names at all (e.g. map 667 | # assignments). Loop through the potential variables and get their 668 | # values, then determine if they are HCL or not. 669 | 670 | if [ -n "$all_vars" ]; then 671 | if [ -n "$config_dir" ] && [ ! -d .terraform ]; then 672 | echoerr "Terraform configuration appears uninitialized!" 673 | echoerr "No .terraform directory found in $PWD" 674 | echoerr "" 675 | echoerr "When specifying a config directory run terraform init with:" 676 | echoerr " # This directory is the config directory" 677 | echoerr " terraform init" 678 | echoerr "or" 679 | echoerr " # Another directory is the config directory" 680 | echoerr " terraform init [config dir]" 681 | return 1 682 | fi 683 | 684 | echodebug "Current dir: $PWD" 685 | echodebug "Config dir arg: $config_dir" 686 | echodebug "Var file arg: $var_file_arg" 687 | 688 | for var in $all_vars; do 689 | if [ -n "$var_file_arg" ] && [ -n "$config_dir" ]; then 690 | val_lines="$(echo "var.$var" \ 691 | | terraform console "$var_file_arg" "$config_dir" 2>&1)" 692 | elif [ -n "$var_file_arg" ] && [ -z "$config_dir" ]; then 693 | val_lines="$(echo "var.$var" \ 694 | | terraform console "$var_file_arg" 2>&1)" 695 | elif [ -z "$var_file_arg" ] && [ -n "$config_dir" ]; then 696 | val_lines="$(echo "var.$var" \ 697 | | terraform console "$config_dir" 2>&1)" 698 | else 699 | val_lines="$(echo "var.$var" \ 700 | | terraform console 2>&1)" 701 | fi 702 | 703 | if [ 0 -ne $? ]; then 704 | echodebug "Unable to retrieve value for potential variable $var" 705 | echodebug "Stdout of terraform console was:" 706 | echodebug "$val_lines" 707 | continue 708 | fi 709 | 710 | # Remove texts related to state lock from the output if present. 711 | val_lines="$(echo "$val_lines" \ 712 | | grep -vE "Acquiring state lock|Releasing state lock" 2>&3)" 713 | 714 | val="$(escape_value "$val_lines")" 715 | 716 | # Inspect the first character of the value to see if it is a 717 | # list or map 718 | first="$(echo "$val" | cut -c 1-1 | head -1)" 719 | if [ "$first" = "{" ] || [ "$first" = "[" ]; then 720 | if [ -z "$defaulthclvars" ]; then 721 | defaulthclvars="$var=$val_lines" 722 | else 723 | defaulthclvars="$defaulthclvars$JUNONIA_US$var=$val_lines" 724 | fi 725 | else 726 | if [ -z "$defaultvars" ]; then 727 | defaultvars="$var=$val" 728 | else 729 | defaultvars="$defaultvars$JUNONIA_US$var=$val" 730 | fi 731 | fi 732 | done 733 | fi 734 | 735 | cd "$working_dir" 736 | 737 | # Send each list of the different types of variables through to be created 738 | # or updated, along with the properties that that list should abide by. 739 | 740 | # variable list type hcl sensitive 741 | process_vars "$defaultvars" terraform false false 742 | process_vars "$defaulthclvars" terraform true false 743 | process_vars "$vars" terraform false false 744 | process_vars "$hclvars" terraform true false 745 | process_vars "$svars" terraform false true 746 | process_vars "$shclvars" terraform true true 747 | process_vars "$envvars" env false false 748 | process_vars "$senvvars" env false true 749 | process_vars "$deletes" terraform delete 750 | process_vars "$env_deletes" env delete 751 | } 752 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_run () { 24 | exec $0 run help 25 | } 26 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_run_cancel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_run_cancel () { 24 | run_ids="$1" 25 | comment="$2" 26 | 27 | if ! check_required token address; then 28 | echoerr "must provide an authentication token and address" 29 | return 1 30 | fi 31 | 32 | if [ -z "$run_id" ]; then 33 | # get the run ID of the last run in a cancelable state and use that 34 | if ! check_required org ws; then 35 | echoerr "need org and workspace to locate cancelable run" 36 | return 1 37 | fi 38 | 39 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_workspace.sh" 40 | if ! ws_id="$(_fetch_ws_id "$org" "$ws")"; then 41 | return 1 42 | fi 43 | 44 | echodebug "API request to list runs:" 45 | url="$address/api/v2/workspaces/$ws_id/runs" 46 | if ! list_resp="$(tfh_api_call 1 "$url")"; then 47 | echoerr "failed to list runs for $org/$ws" 48 | return 1 49 | fi 50 | 51 | run_ids="$(printf "%s" "$list_resp" | 52 | jq -r '.data[] | select(.attributes.actions."is-cancelable") | 53 | .id')" 54 | 55 | if [ -z "$run_ids" ]; then 56 | echoerr "unable to locate a cancelable run" 57 | return 1 58 | fi 59 | fi 60 | 61 | echodebug "run ids: $run_ids" 62 | 63 | payload="{\"comment\":\"$comment\"}" 64 | 65 | for run_id in $run_ids; do 66 | url="$address/api/v2/runs/$run_id/actions/cancel" 67 | if ! cancel_resp="$(tfh_api_call -d "$payload" "$url")"; then 68 | echoerr "unable to cancel run $run_id" 69 | return 1 70 | fi 71 | 72 | echo "canceled run $run_id" 73 | done 74 | } 75 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_run_discard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_run_discard () { 24 | run_ids="$1" 25 | comment="$2" 26 | 27 | if ! check_required token address; then 28 | echoerr "must provide an authentication token and address" 29 | return 1 30 | fi 31 | 32 | if [ -z "$run_id" ]; then 33 | # get the run ID of the last run in a discardable state and use that 34 | if ! check_required org ws; then 35 | echoerr "need org and workspace to locate discardable run" 36 | return 1 37 | fi 38 | 39 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_workspace.sh" 40 | if ! ws_id="$(_fetch_ws_id "$org" "$ws")"; then 41 | return 1 42 | fi 43 | 44 | echodebug "API request to list runs:" 45 | url="$address/api/v2/workspaces/$ws_id/runs" 46 | if ! list_resp="$(tfh_api_call 1 "$url")"; then 47 | echoerr "failed to list runs for $org/$ws" 48 | return 1 49 | fi 50 | 51 | run_ids="$(printf "%s" "$list_resp" | 52 | jq -r '.data[] | select(.attributes.actions."is-discardable") | 53 | .id')" 54 | 55 | if [ -z "$run_ids" ]; then 56 | echoerr "unable to locate a discardable run" 57 | return 1 58 | fi 59 | fi 60 | 61 | echodebug "run id: $run_id" 62 | 63 | payload="{\"comment\":\"$comment\"}" 64 | 65 | for run_id in $run_ids; do 66 | url="$address/api/v2/runs/$run_id/actions/discard" 67 | if ! discard_resp="$(tfh_api_call -d "$payload" "$url")"; then 68 | echoerr "unable to discard run $run_id" 69 | return 1 70 | fi 71 | 72 | echo "discarded run $run_id" 73 | done 74 | } 75 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_run_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | 24 | tfh_run_list () { 25 | ws_id="$1" 26 | 27 | if ! check_required org token address; then 28 | return 1 29 | fi 30 | 31 | if [ -z "$ws_id" ]; then 32 | if ! check_required org ws; then 33 | echoerr "no workspace specified to list runs for" 34 | return 1 35 | fi 36 | 37 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_workspace.sh" 38 | if ! ws_id="$(_fetch_ws_id "$org" "$ws")"; then 39 | return 1 40 | fi 41 | 42 | ws_name="$ws" 43 | else 44 | ws_name="$ws_id" 45 | fi 46 | 47 | echodebug "API request to list runs:" 48 | url="$address/api/v2/workspaces/$ws_id/runs" 49 | if ! list_resp="$(tfh_api_call 1 "$url")"; then 50 | echoerr "failed to list runs for $org/$ws_name" 51 | return 1 52 | fi 53 | 54 | listing="$(printf "%s" "$list_resp" | jq -r ' 55 | .data[] | 56 | [ .id, 57 | .attributes.status, 58 | if .attributes.status == "canceled" then 59 | .attributes."status-timestamps"["force-canceled-at"] 60 | else 61 | (.attributes.status + "-at") as $sat | 62 | .attributes."status-timestamps"[$sat] 63 | end, 64 | .attributes.message 65 | ] | join(" ")')" 66 | 67 | echo "$listing" | awk ' 68 | { 69 | $3 = substr($3, 1, 16) "Z" 70 | printf "%s %-10s %s ", $1, $2, $3 71 | $1 = "" 72 | $2 = "" 73 | $3 = "" 74 | sub(/^ */, "") 75 | print 76 | }' 77 | } 78 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_run_show.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_run_show () { 24 | run_id="$2" 25 | 26 | if [ -z $run_id ]; then 27 | if ! check_required; then 28 | echoerr "need org and workspace to locate latest run to show" 29 | return 1 30 | fi 31 | 32 | # Use the list command to retrieve the latest runs 33 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_run_list.sh" 34 | 35 | if ! listing="$(tfh_run_list)"; then 36 | # The listing command will have printed error messages. 37 | return 1 38 | fi 39 | 40 | if ! run_id="$(printf "%s" "$listing" | awk 'NR==1 {print $1; exit}')"; then 41 | echoerr "could not parse run list from $org/$ws for latest run ID" 42 | return 1 43 | fi 44 | fi 45 | 46 | url="$address/api/v2/runs/$run_id" 47 | run_show="$(tfh_api_call "$url")" 48 | 49 | if [ -n "$run_show" ]; then 50 | printf "%s" "$run_show" | jq -r ' 51 | [ .data.id, 52 | .data.attributes.status, 53 | (.data.attributes.status + "-at") as $sat | 54 | .data.attributes."status-timestamps"[$sat], 55 | .data.attributes.message 56 | ] | join(" ")' 57 | else 58 | echoerr "unable to get run details for $run_id" 59 | return 1 60 | fi 61 | } 62 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_ssh () ( 24 | exec $0 ssh help 25 | ) 26 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_assign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_assign_ssh_payload () { 28 | 29 | # $1 SSH key ID 30 | 31 | cat > "$payload" <&3 44 | } 45 | 46 | tfh_ssh_assign () ( 47 | assign_ws="$1" 48 | ssh_name="$2" 49 | ssh_id="$3" 50 | 51 | if [ -z $ssh_name$ssh_id ]; then 52 | exec $0 ssh assign help 53 | return 1 54 | fi 55 | 56 | if [ -z "$assign_ws" ]; then 57 | if ! check_required ws; then 58 | echoerr 'A positional parameter is also accepted for this command:' 59 | echoerr 'tfh ssh assign WORKSPACE_NAME [-ssh-name|-ssh-id]' 60 | return 1 61 | else 62 | assign_ws="$ws" 63 | fi 64 | fi 65 | 66 | payload="$TMPDIR/tfe-new-payload-$(junonia_randomish_int)" 67 | 68 | # Ensure all of the common required variables are set 69 | if ! check_required org token address; then 70 | return 1 71 | fi 72 | 73 | if [ -z "$ssh_name" ] && [ -z "$ssh_id" ]; then 74 | echoerr "One of -ssh-name or -ssh-id is required" 75 | return 1 76 | fi 77 | 78 | if [ -n "$ssh_name" ] && [ -n "$ssh_id" ]; then 79 | echoerr "Only one of -ssh-name or -ssh-id should be specified" 80 | return 1 81 | fi 82 | 83 | if [ -n "$ssh_name" ]; then 84 | # Use the show command to check for and retrieve the ID of the key 85 | # in the case of being passed a name. 86 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_ssh_show.sh" 87 | 88 | # Pass the command line arguments to show and get back a key (or error) 89 | if ! ssh_show="$(tfh_ssh_show "$ssh_name")"; then 90 | # The show command will have printed error messages. 91 | return 1 92 | fi 93 | 94 | # Really, if it's empty then tfh_show should have exited non-zero 95 | if [ -z "$ssh_show" ]; then 96 | echoerr "SSH key not found" 97 | return 1 98 | fi 99 | 100 | ssh_id="$(echo "$ssh_show" | cut -d ' ' -f 2)" 101 | else 102 | # To simplify error reporting later, we'll print ssh_show, but if 103 | # we were given an ID set ssh_show to the ID as it will be empty. 104 | ssh_show="$ssh_id" 105 | fi 106 | 107 | # This shouldn't happen... 108 | if [ -z "$ssh_id" ]; then 109 | echoerr "SSH key not found" 110 | return 1 111 | fi 112 | 113 | if ! make_assign_ssh_payload "$ssh_id"; then 114 | echoerr "Error generating payload file for SSH key assignment" 115 | return 1 116 | fi 117 | 118 | # Need the workspace ID from the workspace name 119 | echodebug "API request to show workspace:" 120 | url="$address/api/v2/organizations/$org/workspaces/$assign_ws" 121 | if ! show_resp="$(tfh_api_call "$url")"; then 122 | echoerr "Error showing workspace information for $assign_ws" 123 | return 1 124 | fi 125 | 126 | workspace_id="$(printf "%s" "$show_resp" | jq -r '.data.id')" 127 | 128 | echodebug "API request for SSH key assignment:" 129 | url="$address/api/v2/workspaces/$workspace_id/relationships/ssh-key" 130 | if ! assign_resp="$(tfh_api_call --request PATCH -d @"$payload" "$url")"; then 131 | echoerr "Error assigning SSH key $ssh_show to $assign_ws" 132 | return 1 133 | fi 134 | 135 | cleanup "$payload" 136 | 137 | echo "Assigned SSH key $ssh_show to $assign_ws" 138 | ) 139 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_delete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_ssh_delete () ( 24 | ssh_name="$1" 25 | ssh_id="$2" 26 | 27 | if [ -z $ssh_name$ssh_id ]; then 28 | exec $0 ssh delete help 29 | return 1 30 | fi 31 | 32 | # Use the show command to check for and retrieve the ID of the key 33 | # in the case of being passed a name. 34 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_ssh_show.sh" 35 | 36 | # Pass the command line arguments to show and get back a key (or error) 37 | if ! ssh_show="$(tfh_ssh_show "$@")"; then 38 | # The show command will have printed error messages. 39 | return 1 40 | fi 41 | 42 | # Really, if it's empty then tfh_show should have exited non-zero 43 | if [ -z "$ssh_show" ]; then 44 | echoerr "SSH key not found" 45 | return 1 46 | fi 47 | 48 | ssh_id="$(echo "$ssh_show" | cut -d ' ' -f 2)" 49 | 50 | echodebug "API request to delete SSH key:" 51 | url="$address/api/v2/ssh-keys/$ssh_id" 52 | if ! tfh_api_call -X DELETE "$url" >/dev/null; then 53 | echoerr "Error deleting SSH key $ssh_id" 54 | return 1 55 | fi 56 | 57 | echo "Deleted SSH key $ssh_show" 58 | ) 59 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_ssh_list () ( 24 | # Ensure all of org, etc, are set. Workspace is not required. 25 | if ! check_required org token address; then 26 | return 1 27 | fi 28 | 29 | echodebug "API request to list SSH keys:" 30 | url="$address/api/v2/organizations/$org/ssh-keys" 31 | if ! list_resp="$(tfh_api_call "$url")"; then 32 | echoerr "Error listing SSH keys for $org" 33 | return 1 34 | fi 35 | 36 | listing="$(printf "%s" "$list_resp" | 37 | jq -r '.data[] | .attributes.name + " " + .id')" 38 | 39 | # Sort the listing by name. 40 | echo "$listing" | sort 41 | ) 42 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_new.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_new_ssh_payload () { 28 | 29 | # $1 new ssh key name 30 | # $2 new ssh key 31 | 32 | cat > "$payload" <&3 46 | } 47 | 48 | tfh_ssh_new () ( 49 | ssh_name="$1" 50 | ssh_file="$2" 51 | 52 | if [ -z $ssh_name$ssh_file ]; then 53 | exec $0 ssh new help 54 | return 1 55 | fi 56 | 57 | payload="$TMPDIR/tfe-new-payload-$(junonia_randomish_int)" 58 | 59 | # Ensure all of org, etc, are set. Workspace is not required. 60 | if ! check_required org token address; then 61 | return 1 62 | fi 63 | 64 | if [ ! -f "$ssh_file" ]; then 65 | echoerr "File not found: $ssh_file" 66 | return 1 67 | else 68 | ssh_key="$(escape_value "$(cat "$ssh_file")")" 69 | fi 70 | 71 | if ! make_new_ssh_payload "$ssh_name" "$ssh_key"; then 72 | echoerr "Error generating payload file for SSH key creation" 73 | return 1 74 | fi 75 | 76 | echodebug "API request for new SSH key:" 77 | url="$address/api/v2/organizations/$org/ssh-keys" 78 | if ! new_resp="$(tfh_api_call -d @"$payload" "$url")"; then 79 | echoerr "Error creating SSH key $ssh_name" 80 | return 1 81 | fi 82 | 83 | cleanup "$payload" 84 | 85 | echo "Created new SSH key $ssh_name" 86 | ) 87 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_show.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_ssh_show () ( 24 | ssh_name="$1" 25 | ssh_id="$2" 26 | 27 | if [ -z $ssh_name$ssh_id ]; then 28 | exec $0 ssh show help 29 | return 1 30 | fi 31 | 32 | # Use the list command to retrieve all keys, then narrow down 33 | # the output to the one of interest. 34 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_ssh_list.sh" 35 | 36 | if ! listing="$(tfh_ssh_list)"; then 37 | # The listing command will have printed error messages. 38 | return 1 39 | fi 40 | 41 | ssh_show="$(echo "$listing" | awk -v name="$ssh_name" -v id="$ssh_id" ' 42 | name && $1 == name; 43 | id && $2 == id')" 44 | 45 | if [ -n "$ssh_show" ]; then 46 | echo "$ssh_show" 47 | else 48 | echoerr "SSH key not found" 49 | return 1 50 | fi 51 | ) 52 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_unassign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_unassign_ssh_payload () { 28 | 29 | cat > "$payload" <&3 42 | } 43 | 44 | tfh_ssh_unassign () ( 45 | unassign_ws="$1" 46 | 47 | if [ -z "$unassign_ws" ]; then 48 | if ! check_required ws; then 49 | echoerr 'A positional parameter is also accepted for this command:' 50 | echoerr 'tfh ssh unassign WORKSPACE_NAME' 51 | return 1 52 | else 53 | unassign_ws="$ws" 54 | fi 55 | fi 56 | 57 | payload="$TMPDIR/tfe-new-payload-$(junonia_randomish_int)" 58 | 59 | # Ensure all of the common required variables are set 60 | if ! check_required org token address; then 61 | return 1 62 | fi 63 | 64 | if ! make_unassign_ssh_payload; then 65 | echoerr "Error generating payload file for SSH key unassignment" 66 | return 1 67 | fi 68 | 69 | # Need the workspace ID from the workspace name 70 | echodebug "API request to show workspace:" 71 | url="$address/api/v2/organizations/$org/workspaces/$unassign_ws" 72 | if ! show_resp="$(tfh_api_call "$url")"; then 73 | echoerr "Error showing workspace information for $unassign_ws" 74 | return 1 75 | fi 76 | 77 | workspace_id="$(printf "%s" "$show_resp" | jq -r '.data.id')" 78 | 79 | echodebug "API request for SSH key unassignment:" 80 | url="$address/api/v2/workspaces/$workspace_id/relationships/ssh-key" 81 | if ! unassign_resp="$(tfh_api_call --request PATCH -d @"$payload" "$url")"; then 82 | echoerr "Error unassigning SSH key from $unassign_ws" 83 | return 1 84 | fi 85 | 86 | cleanup "$payload" 87 | 88 | echo "Unassigned SSH key from $unassign_ws" 89 | ) 90 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_ssh_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_new_ssh_payload () { 28 | 29 | # $1 attribute contents 30 | 31 | cat > "$payload" <&3 44 | } 45 | 46 | tfh_ssh_update () ( 47 | ssh_name="$1" 48 | ssh_id="$2" 49 | ssh_new_name="$3" 50 | ssh_file="$4" 51 | 52 | if [ -z $ssh_name$ssh_id$ssh_new_name$ssh_file ]; then 53 | exec $0 ssh update help 54 | return 1 55 | fi 56 | 57 | payload="$TMPDIR/tfe-new-payload-$(junonia_randomish_int)" 58 | attr_obj= 59 | 60 | if [ -n "$ssh_new_name" ]; then 61 | [ "$attr_obj" ] && attr_obj="$attr_obj," 62 | attr_obj="$attr_obj \"name\": \"$ssh_new_name\"" 63 | elif [ -n "$ssh_file" ]; then 64 | if [ ! -f "$ssh_file" ]; then 65 | echoerr "File not found: $ssh_file" 66 | return 1 67 | else 68 | ssh_key="$(escape_value "$(cat "$ssh_file")")" 69 | [ "$attr_obj" ] && attr_obj="$attr_obj," 70 | attr_obj="$attr_obj \"value\": \"$ssh_key\"" 71 | fi 72 | else 73 | echoerr "One of -ssh-new-name or -ssh-file is required" 74 | return 1 75 | fi 76 | 77 | # Ensure all of org, etc, are set. Workspace is not required. 78 | if ! check_required org token address; then 79 | return 1 80 | fi 81 | 82 | if [ -z "$ssh_name" ] && [ -z "$ssh_id" ]; then 83 | echoerr "One of -ssh-name or -ssh-id is required" 84 | return 1 85 | fi 86 | 87 | if [ -n "$ssh_name" ] && [ -n "$ssh_id" ]; then 88 | echoerr "Only one of -ssh-name or -ssh-id should be specified" 89 | return 1 90 | fi 91 | 92 | if [ -n "$ssh_name" ]; then 93 | # Use the show command to check for and retrieve the ID of the key 94 | # in the case of being passed a name. 95 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_ssh_show.sh" 96 | 97 | # Pass the command line arguments to show and get back a key (or error) 98 | if ! ssh_show="$(tfh_ssh_show "$ssh_name")"; then 99 | # The show command will have printed error messages. 100 | return 1 101 | fi 102 | 103 | # Really, if it's empty then tfh_ssh_show should have exited non-zero 104 | if [ -z "$ssh_show" ]; then 105 | echoerr "SSH key not found" 106 | return 1 107 | fi 108 | 109 | ssh_id="$(echo "$ssh_show" | cut -d ' ' -f 2)" 110 | else 111 | # To simplify error reporting later, we'll print ssh_show, but if 112 | # we were given an ID set ssh_show to the ID as it will be empty. 113 | ssh_show="$ssh_id" 114 | fi 115 | 116 | # This shouldn't happen... 117 | if [ -z "$ssh_id" ]; then 118 | echoerr "SSH key not found" 119 | return 1 120 | fi 121 | 122 | if ! make_new_ssh_payload "$attr_obj"; then 123 | echoerr "Error generating payload file for SSH key update" 124 | return 1 125 | fi 126 | 127 | echodebug "API request for update SSH key:" 128 | url="$address/api/v2/ssh-keys/$ssh_id" 129 | if ! update_resp="$(tfh_api_call --request PATCH -d @"$payload" "$url")"; then 130 | echoerr "Error updating SSH key $ssh_show" 131 | return 1 132 | fi 133 | 134 | cleanup "$payload" 135 | 136 | echo "Updated SSH key $ssh_name" 137 | ) 138 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_workspace () { 24 | echodebug "exec with help command" 25 | exec $0 workspace help 26 | } 27 | 28 | 29 | # Gets the workspace ID given the organization name and workspace name 30 | _fetch_ws_id () { 31 | echodebug "Requesting workspace information for $1/$2" 32 | 33 | url="$address/api/v2/organizations/$1/workspaces/$2" 34 | if ! ws_id_resp="$(tfh_api_call "$url")"; then 35 | echoerr "unable to fetch workspace information for $1/$2" 36 | return 1 37 | fi 38 | 39 | if ! ws_id="$(printf "%s" "$ws_id_resp" | jq -r '.data.id')"; then 40 | echoerr "could not parse response for ID of workspace $1/$2" 41 | return 1 42 | fi 43 | 44 | echodebug "Workspace ID: $ws_id" 45 | 46 | echo "$ws_id" 47 | } 48 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_delete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_workspace_delete () { 24 | # Positional workspace value 25 | del_ws="$prefix$1" 26 | 27 | if [ -z "$del_ws" ]; then 28 | if ! check_required ws; then 29 | echoerr 'For workspace commands, a positional parameter is also accepted:' 30 | echoerr 'tfh workspace delete WORKSPACE_NAME' 31 | return 1 32 | else 33 | del_ws="$ws" 34 | fi 35 | fi 36 | 37 | # Ensure that the rest of the required items have values 38 | if ! check_required org token address; then 39 | return 1 40 | fi 41 | 42 | echodebug "API request to delete workspace:" 43 | url="$address/api/v2/organizations/$org/workspaces/$del_ws" 44 | if ! tfh_api_call -X DELETE "$url" >/dev/null; then 45 | echoerr "Error deleting workspaces $org/$del_ws" 46 | return 1 47 | fi 48 | 49 | echo "Deleted $org/$del_ws" 50 | } 51 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_workspace_list () { 24 | # Ensure all of org, etc, are set. Workspace is not required. 25 | if ! check_required org token address; then 26 | return 1 27 | fi 28 | 29 | echodebug "API request to list workspaces:" 30 | url="$address/api/v2/organizations/$org/workspaces" 31 | if ! list_resp="$(tfh_api_call "$url")"; then 32 | echoerr "Error listing workspaces for $org" 33 | return 1 34 | fi 35 | 36 | listing="$(printf "%s" "$list_resp" | 37 | jq -r --arg ws "$ws" ' 38 | .data[] 39 | | if .attributes.name == $ws then 40 | "* " + .attributes.name 41 | else 42 | " " + .attributes.name 43 | end')" 44 | 45 | # Produce the listing, sorted. Sort on the third character of each line 46 | # as each is indented two spaces and there may be one marked with an *. 47 | echo "$listing" | sort -k 1.3 48 | } 49 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_new.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_new_workspace_payload () { 28 | 29 | # $1 new workspace name 30 | # $2 auto-apply 31 | # $3 TF version 32 | # $4 working dir 33 | # $5 oauth token ID 34 | # $6 VCS branch 35 | # $7 vcs submodules 36 | # $8 VCS repo ID (e.g. "github_org/github_repo") 37 | # $9 queue-all-runs 38 | 39 | cat > "$payload" <> "$payload" <> "$payload" <> "$payload" <&3 75 | } 76 | 77 | tfh_workspace_new () { 78 | new_ws="$prefix$1" 79 | auto_apply="$2" 80 | tf_version="$3" 81 | working_dir="$4" 82 | vcs_id="$5" 83 | vcs_branch="$6" 84 | vcs_submodules="$7" 85 | oauth_id="$8" 86 | queue_all_runs="$9" 87 | 88 | if [ -z "$vcs_submodules" ];then 89 | vcs_submodules=false 90 | fi 91 | 92 | if [ -z "$new_ws" ]; then 93 | if ! check_required ws; then 94 | echoerr 'For workspace commands, a positional parameter is also accepted:' 95 | echoerr 'tfh workspace new WORKSPACE_NAME' 96 | return 1 97 | else 98 | new_ws="$ws" 99 | fi 100 | fi 101 | 102 | # Ensure that the rest of the required items have values 103 | if ! check_required org token address; then 104 | return 1 105 | fi 106 | 107 | if [ $auto_apply ]; then 108 | auto_apply=true 109 | else 110 | auto_apply=false 111 | fi 112 | 113 | if [ $queue_all_runs ]; then 114 | queue_all_runs=true 115 | else 116 | queue_all_runs=false 117 | fi 118 | 119 | payload="$TMPDIR/new-ws-payload-$(junonia_randomish_int)" 120 | 121 | # Need oauth if vcs was specified 122 | if [ -n "$vcs_id" ]; then 123 | # If no oauth id was given, then see if there is only one and use 124 | # that one 125 | if [ -z "$oauth_id" ]; then 126 | echodebug "API request for OAuth tokens for $org" 127 | 128 | url="$address/api/v2/organizations/$org/oauth-tokens" 129 | oauth_list_resp="$(tfh_api_call "$url")" 130 | 131 | oauth_id="$(printf "%s" "$oauth_list_resp" | jq -r '.data[] | .id')" 132 | echodebug "OAuth IDs:" 133 | echodebug "$oauth_id" 134 | 135 | if [ 1 -ne "$(echo "$oauth_id" | wc -l)" ] || [ -z "$oauth_id" ]; then 136 | echoerr "Error obtaining a default OAuth ID. Choices are:" 137 | 138 | oauth_clients="$(printf "%s" "$oauth_list_resp" | jq -r '.data[] | .relationships."oauth-client".data.id')" 139 | 140 | echodebug "OAuth client list:" 141 | echodebug "$oauth_clients" 142 | 143 | # Given 144 | # ot-1 145 | # ot-2 146 | # ot-3 147 | # oc-1 148 | # oc-2 149 | # oc-3 150 | # Create 151 | # ot-1 oc-1 152 | # ot-2 oc-2 153 | # ot-3 oc-3 154 | tokens_clients="$(printf '%s\n%s' "$oauth_id" "$oauth_clients" | awk ' 155 | /^ot/ { 156 | ot[i++]=$0 157 | } 158 | /^oc/ { 159 | print ot[j++], $0 160 | }')" 161 | 162 | echodebug "Tokens and clients:" 163 | echodebug "$tokens_clients" 164 | 165 | echo "$tokens_clients" | while read -r ot_oc; do 166 | ot="$(echo "$ot_oc" | cut -d ' ' -f 1)" 167 | oc="$(echo "$ot_oc" | cut -d ' ' -f 2)" 168 | 169 | url="$address/api/v2/oauth-clients/$oc" 170 | oauth_client_resp="$(tfh_api_call "$url")" 171 | 172 | printf '%s' "$oauth_list_resp" | \ 173 | jq -r --arg ID "$ot" ' 174 | .data [] 175 | | select(.id == $ID) 176 | | "id = " + .id, 177 | "created-at = " + .attributes."created-at", 178 | "user = " + .attributes."service-provider-user"' 179 | 180 | printf '%s' "$oauth_client_resp" | jq -r ' 181 | "oauth-client = " + .data.id, 182 | "created-at = " + .data.attributes."created-at", 183 | "callback-url = " + .data.attributes."callback-url", 184 | "connect-path = " + .data.attributes."connect-path", 185 | "service-provider = " + .data.attributes."service-provider", 186 | "display-name = " + .data.attributes."service-provider-display-name", 187 | "http-url = " + .data.attributes."http-url", 188 | "api-url = " + .data.attributes."api-url", 189 | "key = " + .data.attributes.key, 190 | "secret = " + (.data.attributes.secret|tostring), 191 | "rsa-public-key = " + (.data.attributes."rsa-public-key"|tostring)' 192 | 193 | echo 194 | done 195 | 196 | return 1 197 | fi 198 | fi 199 | fi 200 | 201 | # $1 new workspace name 202 | # $2 auto-apply 203 | # $3 TF version 204 | # $4 working dir 205 | # $5 oauth token ID 206 | # $6 VCS branch 207 | # $7 VCS submodules 208 | # $8 VCS repo ID (e.g. "github_org/github_repo") 209 | # $9 queue-all-runs 210 | make_new_workspace_payload "$new_ws" "$auto_apply" "$tf_version" \ 211 | "$working_dir" "$oauth_id" "$vcs_branch" \ 212 | "$vcs_submodules" "$vcs_id" "$queue_all_runs" 213 | if [ 0 -ne $? ]; then 214 | echoerr "Error generating payload file for workspace creation" 215 | return 1 216 | fi 217 | 218 | echodebug "API request for new workspace:" 219 | url="$address/api/v2/organizations/$org/workspaces" 220 | if ! new_resp="$(tfh_api_call -d @"$payload" "$url")"; then 221 | echoerr "Error creating workspace $org/$new_ws" 222 | return 1 223 | fi 224 | 225 | cleanup "$payload" 226 | 227 | echo "Created new workspace $org/$new_ws" 228 | } 229 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_select.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_workspace_select () { 24 | sel_ws="$prefix$1" 25 | 26 | if [ -z "$sel_ws" ]; then 27 | echoerr "Exactly one argument required: workspace name" 28 | return 1 29 | fi 30 | 31 | . "$JUNONIA_PATH/lib/tfh/cmd/tfh_workspace_list.sh" 32 | 33 | if ! ws_list="$(tfh_workspace_list)"; then 34 | # An error from tfh_list should have been printed 35 | return 1 36 | fi 37 | 38 | if ! echo "$ws_list" | grep -E "^[\* ] $1$" >/dev/null 2>&1; then 39 | echoerr "Workspace not found: $1" 40 | return 1 41 | fi 42 | 43 | # Write the workspace configuration 44 | if err="$(junonia_update_config "$JUNONIA_CONFIG" "TFH_name=$sel_ws")"; then 45 | echo "Switched to workspace: $sel_ws" 46 | else 47 | echoerr "$err" 48 | fi 49 | } 50 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_show.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | tfh_workspace_show () { 24 | show_ws="$1" 25 | 26 | if [ -z "$show_ws" ]; then 27 | if ! check_required ws; then 28 | echoerr 'For workspace commands, a positional parameter is also accepted:' 29 | echoerr 'tfh workspace show WORKSPACE_NAME' 30 | return 1 31 | else 32 | show_ws="$ws" 33 | fi 34 | fi 35 | 36 | echodebug "API request to show workspace:" 37 | url="$address/api/v2/organizations/$org/workspaces/$show_ws" 38 | if ! show_resp="$(tfh_api_call "$url")"; then 39 | echoerr "Error showing workspace information for $show_ws" 40 | return 1 41 | fi 42 | 43 | printf "%s" "$show_resp" | jq -r ' 44 | "name = " + .data.attributes.name, 45 | "id = " + .data.id, 46 | "auto-apply = " + (.data.attributes."auto-apply"|tostring), 47 | "queue-all-runs = " + (.data.attributes."queue-all-runs"|tostring), 48 | "locked = " + (.data.attributes.locked|tostring), 49 | "created-at = " + .data.attributes."created-at", 50 | "working-directory = " + .data.attributes."working-directory", 51 | "terraform-version = " + .data.attributes."terraform-version"' 52 | } 53 | -------------------------------------------------------------------------------- /tfh/lib/tfh/cmd/tfh_workspace_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## ------------------------------------------------------------------- 4 | ## 5 | ## Copyright (c) 2018 HashiCorp. All Rights Reserved. 6 | ## 7 | ## This file is provided to you under the Mozilla Public License 8 | ## Version 2.0 (the "License"); you may not use this file 9 | ## except in compliance with the License. You may obtain 10 | ## a copy of the License at 11 | ## 12 | ## https://www.mozilla.org/en-US/MPL/2.0/ 13 | ## 14 | ## Unless required by applicable law or agreed to in writing, 15 | ## software distributed under the License is distributed on an 16 | ## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | ## KIND, either express or implied. See the License for the 18 | ## specific language governing permissions and limitations 19 | ## under the License. 20 | ## 21 | ## ------------------------------------------------------------------- 22 | 23 | ## 24 | ## Helper functions 25 | ## 26 | 27 | make_update_workspace_payload () { 28 | cat > "$payload" << EOF 29 | { 30 | "data": { 31 | "attributes": { 32 | $1 33 | }, 34 | "type": "workspaces" 35 | } 36 | } 37 | EOF 38 | 39 | echodebug "Payload contents:" 40 | cat $payload 1>&3 41 | } 42 | 43 | tfh_workspace_update () { 44 | up_ws="$prefix$1" 45 | auto_apply="$2" 46 | tf_version="$3" 47 | working_dir="$4" 48 | vcs_id="$5" 49 | vcs_branch="$6" 50 | vcs_submodules="$7" 51 | oauth_id="$8" 52 | queue_all_runs="$9" 53 | 54 | payload="$TMPDIR/tfe-migrate-payload-$(junonia_randomish_int)" 55 | 56 | vcs_obj= 57 | attr_obj= 58 | 59 | if [ -z "$up_ws" ]; then 60 | if ! check_required ws; then 61 | echoerr 'For workspace commands, a positional parameter is also accepted:' 62 | echoerr 'tfh workspace update WORKSPACE_NAME' 63 | return 1 64 | else 65 | up_ws="$ws" 66 | fi 67 | fi 68 | 69 | if [ $auto_apply ] && echo "$TFH_CMDLINE" | grep -Eq -- '-auto-apply'; then 70 | [ "$attr_obj" ] && attr_obj="$attr_obj," 71 | attr_obj="$attr_obj \"auto-apply\": \"$auto_apply\"" 72 | fi 73 | 74 | if [ $queue_all_runs ] && echo "$TFH_CMDLINE" | grep -Eq -- '-queue-all-runs'; then 75 | [ "$attr_obj" ] && attr_obj="$attr_obj," 76 | attr_obj="$attr_obj \"queue-all-runs\": \"$queue_all_runs\"" 77 | fi 78 | 79 | if [ -n "$tf_version" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-terraform-version'; then 80 | [ "$attr_obj" ] && attr_obj="$attr_obj," 81 | attr_obj="$attr_obj \"terraform-version\": \"$tf_version\"" 82 | fi 83 | 84 | if [ -n "$working_dir" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-working-dir'; then 85 | [ "$attr_obj" ] && attr_obj="$attr_obj," 86 | attr_obj="$attr_obj \"working-directory\": \"$working_dir\"" 87 | fi 88 | 89 | if [ -n "$vcs_id" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-vcs-id'; then 90 | [ "$vcs_obj" ] && vcs_obj="$vcs_obj," 91 | vcs_obj="$vcs_obj \"identifier\": \"$vcs_id\"" 92 | fi 93 | 94 | if [ -n "$vcs_branch" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-vcs-branch'; then 95 | [ "$vcs_obj" ] && vcs_obj="$vcs_obj," 96 | vcs_obj="$vcs_obj \"branch\": \"$vcs_branch\"" 97 | fi 98 | 99 | if [ -n "$vcs_submodules" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-vcs-submodules'; then 100 | [ "$vcs_obj" ] && vcs_obj="$vcs_obj," 101 | vcs_obj="$vcs_obj \"ingress-submodules\": \"$vcs_submodules\"" 102 | fi 103 | 104 | if [ -n "$oauth_id" ] && echo "$TFH_CMDLINE" | grep -Eq -- '-oauth-id'; then 105 | [ "$vcs_obj" ] && vcs_obj="$vcs_obj," 106 | vcs_obj="$vcs_obj \"oauth-token-id\": \"$oauth_id\"" 107 | fi 108 | 109 | if [ "$vcs_obj" ]; then 110 | [ "$attr_obj" ] && attr_obj="$attr_obj," 111 | attr_obj="$attr_obj \"vcs-repo\": {$vcs_obj}" 112 | fi 113 | 114 | make_update_workspace_payload "$attr_obj" 115 | if [ 0 -ne $? ]; then 116 | echoerr "Error generating payload file for workspace update" 117 | return 1 118 | fi 119 | 120 | echodebug "API request to update workspace:" 121 | url="$address/api/v2/organizations/$org/workspaces/$up_ws" 122 | if ! update_resp="$(tfh_api_call --request PATCH -d @"$payload" "$url")"; then 123 | echoerr "Error updating workspace $org/$up_ws" 124 | return 1 125 | fi 126 | 127 | cleanup "$payload" 128 | 129 | echo "Updated workspace $org/$up_ws" 130 | } 131 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/runner: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f ./utils ]; then 4 | . ./utils 5 | else 6 | echo "Not found: ./utils. Run testrunner from libexec/tfh/tests" 1>&2 7 | exit 1 8 | fi 9 | 10 | if [ -f ../../../bin/tfh ]; then 11 | tfh_dir="$(cd ../../../bin && pwd -P)" 12 | export PATH="$tfh_dir:$PATH" 13 | else 14 | echoerr "Path to tfh not found. Run testrunner from libexec/tfh/tests" 15 | exit 1 16 | fi 17 | 18 | if [ -n "$TFH_org" ]; then 19 | # TFH_org may be set but not exported 20 | export TFH_org 21 | else 22 | echoerr "TFH_org must be set to run tests" 23 | exit 1 24 | fi 25 | 26 | # A particular test file was requested 27 | if [ -n "$1" ]; then 28 | # We'll cd into the tests directory, so we only want the test file name. 29 | tfh_tests="$(basename $1)" 30 | 31 | if [ ! -f "./tests/$tfh_tests" ]; then 32 | echoerr "Specific test requested but not found:" 33 | echoerr "$tfh_tests" 34 | exit 1 35 | fi 36 | fi 37 | 38 | if [ -n "$TFH_TEST_SHELL" ]; then 39 | shells="$TFH_TEST_SHELL" 40 | elif [ -n "$TFH_TEST_ALL_SHELLS" ]; then 41 | # posh seems broken for sourcing files when run as 42 | # posh ./tests/test_name 43 | # When run interatively it fails to have the right return code for 44 | # at least listing workspaces. 45 | 46 | # Heirloom bourne shell produces the wrong exit code when executed as 47 | # heirloom-sh ./tests/test_name 48 | # It does seem to work interactively so I'll only test it every once in 49 | # a while 50 | 51 | # Might want to test with /bin/sh also if your /bin/sh is not named 52 | # anything else below. 53 | shells="bash dash ksh zsh mksh yash pdksh busybox ash" 54 | else 55 | # If nothing specific is requested, run tests with whatever /bin/sh is. 56 | shells="/bin/sh" 57 | fi 58 | 59 | cd ./tests 60 | for shell in $shells; do 61 | # Verify the shell is present 62 | if [ -z "$(command -v $shell)" ]; then 63 | echoerr "Skipping tests with $shell - not found" 64 | continue 65 | fi 66 | 67 | echoerr "Testing with $shell" 68 | if [ -z "$tfh_tests" ]; then 69 | tfh_tests="$(find . -type f -name '[0-9][0-9]*_test*' ! -path '*/.*' | sort)" 70 | fi 71 | 72 | for tfh_test in $tfh_tests; do 73 | # busybox is special because it has to be called as `busybox sh` 74 | if [ "$shell" = busybox ]; then 75 | shell="busybox sh" 76 | fi 77 | 78 | if ! $shell "$tfh_test"; then 79 | exit 1 80 | fi 81 | done 82 | echoerr "Completed $shell tests" 83 | echoerr 84 | done 85 | cd .. 86 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/tests/0000_test_help: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ../utils 4 | 5 | # tfh by itself should return 1 and contain certain text 6 | output="$(tfh 2>&1)" 7 | assert_equal "tfh_ret_1" 1 $? 8 | assert_contains "tfh_has_synopsis" "SYNOPSIS" "$output" 9 | assert_contains "tfh_has_description" "DESCRIPTION" "$output" 10 | assert_contains "tfh_has_help_describe" " help" "$(echo "$output" | awk 'NR>6')" 11 | 12 | # tfh help pushconfig should return 0 and contain the tfh pushconfig synopsis 13 | pushconfig_output="$(tfh help pushconfig 2>&1)" 14 | assert_equal "tfh_help_pushconfig_ret_0" 0 $? 15 | assert_contains "tfh_help_pushconfig_has_synopsis" "tfh pushconfig -- " "$pushconfig_output" 16 | 17 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/tests/0010_test_workspace: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ../utils 4 | 5 | ws_name="tfh-test-workspace-$(random_enough)" 6 | 7 | # List workspaces should succeed and return 0 8 | tfh workspace list -org "$TFH_org" >/dev/null 2>&1 9 | assert_equal "tfh_ws_list_ret_0" 0 $? 10 | 11 | # create a new workspace without any options 12 | output="$(tfh workspace new -name "$ws_name" 2>&1)" 13 | assert_equal "tfh_ws_new_noop_ret_0" 0 $? 14 | assert_contains "tfh_ws_new_noop_has_created" "Created" "$output" 15 | 16 | # List workspaces should now contain the new workspace 17 | output="$(tfh workspace list -org "$TFH_org" 2>&1)" 18 | assert_equal "tfh_ws_list_new_ret_0" 0 $? 19 | assert_contains "tfh_ws_list_contains_new" "$ws_name" "$output" 20 | 21 | # Deleting the workspace should succeed and return 0 22 | tfh workspace delete -name "$ws_name" >/dev/null 2>&1 23 | assert_equal "tfh_ws_delete_ret_0" 0 $? 24 | 25 | # List workspaces should now not contain the new/deleted workspace 26 | output="$(tfh workspace list 2>&1)" 27 | assert_equal "tfh_ws_list_deleted_ret_0" 0 $? 28 | assert_contains_not "tfh_ws_list_deleted_no_contains_new" "$ws_name" "$output" 29 | 30 | 31 | ws_name="tfh-test-workspace-$(random_enough)" 32 | 33 | # create a new workspace with all non-vcs options 34 | output="$(tfh workspace new -name "$ws_name" -auto-apply -terraform-version 0.10.8 -working-dir foo -queue-all-runs true 2>&1)" 35 | assert_equal "tfh_ws_new_op_ret_0" 0 $? 36 | assert_contains "tfh_ws_new_op_has_created" "Created" "$output" 37 | 38 | # List workspaces should now contain the new workspace 39 | output="$(tfh workspace list -org "$TFH_org" 2>&1)" 40 | assert_equal "tfh_ws_list_new_op_ret_0" 0 $? 41 | assert_contains "tfh_ws_list_contains_new_op" "$ws_name" "$output" 42 | 43 | # Update the workspace 44 | output="$(tfh workspace update -name "$ws_name" -auto-apply false -terraform-version 0.11.11 -working-dir $empty -queue-all-runs 0 2>&1)" 45 | assert_equal "tfh_ws_update_ret_0" 0 $? 46 | assert_contains "tfh_ws_update_has_updated" "Updated" "$output" 47 | 48 | # Deleting the workspace should succeed and return 0 49 | tfh workspace delete -org "$TFH_org" -name "$ws_name" >/dev/null 2>&1 50 | assert_equal "tfh_ws_delete_ret_0" 0 $? 51 | 52 | # List workspaces should now not contain the new/deleted workspace 53 | output="$(tfh workspace list 2>&1)" 54 | assert_equal "tfh_ws_list_deleted_updated_ret_0" 0 $? 55 | assert_contains_not "tfh_ws_list_deleted_updated_no_contains_new" "$ws_name" "$output" 56 | 57 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/tests/0020.tfvars: -------------------------------------------------------------------------------- 1 | var1_0020 = "0020" 2 | var2_0020 = "0020" 3 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/tests/0020_config/vars.tf: -------------------------------------------------------------------------------- 1 | variable "var1_0020" {} 2 | variable "var2_0020" {} 3 | 4 | variable "var_int_0020" { 5 | default = 42 6 | } 7 | 8 | variable "var_str_0020" { 9 | default = "foo" 10 | } 11 | 12 | variable "var_list_0020" { 13 | default = ["one", "two"] 14 | } 15 | 16 | variable "var_map_0020" { 17 | default = { 18 | k1 = "v1", 19 | k2 = "v2" 20 | } 21 | } 22 | 23 | resource "null_resource" "n" {} 24 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/tests/0020_test_vars: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . ../utils 4 | 5 | # Workspace creation and deletion are confirmed to work by previous tests. 6 | # Create a workspace to use for these tests. 7 | ws_name="tfh-test-workspace-$(random_enough)" 8 | tfh workspace new -name "$ws_name" >/dev/null 2>&1 9 | 10 | # After initial creation there should be no tf variables 11 | output="$(tfh pullvars -name "$ws_name" 2>&1)" 12 | assert_equal "tfh_pullvars_empty_ret_0" 0 $? 13 | assert_equal "tfh_pullvars_empty" "" "$output" 14 | 15 | # After initial creation there should be no env variables 16 | output="$(tfh pullvars -name "$ws_name" -env true 2>&1)" 17 | assert_equal "tfh_pullvars_empty_env_ret_0" 0 $? 18 | assert_equal "tfh_pullvars_empty_env" "" "$output" 19 | 20 | # Pushing up a tf variable should succeed and have outputted the creation info 21 | output="$(tfh pushvars -name "$ws_name" -var 'var=foo' 2>&1)" 22 | assert_equal "tfh_pushvars_var_ret_0" 0 $? 23 | assert_contains "tfh_pushvars_var_output" "Creating var type:terraform hcl:false sensitive:false value:foo" "$output" 24 | 25 | # Pulling it should succeed and contain the name and value in tfvars format 26 | output="$(tfh pullvars -name "$ws_name" -var 'var' 2>&1)" 27 | assert_equal "tfh_pullvars_var_ret_0" 0 $? 28 | assert_contains "tfh_pullvars_var_output" 'var = "foo"' "$output" 29 | 30 | # Pushing up a sensitive tf variable should succeed 31 | output="$(tfh pushvars -name "$ws_name" -svar 'svar=bar' 2>&1)" 32 | assert_equal "tfh_pushvars_svar_ret_0" 0 $? 33 | assert_contains "tfh_pushvars_svar_output" "Creating svar type:terraform hcl:false sensitive:true value:REDACTED" "$output" 34 | 35 | # Pulling it should succeed and contain the name but the value should be empty 36 | output="$(tfh pullvars -name "$ws_name" -var 'svar' 2>&1)" 37 | assert_equal "tfh_pullvars_var_ret_0" 0 $? 38 | assert_contains "tfh_pullvars_svar_output" 'svar = ""' "$output" 39 | 40 | # Pushing up an hcl tf variable should succeed 41 | output="$(tfh pushvars -name "$ws_name" -hcl-var 'hvar=["one", "two"]' 2>&1)" 42 | assert_equal "tfh_pushvars_hvar_ret_0" 0 $? 43 | 44 | # Note: Care here has to be taken as the quotes and brackets need extra 45 | # escaping. We expect for the output in this case to contain brackets and 46 | # backslashes. The search term is fed into grep so things need to be properly 47 | # escaped going into it. 48 | # At any rate the output should look like: 49 | # Creating hvar type:terraform hcl:true sensitive:false value:[\"one\", \"two\"] 50 | assert_contains "tfh_pushvars_hclvar_output" 'Creating hvar type:terraform hcl:true sensitive:false value:\[\\"one\\", \\"two\\"\]' "$output" 51 | 52 | # Pulling it should succeed and contain the name and value in tfvars format, with the value in the same format as when it was pushed. 53 | output="$(tfh pullvars -name "$ws_name" -var 'hvar' 2>&1)" 54 | assert_equal "tfh_pullvars_hclvar_ret_0" 0 $? 55 | assert_contains "tfh_pullvars_hclvar_output" 'hvar = \[\"one\", \"two\"\]' "$output" 56 | 57 | # Pushing up a sensitive hcl variable should succeed 58 | output="$(tfh pushvars -name "$ws_name" -shcl-var 'shvar={one="one", two="two"}' 2>&1)" 59 | assert_equal "tfh_pushvars_shvar_ret_0" 0 $? 60 | assert_contains "tfh_pushvars_var_output" 'Creating shvar type:terraform hcl:true sensitive:true value:REDACTED' "$output" 61 | 62 | # Pulling it should succeed and contain the name but the value should be empty 63 | output="$(tfh pullvars -name "$ws_name" -var 'shvar' 2>&1)" 64 | assert_equal "tfh_pullvars_shvar_ret_0" 0 $? 65 | assert_contains "tfh_pullvars_shvar_output" 'shvar = ""' "$output" 66 | 67 | # Pushing up an environment variable should succeed 68 | output="$(tfh pushvars -name "$ws_name" -env-var 'evar=baz' 2>&1)" 69 | assert_equal "tfh_pushvars_evar_ret_0" 0 $? 70 | assert_contains "tfh_pushvars_var_output" "Creating evar type:env hcl:false sensitive:false value:baz" "$output" 71 | 72 | # Pulling it should succeed and contain the name and value in shell format 73 | output="$(tfh pullvars -name "$ws_name" -env-var 'evar' 2>&1)" 74 | assert_equal "tfh_pullvars_evar_ret_0" 0 $? 75 | assert_contains "tfh_pullvars_evar_output" 'evar="baz"' "$output" 76 | 77 | # Pushing up a sensitive environment variable should succeed 78 | output="$(tfh pushvars -name "$ws_name" -senv-var 'sevar=qux' 2>&1)" 79 | assert_equal "tfh_pushvars_sevar_ret_0" 0 $? 80 | assert_contains "tfh_pushvars_var_output" "Creating sevar type:env hcl:false sensitive:true value:REDACTED" "$output" 81 | 82 | # Pulling it should succeed and contain the name but the value should be empty 83 | output="$(tfh pullvars -name "$ws_name" -env-var 'sevar' 2>&1)" 84 | assert_equal "tfh_pullvars_sevar_ret_0" 0 $? 85 | assert_contains "tfh_pullvars_sevar_output" 'sevar=' "$output" 86 | 87 | # Pushing variables from a tf file should succeed 88 | 89 | terraform init 0020_config > /dev/null 2>&1 90 | 91 | output="$(tfh pushvars ./0020_config -name "$ws_name")" 92 | assert_equal "tfh_pushvars_tf_file_ret_0" 0 $? 93 | assert_contains "tfh_pushvars_tf1_file_output" "Creating var_int_0020 type:terraform hcl:false sensitive:false value:42" "$output" 94 | assert_contains "tfh_pushvars_tf2_file_output" "Creating var_str_0020 type:terraform hcl:false sensitive:false value:foo" "$output" 95 | assert_contains "tfh_pushvars_tf3_file_output" "Creating var_list_0020 type:terraform hcl:true sensitive:false" "$output" 96 | assert_contains "tfh_pushvars_tf4_file_output" "Creating var_map_0020 type:terraform hcl:true sensitive:false" "$output" 97 | 98 | # Pulling them should succeed and contain the names and values in tfvars format 99 | output="$(tfh pullvars -name "$ws_name" 2>&1)" 100 | assert_equal "tfh_pullvars_tf_file_ret_0" 0 $? 101 | assert_contains "tfh_pullvars_tf1_file_output" 'var_int_0020 = "42"' "$output" 102 | assert_contains "tfh_pullvars_tf2_file_output" 'var_str_0020 = "foo"' "$output" 103 | assert_contains "tfh_pullvars_tf3_file_output" 'var_list_0020 = \[' "$output" 104 | assert_contains "tfh_pullvars_tf4_file_output" 'var_map_0020 = {' "$output" 105 | 106 | # Pushing variables from a tfvars file should succeed 107 | output="$(tfh pushvars -name "$ws_name" -var-file 0020.tfvars)" 108 | assert_equal "tfh_pushvars_var_file_ret_0" 0 $? 109 | assert_contains "tfh_pushvars_var_file_output" "Creating var1_0020 type:terraform hcl:false sensitive:false value:0020" "$output" 110 | assert_contains "tfh_pushvars_var_file_output" "Creating var2_0020 type:terraform hcl:false sensitive:false value:0020" "$output" 111 | 112 | # Pulling them should succeed and contain the names and values in tfvars format 113 | output="$(tfh pullvars -name "$ws_name" 2>&1)" 114 | assert_equal "tfh_pullvars_var_file_ret_0" 0 $? 115 | assert_contains "tfh_pullvars_var1_file_output" 'var1_0020 = "0020"' "$output" 116 | assert_contains "tfh_pullvars_var2_file_output" 'var2_0020 = "0020"' "$output" 117 | 118 | tfh workspace delete -name "$ws_name" >/dev/null 2>&1 119 | 120 | ws_name="tfh-test-workspace-$(random_enough)" 121 | tfh workspace new -name "$ws_name" >/dev/null 2>&1 122 | 123 | # Pushing variables from both a tfvars and tf file should succeed 124 | output="$(tfh pushvars ./0020_config -name "$ws_name" -var-file 0020.tfvars)" 125 | assert_equal "tfh_pushvars_tf_tfvars_file_ret_0" 0 $? 126 | assert_contains "tfh_pushvars_tf_tfvars1_file_output" "Creating var_int_0020 type:terraform hcl:false sensitive:false value:42" "$output" 127 | assert_contains "tfh_pushvars_tf_tfvars2_file_output" "Creating var_str_0020 type:terraform hcl:false sensitive:false value:foo" "$output" 128 | assert_contains "tfh_pushvars_tf_tfvars3_file_output" "Creating var_list_0020 type:terraform hcl:true sensitive:false" "$output" 129 | assert_contains "tfh_pushvars_tf_tfvars4_file_output" "Creating var_map_0020 type:terraform hcl:true sensitive:false" "$output" 130 | assert_contains "tfh_pushvars_tf_tfvars5_file_output" "Creating var1_0020 type:terraform hcl:false sensitive:false value:0020" "$output" 131 | assert_contains "tfh_pushvars_tf_tfvars6_file_output" "Creating var2_0020 type:terraform hcl:false sensitive:false value:0020" "$output" 132 | 133 | # Pulling them should succeed and contain the names and values in tfvars format 134 | output="$(tfh pullvars -name "$ws_name" 2>&1)" 135 | assert_equal "tfh_pullvars_tf_tfvars_file_ret_0" 0 $? 136 | assert_contains "tfh_pullvars_tf_tfvars1_file_output" 'var_int_0020 = "42"' "$output" 137 | assert_contains "tfh_pullvars_tf_tfvars2_file_output" 'var_str_0020 = "foo"' "$output" 138 | assert_contains "tfh_pullvars_tf_tfvars3_file_output" 'var_list_0020 = \[' "$output" 139 | assert_contains "tfh_pullvars_tf_tfvars4_file_output" 'var_map_0020 = {' "$output" 140 | assert_contains "tfh_pullvars_tf_tfvars5_file_output" 'var1_0020 = "0020"' "$output" 141 | assert_contains "tfh_pullvars_tf_tfvars6_file_output" 'var2_0020 = "0020"' "$output" 142 | 143 | rm -rf .terraform 144 | 145 | -------------------------------------------------------------------------------- /tfh/libexec/tfh/tests/utils: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # All output should go to stderr so that nothing is buffered 4 | echoerr () { printf "%s\n" "$@" 1>&2; } 5 | 6 | random_enough () { 7 | awk -v s="$(/bin/sh -c 'echo $$')" ' 8 | BEGIN{ 9 | srand(s) 10 | r=rand() rand() 11 | gsub(/0\./,"",r) 12 | print r 13 | }' 14 | } 15 | 16 | # Format test results and handle exiting 17 | _handle_test () { 18 | printf "$1 ... " 19 | if [ 0 -eq "$2" ]; then 20 | echoerr "passed" 21 | else 22 | echoerr "failed" 23 | echoerr "$3" 24 | 25 | # can't exit from here so instead kill our process group 26 | kill -PIPE 0 27 | fi 28 | } 29 | 30 | # Compare an expected value to a given value 31 | # $1 test name 32 | # $2 expected value 33 | # $3 value 34 | assert_equal () { 35 | test "$2" = "$3" 36 | _handle_test \ 37 | $1 \ 38 | $? \ 39 | "expected $3 to be $2" 40 | } 41 | 42 | # Ensure the contents of two files are the same 43 | # $1 test name 44 | # $2 file a 45 | # $3 file b 46 | assert_files_equal () { 47 | cmp -s "$2" "$3" 48 | _handle_test \ 49 | $1 \ 50 | $? \ 51 | "$(printf "files differ\n\n%s\n%s\n\n%s\n%s\n" "$2" "$(cat "$2")" $3 "$(cat "$3")")" 52 | } 53 | 54 | # Ensure a given text contains a term 55 | # $1 test name 56 | # $2 search term 57 | # $3 text to search 58 | assert_contains () { 59 | echo "$3" | grep "$2" 2>&1 >/dev/null 60 | _handle_test \ 61 | $1 \ 62 | $? \ 63 | "$(printf "term '%s' not found in:\n\n%s\n" "$2" "$3")" 64 | } 65 | 66 | # Ensure a given text is not present in a term 67 | # $1 test name 68 | # $2 search term 69 | # $3 text to search 70 | assert_contains_not () { 71 | ! echo "$3" | grep "$2" 2>&1 >/dev/null 72 | _handle_test \ 73 | $1 \ 74 | $? \ 75 | "$(printf "term '%s' was found in:\n\n%s\n" "$2" "$3")" 76 | } 77 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh.md: -------------------------------------------------------------------------------- 1 | ## `tfh` 2 | 3 | Perform operations relating to HashiCorp Terraform 4 | 5 | ### Synopsis 6 | 7 | tfh [SUBCOMMANDS] [PARAMETERS] [OPTIONS] 8 | 9 | ### Description 10 | 11 | Perform operations relating to HashiCorp Terraform artifacts (configurations, states) and services (Terraform Enterprise). 12 | 13 | ### Options 14 | 15 | * `-org ORGANIZATION` 16 | 17 | The name of the Terraform Enterprise organization. 18 | 19 | * `-name WORKSPACE_NAME` 20 | 21 | The name of the Terraform Enterprise workspace. 22 | 23 | * `-prefix WORKSPACE_PREFIX` 24 | 25 | Terraform Enterprise workspace prefix. Used when working with multiple workspaces in a single configuration. 26 | 27 | * `-token TOKEN` 28 | 29 | Access token for Terraform Enterprise API requests. Use of a `curlrc` file is encouraged to keep tokens out of environment variables and the process list. 30 | 31 | * `-curlrc FILEPATH` 32 | 33 | Curl configuration file providing an access token for Terraform Enterprise API requests. This file can be managed using the `tfh curl-config` command. 34 | 35 | * `-hostname HOSTNAME=app.terraform.io` 36 | 37 | The address of your Terraform Enterprise instance. Defaults to the SaaS hostname at https://app.terraform.io 38 | 39 | * `-v, -verbose` 40 | 41 | Enable verbose messages. 42 | 43 | * `-vv, -vverbose` 44 | 45 | Enable very verbose messages. 46 | 47 | * `-vvv, -vvverbose` 48 | 49 | Enable very, very verbose messages. 50 | 51 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_curl_config.md: -------------------------------------------------------------------------------- 1 | ## `tfh curl-config` 2 | 3 | Show and modify the curl configuration 4 | 5 | ### Synopsis 6 | 7 | tfe curl-config [TOKEN] [-tfrc] 8 | 9 | ### Description 10 | 11 | View or create a curlrc file for use with TFE. With no arguments, the curlrc file is displayed. If arguments are supplied a curlrc will be recreated. 12 | 13 | ### Options 14 | 15 | * `-tfrc` 16 | 17 | Recreate the curlrc file using the token in the .terraformrc file. 18 | 19 | * `-curltoken TOKEN` 20 | 21 | Recreate the curlrc file with the given token. 22 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_pullvars.md: -------------------------------------------------------------------------------- 1 | ## `tfh pullvars` 2 | 3 | Get one or more variables and values from a TFE workspace 4 | 5 | ### Synopsis 6 | 7 | tfh pullvars [OPTIONS] 8 | 9 | ### Description 10 | 11 | Get variables from a Terraform Enterprise workspace and write them to stdout. Displays Terraform variables in tfvars format and environment variables in shell format. 12 | 13 | By default, this command returns all Terraform variables. You can output all environment variables with '-env true', or output specific variables with the -var or -env-var options. 14 | 15 | Sensitive variable names are listed with empty values, since sensitive values can't be retrieved. 16 | 17 | ### Options 18 | 19 | * `-var NAME` 20 | 21 | Get a Terraform variable from the Terraform Enterprise workspace. This argument can be specified multiple times. 22 | 23 | * `-env-var NAME` 24 | 25 | Get an environment variable from the Terraform Enterprise workspace. This argument can be specified multiple times. 26 | 27 | * `-env` 28 | 29 | Whether to get all environment variables instead of Terraform variables. Defaults to false. 30 | 31 | ### Extended description 32 | 33 | Variables stored in a Terraform Enterprise workspace can be retrieved using the 34 | Terraform Enterprise API. The API includes both Terraform and environment 35 | variables. When retrieving variables, Terraform variable output is in `.tfvars` 36 | format, and environment variable output is in shell format. Thus the output is 37 | appropriate for redirecting into a `.tfvars` or `.sh` file. This can be useful 38 | for pulling variables from one workspace and pushing them to another workspace. 39 | 40 | Sensitive variables are write-only, so their values cannot be retrieved via the 41 | API. When requested, sensitive variable names are printed with an empty value. 42 | 43 | #### Examples 44 | 45 | ``` 46 | # Pull all Terraform variables from a workspace and output them 47 | # in .tfvars format to stdout 48 | tfh pullvars -org org_name -name workspace_name 49 | 50 | # Same as above, but redirect into a tfvars file 51 | tfh pullvars -org org_name -name workspace_name > my.tfvars 52 | 53 | # Output only the one Terraform variable specified 54 | tfh pullvars -org org_name -name workspace_name -var foo 55 | 56 | # Output all environment variables from the workspace. Redirect the output. 57 | tfh pullvars -org org_name -name workspace_name -env true > workspace.sh 58 | 59 | # Output a Terraform variable and an environment variable 60 | tfh pullvars -org org_name -name workspace_name -var foo -env-var CONFIRM_DESTROY 61 | ``` 62 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_pushconfig.md: -------------------------------------------------------------------------------- 1 | ## `tfh pushconfig` 2 | 3 | Upload a Terraform config to a TFE workspace and begin a run 4 | 5 | ### Synopsis 6 | 7 | tfh pushconfig [CONFIG_DIR] [OPTIONS] 8 | 9 | ### Description 10 | 11 | Use `tfh pushconfig [CONFIG_DIR] [-org ORG] [-name WORKSPACE]` to begin a run in TFE. Either a configuration can be uploaded, or the current configuration can be run, or a destroy plan can be run. 12 | 13 | ### Positional parameters 14 | 15 | * `CONFIG_DIR` 16 | 17 | Path to the configuration that should be uploaded. Defaults to the current working directory. 18 | 19 | ### Options 20 | 21 | * `-message MESSAGE=Queued via tfh` 22 | 23 | An optional message to associate with the run. Defaults to "Queued via tfh". 24 | 25 | * `-destroy` 26 | 27 | Queue a destroy plan. Defaults to false. 28 | 29 | * `-current-config BOOLEAN` 30 | 31 | Do not push a local configuration. Instead, queue a plan with the latest configuration supplied. 32 | 33 | * `-upload-modules=1` 34 | 35 | If true (default), then the modules are locked at their current checkout and uploaded completely. This prevents modules from being retrieved with "terraform init". This does not lock provider versions; use the "version" parameter in provider blocks in the configuration to accomplish that. 36 | 37 | * `-vcs=1` 38 | 39 | If true (default), push will upload only files committed to your VCS, if detected. Currently supports git repositories. 40 | 41 | * `-stream` 42 | 43 | After staring a plan, stream the logs back to the console. 44 | 45 | * `-poll SECONDS=0` 46 | 47 | Number of seconds to wait between polling the submitted run for a non-active status. Defaults to 0 (no polling). If streaming logs, controls the seconds between updates. 48 | 49 | ### Extended description 50 | 51 | If you've used `terraform push` with the legacy version of Terraform Enterprise, 52 | here are the important differences to notice: 53 | 54 | - `tfh pushconfig` does not read the remote configuration name from an `atlas` 55 | block in the configuration. It must be provided with the `-org` and `-name` 56 | options or with the `TFH_org` and `TFH_name` environment variables or with 57 | `TFH_org` and `TFH_name` in the configuration file. 58 | 59 | - `tfh pushconfig` does not upload providers or pin provider versions. 60 | Terraform Enterprise always downloads providers during the run. Use 61 | Terraform's built-in support for [pinning provider versions][pin] by setting 62 | `version` in the provider configuration block. Custom providers will be 63 | uploaded and used if they are tracked by the VCS and placed in the directory 64 | where `terraform` will be executed. 65 | 66 | [pin]: https://www.terraform.io/docs/configuration/providers.html#provider-versions 67 | 68 | 69 | #### Example 70 | 71 | Given the following repository layout: 72 | 73 | ``` 74 | infrastructure 75 | ├── env 76 | │   ├── dev 77 | │   │   └── dev.tf 78 | │   └── prod 79 | │   └── prod.tf 80 | └── modules 81 | ├── mod1 82 | │   └── mod1.tf 83 | └── mod2 84 | └── mod2.tf 85 | ``` 86 | 87 | Run any of the following commands to push the `prod` configuration (note that 88 | the workspace name must be specified on the command line with `-name` or using 89 | `TFH_org` and `TFH_name`): 90 | 91 | ``` 92 | # If run from the root of the repository 93 | [infrastructure]$ tfh pushconfig env/prog -org org_name -name workspace_name 94 | 95 | # If run from the prod directory 96 | [prod]$ tfh pushconfig -org org_name -name workspace_name 97 | 98 | # If run from anywhere else on the filesystem 99 | [anywhere]$ tfh pushconfig path/to/infrastructure/env/prod -org org_name -name workspace_name 100 | ``` 101 | 102 | In each of the above commands the contents of the `env/prod` directory 103 | (`prod.tf`) that are tracked by the VCS (git) are archived and uploaded to the 104 | Terraform Enterprise workspace. The files are located using effectively `cd 105 | env/prod && git ls-files`. 106 | 107 | If `-vcs 0` is specified then all files in `env/prod` are uploaded. If 108 | `-upload-modules 0` is specified then the `.terraform/modules` directory 109 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_pushvars.md: -------------------------------------------------------------------------------- 1 | ## `tfh pushvars` 2 | 3 | Upload Terraform and/or environment variables to a TFE workspace 4 | 5 | ### Synopsis 6 | 7 | tfh pushvars [OPTIONS] [CONFIG_DIR] 8 | 9 | ### Description 10 | 11 | Update variables in a Terraform Enterprise workspace, using values from files or from the command line. By default, this command skips variables that already have a value in Terraform Enterprise; to replace existing values, use the `-overwrite NAME` option. 12 | 13 | If a config dir is specified, pushvars loads the `terraform.tfvars` and `*.auto.tfvars` files in that directory and adds them to the variables set on the command line. If the config dir is omitted, pushvars only uses variables from the command line. 14 | 15 | All `-var` style args (except `-var-file`) can be set multiple times to operate on multiple variables with one command. 16 | 17 | If you specify a config directory to load variables from, it must be a directory where "terraform init" has been run. 18 | 19 | The terraform command is required if operating on a tfvars file or a Terraform configuration. 20 | 21 | ### Positional parameters 22 | 23 | * `CONFIG_DIR` 24 | 25 | ### Options 26 | 27 | * `-dry-run` 28 | 29 | Show what would be done without changing any variables in the workspace. Defaults to false. 30 | 31 | * `-var ASSIGNMENT [-var ASSIGNMENT ...]` 32 | 33 | Set a Terraform variable with a basic string value. 34 | 35 | * `-svar ASSIGNMENT [-svar ASSIGNMENT ...]` 36 | 37 | Set a sensitive Terraform variable with a basic string value. 38 | 39 | * `-hcl-var ASSIGNMENT [-hcl-var ASSIGNMENT ...]` 40 | 41 | Set a Terraform variable with an HCL value. 42 | 43 | * `-shcl-var ASSIGNMENT [-shcl-var ASSIGNMENT ...]` 44 | 45 | Set a sensitive Terraform variable with an HCL value. 46 | 47 | * `-env-var ASSIGNMENT [-env-var ASSIGNMENT ...]` 48 | 49 | Set an environment variable. 50 | 51 | * `-senv-var ASSIGNMENT [-senv-var ASSIGNMENT ...]` 52 | 53 | Set a sensitive environment variable. 54 | 55 | * `-delete NAME [-delete NAME]` 56 | 57 | Delete an existing Terraform variable. Can be set multiple times. 58 | 59 | * `-delete-env NAME [-delete-env NAME]` 60 | 61 | Delete an existing environment variable. Can be set multiple times. 62 | 63 | * `-overwrite NAME [-overwrite NAME]` 64 | 65 | Overwrite an existing value if the Terraform variable is already set in Terraform Enterprise. You must enable this for every variable you want to overwrite. Can be set multiple times. 66 | 67 | * `-overwrite-env NAME [-overwrite-env NAME]` 68 | 69 | Overwrite an existing value if the environment variable is already set in Terraform Enterprise. You must enable this for every variable you want to overwrite. Can be set multiple times. 70 | 71 | * `-overwrite-all` 72 | 73 | Overwrite the existing value of every variable being operated on if that variable is already set in Terraform Enterprise. Defaults to false. Use with extreme caution. To perform the overwrites, -dry-run must be explicitly set to false, otherwise a dry run will be performed. 74 | 75 | * `-var-file FILE` 76 | 77 | Load Terraform variables from a tfvars file. Files can only set non-sensitive variables. 78 | 79 | * `-hide-sensitive=1` 80 | 81 | Whether to hide sensitive values in output. Defaults to true. 82 | 83 | ### Extended description 84 | 85 | Like `terraform push`, `tfh pushvars` can set Terraform variables that are 86 | strings as well as HCL lists and maps. To set strings use `-var` and to set HCL 87 | lists and maps use `-hcl-var`. Also, environment variables can be set 88 | using `-env-var`. Each option has a "sensitive" counterpart for creating 89 | write-only variables. These options are `-svar`, `-shcl-var`, and `-senv-var`. 90 | 91 | A configuration directory can be supplied in the same manner as with `terraform 92 | push`. The configuration will be inspected for default variables, use 93 | automatically loaded tfvars files such as `terraform.tfvars`, and can use 94 | `-var-file` to load any additional tfvars file. Unlike `terraform push` the 95 | configuration directory must be explicitly provided when using a configuration. 96 | If the current directory contains a configuration that should be inspected then 97 | `.` should be supplied as the directory. 98 | 99 | The `tfh pushvars` command allows tfvars files and `-var` style arguments to 100 | be used independently, outside the context of any configuration. To operate on 101 | a tfvars file, supply the `-var-file` argument and do not provide any path to a 102 | Terraform configuration. The variables and values in the tfvars file will be 103 | used to create and update variables, along with any additional `-var` style 104 | argument (`-var`, `-svar`, `-env-var`, etc). 105 | 106 | The variable manipulation logic for all command variations follows the earlier 107 | `terraform push` behavior. By default, variables that already exist in 108 | Terraform Enterprise are not created or updated. To overwrite existing values 109 | use the `-overwrite ` option. 110 | 111 | The `-dry-run` option can show what changes would be made if the command were 112 | run. Use it to avoid surprises, especially if you are loading variables from 113 | multiple sources. 114 | 115 | #### Examples 116 | 117 | ``` 118 | # Create Terraform variable 'foo' if it does not exist. 119 | tfh pushvars -org org_name -name workspace_name -var 'foo=bar' 120 | 121 | # Output a dry run of what would be attempted when running the above command. 122 | tfh pushvars -org org_name -name workspace_name -var 'foo=bar' -dry-run 123 | 124 | # Set the environment variable CONFIRM_DESTROY to 1 to enable destroy plans. 125 | tfh pushvars -org org_name -name workspace_name -env-var 'CONFIRM_DESTROY=1' 126 | 127 | # Create Terraform variable 'foo' if it does not exist, overwrite its value 128 | # with 'bar' if it does exist. 129 | tfh pushvars -org org_name -name workspace_name -var 'foo=bar' -overwrite foo 130 | 131 | # Create (but don't overwrite) a sensitive HCL variable that is a list and a 132 | # non-sensitive HCL variable that is a map. 133 | tfh pushvars -org org_name -name workspace_name \ 134 | -shcl-var 'my_list=["one", "two"]' \ 135 | -hcl-var 'my_map={foo="bar", baz="qux"}' 136 | 137 | # Create all variables from a my.tfvars file only. The tfvars file does not 138 | # need to be associated with any Terraform config. Note that tfvars files can 139 | # only set non-sensitive variables. 140 | tfh pushvars -org org_name -name workspace_name -var-file my.tfvars 141 | 142 | # Create any number of variables from my.tfvars, and overwrite one variable if 143 | # it already exists. 144 | tfh pushvars -org org_name -name workspace_name -var-file my.tfvars \ 145 | -overwrite foo 146 | 147 | # Inspect a configuration in the current directory for variable information; 148 | # create any variables that do not exist in the Terraform Enterprise workspace 149 | # but which may have values in the automatically loaded tfvars sources such as 150 | # terraform.tfvars. 151 | tfh pushvars . -org org_name -name workspace_name 152 | 153 | # Same as above but also specify a -var-file to include, just as when 154 | # specifying -var-file when running terraform apply 155 | tfh pushvars . -org org_name -name workspace_name -var-file my.tfvars 156 | 157 | # Same as above but add a command line variable which will override anything 158 | # found in the configuration and .tfvars file(s) 159 | tfh pushvars . -org org_name -name workspace_name -var-file my.tfvars \ 160 | -var 'foo=bar' 161 | ``` 162 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_run.md: -------------------------------------------------------------------------------- 1 | ## `tfh run` 2 | 3 | Perform operations on TFE runs 4 | 5 | ### Synopsis 6 | 7 | tfh run SUBCOMMAND 8 | 9 | ### Description 10 | 11 | Perform a variety of operations on TFE runs, where subcommand is one of the following. 12 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_run_cancel.md: -------------------------------------------------------------------------------- 1 | ## `tfh run cancel` 2 | 3 | Stop an active run or prevent a pending plan from running. 4 | 5 | ### Synopsis 6 | 7 | tfh run cancel [RUNID] [-comment COMMENT] 8 | 9 | ### Description 10 | 11 | Request that a running plan be canceled, or that a pending plan have its run canceled. 12 | 13 | ### Positional parameters 14 | 15 | * `RUNID` 16 | 17 | ID of the run to be canceled. If omitted, the lastest run with `is-cancelable` action available is canceled. 18 | 19 | ### Options 20 | 21 | * `-m, -message, -comment COMMENT` 22 | 23 | An optional explanation for why the run was canceled. 24 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_run_discard.md: -------------------------------------------------------------------------------- 1 | ## `tfh run discard` 2 | 3 | Skip remaining operations on a paused run 4 | 5 | ### Synopsis 6 | 7 | tfh run discard [RUNID] [-comment COMMENT] 8 | 9 | ### Description 10 | 11 | Skip remaining operations on a paused run such that it can no longer be acted on. 12 | 13 | ### Positional parameters 14 | 15 | * `RUNID` 16 | 17 | ID of the run to be discarded. If omitted, the lastest run with the `is-discardable` action available is discarded. 18 | 19 | ### Options 20 | 21 | * `-m, -message, -comment COMMENT` 22 | 23 | An optional explanation for why the run was discarded. 24 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_run_list.md: -------------------------------------------------------------------------------- 1 | ## `tfh run list` 2 | 3 | List runs for a workspace 4 | 5 | ### Synopsis 6 | 7 | tfh run list [OPTIONS] 8 | 9 | ### Description 10 | 11 | List Terraform Enterprise runs for a workspace. 12 | 13 | ### Options 14 | 15 | * `-id WORKSPACE_ID` 16 | 17 | The ID of the workspace to list runs for. The ID can be specified directly using `-id`, or the workspace name can be specified using the common options `-name` and, if desired, `-prefix`. 18 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_run_show.md: -------------------------------------------------------------------------------- 1 | ## `tfh run show, get` 2 | 3 | Show Terraform Enterprise run details 4 | 5 | ### Synopsis 6 | 7 | tfh run show [RUNID_OR_STATUS] 8 | 9 | ### Description 10 | 11 | Show run details, including properties such as if the run has auto-apply set, the run status, various timestamps, and various permissions. 12 | 13 | ### Positional parameters 14 | 15 | * `RUNID_OR_STATUS` 16 | 17 | The ID of the run show or the status of the latest run to show. For example, `tfh run show planned` to see the last successfully planned run. -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh` 2 | 3 | Perform operations on SSH keys 4 | 5 | ### Synopsis 6 | 7 | tfh ssh SUBCOMMAND 8 | 9 | ### Description 10 | 11 | Perform a variety of operations on SSH keys, where subcommand is one of the following. 12 | 13 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_assign.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh assign` 2 | 3 | Assign an SSH key to a Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh ssh assign [OPTIONS] 8 | 9 | ### Description 10 | 11 | Assign a Terraform Enterprise SSH key to a workspace. The workspace and SSH key must already exist. 12 | 13 | ### Positional parameters 14 | 15 | * `NAME` 16 | 17 | Workspace name to assign the ssh key to. Overrides the `-name` common option. 18 | 19 | ### Options 20 | 21 | * `-ssh-name NAME` 22 | 23 | The name of the SSH key in TFE to assign. 24 | 25 | * `-ssh-id ID` 26 | 27 | The ID of the SSH key to assign. 28 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_delete.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh delete` 2 | 3 | Delete a Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh ssh delete [OPTIONS] 8 | 9 | ### Description 10 | 11 | SSH keys are defined at the organization level. An organization must be specified with the -name argument, the -tfe-org argument, or the TFE_ORG environment variable. A workspace does not need to be specified. 12 | 13 | ### Options 14 | 15 | * `-ssh-name NAME` 16 | 17 | The name of the SSH key to show. 18 | 19 | * `-ssh-id ID` 20 | 21 | The ID of the SSH key to show. 22 | 23 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_list.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh list` 2 | 3 | List Terraform Enterprise workspaces for an organization 4 | 5 | ### Synopsis 6 | 7 | tfh ssh list 8 | 9 | ### Description 10 | 11 | List Terraform Enterprise SSH keys for an organization. SSH keys are defined at the organization level. An organization must be specified with the -name argument, the -tfe-org argument, or the TFE_ORG environment variable. A workspace does not need to be specified. 12 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_new.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh new, create` 2 | 3 | Create a new Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh ssh new [OPTIONS] 8 | 9 | ### Description 10 | 11 | Create a new Terraform Enterprise SSH key. SSH keys are defined at the organization level. An organization must be specified with the -name argument, the -tfe-org argument, or the TFE_ORG environment variable. A workspace does not need to be specified. 12 | 13 | ### Options 14 | 15 | * `-ssh-name NAME` 16 | 17 | The name to be used to identify the SSH key in TFE. 18 | 19 | * `-ssh-file KEYFILE` 20 | 21 | The SSH private key file. 22 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_show.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh show, get` 2 | 3 | Show Terraform Enterprise workspace details 4 | 5 | ### Synopsis 6 | 7 | tfh ssh show [OPTIONS] 8 | 9 | ### Description 10 | 11 | Show Terraform Enterprise SSH key details. SSH keys are defined at the organization level. An organization must be specified with the -name argument, the -tfe-org argument, or the TFE_ORG environment variable. A workspace does not need to be specified. 12 | 13 | ### Options 14 | 15 | * `-ssh-name NAME` 16 | 17 | The name of the SSH key to show. 18 | 19 | * `-ssh-id ID` 20 | 21 | The ID of the SSH key to show. 22 | 23 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_unassign.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh unassign` 2 | 3 | Unassign the current SSH key from a workspace 4 | 5 | ### Synopsis 6 | 7 | tfh ssh unassign 8 | 9 | ### Description 10 | 11 | Unassign the current SSH key from a Terraform Enterprise workspace 12 | 13 | ### Positional parameters 14 | 15 | * `NAME` 16 | 17 | Workspace name to unassign the ssh key from. Overrides the `-name` common option. 18 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_ssh_update.md: -------------------------------------------------------------------------------- 1 | ## `tfh ssh update` 2 | 3 | Modify a Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh ssh update [OPTIONS] 8 | 9 | ### Description 10 | 11 | Update a Terraform Enterprise SSH key. SSH keys are defined at the organization level. An organization must be specified with the -name argument, the -tfe-org argument, or the TFE_ORG environment variable. A workspace does not need to be specified. 12 | 13 | ### Options 14 | 15 | * `-ssh-name NAME` 16 | 17 | The name of the SSH key in TFE to update. 18 | 19 | * `-ssh-id ID` 20 | 21 | The ID of the SSH key to update. 22 | 23 | * `-ssh-new-name NAME` 24 | 25 | The name to rename the SSH key to. 26 | 27 | * `-ssh-file KEYFILE` 28 | 29 | SSH private key file. 30 | 31 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace, ws` 2 | 3 | Perform operations on workspaces 4 | 5 | ### Synopsis 6 | 7 | tfh workspace SUBCOMMAND 8 | 9 | ### Description 10 | 11 | Perform a variety of operations on workspaces, where subcommand is one of the following. 12 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_delete.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace delete` 2 | 3 | Delete a Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh workspace delete [OPTIONS] 8 | 9 | ### Description 10 | 11 | Delete a Terraform Enterprise workspace. 12 | 13 | ### Positional parameters 14 | 15 | * `NAME` 16 | 17 | Workspace name to show. Overrides the `-name` common option. 18 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_list.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace list` 2 | 3 | List Terraform Enterprise workspaces for an organization 4 | 5 | ### Synopsis 6 | 7 | tfh workspace list [OPTIONS] 8 | 9 | ### Description 10 | 11 | List Terraform Enterprise workspaces for an organization. An organization must be specified with the `-org` argument, or the `TFH_org` environment variable. Specifying a workspace is optional. If a workspace is specified with the `-name` argument or the `TFH_name` environment variable it will be preceded by an asterisk. 12 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_new.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace new, create` 2 | 3 | Create a new Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh workspace new [OPTIONS] 8 | 9 | ### Description 10 | 11 | Create a new Terraform Enterprise workspace. If more than one OAuth client is configured use the OAuth Tokens API to list the clients and their IDs. Provide the appropriate ID from the list to the -oauth-id option. 12 | 13 | https://www.terraform.io/docs/enterprise/api/oauth-tokens.html 14 | 15 | ### Positional parameters 16 | 17 | * `NAME` 18 | 19 | Workspace name to show. Overrides the `-name` common option. 20 | 21 | ### Options 22 | 23 | * `-auto-apply` 24 | 25 | Specifies if, upon a successful plan, the workspace should automatically run an apply. Defaults to false. 26 | 27 | * `-terraform-version X.Y.Z` 28 | 29 | The version of Terraform that the workspace should use to perform runs. Defaults to the latest Terraform release at the time of workspace creation. 30 | 31 | * `-working-dir DIRECTORY` 32 | 33 | The directory relative to the root of the VCS repository where the 'terraform' command should be run. Defaults to the root of the VCS repository. 34 | 35 | * `-vcs-id ID` 36 | 37 | The name of the VCS repository ID. Typically in a format similar to "/". 38 | 39 | * `-vcs-branch BRANCH` 40 | 41 | The name of the VCS branch to use. Defaults to being unspecified so that defalt branch is used. 42 | 43 | * `-vcs-submodules` 44 | 45 | If true, when the configuration is ingressed from the VCS service VCS submodules will be retrieved as well. Defaults to false. 46 | 47 | * `-oauth-id ID` 48 | 49 | The OAuth ID, obtained from the Terraform Enterprise API, which corresponds to the VCS ID provided. Defaults to the OAuth ID of the configured client if there is only one. 50 | 51 | * `-queue-all-runs` 52 | 53 | If true, runs will be queued immediately after workspace creation. If false, runs will not queue until a run is manually queued first. Defaults to false. 54 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_select.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace select` 2 | 3 | Set `TFH_workspace` in the tfh configuration file 4 | 5 | ### Synopsis 6 | 7 | tfh workspace select WORKSPACE_NAME 8 | 9 | ### Description 10 | 11 | Selecting a Terraform Enterprise workspace edits the tfe-cli configuration file, setting the TFE_WORKSPACE variable to the specified value. The workspace must exist in TFE. To set TFE_WORKSPACE to a non-existent name (for the purpose of, for example, using tfe workspace new), use tfe config. 12 | 13 | This behavior mimics terraform workspace select. The workspace cannot be specified via any other source than the single named argument. 14 | 15 | An organization must be specified via the environment, configuration file, or command argument. 16 | 17 | An asterisk in the listing indicates the Terraform Enterprise workspace currently specified by -name, -tfe-workspace, or the TFE_WORKSPACE environment variable. 18 | 19 | ### Positional parameters 20 | 21 | * `NAME` 22 | 23 | Workspace name to show. Overrides the `-name` common option. 24 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_show.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace show, get` 2 | 3 | Show Terraform Enterprise workspace details 4 | 5 | ### Synopsis 6 | 7 | tfh workspace show [OPTIONS] 8 | 9 | ### Description 10 | 11 | Show Terraform Enterprise workspace details. 12 | 13 | ### Positional parameters 14 | 15 | * `NAME` 16 | 17 | Workspace name to show. Overrides the `-name` common option. 18 | -------------------------------------------------------------------------------- /tfh/usr/share/doc/tfh/tfh_workspace_update.md: -------------------------------------------------------------------------------- 1 | ## `tfh workspace update` 2 | 3 | Modify a Terraform Enterprise workspace 4 | 5 | ### Synopsis 6 | 7 | tfh workspace update [OPTIONS] 8 | 9 | ### Description 10 | 11 | Modify a Terraform Enterprise workspace. 12 | 13 | ### Positional parameters 14 | 15 | * `NAME` 16 | 17 | Workspace name to show. Overrides the `-name` common option. 18 | 19 | ### Options 20 | 21 | * `-auto-apply` 22 | 23 | Specifies if, upon a successful plan, the workspace should automatically run an apply. Defaults to false. 24 | 25 | * `-terraform-version X.Y.Z` 26 | 27 | The version of Terraform that the workspace should use to perform runs. Defaults to the latest Terraform release at the time of workspace creation. 28 | 29 | * `-working-dir DIRECTORY` 30 | 31 | The directory relative to the root of the VCS repository where the 'terraform' command should be run. Defaults to the root of the VCS repository. To set this to an empty string, it is easiest to use an empty variable, as passing any combination of quotes will be either wrong or fail to pass any value. For example, using `-working-dir $empty` correctly passes a null value / empty string. 32 | 33 | * `-vcs-id ID` 34 | 35 | The name of the VCS repository ID. Typically in a format similar to "/". 36 | 37 | * `-vcs-branch BRANCH` 38 | 39 | The name of the VCS branch to use. Defaults to being unspecified so that defalt branch is used. 40 | 41 | * `-vcs-submodules` 42 | 43 | If true, when the configuration is ingressed from the VCS service VCS submodules will be retrieved as well. Defaults to false. 44 | 45 | * `-oauth-id ID` 46 | 47 | The OAuth ID to be used with the VCS integration. 48 | 49 | * `-queue-all-runs` 50 | 51 | If true, runs will be queued immediately after workspace creation. If false, runs will not queue until a run is manually queued first. Defaults to false. 52 | 53 | --------------------------------------------------------------------------------