├── .gitignore ├── LICENSE ├── README.md ├── examples ├── basic │ ├── README.md │ └── main.tf ├── count │ ├── README.md │ └── main.tf ├── for-each │ ├── README.md │ ├── main.tf │ ├── terraform.tfvars.example │ └── variables.tf └── with-vcs │ ├── README.md │ └── main.tf ├── notifications.tf ├── outputs.tf ├── policy_sets.tf ├── project.tf ├── run_triggers.tf ├── team_access.tf ├── tests ├── basic │ ├── main.tf │ └── variables.tf └── with-vcs │ ├── main.tf │ ├── tf-working-dir-test │ └── test_main.tf │ └── variables.tf ├── variable_sets.tf ├── variables.tf ├── versions.tf ├── workspace.tf └── workspace_variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | # example.tfvars 16 | *.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | *tfplan* 32 | 33 | # Terraform Dependency Lock file 34 | .terraform.lock.hcl -------------------------------------------------------------------------------- /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 | # Workspacer 2 | 3 | Terraform module to create, configure, and manage Workspaces in HCP Terraform or Terraform Enterprise. 4 | 5 | ## Usage 6 | 7 | ```hcl 8 | module "workspacer" { 9 | source = "alexbasista/workspacer/tfe" 10 | version = "x.x.x" 11 | 12 | organization = "my-hcptf-or-tfe-org-name" 13 | workspace_name = "my-new-ws" 14 | workspace_desc = "Description of my new Workspace." 15 | workspace_tags = ["tag1", "tag2", "tagz"] 16 | project_name = "Default Project" 17 | 18 | tfvars = { 19 | teststring = "iamstring" 20 | testlist = ["1", "2", "3"] 21 | testmap = { "a" = "1", "b" = "2", "c" = "3" } 22 | } 23 | } 24 | ``` 25 | >📝 Note: Setting a `TFE_TOKEN` environment variable is the recommended approach for the TFE provider authentication. 26 | 27 | See the [examples](./examples) directory for more detailed example scenarios, and see the following section for optional configurations/features. 28 | 29 | ## Configuration Options 30 | 31 | ### Projects 32 | 33 | To place the Workspace into an existing Project, set the input variable `project_name`. 34 | 35 | ```hcl 36 | project_name = "my-project" 37 | ``` 38 | 39 | ### With VCS 40 | 41 | The optional `vcs_repo` input variable expects a map of key/value pairs with up to six attributes. 42 | 43 | 44 | #### Using an OAuth Token 45 | 46 | ```hcl 47 | vcs_repo = { 48 | identifier = "/" 49 | branch = "main" 50 | oauth_token_id = "ot-abcdefg123456789" 51 | } 52 | ``` 53 | 54 | #### Using a GitHub App Installation ID 55 | 56 | ```hcl 57 | vcs_repo = { 58 | identifier = "/" 59 | branch = "main" 60 | github_app_installation_id = "ghain-abcdefg123456789" 61 | } 62 | ``` 63 | 64 | ### Workspace Variables 65 | 66 | This module strives to make creating Workspace Variables more streamlined, and closer to the look and feel of using a `terraform.tfvars` file (key/value pairs) when creating them. There are four different optional input variables available for creating Workspace Variables: 67 | 68 | #### Terraform Variables 69 | 70 | `tfvars` accepts a map of key/value pairs of any type, and `tfvars_sensitive` is the same except it will also mark the variable(s) as sensitive upon creation. 71 | ```hcl 72 | tfvars = { 73 | teststring = "iamstring" 74 | testlist = ["1", "2", "3"] 75 | testmap = { "a" = "1", "b" = "2", "c" = "3" } 76 | } 77 | 78 | tfvars_sensitive = { 79 | secret = "securestring" 80 | secret_list = ["sec1", "sec2", "sec3"] 81 | secret_map = { "x" = "sec4", "y" = "sec5", "z" = "sec6" } 82 | } 83 | ``` 84 | 85 | #### Environment Variables 86 | 87 | `envvars` accepts a map of strings, and `envvars_sensitive` is the same except it will also mark the variable(s) as sensitive upon creation. 88 | ```hcl 89 | envvars = { 90 | AWS_ACCESS_KEY_ID = "ABCDEFGHIJKLMNOPQRST" 91 | } 92 | 93 | envvars_sensitive = { 94 | AWS_SECRET_ACCESS_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$" 95 | } 96 | ``` 97 | 98 | ### Team Access 99 | 100 | To configure RBAC on the Workspace, there are two options: 101 | 102 | #### Built-In Permissions 103 | 104 | The `team_access` input variable accepts a map of strings whereby each key/value pair is the (existing) Team name and built-in permission level. 105 | 106 | ```hcl 107 | team_access = { 108 | "example-team-1" = "read" 109 | "example-team-2" = "write" 110 | "example-team-3" = "admin" 111 | } 112 | ``` 113 | 114 | #### Custom Permissions 115 | 116 | The `custom_team_access` input variable accepts a map of objects whereby each object represents a set of custom team permission levels. The object key is the (existing) Team name. The way the TFE provider and API currently work, all five of the object attributes must be specified together when using. 117 | 118 | ```hcl 119 | custom_team_access = { 120 | "example-team-1" = { 121 | runs = "read" 122 | variables = "read" 123 | state_versions = "read" 124 | sentinel_mocks = "none" 125 | workspace_locking = false 126 | run_tasks = false 127 | } 128 | "example-team-2" = { 129 | runs = "plan" 130 | variables = "write" 131 | state_versions = "read-outputs" 132 | sentinel_mocks = "read" 133 | workspace_locking = true 134 | run_tasks = true 135 | } 136 | } 137 | ``` 138 | 139 | ### Notifications 140 | 141 | To create Notifications, the `notifications` input variable accepts a list of objects, whereby each object is a Notification configuration. 142 | 143 | ```hcl 144 | notifications = [ 145 | { 146 | name = "test-notification-generic" 147 | destination_type = "generic" 148 | url = "http://example.com/receive-notifications-api" 149 | token = "abcdefg123456789" 150 | triggers = ["run:needs_attention"] 151 | enabled = true 152 | }, 153 | { 154 | name = "test-notification-email" 155 | destination_type = "email" 156 | email_user_ids = ["abasista"] 157 | triggers = ["run:completed", "run:errored"] 158 | enabled = true 159 | }, 160 | { 161 | name = "test-notification-slack" 162 | destination_type = "slack" 163 | url = "https://hooks.slack.com/example" 164 | triggers = ["run:completed", "run:errored"] 165 | enabled = true 166 | } 167 | ] 168 | ``` 169 | 170 | ### Run Triggers 171 | 172 | To add Run Triggers, the `run_trigger_source_workspaces` input variable accepts a list of (existing) Workspace names. 173 | 174 | ```hcl 175 | run_trigger_source_workspaces = [ 176 | "example-src-workspace-1", 177 | "example-src-workspace-2" 178 | ] 179 | ``` 180 | 181 | ### Variable Sets 182 | 183 | To add the Workspace into one or more already existing Variable Sets, the input variable `variable_set_names` accepts a list of Variable Set names. 184 | 185 | ```hcl 186 | variable_set_names = [ 187 | "example-varset-1", 188 | "example-varset-2" 189 | ] 190 | ``` 191 | 192 | ### Policy Sets 193 | 194 | To add the Workspace into one or more already existing Policy Sets, the input variable `policy_set_names` accepts a list of Policy Set names. 195 | 196 | ```hcl 197 | policy_set_names = [ 198 | "example-sentinel-global", 199 | "example-sentinel-prod" 200 | ] 201 | ``` 202 | 203 | ### SSH Key ID 204 | 205 | To configure an SSH key on your Workspace, set the SSH key ID via the input `ssh_key_id`. This value should NOT be the name of the SSH key as it appears in the HCP Terraform or TFE UI. If you do not have the ID of your SSH key, you can extract it using the command below. **Note:** This key is only used when a workspace needs to access a private git repository to pull in a module from a git-based module URL or git submodule. 206 | 207 | ```sh 208 | $ curl --header "Authorization: Bearer $TFE_TOKEN \ 209 | https://app.terraform.io/api/v2/organizations/myorg/ssh-keys 210 | 211 | {"data":[{"id":"sshkey-abcdefgh12345678","type":"ssh-keys","attributes":{"name":"my-github-ssh-key"},"links":{"self":"/api/v2/ssh-keys/sshkey-abcdefgh12345678"}}]}⏎ 212 | ``` 213 | 214 | --- 215 | 216 | ## Caveats/Limitations 217 | - Due to some current provider-interfacing/API challenges with Workspace Variables, any non-string Workspace Variable value (where the `hcl` attribute would equal `true`) will be JSON-encoded and subsequently any `:` characters will be replaced with `=`. Therefore, _non-string_ Workspace Variable values that contain a colon character are not currently supported. 218 | 219 | --- 220 | 221 | 222 | ## Requirements 223 | 224 | | Name | Version | 225 | |------|---------| 226 | | [terraform](#requirement\_terraform) | >= 1.9 | 227 | | [tfe](#requirement\_tfe) | ~> 0.62 | 228 | 229 | ## Providers 230 | 231 | | Name | Version | 232 | |------|---------| 233 | | [tfe](#provider\_tfe) | ~> 0.62 | 234 | 235 | ## Resources 236 | 237 | | Name | Type | 238 | |------|------| 239 | | [tfe_notification_configuration.nc](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/notification_configuration) | resource | 240 | | [tfe_run_trigger.rt](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/run_trigger) | resource | 241 | | [tfe_team_access.custom](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/team_access) | resource | 242 | | [tfe_team_access.managed](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/team_access) | resource | 243 | | [tfe_variable.envvars](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 244 | | [tfe_variable.envvars_ignore_changes](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 245 | | [tfe_variable.envvars_sensitive](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 246 | | [tfe_variable.tfvars](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 247 | | [tfe_variable.tfvars_ignore_changes](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 248 | | [tfe_variable.tfvars_sensitive](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | 249 | | [tfe_workspace.ws](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace) | resource | 250 | | [tfe_workspace_policy_set.ps](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace_policy_set) | resource | 251 | | [tfe_workspace_settings.ws](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace_settings) | resource | 252 | | [tfe_workspace_variable_set.vs](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace_variable_set) | resource | 253 | | [tfe_policy_set.ps](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/policy_set) | data source | 254 | | [tfe_project.ws](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/project) | data source | 255 | | [tfe_team.custom](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/team) | data source | 256 | | [tfe_team.managed](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/team) | data source | 257 | | [tfe_variable_set.vs](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/variable_set) | data source | 258 | | [tfe_workspace_ids.run_triggers](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/workspace_ids) | data source | 259 | 260 | ## Inputs 261 | 262 | | Name | Description | Type | Default | Required | 263 | |------|-------------|------|---------|:--------:| 264 | | [organization](#input\_organization) | Name of Organization to create Workspace in. | `string` | n/a | yes | 265 | | [workspace\_name](#input\_workspace\_name) | Name of Workspace to create. | `string` | n/a | yes | 266 | | [agent\_pool\_id](#input\_agent\_pool\_id) | ID of existing Agent Pool to assign to Workspace. Only valid when `execution_mode` is set to `agent`. | `string` | `null` | no | 267 | | [allow\_destroy\_plan](#input\_allow\_destroy\_plan) | Boolean setting to allow destroy plans on Workspace. | `bool` | `true` | no | 268 | | [assessments\_enabled](#input\_assessments\_enabled) | Boolean to enable Health Assessments such as Drift Detection on Workspace. | `bool` | `false` | no | 269 | | [auto\_apply](#input\_auto\_apply) | Boolean to automatically run a Terraform apply after a successful Terraform plan. | `bool` | `false` | no | 270 | | [custom\_team\_access](#input\_custom\_team\_access) | Map of existing Team(s) and custom permissions to grant on Workspace. If used, all keys in the object must be specified. |
map(
object(
{
runs = string
variables = string
state_versions = string
sentinel_mocks = string
workspace_locking = bool
run_tasks = bool
}
)
)
| `{}` | no | 271 | | [envvars](#input\_envvars) | Map of Environment variables to add to Workspace. | `map(string)` | `{}` | no | 272 | | [envvars\_ignore\_changes](#input\_envvars\_ignore\_changes) | Map of sensitive Environment variables to add to Workspace whereby changes made outside of Terraform will be ignored. | `map(string)` | `{}` | no | 273 | | [envvars\_sensitive](#input\_envvars\_sensitive) | Map of sensitive Environment variables to add to Workspace. | `map(string)` | `{}` | no | 274 | | [execution\_mode](#input\_execution\_mode) | Execution mode of Workspace. Valid values are `remote`, `local`, or `agent`. | `string` | `null` | no | 275 | | [file\_triggers\_enabled](#input\_file\_triggers\_enabled) | Boolean to filter Runs triggered via webhook (VCS push) based on `working_directory` and `trigger_prefixes`. | `bool` | `true` | no | 276 | | [force\_delete](#input\_force\_delete) | Boolean to allow deletion of the Workspace if there is a Terraform state that contains resources. | `bool` | `null` | no | 277 | | [global\_remote\_state](#input\_global\_remote\_state) | Boolean to allow all Workspaces within the Organization to remotely access the State of this Workspace. | `bool` | `false` | no | 278 | | [notifications](#input\_notifications) | List of Notification objects to configure on Workspace. |
list(
object(
{
name = string
destination_type = string
url = optional(string)
token = optional(string)
email_addresses = optional(list(string))
email_user_ids = optional(list(string))
triggers = list(string)
enabled = bool
}
)
)
| `[]` | no | 279 | | [policy\_set\_names](#input\_policy\_set\_names) | List of names of existing Policy Sets to add this Workspace into. | `list(string)` | `[]` | no | 280 | | [project\_name](#input\_project\_name) | Name of existing Project to create Workspace in. | `string` | `null` | no | 281 | | [queue\_all\_runs](#input\_queue\_all\_runs) | Boolean setting for Workspace to automatically queue all Runs after creation. | `bool` | `true` | no | 282 | | [remote\_state\_consumer\_ids](#input\_remote\_state\_consumer\_ids) | List of existing Workspace IDs allowed to remotely access the State of Workspace. | `list(string)` | `null` | no | 283 | | [run\_trigger\_source\_workspaces](#input\_run\_trigger\_source\_workspaces) | List of existing Workspace names that will trigger runs on Workspace. | `list(string)` | `[]` | no | 284 | | [speculative\_enabled](#input\_speculative\_enabled) | Boolean to allow Speculative Plans on Workspace. | `bool` | `true` | no | 285 | | [ssh\_key\_id](#input\_ssh\_key\_id) | SSH private key the Workspace will use for downloading Terraform modules from Git-based module sources. Key must exist in Organization first. | `string` | `null` | no | 286 | | [structured\_run\_output\_enabled](#input\_structured\_run\_output\_enabled) | Boolean to enable the advanced Run UI. Set to `false` for the traditional console-based Run output. | `bool` | `true` | no | 287 | | [tags\_regex](#input\_tags\_regex) | A regular expression used to trigger a Run in Workspace for matching Git tags. This option conflicts with `trigger_patterns` and `trigger_prefixes`. Should only set this value if the former is not being used. | `string` | `null` | no | 288 | | [team\_access](#input\_team\_access) | Map of existing Team(s) and built-in permissions to grant on Workspace. | `map(string)` | `{}` | no | 289 | | [terraform\_version](#input\_terraform\_version) | Version of Terraform to use for this Workspace. | `string` | `null` | no | 290 | | [tfvars](#input\_tfvars) | Map of Terraform variables to add to Workspace. | `any` | `{}` | no | 291 | | [tfvars\_ignore\_changes](#input\_tfvars\_ignore\_changes) | Map of Terraform variables to add to Workspace whereby changes made outside of Terraform will be ignored. | `any` | `{}` | no | 292 | | [tfvars\_sensitive](#input\_tfvars\_sensitive) | Map of sensitive Terraform variables to add to Workspace. | `any` | `{}` | no | 293 | | [trigger\_patterns](#input\_trigger\_patterns) | List of glob patterns that describe the files monitored for changes to trigger Runs in Workspace. Mutually exclusive with `trigger_prefixes`. Only available with TFC. | `list(string)` | `null` | no | 294 | | [trigger\_prefixes](#input\_trigger\_prefixes) | List of paths relative to the root of the VCS repo to filter on when `file_triggers_enabled` is `true`. | `list(string)` | `null` | no | 295 | | [variable\_set\_names](#input\_variable\_set\_names) | List of names of existing Variable Sets to add this Workspace into. | `list(string)` | `[]` | no | 296 | | [vcs\_repo](#input\_vcs\_repo) | Object containing settings to connect Workspace to a VCS repository. |
object({
identifier = string
branch = optional(string, null)
oauth_token_id = optional(string, null)
github_app_installation_id = optional(string, null)
ingress_submodules = optional(bool, false)
tags_regex = optional(string, null)
})
| `null` | no | 297 | | [working\_directory](#input\_working\_directory) | The relative path that Terraform will execute within. Defaults to the root of the repo. | `string` | `null` | no | 298 | | [workspace\_desc](#input\_workspace\_desc) | Description of Workspace. | `string` | `"Created by 'workspacer' Terraform module."` | no | 299 | | [workspace\_tags](#input\_workspace\_tags) | List of tag names to apply to Workspace. Tags must only contain letters, numbers, or colons. | `list(string)` | `[]` | no | 300 | 301 | ## Outputs 302 | 303 | | Name | Description | 304 | |------|-------------| 305 | | [workspace\_id](#output\_workspace\_id) | ID of Workspace. | 306 | 307 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Example - Basic Usage 2 | 3 | ```hcl 4 | module "workspacer" { 5 | source = "alexbasista/workspacer/tfe" 6 | version = "x.x.x" 7 | 8 | organization = "" 9 | workspace_name = "workspacer-basic-example" 10 | workspace_desc = "Created by 'workspacer' Terraform module." 11 | workspace_tags = ["app:acme", "env:test", "cloud:aws"] 12 | project_name = "Default Project" 13 | 14 | tfvars = { 15 | example_var = "example_value" 16 | } 17 | 18 | tfvars_sensitive = { 19 | example_sensitive_var = "example_sensitive_value" 20 | } 21 | 22 | envvars = { 23 | AWS_ACCESS_KEY_ID = "TH1S1SNOTAREAL@CC3SSK3Y" 24 | } 25 | 26 | envvars_sensitive = { 27 | AWS_SECRET_ACCESS_KEY = "TH1S1$NOTAREALS3CR3TK3Y!" 28 | } 29 | 30 | team_access = { 31 | example-team-1 = "read" 32 | example-team-2 = "write" 33 | } 34 | } 35 | ``` -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = "app.terraform.io" 3 | } 4 | 5 | module "workspacer" { 6 | source = "alexbasista/workspacer/tfe" 7 | version = "0.12.0" 8 | 9 | organization = "" 10 | workspace_name = "workspacer-basic-example" 11 | workspace_desc = "Created by 'workspacer' Terraform module." 12 | workspace_tags = ["app:acme", "env:test", "cloud:aws"] 13 | project_name = "Default Project" 14 | 15 | tfvars = { 16 | example_var = "example_value" 17 | } 18 | 19 | tfvars_sensitive = { 20 | example_sensitive_var = "example_sensitive_value" 21 | } 22 | 23 | envvars = { 24 | AWS_ACCESS_KEY_ID = "TH1S1SNOTAREAL@CC3SSK3Y" 25 | } 26 | 27 | envvars_sensitive = { 28 | AWS_SECRET_ACCESS_KEY = "TH1S1$NOTAREALS3CR3TK3Y!" 29 | } 30 | 31 | team_access = { 32 | example-team-1 = "read" 33 | example-team-2 = "write" 34 | } 35 | } -------------------------------------------------------------------------------- /examples/count/README.md: -------------------------------------------------------------------------------- 1 | # Example - Using the `count` Meta-Argument 2 | 3 | ```hcl 4 | module "workspacer" { 5 | source = "alexbasista/workspacer/tfe" 6 | version = "x.x.x" 7 | count = 8 8 | 9 | organization = "" 10 | workspace_name = "workspacer-count-ex-${count.index}" 11 | workspace_tags = ["app:acme", "env:test", "cloud:aws"] 12 | project_name = "Default Project" 13 | } 14 | ``` -------------------------------------------------------------------------------- /examples/count/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = "app.terraform.io" 3 | } 4 | 5 | module "workspacer" { 6 | source = "alexbasista/workspacer/tfe" 7 | version = "0.12.0" 8 | count = 8 9 | 10 | organization = "" 11 | workspace_name = "workspacer-count-ex-${count.index}" 12 | workspace_tags = ["app:acme", "env:test", "cloud:aws"] 13 | project_name = "Default Project" 14 | } -------------------------------------------------------------------------------- /examples/for-each/README.md: -------------------------------------------------------------------------------- 1 | # Example - Using the `for_each` Meta-Argument 2 | 3 | ```hcl 4 | module "workspacer" { 5 | source = "alexbasista/workspacer/tfe" 6 | version = "x.x.x" 7 | for_each = var.workspaces 8 | 9 | organization = var.organization 10 | workspace_name = each.value.name 11 | workspace_desc = each.value.description 12 | workspace_tags = each.value.tags 13 | project_name = each.value.project_name 14 | vcs_repo = each.value.vcs_repo 15 | } 16 | ``` -------------------------------------------------------------------------------- /examples/for-each/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = var.tfe_hostname 3 | } 4 | 5 | module "workspacer" { 6 | source = "alexbasista/workspacer/tfe" 7 | version = "0.12.0" 8 | for_each = var.workspaces 9 | 10 | organization = var.organization 11 | workspace_name = each.value.name 12 | workspace_desc = each.value.description 13 | workspace_tags = each.value.tags 14 | project_name = each.value.project_name 15 | vcs_repo = each.value.vcs_repo 16 | } -------------------------------------------------------------------------------- /examples/for-each/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | tfe_hostname = "app.terraform.io" 2 | 3 | organization = "my-org-name" 4 | 5 | workspaces = { 6 | workspace_1 = { 7 | name = "workspacer-foreach-ex-1" 8 | description = "Workspace 1 created by workspacer Terraform module." 9 | tags = ["env:dev", "app:acme", "cloud:aws"] 10 | project_name = "Default Project" 11 | vcs_repo = { 12 | identifier = "/" 13 | branch = "
" 14 | oauth_token_id = "" 15 | } 16 | } 17 | workspace_2 = { 18 | name = "workspacer-foreach-ex-2" 19 | description = "Workspace 2 created by workspacer Terraform module." 20 | tags = ["env:stage", "app:acme", "cloud:aws"] 21 | project_name = "Default Project" 22 | vcs_repo = { 23 | identifier = "/" 24 | github_app_installation_id = "" 25 | } 26 | } 27 | workspace_3 = { 28 | name = "workspacer-foreach-ex-3" 29 | description = "Workspace 3 created by workspacer Terraform module." 30 | tags = ["env:prod", "app:acme", "cloud:aws"] 31 | project_name = "Default Project" 32 | } 33 | } -------------------------------------------------------------------------------- /examples/for-each/variables.tf: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Provider 3 | #------------------------------------------------------------------------------ 4 | variable "tfe_hostname" { 5 | type = string 6 | description = "Hostname of self-hosted TFE instance. Leave as default when using HCP Terraform." 7 | default = "app.terraform.io" 8 | } 9 | 10 | #------------------------------------------------------------------------------ 11 | # Module 12 | #------------------------------------------------------------------------------ 13 | variable "organization" { 14 | type = string 15 | description = "Name of Organization to create Workspaces in." 16 | } 17 | 18 | variable "workspaces" { 19 | type = map( 20 | object( 21 | { 22 | name = string 23 | description = optional(string, null) 24 | tags = optional(list(string), null) 25 | project_name = optional(string, null) 26 | vcs_repo = optional( 27 | object( 28 | { 29 | identifier = string 30 | branch = optional(string, null) 31 | oauth_token_id = optional(string, null) 32 | github_app_installation_id = optional(string, null) 33 | ingress_submodules = optional(bool, false) 34 | tags_regex = optional(string, null) 35 | } 36 | ) 37 | ) 38 | } 39 | ) 40 | ) 41 | 42 | description = "Map of objects for Workspaces to create." 43 | } -------------------------------------------------------------------------------- /examples/with-vcs/README.md: -------------------------------------------------------------------------------- 1 | # Example - With VCS Providers 2 | 3 | ## Normal VCS Provider (OAuth) 4 | 5 | ```hcl 6 | module "workspacer_vcs_oauth_token" { 7 | source = "alexbasista/workspacer/tfe" 8 | version = "x.x.x" 9 | 10 | organization = "" 11 | workspace_name = "workspacer-vcs-oauth-ex" 12 | workspace_tags = ["env:test", "app:acme"] 13 | project_name = "Default Project" 14 | 15 | working_directory = "" 16 | auto_apply = true 17 | file_triggers_enabled = true 18 | trigger_patterns = [""] 19 | queue_all_runs = true 20 | 21 | vcs_repo = { 22 | identifier = "/" 23 | branch = "main" 24 | oauth_token_id = "" 25 | ingress_submodules = false 26 | tags_regex = null 27 | } 28 | } 29 | ``` 30 | 31 | ## GitHub App 32 | ```hcl 33 | module "workspacer_vcs_github_app" { 34 | source = "alexbasista/workspacer/tfe" 35 | version = "x.x.x" 36 | 37 | organization = "" 38 | workspace_name = "workspacer-vcs-github-app-ex" 39 | workspace_tags = ["env:test", "app:acme"] 40 | project_name = "Default Project" 41 | 42 | working_directory = "" 43 | auto_apply = false 44 | file_triggers_enabled = true 45 | trigger_patterns = [""] 46 | queue_all_runs = true 47 | 48 | vcs_repo = { 49 | identifier = "/" 50 | branch = "main" 51 | github_app_installation_id = "" 52 | ingress_submodules = false 53 | tags_regex = null 54 | } 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /examples/with-vcs/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = "app.terraform.io" 3 | } 4 | 5 | module "workspacer_vcs_oauth_token" { 6 | source = "alexbasista/workspacer/tfe" 7 | version = "0.12.0" 8 | 9 | organization = "" 10 | workspace_name = "workspacer-vcs-oauth-ex" 11 | workspace_tags = ["env:test", "app:acme"] 12 | project_name = "Default Project" 13 | 14 | working_directory = "" 15 | auto_apply = true 16 | file_triggers_enabled = true 17 | trigger_patterns = [""] 18 | queue_all_runs = true 19 | 20 | vcs_repo = { 21 | identifier = "/" 22 | branch = "main" 23 | oauth_token_id = "" 24 | ingress_submodules = false 25 | tags_regex = null 26 | } 27 | } 28 | 29 | module "workspacer_vcs_github_app" { 30 | source = "alexbasista/workspacer/tfe" 31 | version = "0.12.0" 32 | 33 | organization = "" 34 | workspace_name = "workspacer-vcs-github-app-ex" 35 | workspace_tags = ["env:test", "app:acme"] 36 | project_name = "Default Project" 37 | 38 | working_directory = "/example/tf/directory" 39 | auto_apply = false 40 | file_triggers_enabled = true 41 | trigger_patterns = [""] 42 | queue_all_runs = true 43 | 44 | vcs_repo = { 45 | identifier = "/" 46 | branch = "main" 47 | github_app_installation_id = "" 48 | ingress_submodules = false 49 | tags_regex = null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /notifications.tf: -------------------------------------------------------------------------------- 1 | resource "tfe_notification_configuration" "nc" { 2 | for_each = { for i, n in var.notifications : i => n } 3 | 4 | workspace_id = tfe_workspace.ws.id 5 | name = each.value.name 6 | destination_type = each.value.destination_type 7 | url = each.value.url 8 | token = each.value.token 9 | email_addresses = each.value.email_addresses 10 | email_user_ids = each.value.email_user_ids 11 | triggers = each.value.triggers 12 | enabled = each.value.enabled 13 | } -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "workspace_id" { 2 | value = tfe_workspace.ws.id 3 | description = "ID of Workspace." 4 | } -------------------------------------------------------------------------------- /policy_sets.tf: -------------------------------------------------------------------------------- 1 | data "tfe_policy_set" "ps" { 2 | for_each = toset(var.policy_set_names) 3 | 4 | name = each.value 5 | organization = var.organization 6 | } 7 | 8 | resource "tfe_workspace_policy_set" "ps" { 9 | for_each = data.tfe_policy_set.ps 10 | 11 | policy_set_id = each.value.id 12 | workspace_id = tfe_workspace.ws.id 13 | } -------------------------------------------------------------------------------- /project.tf: -------------------------------------------------------------------------------- 1 | data "tfe_project" "ws" { 2 | count = var.project_name != null ? 1 : 0 3 | 4 | name = var.project_name 5 | organization = var.organization 6 | } -------------------------------------------------------------------------------- /run_triggers.tf: -------------------------------------------------------------------------------- 1 | data "tfe_workspace_ids" "run_triggers" { 2 | count = length(var.run_trigger_source_workspaces) == 0 ? 0 : 1 3 | 4 | names = var.run_trigger_source_workspaces 5 | organization = var.organization 6 | } 7 | 8 | resource "tfe_run_trigger" "rt" { 9 | for_each = length(var.run_trigger_source_workspaces) == 0 ? {} : data.tfe_workspace_ids.run_triggers[0].ids 10 | 11 | workspace_id = tfe_workspace.ws.id 12 | sourceable_id = each.value 13 | } -------------------------------------------------------------------------------- /team_access.tf: -------------------------------------------------------------------------------- 1 | data "tfe_team" "managed" { 2 | for_each = var.team_access 3 | 4 | name = each.key 5 | organization = var.organization 6 | } 7 | 8 | resource "tfe_team_access" "managed" { 9 | for_each = var.team_access 10 | 11 | workspace_id = tfe_workspace.ws.id 12 | team_id = [for t in [data.tfe_team.managed[each.key]] : t.id if t.name == each.key][0] 13 | access = each.value 14 | } 15 | 16 | data "tfe_team" "custom" { 17 | for_each = var.custom_team_access 18 | 19 | name = each.key 20 | organization = var.organization 21 | } 22 | 23 | resource "tfe_team_access" "custom" { 24 | for_each = var.custom_team_access 25 | 26 | workspace_id = tfe_workspace.ws.id 27 | team_id = [for t in [data.tfe_team.custom[each.key]] : t.id if t.name == each.key][0] 28 | 29 | permissions { 30 | runs = lookup(each.value, "runs", "read") 31 | variables = lookup(each.value, "variables", "none") 32 | state_versions = lookup(each.value, "state_versions", "none") 33 | sentinel_mocks = lookup(each.value, "sentinel_mocks", "none") 34 | workspace_locking = lookup(each.value, "workspace_locking", false) 35 | run_tasks = lookup(each.value, "run_tasks", false) 36 | } 37 | } -------------------------------------------------------------------------------- /tests/basic/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = var.tfe_hostname 3 | } 4 | 5 | module "workspacer" { 6 | source = "../.." 7 | 8 | organization = var.organization 9 | workspace_name = "workspacer-basic-example" 10 | workspace_desc = "Created by 'workspacer' Terraform module." 11 | workspace_tags = ["app:acme", "env:test", "cloud:aws"] 12 | project_name = "Default Project" 13 | 14 | tfvars = { 15 | example_var = "example_value" 16 | } 17 | 18 | tfvars_sensitive = { 19 | example_sensitive_var = "example_sensitive_value" 20 | } 21 | 22 | envvars = { 23 | AWS_ACCESS_KEY_ID = "TH1S1SNOTAREAL@CC3SSK3Y" 24 | } 25 | 26 | envvars_sensitive = { 27 | AWS_SECRET_ACCESS_KEY = "TH1S1$NOTAREALS3CR3TK3Y!" 28 | } 29 | 30 | team_access = { 31 | example-team-1 = "read" 32 | example-team-2 = "write" 33 | } 34 | } -------------------------------------------------------------------------------- /tests/basic/variables.tf: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Provider 3 | #------------------------------------------------------------------------------ 4 | variable "tfe_hostname" { 5 | type = string 6 | description = "Hostname of self-hosted TFE instance. Leave as default when using HCP Terraform." 7 | default = "app.terraform.io" 8 | } 9 | 10 | #------------------------------------------------------------------------------ 11 | # Module 12 | #------------------------------------------------------------------------------ 13 | variable "organization" { 14 | type = string 15 | description = "Name of Organization to create Workspace in." 16 | } -------------------------------------------------------------------------------- /tests/with-vcs/main.tf: -------------------------------------------------------------------------------- 1 | provider "tfe" { 2 | hostname = var.tfe_hostname 3 | } 4 | 5 | module "workspacer_oauth_token" { 6 | source = "../.." 7 | 8 | organization = var.organization 9 | workspace_name = "workspacer-vcs-oauth-ex" 10 | workspace_tags = ["env:test", "app:acme"] 11 | project_name = "Default Project" 12 | 13 | working_directory = "/tests/with-vcs/tf-working-dir-test" 14 | auto_apply = true 15 | file_triggers_enabled = true 16 | trigger_patterns = ["/tests/with-vcs/tf-working-dir-test/**/*"] 17 | queue_all_runs = true 18 | force_delete = true 19 | 20 | vcs_repo = { 21 | identifier = "alexbasista/terraform-tfe-workspacer" 22 | branch = "main" 23 | oauth_token_id = var.oauth_token_id 24 | ingress_submodules = false 25 | tags_regex = null 26 | } 27 | } 28 | 29 | module "workspacer_github_app" { 30 | source = "../.." 31 | 32 | organization = var.organization 33 | workspace_name = "workspacer-vcs-github-app-ex" 34 | workspace_tags = ["env:test", "app:acme"] 35 | project_name = "Default Project" 36 | 37 | working_directory = "/tests/with-vcs/tf-working-dir-test" 38 | auto_apply = true 39 | file_triggers_enabled = true 40 | trigger_patterns = ["/tests/with-vcs/tf-working-dir-test/**/*"] 41 | queue_all_runs = true 42 | 43 | vcs_repo = { 44 | identifier = "alexbasista/terraform-tfe-workspacer" 45 | branch = "main" 46 | github_app_installation_id = var.github_app_installation_id 47 | ingress_submodules = false 48 | tags_regex = null 49 | } 50 | } -------------------------------------------------------------------------------- /tests/with-vcs/tf-working-dir-test/test_main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | random = { 4 | source = "hashicorp/random" 5 | version = "3.6.3" 6 | } 7 | } 8 | } 9 | 10 | resource "random_pet" "test_1" {} 11 | 12 | resource "random_pet" "test_2" {} -------------------------------------------------------------------------------- /tests/with-vcs/variables.tf: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Provider 3 | #------------------------------------------------------------------------------ 4 | variable "tfe_hostname" { 5 | type = string 6 | description = "Hostname of self-hosted TFE instance. Leave as default when using HCP Terraform." 7 | default = "app.terraform.io" 8 | } 9 | 10 | #------------------------------------------------------------------------------ 11 | # Module 12 | #------------------------------------------------------------------------------ 13 | variable "organization" { 14 | type = string 15 | description = "Name of Organization to create Workspace in." 16 | } 17 | 18 | variable "oauth_token_id" { 19 | type = string 20 | sensitive = true 21 | default = null 22 | } 23 | 24 | variable "github_app_installation_id" { 25 | type = string 26 | sensitive = true 27 | } -------------------------------------------------------------------------------- /variable_sets.tf: -------------------------------------------------------------------------------- 1 | data "tfe_variable_set" "vs" { 2 | for_each = toset(var.variable_set_names) 3 | 4 | name = each.value 5 | organization = var.organization 6 | } 7 | 8 | resource "tfe_workspace_variable_set" "vs" { 9 | for_each = data.tfe_variable_set.vs 10 | 11 | variable_set_id = each.value.id 12 | workspace_id = tfe_workspace.ws.id 13 | } -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Workspace 3 | #------------------------------------------------------------------------------ 4 | variable "organization" { 5 | type = string 6 | description = "Name of Organization to create Workspace in." 7 | } 8 | 9 | variable "workspace_name" { 10 | type = string 11 | description = "Name of Workspace to create." 12 | } 13 | 14 | variable "workspace_desc" { 15 | type = string 16 | description = "Description of Workspace." 17 | default = "Created by 'workspacer' Terraform module." 18 | } 19 | 20 | variable "agent_pool_id" { 21 | type = string 22 | description = "ID of existing Agent Pool to assign to Workspace. Only valid when `execution_mode` is set to `agent`." 23 | default = null 24 | 25 | validation { 26 | condition = var.execution_mode == "agent" ? var.agent_pool_id != null : true 27 | error_message = "Value must be set if `execution_mode` is set to `agent`." 28 | } 29 | } 30 | 31 | variable "allow_destroy_plan" { 32 | type = bool 33 | description = "Boolean setting to allow destroy plans on Workspace." 34 | default = true 35 | } 36 | 37 | variable "auto_apply" { 38 | type = bool 39 | description = "Boolean to automatically run a Terraform apply after a successful Terraform plan." 40 | default = false 41 | } 42 | 43 | variable "execution_mode" { 44 | type = string 45 | description = "Execution mode of Workspace. Valid values are `remote`, `local`, or `agent`." 46 | default = null 47 | 48 | validation { 49 | condition = var.execution_mode != null ? contains(["remote", "local", "agent"], var.execution_mode) : true 50 | error_message = "If not `null`, valid values are `remote`, `local`, or `agent`." 51 | } 52 | } 53 | 54 | variable "assessments_enabled" { 55 | type = bool 56 | description = "Boolean to enable Health Assessments such as Drift Detection on Workspace." 57 | default = false 58 | } 59 | 60 | variable "file_triggers_enabled" { 61 | type = bool 62 | description = "Boolean to filter Runs triggered via webhook (VCS push) based on `working_directory` and `trigger_prefixes`." 63 | default = true 64 | } 65 | 66 | variable "global_remote_state" { 67 | type = bool 68 | description = "Boolean to allow all Workspaces within the Organization to remotely access the State of this Workspace." 69 | default = false 70 | } 71 | 72 | variable "remote_state_consumer_ids" { 73 | type = list(string) 74 | description = "List of existing Workspace IDs allowed to remotely access the State of Workspace." 75 | default = null 76 | } 77 | 78 | variable "queue_all_runs" { 79 | type = bool 80 | description = "Boolean setting for Workspace to automatically queue all Runs after creation." 81 | default = true 82 | } 83 | 84 | variable "speculative_enabled" { 85 | type = bool 86 | description = "Boolean to allow Speculative Plans on Workspace." 87 | default = true 88 | } 89 | 90 | variable "structured_run_output_enabled" { 91 | type = bool 92 | description = "Boolean to enable the advanced Run UI. Set to `false` for the traditional console-based Run output." 93 | default = true 94 | } 95 | 96 | variable "ssh_key_id" { 97 | type = string 98 | description = "SSH private key the Workspace will use for downloading Terraform modules from Git-based module sources. Key must exist in Organization first." 99 | default = null 100 | } 101 | 102 | variable "workspace_tags" { 103 | type = list(string) 104 | description = "List of tag names to apply to Workspace. Tags must only contain letters, numbers, or colons." 105 | default = [] 106 | } 107 | 108 | variable "terraform_version" { 109 | type = string 110 | description = "Version of Terraform to use for this Workspace." 111 | default = null 112 | } 113 | 114 | variable "trigger_prefixes" { 115 | type = list(string) 116 | description = "List of paths relative to the root of the VCS repo to filter on when `file_triggers_enabled` is `true`." 117 | default = null 118 | } 119 | 120 | variable "trigger_patterns" { 121 | type = list(string) 122 | description = "List of glob patterns that describe the files monitored for changes to trigger Runs in Workspace. Mutually exclusive with `trigger_prefixes`. Only available with TFC." 123 | default = null 124 | } 125 | 126 | variable "working_directory" { 127 | type = string 128 | description = "The relative path that Terraform will execute within. Defaults to the root of the repo." 129 | default = null 130 | } 131 | 132 | variable "vcs_repo" { 133 | type = object({ 134 | identifier = string 135 | branch = optional(string, null) 136 | oauth_token_id = optional(string, null) 137 | github_app_installation_id = optional(string, null) 138 | ingress_submodules = optional(bool, false) 139 | tags_regex = optional(string, null) 140 | }) 141 | 142 | description = "Object containing settings to connect Workspace to a VCS repository." 143 | default = null 144 | 145 | validation { 146 | condition = var.vcs_repo != null && var.trigger_prefixes != null ? var.vcs_repo.tags_regex == null : true 147 | error_message = "vcs_repo.tags_regex must be `null` when `trigger_prefixes` is set." 148 | } 149 | 150 | validation { 151 | condition = var.vcs_repo != null && var.trigger_patterns != null ? var.vcs_repo.tags_regex == null : true 152 | error_message = "vcs_repo.tags_regex must be `null` when `trigger_patterns` is set." 153 | } 154 | 155 | validation { 156 | condition = var.vcs_repo != null && var.file_triggers_enabled ? var.vcs_repo.tags_regex == null : true 157 | error_message = "`vcs_repo.tags_regex` cannot be set `file_triggers_enabled` is `true`." 158 | } 159 | } 160 | 161 | variable "tags_regex" { 162 | type = string 163 | description = "A regular expression used to trigger a Run in Workspace for matching Git tags. This option conflicts with `trigger_patterns` and `trigger_prefixes`. Should only set this value if the former is not being used." 164 | default = null 165 | } 166 | 167 | variable "force_delete" { 168 | type = bool 169 | description = "Boolean to allow deletion of the Workspace if there is a Terraform state that contains resources." 170 | default = null 171 | } 172 | 173 | variable "project_name" { 174 | type = string 175 | description = "Name of existing Project to create Workspace in." 176 | default = null 177 | } 178 | 179 | #------------------------------------------------------------------------------ 180 | # Workspace Variables 181 | #------------------------------------------------------------------------------ 182 | variable "tfvars" { 183 | type = any 184 | description = "Map of Terraform variables to add to Workspace." 185 | default = {} 186 | } 187 | 188 | variable "tfvars_sensitive" { 189 | type = any 190 | description = "Map of sensitive Terraform variables to add to Workspace." 191 | default = {} 192 | } 193 | 194 | variable "tfvars_ignore_changes" { 195 | type = any 196 | description = "Map of Terraform variables to add to Workspace whereby changes made outside of Terraform will be ignored." 197 | default = {} 198 | } 199 | 200 | variable "envvars" { 201 | type = map(string) 202 | description = "Map of Environment variables to add to Workspace." 203 | default = {} 204 | } 205 | 206 | variable "envvars_sensitive" { 207 | type = map(string) 208 | description = "Map of sensitive Environment variables to add to Workspace." 209 | default = {} 210 | } 211 | 212 | variable "envvars_ignore_changes" { 213 | type = map(string) 214 | description = "Map of sensitive Environment variables to add to Workspace whereby changes made outside of Terraform will be ignored." 215 | default = {} 216 | } 217 | 218 | #------------------------------------------------------------------------------ 219 | # Team Access 220 | #------------------------------------------------------------------------------ 221 | variable "team_access" { 222 | type = map(string) 223 | description = "Map of existing Team(s) and built-in permissions to grant on Workspace." 224 | default = {} 225 | } 226 | 227 | variable "custom_team_access" { 228 | type = map( 229 | object( 230 | { 231 | runs = string 232 | variables = string 233 | state_versions = string 234 | sentinel_mocks = string 235 | workspace_locking = bool 236 | run_tasks = bool 237 | } 238 | ) 239 | ) 240 | description = "Map of existing Team(s) and custom permissions to grant on Workspace. If used, all keys in the object must be specified." 241 | default = {} 242 | } 243 | 244 | #------------------------------------------------------------------------------ 245 | # Notifications 246 | #------------------------------------------------------------------------------ 247 | variable "notifications" { 248 | type = list( 249 | object( 250 | { 251 | name = string 252 | destination_type = string 253 | url = optional(string) 254 | token = optional(string) 255 | email_addresses = optional(list(string)) 256 | email_user_ids = optional(list(string)) 257 | triggers = list(string) 258 | enabled = bool 259 | } 260 | ) 261 | ) 262 | description = "List of Notification objects to configure on Workspace." 263 | default = [] 264 | } 265 | 266 | #------------------------------------------------------------------------------ 267 | # Run Triggers 268 | #------------------------------------------------------------------------------ 269 | variable "run_trigger_source_workspaces" { 270 | type = list(string) 271 | description = "List of existing Workspace names that will trigger runs on Workspace." 272 | default = [] 273 | } 274 | 275 | #------------------------------------------------------------------------------ 276 | # Workspace Variable Sets 277 | #------------------------------------------------------------------------------ 278 | variable "variable_set_names" { 279 | type = list(string) 280 | description = "List of names of existing Variable Sets to add this Workspace into." 281 | default = [] 282 | } 283 | 284 | #------------------------------------------------------------------------------ 285 | # Workspace Policy Sets 286 | #------------------------------------------------------------------------------ 287 | variable "policy_set_names" { 288 | type = list(string) 289 | description = "List of names of existing Policy Sets to add this Workspace into." 290 | default = [] 291 | } 292 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.9" 3 | 4 | required_providers { 5 | tfe = { 6 | source = "hashicorp/tfe" 7 | version = "~> 0.62" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /workspace.tf: -------------------------------------------------------------------------------- 1 | resource "tfe_workspace" "ws" { 2 | organization = var.organization 3 | name = var.workspace_name 4 | description = var.workspace_desc 5 | allow_destroy_plan = var.allow_destroy_plan 6 | auto_apply = var.auto_apply 7 | assessments_enabled = var.assessments_enabled 8 | file_triggers_enabled = var.file_triggers_enabled 9 | queue_all_runs = var.queue_all_runs 10 | speculative_enabled = var.speculative_enabled 11 | structured_run_output_enabled = var.structured_run_output_enabled 12 | ssh_key_id = var.ssh_key_id 13 | tag_names = var.workspace_tags 14 | terraform_version = var.terraform_version 15 | trigger_prefixes = var.trigger_prefixes 16 | trigger_patterns = var.trigger_patterns 17 | working_directory = var.working_directory 18 | force_delete = var.force_delete 19 | project_id = var.project_name != null ? data.tfe_project.ws[0].id : null 20 | 21 | dynamic "vcs_repo" { 22 | for_each = var.vcs_repo != null ? [var.vcs_repo] : [] 23 | 24 | content { 25 | identifier = var.vcs_repo.identifier 26 | branch = var.vcs_repo.branch 27 | oauth_token_id = var.vcs_repo.oauth_token_id 28 | github_app_installation_id = var.vcs_repo.github_app_installation_id 29 | ingress_submodules = var.vcs_repo.ingress_submodules 30 | tags_regex = var.vcs_repo.tags_regex 31 | } 32 | } 33 | } 34 | 35 | resource "tfe_workspace_settings" "ws" { 36 | workspace_id = tfe_workspace.ws.id 37 | execution_mode = var.execution_mode 38 | global_remote_state = var.global_remote_state 39 | remote_state_consumer_ids = var.remote_state_consumer_ids 40 | 41 | 42 | agent_pool_id = var.execution_mode == "agent" ? var.agent_pool_id : null 43 | } 44 | 45 | -------------------------------------------------------------------------------- /workspace_variables.tf: -------------------------------------------------------------------------------- 1 | resource "tfe_variable" "tfvars" { 2 | for_each = var.tfvars 3 | 4 | workspace_id = tfe_workspace.ws.id 5 | key = each.key 6 | value = try(tostring(each.value), "nostring") == "nostring" ? replace(jsonencode(each.value), ":", "=") : tostring(each.value) 7 | description = "Managed by TFE Terraform provider." 8 | hcl = try(tostring(each.value), "nostring") == "nostring" ? true : false 9 | sensitive = false 10 | category = "terraform" 11 | } 12 | 13 | resource "tfe_variable" "tfvars_sensitive" { 14 | for_each = var.tfvars_sensitive 15 | 16 | workspace_id = tfe_workspace.ws.id 17 | key = each.key 18 | value = try(tostring(each.value), "nostring") == "nostring" ? replace(jsonencode(each.value), ":", "=") : tostring(each.value) 19 | description = "Managed by TFE Terraform provider." 20 | hcl = try(tostring(each.value), "nostring") == "nostring" ? true : false 21 | sensitive = true 22 | category = "terraform" 23 | } 24 | 25 | resource "tfe_variable" "envvars" { 26 | for_each = var.envvars 27 | 28 | workspace_id = tfe_workspace.ws.id 29 | key = each.key 30 | value = each.value 31 | description = "Managed by TFE Terraform provider." 32 | hcl = false 33 | sensitive = false 34 | category = "env" 35 | } 36 | 37 | resource "tfe_variable" "envvars_sensitive" { 38 | for_each = var.envvars_sensitive 39 | 40 | workspace_id = tfe_workspace.ws.id 41 | key = each.key 42 | value = each.value 43 | description = "Managed by TFE Terraform provider." 44 | hcl = false 45 | sensitive = true 46 | category = "env" 47 | } 48 | 49 | resource "tfe_variable" "tfvars_ignore_changes" { 50 | for_each = var.tfvars_ignore_changes 51 | 52 | workspace_id = tfe_workspace.ws.id 53 | key = each.key 54 | value = try(tostring(each.value), "nostring") == "nostring" ? replace(jsonencode(each.value), ":", "=") : tostring(each.value) 55 | description = "Managed by TFE Terraform provider." 56 | hcl = try(tostring(each.value), "nostring") == "nostring" ? true : false 57 | sensitive = false 58 | category = "terraform" 59 | 60 | lifecycle { 61 | ignore_changes = [ 62 | value, 63 | ] 64 | } 65 | } 66 | 67 | resource "tfe_variable" "envvars_ignore_changes" { 68 | for_each = var.envvars_ignore_changes 69 | 70 | workspace_id = tfe_workspace.ws.id 71 | key = each.key 72 | value = each.value 73 | description = "Managed by TFE Terraform provider." 74 | hcl = false 75 | sensitive = false 76 | category = "env" 77 | 78 | lifecycle { 79 | ignore_changes = [ 80 | value, 81 | ] 82 | } 83 | } --------------------------------------------------------------------------------