Compare commits

...

154 Commits

Author SHA1 Message Date
Anoop Gopalakrishnan c0557127de
Merge pull request #396 from roguepikachu/chore/upgrade-go-k8s-version
Chore: upgrades Go (1.23.8) and K8s (1.31.0) versions
2025-07-07 10:12:58 +05:30
Ayush 2e7d7be21b test: improve error messages and assertions in configuration tests
Signed-off-by: Ayush <ayushshyamkumar888@gmail.com>
2025-07-04 09:09:30 +05:30
Ayush 599f5b83b4 refactor: update provider package references and enhance fake client setup in tests
Signed-off-by: Ayush <ayushshyamkumar888@gmail.com>
2025-07-03 12:56:28 +05:30
Ayush d68a2bc207 fix: update test cases and error messages for better clarity
Signed-off-by: Ayush <ayushshyamkumar888@gmail.com>
2025-07-03 12:56:28 +05:30
Pushparaj Shetty K S ee52ee1f10 update terraform crds
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S 708116dca3 update makefile
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S c602f9ba6b update unit test
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S fe39f2314c update makefile
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S 56411056ed update lint and some dependency version
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S d2f5598bd2 update ubuntu version and fix some dependency issue
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Pushparaj Shetty K S 5618fc875f -supdate the go and k8s version
Signed-off-by: Pushparaj Shetty K S <pshettyks@guidewire.com>
2025-06-30 10:20:11 +05:30
Zheng Xi Zhou 0845592ef1
Update the link of kubevela project (#386)
As the project of KubeVela was moved from `oam` to `kubevela` space, updated the link for it.
2024-03-12 21:10:54 +08:00
qiaozp 966471af19
Refactor constructing containers and support checkout certain git refs (#382) 2023-09-25 14:25:36 +08:00
zhangsiwei 3536da5d3d
Fix: Keep the backend secret when clean up SubResources if the deleteResou… (#381)
Co-authored-by: zhangsiwei <zhangsiwei@lizhi.fm>
2023-09-22 21:31:21 -05:00
qiaozp eded6d09e8
Refactor: split TFConfigurationMeta to single package (#379) 2023-09-22 11:02:51 +08:00
jaswalkiranavtar 4e21b54765
ISSUE-376: Removing the condition that looks for outputs in tfstate to decide if tfstate is generated or not (#377)
Co-authored-by: Gaurav Jaswal <gjaswal@guidewire.com>
2023-09-01 14:09:31 +08:00
Anoop Gopalakrishnan 1dfb4a3ec8
Fix: replacing tags of busybox and alpine (#370)
- fixes issue #369

Signed-off-by: Anoop Gopalakrishnan <agopalakrishnan@guidewire.com>
Co-authored-by: Lin Ling <lling@guidewire.com>
2023-07-26 11:12:54 +08:00
Somefive b69b78a585
Fix: backup and restore is blocking release, pend it (#368) 2023-06-27 10:10:07 +08:00
Somefive 87fb0fe348
Fix: update chart release (#367) 2023-06-21 14:29:36 +08:00
xingming01 3afed4a627
Fix: reconcile after configuration's hcl changed (#360)
Co-authored-by: caoxingming <caoxingming@jd.com>
2023-06-08 10:23:34 +08:00
吴就业 d2e6f21b29
fix: The execution of the terraformDestroy method fails and the appli… (#366)
Co-authored-by: 吴就业 <wujiuye@lizhi.fm>
2023-06-01 14:00:59 +08:00
吴就业 a8a6504ded
fix: Check whether the namespace of Secret and ConfigMap is the same … (#365)
Co-authored-by: 吴就业 <wujiuye@lizhi.fm>
2023-05-30 10:00:59 +08:00
吴就业 6e51e97210
feat: Supports marking a job as failed after specifying a few retries… (#362)
* feat: Supports marking a job as failed after specifying a few retries #361

Signed-off-by: 吴就业 <wujiuye@lizhi.fm>

* Values.jobBackoffLimit defaults to ""

Signed-off-by: 吴就业 <wujiuye@lizhi.fm>

---------

Signed-off-by: 吴就业 <wujiuye@lizhi.fm>
Co-authored-by: 吴就业 <wujiuye@lizhi.fm>
2023-05-19 17:50:07 +08:00
qiaozp f8184ed615
Bump docker-terraform image to 1.1.5 (#356)
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2023-02-12 21:43:16 +08:00
qiaozp e2f31a4b6b
Feat: allow delete the provisioning resources (#354)
* Feat: allow delete configuration halfway the apply

* Modify chart

* Add test

Fix test script

* smaller quota

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>

---------

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2023-02-09 14:03:50 +08:00
qiaozp 58fad4c416
Revert "feat: add libc6-compat to fix dynamic linking issue, add ca-certificates" (#355)
This reverts commit 7a102d31a6.

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2023-02-09 13:49:36 +08:00
Ramesh Krishna 7a102d31a6
feat: add libc6-compat to fix dynamic linking issue, add ca-certificates (#353)
Signed-off-by: raradhakrishnan <raradhakrishnan@guidewire.com>
2023-02-07 10:03:13 +08:00
Ramesh Krishna c6d69a8828
Feat: support private terraform registry (#352)
Co-authored-by: qiaozp <47812250+chivalryq@users.noreply.github.com>
2023-01-19 11:36:33 +08:00
Joshua Agboola 841b0d17d9
Feat: Support retrieving modules in private git repo through SSH (#349) 2022-12-09 10:31:30 +08:00
Nan Li 55fd92efa4
Fix the build error of the Release action (#344) 2022-08-23 11:21:24 +08:00
Nan Li b3610336f2
Add Release Action to Github Workflow (#330) 2022-08-18 11:02:08 +08:00
leason 34d5b1b4aa
Fix: delete Job cascading delete Pod fail (#343)
Co-authored-by: lisen <lisen@youzan.com>
2022-08-17 10:31:18 +08:00
Somefive d9360c0b91
Merge pull request #342 from chivalryq/fix-destroy
Fix: async delete cause duplicated destroy
2022-08-16 21:19:24 +08:00
Qiaozp 3a96f687c1 Fix: async delete cause duplicated destroy
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-16 18:04:00 +08:00
Somefive b224570ad0
Merge pull request #341 from chivalryq/delete-resource
Fix: rollback delete resource logic
2022-08-16 15:17:46 +08:00
Qiaozp 1f49401f92 fix test
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-16 15:06:16 +08:00
Qiaozp d9e9d6be4f Fix: rollback delete resource logic
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-16 14:55:09 +08:00
leason 989407ed13
Support for specifying job environment variables (#337)
Co-authored-by: lisen <lisen@youzan.com>
2022-08-15 12:04:09 +08:00
qiaozp cb66ba89fd
Fix: legacy backend state reading and GC (#338) 2022-08-12 11:53:45 +08:00
qiaozp 8620dbb266
Merge pull request #333 from loheagn/backup_restore_fix 2022-08-09 11:55:38 +08:00
loheagn 5d81c7b409 fix for "remove `goto`"
Signed-off-by: loheagn <loheagn@icloud.com>
2022-08-09 11:33:04 +08:00
loheagn d1976ed252 check the container name when try to fetch the `TERRAFORM_BACKEND_NAMESPACE` env from terraform-controller deployment
Signed-off-by: loheagn <loheagn@icloud.com>
2022-08-09 11:26:26 +08:00
loheagn 7b0e38653d remove `goto`
Signed-off-by: loheagn <loheagn@icloud.com>
2022-08-09 11:25:18 +08:00
Somefive 337610ede1
Merge pull request #336 from chivalryq/fix-apply
Fix: apply job recreation
2022-08-09 10:39:58 +08:00
Qiaozp 9e37965ba9 fix nil pointer
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-08 20:19:35 +08:00
Qiaozp 03d056595e fix
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-08 18:10:19 +08:00
Qiaozp d64230ae7e fix
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-08 17:41:09 +08:00
Qiaozp abe42943b2 Fix: apply job recreation
Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-08 17:29:33 +08:00
qiaozp cd3c258319
Merge pull request #334 from leason00/bugfix_ucloud_region 2022-08-08 16:30:25 +08:00
lisen 689a4db96c bugfix ucloud region
Signed-off-by: lisen <lisen@youzan.com>
2022-08-05 17:30:03 +08:00
loheagn 2881dbdb35 Fix some bugs of `backup_restore` tool
Signed-off-by: loheagn <loheagn@icloud.com>
2022-08-05 09:50:36 +08:00
qiaozp 43c73a54b2
Fix: compatible to gc legacy resources when switch to controller-namespace (#332)
* Fix: compatible to gc legacy resources when switch to controller-namespace

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>

* use forloop to gc sub-resources

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>

* fix lint

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>

* fix

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-08-03 17:15:44 +08:00
qiaozp 791d6a2a6f
Merge pull request #331 from loheagn/backup_restore_fix 2022-07-28 22:59:30 -05:00
loheagn ce3902a7a6 fix argument bug for `restore` command
Signed-off-by: loheagn <loheagn@icloud.com>
2022-07-29 11:10:27 +08:00
Rohith Jayawardene 4b5a5ebe47
[FEATURE] - Run Jobs in central namespace (#295)
* - updating the chart to support job namespaces

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - updating the controllers to support the ability to run the jobs in a central namespace and avoiding the need to copy global secrets around

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - updating the cmd to support passing the job namespace and support localizing to a single namespace if required

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* adding the resource limits

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - trying to fix the linting and unit tests

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - improving the test coverage

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - changing the job-namespace command line flag to --controller-namespace
- changing the variable name in controller and meta to reflect changes i.e JobNamespace -> ControllerNamespace
- updated the helm chart to reflect the changes

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - uncommenting the tests

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - commenting out the test for a moment

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - fixing up the issue highlighted in the review

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>

* - rebasing master onto the branch and fixing the units tests / ensuring it works

Signed-off-by: Rohith Jayawardene <gambol99@gmail.com>
2022-07-25 08:48:30 +08:00
Nan Li 88aa8b9557
enhance `backup_restore` tool (#327)
* backup_restore:
1. support to back up the configurations used s3 backend
2. support to back up te configurations created by KubeVela Application

Signed-off-by: loheagn <loheagn@icloud.com>

* backup_restore:
1. support to restore the cloud resource from a KubeVela Application

Signed-off-by: loheagn <loheagn@icloud.com>
2022-06-30 10:49:47 +08:00
Zheng Xi Zhou 1478cddfb2
Fix: Terraform state is being overwritten each time (#326)
* Fix: Terraform state is being overwritten each time

When using Terraform component to create applications in different
namespaces, Terraform state is being overwritten each time

Fix https://github.com/kubevela/kubevela/issues/4215

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix UT

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-06-27 11:33:16 +08:00
Zheng Xi Zhou 92c9496023
Add some Terraform native examples (#325)
- AWS s3 exmples
- Backend examples

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-06-27 11:11:13 +08:00
Nan Li 5d0ff0ba67
Enhance S3Backend (#323)
* make `configuration.spec.backend.s3.region` optional

Signed-off-by: loheagn <loheagn@icloud.com>

* check if the bucket exists when build s3 backend

Signed-off-by: loheagn <loheagn@icloud.com>

* fix typo

Signed-off-by: loheagn <loheagn@icloud.com>
2022-06-27 11:11:01 +08:00
Nan Li 487809a0fe
Add support for custom s3 backend (#316)
* Add support for custom s3 backend

Signed-off-by: loheagn <loheagn@icloud.com>

* Rename some examples

Signed-off-by: loheagn <loheagn@icloud.com>

* Rename `S3Backend.Token` to `S3Backend.SessionToken`

Signed-off-by: loheagn <loheagn@icloud.com>

* Fix go.mod

Signed-off-by: loheagn <loheagn@icloud.com>

* Refactor the `backend.Backend` initialization logic

Signed-off-by: loheagn <loheagn@icloud.com>

* lint code

Signed-off-by: loheagn <loheagn@icloud.com>

* lint code

Signed-off-by: loheagn <loheagn@icloud.com>

* lint code

Signed-off-by: loheagn <loheagn@icloud.com>

* lint code

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test retry

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test retry

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test try again

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test try again

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test try again

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test try again

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test try again

Signed-off-by: loheagn <loheagn@icloud.com>

* e2e test

Signed-off-by: loheagn <loheagn@icloud.com>

* Reuse the meta.Credentials to build `backend.Backend`

Signed-off-by: loheagn <loheagn@icloud.com>

* make "kubernetes" and "s3" constants

Signed-off-by: loheagn <loheagn@icloud.com>
2022-06-09 10:08:19 +08:00
leason 6a3bc1a525
bugfix delete fail when deleteConfigurationDirectly (#320)
Signed-off-by: lisen <lisen@youzan.com>

Co-authored-by: lisen <lisen@youzan.com>
2022-06-08 15:58:53 +08:00
Zheng Xi Zhou b3e099a211
Remove color codes from Configuration status (#322)
Removed color codes from Configuration status, while keep the color
in the Terraform log for a better visualbility.

Signed-off-by: Zheng Xi Zhou <zhengxi.zzx@alibaba-inc.com>

Co-authored-by: Zheng Xi Zhou <zhengxi.zzx@alibaba-inc.com>
2022-06-08 10:06:04 +08:00
Zheng Xi Zhou cef58c7e58
Feat: expose region in status (#318)
Region is the region for the cloud resources created by this Configuration.
If spec.region is not empty, it's the value of it. Otherwise, it's the value
of spec.providerReference.region.

Signed-off-by: Zheng Xi Zhou <zhengxi.zzx@alibaba-inc.com>

Co-authored-by: Zheng Xi Zhou <zhengxi.zzx@alibaba-inc.com>
2022-05-31 15:22:23 +08:00
Nan Li ae8296e17e
Add `backup` tool (#315)
* Enhance the `restore` tool

Signed-off-by: loheagn <loheagn@icloud.com>

* Add `backup` tool

Signed-off-by: loheagn <loheagn@icloud.com>
2022-05-27 10:47:24 +08:00
Nan Li 2117b20c64
Terraform state backup and restore tool (#314)
* Add `restore` tool

Signed-off-by: loheagn <loheagn@icloud.com>

* Add examples and README for `restore` tool

Signed-off-by: loheagn <loheagn@icloud.com>

* Fix typo

Signed-off-by: loheagn <loheagn@icloud.com>
2022-05-26 10:07:29 +08:00
Nan Li 971d169b2b
Support custom Terraform backends (#291)
* Update the specification of the `spec.backend` to enable the end-users to custom the Terraform backend configuration

Signed-off-by: loheagn <loheagn@icloud.com>
2022-05-25 10:51:29 +08:00
Zheng Xi Zhou 8601f66f16
Merge pull request #313 from loheagn/fix_BaseConfigurationSpec
Remove `spec.BaseConfigurationSpec` from `configuration_controller_test.go`
2022-05-25 09:06:48 +08:00
loheagn db94a329fe Remove `spec.BaseConfigurationSpec` from configuration_controller_test.go
Signed-off-by: loheagn <loheagn@icloud.com>
2022-05-24 22:21:27 +08:00
Zheng Xi Zhou 16bd07ec8e
Merge pull request #307 from leason00/bugfix_bool
The type of DeleteResource is changed to pointer
2022-05-24 16:09:33 +08:00
Somefive 4cdfcd2fcd
Merge pull request #312 from zzxwill/baseconfiguration
Remove the inlined BaseConfiguration
2022-05-20 15:58:20 +08:00
Zheng Xi Zhou 90b168fa55 Remove the inlined BaseConfiguration
Removed the inlined BaseConfiguration to make it more straightforward.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-20 15:44:38 +08:00
Zheng Xi Zhou fdaf7d00ac
Merge pull request #309 from zzxwill/api-version
Upgrade appversion for the Chart
2022-05-18 15:18:50 +08:00
Zheng Xi Zhou 5fab8be11b Upgrade appversion for the Chart
As the API of Configuration changed, upgraded the version for the
appVersion for the Chart

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-18 15:17:45 +08:00
Zheng Xi Zhou 37a25a1dd6
Merge pull request #305 from zzxwill/force-deletion
Support force deletion of Configuration
2022-05-18 15:14:36 +08:00
Zheng Xi Zhou bc053fb0d6 Support force deletion of Configuration
Force delete Configuration no matter which state it is or whether it has
provisioned some resources. This feature will help delete Configuration
in unexpected cases.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-18 14:43:40 +08:00
kang.feng 99eb18e5a8
add huaweicloud support (#308)
* feat: add HuaweiCloud provider support

Signed-off-by: fengkang <fengkangb@digitalchina.com>

* feat: add HuaweiCloud provider support

Signed-off-by: fengkang <fengkangb@digitalchina.com>

* feat: add HuaweiCloud provider support

Signed-off-by: fengkang <fengkangb@digitalchina.com>
2022-05-18 14:02:51 +08:00
lisen ff799ec0b6 The type of The type of DeleteResource is changed to pointer
Signed-off-by: lisen <lisen@youzan.com>
2022-05-16 20:03:59 +08:00
Zheng Xi Zhou 557884198f
Merge pull request #304 from wanfuuyu/reduce_image_size
use alpine-based image
2022-05-16 17:55:31 +08:00
wanfuuyu 4cd838bc6d use alpine-based image
Signed-off-by: wanfuuyu <wanfuuyu@gmail.com>
2022-05-16 09:33:29 +08:00
Somefive cd37e5c198
Merge pull request #301 from zzxwill/force-deletion
Add e2e test
2022-05-13 16:56:10 +08:00
Zheng Xi Zhou 92a47d7345 Add e2e test
- Added e2e test workflow
- Added e2e tests for Configuration which doesn't have a provider

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-13 16:45:54 +08:00
Zheng Xi Zhou 50994f3235
Merge pull request #297 from zzxwill/inline-credentials
Support inline credentials besides provider
2022-05-10 09:46:19 +08:00
Zheng Xi Zhou d40417a478 fix UT
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-10 09:41:04 +08:00
Zheng Xi Zhou 7635134cd8 add e2e
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-10 09:41:04 +08:00
Zheng Xi Zhou 6bc093f5b2 Support inline credentials besides provider
Fix https://github.com/oam-dev/terraform-controller/issues/290

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-05-10 09:40:54 +08:00
leason 80fa089da7
Assign nodeselector to job. (#300)
Signed-off-by: lisen <lisen@youzan.com>

Co-authored-by: lisen <lisen@youzan.com>
2022-05-09 17:24:29 +08:00
Zheng Xi Zhou 3b4fde8230
Merge pull request #276 from redsnapper2006/fix-244
Introduce resource limits for Terraform controller
2022-04-07 15:20:05 +08:00
Yan Du 6f15673a25 Merge branch 'fix-244' of https://github.com/redsnapper2006/terraform-controller into fix-244 2022-04-06 22:49:01 +08:00
Yan Du 465171f59d add resources.(limits|requests).(cpu|memory) value
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-04-06 21:42:11 +08:00
Yan Du 6fd6744a81 resolve conflict
Signed-off-by: Yan Du <yan.du@kyndryl.com>
2022-03-24 17:40:05 +08:00
Yan Du 1b2ed84d2a Merge branch 'master' into fix-244
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-24 17:18:03 +08:00
Zheng Xi Zhou 750d1a8d1e
Merge pull request #284 from zzxwill/init-syntax-issues
Check whether there is error in Terraform init stage
2022-03-23 20:56:08 +08:00
Zheng Xi Zhou 75ae1d409f fix CI issues
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-23 20:51:43 +08:00
Zheng Xi Zhou 5bb2ef99a0 fix format issues
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-23 20:49:55 +08:00
Zheng Xi Zhou 7879374150 revise ut
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-23 20:49:55 +08:00
Zheng Xi Zhou d837a0ffc3 add unit tests
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-23 20:49:55 +08:00
Zheng Xi Zhou ede04da45e Check whether there is error in Terraform init stage
- check logs when `terraform init`
- allow a Configuration to delete when hiting init issues

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-23 20:49:54 +08:00
Zheng Xi Zhou dd0447ea8d
Merge pull request #289 from loheagn/gfi
Update the error string reported when different configurations try to update the same secret
2022-03-23 20:41:42 +08:00
loheagn e8458e6e38 Update the error string reported when different configurations try to update the same secret
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-23 20:36:01 +08:00
Zheng Xi Zhou 52091ac0e0
Merge pull request #287 from zzxwill/ut
Add unit testcases
2022-03-20 19:18:11 +08:00
Zheng Xi Zhou d1a5282610 Add unit testcases
Added more uts

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-20 16:50:32 +08:00
Zheng Xi Zhou c2efca45f5
Merge pull request #286 from zzxwill/json
Remove Terraform JSON support
2022-03-20 11:32:50 +08:00
Zheng Xi Zhou 36395ff716 fix typo
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-20 11:14:33 +08:00
Zheng Xi Zhou 7e7efc28ba Remove Terraform JSON support
JSON typed configuration is deprecated in v0.3.1, now removed it.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-19 14:43:51 +08:00
Zheng Xi Zhou 6ee62c5990
Merge pull request #283 from loheagn/gfi
Fix the incompatibility issue about the previous connection secrets
2022-03-17 16:00:23 +08:00
loheagn f6ac372438 Fix compile errors
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-17 15:54:13 +08:00
loheagn 33fa1de0d2 Fix the incompatibility issue that the configurations can not update the previous connection secrets which have no owner labels
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-17 15:53:25 +08:00
Zheng Xi Zhou ea44f4d095
Merge pull request #250 from loheagn/gfi
Fix: Avoid writing connection string to a same secret
2022-03-17 14:01:16 +08:00
Zheng Xi Zhou 9bdd567f7c
Merge pull request #281 from loheagn/hack_validation_backend
Add `tf_validation_backend` tool to the `hack` dir
2022-03-17 14:00:27 +08:00
loheagn 7810dc21a1 Add `tf_validation_backend` tool to the `hack` dir
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-17 00:31:21 +08:00
redsnapper2006 69baabd2b8
Typo in getting-started.md & make command console output need to be updated due to changing from [go get] to [go install] in Makefile #278 (#279)
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-14 10:11:13 +08:00
Zheng Xi Zhou 33b256c069
Merge pull request #277 from redsnapper2006/fix-273
Fix: go get issue
2022-03-12 20:37:06 +08:00
Yan Du eab616291a Introduce resource limits for Terraform controller #244
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 18:32:13 +08:00
Yan Du 98befc788f oam-dev/terraform-controller#273 go get: installing executables with 'go get' in module mode is deprecated.
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:53:00 +08:00
Yan Du 4bf779853b oam-dev/terraform-controller#244 Introduce resource limits for Terraform controller
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:43:04 +08:00
Yan Du 13a46d4eb7 Merge branch 'master' into fix-244
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:40:49 +08:00
Yan Du e32c9da466 oam-dev/terraform-controller#244 Introduce resource limits for Terraform controller
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:26:32 +08:00
Zheng Xi Zhou 463e0570b0 upgrade api version
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Zheng Xi Zhou fc01f1a894 Removed `type` from outputs
Here are two drawbacks of exposing type in outputs.
- it’s in Terraform context, not friendly to Golang/Kubernetes
- it’s complicated

Fix #274

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Yan Du 7b8de08512 oam-dev/terraform-controller#244 Introduce resource limits for Terraform controller
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Yan Du 7bb35601b9 oam-dev/terraform-controller#244 fix
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Zheng Xi Zhou aa9c3e835c update observedGeneration in right location
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Zheng Xi Zhou c5f6ec47bd Switch Configuration API from v1beta1 to v1beta2
Use new version API for Configuration

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
Signed-off-by: Yan Du <red.snapper@qq.com>
2022-03-12 17:22:23 +08:00
Zheng Xi Zhou 36098a7c32
Merge pull request #275 from zzxwill/complicated-output-type
Removed `type` from outputs
2022-03-12 11:19:23 +08:00
Zheng Xi Zhou c4176f3707 upgrade api version
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-12 11:07:11 +08:00
Zheng Xi Zhou a369b1a435 Removed `type` from outputs
Here are two drawbacks of exposing type in outputs.
- it’s in Terraform context, not friendly to Golang/Kubernetes
- it’s complicated

Fix #274

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-12 11:05:21 +08:00
loheagn edf9d98658 Fix typo
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-11 11:45:21 +08:00
loheagn a6bca47291 Add "owned-namespace" to the labels of connection-secret
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-11 11:31:54 +08:00
loheagn 0c8186ecd0 Rename "terraform-controller-owner" to "owned-by"
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-11 11:16:55 +08:00
loheagn 2e58c7b13f Lint code
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-10 19:10:09 +08:00
loheagn 48f5f8929f Lint code
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-10 19:01:03 +08:00
loheagn 4940e4fb5e Add test cases to configuration_controller_test.TestGetTFOutputs
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-10 18:48:13 +08:00
loheagn cbe049f558 Avoid writing connection string to a same secret
Signed-off-by: loheagn <loheagn@icloud.com>
2022-03-10 16:00:10 +08:00
Zheng Xi Zhou 411ac7ebd9
Merge pull request #272 from zzxwill/v1beta2
Switch Configuration API from v1beta1 to v1beta2
2022-03-09 18:11:06 +08:00
Zheng Xi Zhou d71fbcf925 update observedGeneration in right location
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-09 18:02:21 +08:00
Zheng Xi Zhou 53e97162e1 Switch Configuration API from v1beta1 to v1beta2
Use new version API for Configuration

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-09 16:20:33 +08:00
Zheng Xi Zhou 3df9d72424
Merge pull request #257 from zzxwill/dockerfile
Upgrade go version to 1.17 in Dockerfile
2022-03-09 15:29:13 +08:00
Zheng Xi Zhou 10ac87dd05
Merge pull request #271 from zzxwill/variable-secret
Ensusre credentials are retrieved before assemble a job or delete Configuration
2022-03-09 15:26:53 +08:00
Zheng Xi Zhou 6b54476f5c fix ci issue
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-09 15:21:29 +08:00
Zheng Xi Zhou ace11a0318 Ensusre credentials are retrieved before assemble a job or delete Configuration
Fix #270

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-09 14:58:37 +08:00
Zheng Xi Zhou 616070b3c4
Merge pull request #269 from evanli18/feat/status
Feat: add .status.observedGeneration to Configuration
2022-03-09 12:03:23 +08:00
Evan Li c9c55b8b94
Merge branch 'master' into feat/status 2022-03-09 11:57:38 +08:00
Evan Li 3d13325375 Feat: add .status.observedGeneration to Configuration
Signed-off-by: Evan Li <evan.li97@outlook.com>
2022-03-09 11:54:52 +08:00
Zheng Xi Zhou 37972ac887
Merge pull request #267 from zzxwill/modified
Fix Configuration update issue
2022-03-09 10:42:34 +08:00
Zheng Xi Zhou 40f0fe2ba8 fix CI
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 21:55:09 +08:00
Zheng Xi Zhou a7dd292eb0 fix CI
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 21:18:23 +08:00
Zheng Xi Zhou 37b029b61a Fix Configuration update issue
Fix #266

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 20:38:56 +08:00
Zheng Xi Zhou 5be51bb835
Merge pull request #268 from zzxwill/security
Fix security issue by upgrading "github.com/go-yaml/yaml"
2022-03-08 16:17:38 +08:00
Zheng Xi Zhou 34bd2d98d1 fix CI
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 16:09:44 +08:00
Zheng Xi Zhou 6d20e4b63c fix CI
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 16:02:45 +08:00
Zheng Xi Zhou 5393f4294a Fix security issue by upgrading "github.com/go-yaml/yaml"
Fix security issue by upgrading "github.com/go-yaml/yaml"
to v2.2.8

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-08 15:51:31 +08:00
Zheng Xi Zhou 355a7d93ff
Merge pull request #262 from leason00/bugfix_tf_outputs
bugfix get TF Outputs type err
2022-03-08 15:14:37 +08:00
lisen 17f2e6397e bugfix get TF Outputs type err
Signed-off-by: lisen <lisen@youzan.com>
2022-03-08 14:49:20 +08:00
Zheng Xi Zhou bacb79de94 Upgrade go version to 1.17 in Dockerfile
Upgrade go version in Dockerfile

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-01 14:44:18 +08:00
127 changed files with 13524 additions and 2618 deletions

54
.github/workflows/build-image.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: BuildImage
on:
push:
tags:
- 'v*'
workflow_dispatch: { }
jobs:
build-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oamdev/terraform-controller
ghcr.io/kubevela/oamdev/terraform-controller
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Login docker.io
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,109 +1,48 @@
name: Chart
name: HelmChart
on:
push:
branches:
- master
tags:
- "v*"
workflow_dispatch: {}
env:
BUCKET: kubevelacharts
ENDPOINT: oss-cn-hangzhou.aliyuncs.com
ACCESS_KEY: ${{ secrets.OSS_ACCESS_KEY }}
ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
HELM_CHART: chart
LOCAL_OSS_DIRECTORY: .oss/
jobs:
chart-build:
runs-on: ubuntu-20.04
publish-charts:
env:
HELM_CHART: chart/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Get the version
id: get_version
run: |
tag=${GITHUB_REF#refs/tags/}
VERSION=${tag#"v"}
if [[ ${GITHUB_REF} == "refs/heads/master" ]]; then
VERSION=latest
fi
echo ::set-output name=VERSION::${VERSION}
- name: Get git revision
id: vars
shell: bash
run: |
echo "::set-output name=git_revision::$(git rev-parse --short HEAD)"
- name: Login docker.io
uses: docker/login-action@v1
with:
registry: docker.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
with:
driver-opts: image=moby/buildkit:master
- uses: docker/build-push-action@v2
name: Build & Pushing terraform controller for Dockerhub
with:
context: .
file: Dockerfile
labels: |-
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
GOPROXY=https://proxy.golang.org
tags: |-
docker.io/oamdev/terraform-controller:${{ steps.get_version.outputs.VERSION }}
- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.4.0
- uses: oprypin/find-latest-tag@v1
with:
repository: oam-dev/terraform-controller
releases-only: true
id: latest_tag
- name: Get the version
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
- name: Tag helm chart image
run: |
latest_repo_tag=${{ steps.latest_tag.outputs.tag }}
sub="."
major="$(cut -d"$sub" -f1 <<<"$latest_repo_tag")"
minor="$(cut -d"$sub" -f2 <<<"$latest_repo_tag")"
patch="0"
current_repo_tag="$major.$minor.$patch"
tag=${GITHUB_REF#refs/tags/}
image_tag=${tag#"v"}
chart_version=$latest_repo_tag
if [[ ${GITHUB_REF} == "refs/heads/master" ]]; then
image_tag=latest
chart_version=${current_repo_tag}-nightly-build
fi
# sed -i "s/0.2.8/${image_tag}/g" $HELM_CHART/Makefile
chart_smever=${chart_version#"v"}
sed -i "s/0.2.8/$chart_smever/g" $HELM_CHART/Chart.yaml
sed -i "s/0.2.8/${image_tag}/g" $HELM_CHART/values.yaml
- name: Install ossutil
run: wget http://gosspublic.alicdn.com/ossutil/1.7.0/ossutil64 && chmod +x ossutil64 && mv ossutil64 ossutil
- name: Configure Alibaba Cloud OSSUTIL
run: ./ossutil --config-file .ossutilconfig config -i ${ACCESS_KEY} -k ${ACCESS_KEY_SECRET} -e ${ENDPOINT} -c .ossutilconfig
- name: sync cloud to local
run: ./ossutil --config-file .ossutilconfig cp oss://$BUCKET/addons/index.yaml $LOCAL_OSS_DIRECTORY -f
- name: Package helm charts
image_tag=${{ steps.get_version.outputs.VERSION }}
chart_version=${{ steps.get_version.outputs.VERSION }}
sed -i "s/tag: latest/tag: ${image_tag}/g" $HELM_CHART/values.yaml
chart_semver=${chart_version#"v"}
sed -i "s/0.1.0/$chart_semver/g" $HELM_CHART/Chart.yaml
- uses: jnwng/github-app-installation-token-action@v2
id: get_app_token
with:
appId: 340472
installationId: 38064967
privateKey: ${{ secrets.GH_KUBEVELA_APP_PRIVATE_KEY }}
- name: Sync Chart Repo
run: |
helm package $HELM_CHART --destination $LOCAL_OSS_DIRECTORY
helm repo index --url https://charts.kubevela.net/addons $LOCAL_OSS_DIRECTORY/ --merge $LOCAL_OSS_DIRECTORY/index.yaml
- name: sync local to cloud
run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/addons -f
git config --global user.email "135009839+kubevela[bot]@users.noreply.github.com"
git config --global user.name "kubevela[bot]"
git clone https://x-access-token:${{ steps.get_app_token.outputs.token }}@github.com/kubevela/charts.git kubevela-charts
helm package $HELM_CHART --destination ./kubevela-charts/docs/
helm repo index --url https://kubevela.github.io/charts ./kubevela-charts/docs/
cd kubevela-charts/
git add docs/
chart_version=${GITHUB_REF#refs/tags/}
git commit -m "update terraform-controller chart ${chart_version}"
git push https://x-access-token:${{ steps.get_app_token.outputs.token }}@github.com/kubevela/charts.git

View File

@ -4,19 +4,20 @@ on:
push:
branches:
- master
tags:
- v*
workflow_dispatch: {}
pull_request:
branches:
- master
env:
GO_VERSION: '1.17.6'
GOLANGCI_VERSION: 'v1.38'
KUBECONFIG: /home/github/.kube/config
GO_VERSION: '1.23.8'
KIND_VERSION: 'v0.12.0'
jobs:
e2e-tests:
runs-on: self-hosted
runs-on: ubuntu-22.04
steps:
- name: Check out code into the Go module directory
@ -31,28 +32,38 @@ jobs:
run: |
go get -v -t -d ./...
- name: Build and push images
- name: Setup Kind
uses: engineerd/setup-kind@v0.5.0
with:
version: ${{ env.KIND_VERSION }}
skipClusterCreation: true
- name: Setup Kind Cluster
run: |
sed -i "s/0.2.8/latest/g" Makefile
make docker-build
make docker-push
kind delete cluster
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info
- name: Load Image to kind cluster
run: make kind-load
- name: Install chart
run: |
kubectl cluster-info
echo "current-context:" $(kubectl config current-context)
helm delete terraform-controller -n terraform
sed -i "s/0.2.8/latest/g" chart/values.yaml
helm lint ./chart --debug
helm upgrade --install --create-namespace --namespace terraform terraform-controller ./chart
helm upgrade --install --create-namespace --namespace terraform terraform-controller ./chart --set image.tag=e2e --set image.pullPolicy=IfNotPresent --set backend.namespace=terraform --wait
helm test -n terraform terraform-controller --timeout 5m
kubectl get pod -n terraform -l "app=terraform-controller"
- name: E2E tests
run: |
make configuration
env:
TERRAFORM_BACKEND_NAMESPACE: terraform
- name: dump controller logs
if: ${{ always() }}
run: kubectl logs deploy/terraform-controller -n terraform
- name: Upload coverage report
uses: codecov/codecov-action@v2

View File

@ -13,12 +13,12 @@ on:
env:
# Common versions
GO_VERSION: '1.17.6'
GO_VERSION: '1.23.8'
KIND_VERSION: 'v0.7.0'
jobs:
detect-noop:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
@ -32,7 +32,7 @@ jobs:
concurrent_skipping: false
make-reviewable:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'

71
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,71 @@
name: Release
on:
workflow_dispatch: { }
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
name: build
strategy:
matrix:
TARGETS: [ linux/amd64, darwin/amd64, windows/amd64, linux/arm64, darwin/arm64 ]
env:
BACKUP_RESTORE_TOOL_VERSION_KEY: github.com/kubevela/terraform-controller/version.BackupRestoreToolVersion
BACKUP_RESTORE_TOOL_VERSION: cat hack/tool/backup_restore/VERSION
GO_BUILD_ENV: GO111MODULE=on
DIST_DIRS: find * -type d -exec
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.23
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.2.2
- name: Get matrix
id: get_matrix
run: |
TARGETS=${{matrix.TARGETS}}
echo ::set-output name=OS::${TARGETS%/*}
echo ::set-output name=ARCH::${TARGETS#*/}
- name: Get ldflags
id: get_ldflags
run: |
LDFLAGS="-s -w -X ${{ env.BACKUP_RESTORE_TOOL_VERSION_KEY }}=${{ env.BACKUP_RESTORE_TOOL_VERSION }}"
echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
- name: Build
run: |
cd ./hack/tool/backup_restore && \
${{ env.GO_BUILD_ENV }} GOOS=${{ steps.get_matrix.outputs.OS }} GOARCH=${{ steps.get_matrix.outputs.ARCH }} \
go build -ldflags "${{ env.LDFLAGS }}" \
-o _bin/backup_restore/${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}/backup_restore \
-v .
- name: Compress
run: |
cd _bin/backup_restore && \
${{ env.DIST_DIRS }} cp ../../LICENSE {} \; && \
${{ env.DIST_DIRS }} cp ../../README.md {} \; && \
${{ env.DIST_DIRS }} tar -zcf backup-restore-{}.tar.gz {} \; && \
${{ env.DIST_DIRS }} zip -r backup-restore-{}.zip {} \; && \
cd .. && \
sha256sum backup_restore/backup-restore-* >> sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt \
- name: Upload backup-restore tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./_bin/backup_restore/backup-restore-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
asset_name: backup-restore-${{ env.BACKUP_RESTORE_TOOL_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
asset_content_type: binary/octet-stream
- name: Upload backup-restore zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./_bin/backup_restore/backup-restore-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip
asset_name: backup-restore-${{ env.BACKUP_RESTORE_TOOL_VERSION }}-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.zip
asset_content_type: binary/octet-stream

View File

@ -15,7 +15,7 @@ on:
env:
# Common versions
GO_VERSION: '1.17.6'
GO_VERSION: '1.23.8'
jobs:
lint:
@ -31,7 +31,7 @@ jobs:
make lint
unit-tests:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Set up Go
@ -46,7 +46,7 @@ jobs:
submodules: true
- name: Cache Go Dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}

3
.gitignore vendored
View File

@ -30,3 +30,6 @@ terraform-controller-*
examples/tf-native/alibaba/cs/kubeconfig
bin/manager
# Secret for git server
examples/git-credentials/git-ssh-auth-secret.yaml

View File

@ -2,12 +2,11 @@ run:
timeout: 10m
skip-files:
- "zz_generated\\..+\\.go$"
- ".*_test.go$"
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number
formats: colored-line-number
linters-settings:
errcheck:
@ -22,7 +21,7 @@ linters-settings:
# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
ignore: fmt:.*,io/ioutil:^Read.*
exclude-functions: fmt:.*,io/ioutil:^Read.*
exhaustive:
# indicates that switch statements are to be considered exhaustive if a
@ -44,7 +43,7 @@ linters-settings:
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 30
min-complexity: 32
maligned:
# print struct with more effective memory layout or not, false by default
@ -100,7 +99,6 @@ linters-settings:
linters:
enable:
- megacheck
- govet
- gocyclo
- gocritic
@ -111,6 +109,9 @@ linters:
- unconvert
- misspell
- nakedret
- staticcheck
- gosimple
- unused
presets:
- bugs
@ -119,6 +120,12 @@ linters:
issues:
exclude-files:
# Exclude files that are generated by controller-gen.
- "zz_generated\\..+\\.go$"
# Exclude test files.
- ".*_test.go$"
# Excluding configuration per-path and per-linter
exclude-rules:
# Exclude some linters from running on tests files.
@ -179,6 +186,14 @@ issues:
linters:
- revive
- text: "package-comments:"
linters:
- revive
- text: "exported:"
linters:
- revive
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.

View File

@ -20,13 +20,17 @@ Refer to [Helm official Doc](https://helm.sh/docs/intro/install/) to install `he
$ make install
go: creating new go.mod: module tmp
...
go get: added sigs.k8s.io/controller-tools v0.6.0
go get: added sigs.k8s.io/structured-merge-diff/v4 v4.1.0
go get: added sigs.k8s.io/yaml v1.2.0
go: downloading sigs.k8s.io/controller-tools v0.6.0
go: downloading k8s.io/apiextensions-apiserver v0.21.1
go: downloading k8s.io/apimachinery v0.21.1
go: downloading k8s.io/api v0.21.1
go: downloading k8s.io/utils v0.0.0-20201110183641-67b214c5f920
go: downloading k8s.io/klog/v2 v2.8.0
go: downloading sigs.k8s.io/structured-merge-diff/v4 v4.1.0
/Users/zhouzhengxi/go/bin/controller-gen "crd:trivialVersions=true" webhook paths="./..." output:crd:artifacts:config=chart/crds
kubectl apply -f chart/crds
customresourcedefinition.apiextensions.k8s.io/configurations.terraform.core.oam.dev configured
customresourcedefinition.apiextensions.k8s.io/providers.terraform.core.oam.dev configured
customresourcedefinition.apiextensions.k8s.io/configurations.terraform.core.oam.dev created
customresourcedefinition.apiextensions.k8s.io/providers.terraform.core.oam.dev created
```
- Run Terraform Controller
@ -35,7 +39,7 @@ customresourcedefinition.apiextensions.k8s.io/providers.terraform.core.oam.dev c
$ make run
go: creating new go.mod: module tmp
...
go get: added sigs.k8s.io/yaml v1.2.0
go: downloading sigs.k8s.io/yaml v1.2.0
/Users/zhouzhengxi/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
@ -147,3 +151,11 @@ Bucket Number is: 0
0.030917(s) elapsed
```
## Generate CRDs
```shell
$ make manifests
go: creating new go.mod: module tmp
/Users/zhouzhengxi/go/bin/controller-gen "crd:trivialVersions=true" webhook paths="./..." output:crd:artifacts:config=chart/crds
```

View File

@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.16 as builder
FROM golang:1.23-alpine as builder
WORKDIR /workspace
# Copy the Go Modules manifests
@ -19,7 +19,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM golang:1.16
FROM alpine
WORKDIR /
COPY --from=builder /workspace/manager .
#USER nonroot:nonroot
@ -27,6 +27,6 @@ COPY --from=builder /workspace/manager .
# COPY terraform binary
COPY bin/terraform /usr/bin/terraform
#RUN chmod +x /usr/bin/terraform
RUN apt-get install git
RUN apk add git
ENTRYPOINT ["/manager"]

View File

@ -3,7 +3,7 @@
IMG ?= oamdev/terraform-controller:0.2.8
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true"
CRD_OPTIONS ?= "crd"
TIME_SHORT = `date +%H:%M:%S`
TIME = $(TIME_SHORT)
@ -82,7 +82,7 @@ ifeq (, $(shell which controller-gen))
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.0 ;\
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.5 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
@ -90,7 +90,7 @@ else
CONTROLLER_GEN=$(shell which controller-gen)
endif
GOLANGCILINT_VERSION ?= v1.38.0
GOLANGCILINT_VERSION ?= v1.60.1
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
HOSTARCH := $(shell uname -m)
ifeq ($(HOSTARCH),x86_64)
@ -131,7 +131,7 @@ goimports:
ifeq (, $(shell which goimports))
@{ \
set -e ;\
GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports ;\
go install golang.org/x/tools/cmd/goimports@latest ;\
}
GOIMPORTS=$(GOBIN)/goimports
else
@ -144,6 +144,12 @@ install-chart:
helm test -n terraform terraform-controller --timeout 5m
kubectl get pod -n terraform -l "app=terraform-controller"
# load docker image to the kind cluster
kind-load:
docker build -t oamdev/terraform-controller:e2e .
kind load docker-image oamdev/terraform-controller:e2e
alibaba-credentials:
ifeq (, $(ALICLOUD_ACCESS_KEY))
@echo "Environment variable ALICLOUD_ACCESS_KEY is not set"
@ -261,7 +267,8 @@ custom: custom-credentials custom-provider
configuration:
go test -coverprofile=e2e-coverage1.xml -v ./e2e/... -count=1
go test -coverprofile=e2e-coverage1.xml -v $(shell go list ./e2e/...|grep -v controllernamespace) -count=1
go test -v ./e2e/controllernamespace/...
e2e-setup: install-chart alibaba

View File

@ -28,7 +28,7 @@ Terraform Controller is a Kubernetes Controller for Terraform.
## Supported Terraform Configuration
- HCL
- JSON (Deprecated in v0.3.1)
- JSON (Deprecated in v0.3.1, removed in v0.4.6)
# Get started

77
api/types/const.go Normal file
View File

@ -0,0 +1,77 @@
package types
const (
DefaultNamespace = "default"
GitCredsKnownHosts = "known_hosts"
// TerraformCredentials -
TerraformCredentials = "credentials.tfrc.json"
// TerraformRegistryConfig -
TerraformRegistryConfig = ".terraformrc"
)
const (
// TerraformContainerName is the name of the container that executes terraform in the pod
TerraformContainerName = "terraform-executor"
TerraformInitContainerName = "terraform-init"
)
const (
// TFInputConfigMapName is the CM name for Terraform Input Configuration
TFInputConfigMapName = "tf-%s"
// TFVariableSecret is the Secret name for variables, including credentials from Provider
TFVariableSecret = "variable-%s"
)
// TerraformExecutionType is the type for Terraform execution
type TerraformExecutionType string
const (
// TerraformApply is the name to mark `terraform apply`
TerraformApply TerraformExecutionType = "apply"
// TerraformDestroy is the name to mark `terraform destroy`
TerraformDestroy TerraformExecutionType = "destroy"
)
const (
// ClusterRoleName is the name of the ClusterRole for Terraform Job
ClusterRoleName = "tf-executor-clusterrole"
// ServiceAccountName is the name of the ServiceAccount for Terraform Job
ServiceAccountName = "tf-executor-service-account"
)
// Volume names and mount paths
const (
// WorkingVolumeMountPath is the mount path for working volume
WorkingVolumeMountPath = "/data"
// InputTFConfigurationVolumeName is the volume name for input Terraform Configuration
InputTFConfigurationVolumeName = "tf-input-configuration"
// InputTFConfigurationVolumeMountPath is the volume mount path for input Terraform Configuration
InputTFConfigurationVolumeMountPath = "/opt/tf-configuration"
// BackendVolumeName is the volume name for Terraform backend
BackendVolumeName = "tf-backend"
// BackendVolumeMountPath is the volume mount path for Terraform backend
BackendVolumeMountPath = "/opt/tf-backend"
// GitAuthConfigVolumeName is the volume name for git auth configurtaion
GitAuthConfigVolumeName = "git-auth-configuration"
// GitAuthConfigVolumeMountPath is the volume mount path for git auth configurtaion
GitAuthConfigVolumeMountPath = "/root/.ssh"
// TerraformCredentialsConfigVolumeName is the volume name for terraform auth configurtaion
TerraformCredentialsConfigVolumeName = "terraform-credentials-configuration"
// TerraformCredentialsConfigVolumeMountPath is the volume mount path for terraform auth configurtaion
TerraformCredentialsConfigVolumeMountPath = "/root/.terraform.d"
// TerraformRCConfigVolumeName is the volume name of the terraform registry configuration
TerraformRCConfigVolumeName = "terraform-rc-configuration"
// TerraformRCConfigVolumeMountPath is the volume mount path for registry configuration
TerraformRCConfigVolumeMountPath = "/root"
// TerraformCredentialsHelperConfigVolumeName is the volume name for terraform auth configurtaion
TerraformCredentialsHelperConfigVolumeName = "terraform-credentials-helper-configuration"
// TerraformCredentialsHelperConfigVolumeMountPath is the volume mount path for terraform auth configurtaion
TerraformCredentialsHelperConfigVolumeMountPath = "/root/.terraform.d/plugins"
)

View File

@ -21,18 +21,31 @@ type ConfigurationState string
// Reasons a resource is or is not ready.
const (
Authorizing ConfigurationState = "Authorizing"
ProviderNotFound ConfigurationState = "ProviderNotFound"
ProviderNotReady ConfigurationState = "ProviderNotReady"
ConfigurationStaticCheckFailed ConfigurationState = "ConfigurationSpecNotValid"
Available ConfigurationState = "Available"
ConfigurationProvisioningAndChecking ConfigurationState = "ProvisioningAndChecking"
ConfigurationDestroying ConfigurationState = "Destroying"
ConfigurationApplyFailed ConfigurationState = "ApplyFailed"
ConfigurationDestroyFailed ConfigurationState = "DestroyFailed"
ConfigurationReloading ConfigurationState = "ConfigurationReloading"
GeneratingOutputs ConfigurationState = "GeneratingTerraformOutputs"
InvalidRegion ConfigurationState = "InvalidRegion"
Authorizing ConfigurationState = "Authorizing"
ProviderNotFound ConfigurationState = "ProviderNotFound"
ProviderNotReady ConfigurationState = "ProviderNotReady"
ConfigurationStaticCheckFailed ConfigurationState = "ConfigurationSpecNotValid"
Available ConfigurationState = "Available"
ConfigurationProvisioningAndChecking ConfigurationState = "ProvisioningAndChecking"
ConfigurationDestroying ConfigurationState = "Destroying"
ConfigurationApplyFailed ConfigurationState = "ApplyFailed"
ConfigurationDestroyFailed ConfigurationState = "DestroyFailed"
ConfigurationReloading ConfigurationState = "ConfigurationReloading"
GeneratingOutputs ConfigurationState = "GeneratingTerraformOutputs"
InvalidRegion ConfigurationState = "InvalidRegion"
TerraformInitError ConfigurationState = "TerraformInitError"
InvalidGitCredentialsSecretReference ConfigurationState = "InvalidGitCredentialsSecretReference"
InvalidTerraformCredentialsSecretReference ConfigurationState = "InvalidTerraformCredentialsSecretReference"
InvalidTerraformRCConfigMapReference ConfigurationState = "InvalidTerraformRCConfigMapReference"
InvalidTerraformCredentialsHelperConfigMapReference ConfigurationState = "InvalidTerraformCredentialsHelperConfigMapReference"
)
// Stage is the Terraform stage
type Stage string
const (
InitStage Stage = "InitStage"
ApplyStage Stage = "Apply"
)
const (

View File

@ -1,8 +1,8 @@
package types
import "k8s.io/apimachinery/pkg/api/resource"
const (
// TerraformJSONConfigurationName is the file name for Terraform json Configuration
TerraformJSONConfigurationName = "main.tf.json"
// TerraformHCLConfigurationName is the file name for Terraform hcl Configuration
TerraformHCLConfigurationName = "main.tf"
)
@ -11,10 +11,32 @@ const (
type ConfigurationType string
const (
// ConfigurationJSON is the json type Configuration
ConfigurationJSON ConfigurationType = "JSON"
// ConfigurationHCL is the HCL type Configuration
ConfigurationHCL ConfigurationType = "HCL"
// ConfigurationRemote means HCL stores in a remote git repository
ConfigurationRemote ConfigurationType = "Remote"
)
type Git struct {
URL string
Path string
Ref GitRef
}
// GitRef specifies the git reference
type GitRef struct {
Branch string `json:"branch,omitempty"`
Tag string `json:"tag,omitempty"`
Commit string `json:"commit,omitempty"`
}
type ResourceQuota struct {
ResourcesLimitsCPU string
ResourcesLimitsCPUQuantity resource.Quantity
ResourcesLimitsMemory string
ResourcesLimitsMemoryQuantity resource.Quantity
ResourcesRequestsCPU string
ResourcesRequestsCPUQuantity resource.Quantity
ResourcesRequestsMemory string
ResourcesRequestsMemoryQuantity resource.Quantity
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package v1beta1
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -47,6 +48,18 @@ type ConfigurationSpec struct {
Path string `json:"path,omitempty"`
BaseConfigurationSpec `json:",inline"`
// GitCredentialsSecretReference specifies the reference to the secret containing the git credentials
GitCredentialsSecretReference *v1.SecretReference `json:"gitCredentialsSecretReference,omitempty"`
// TerraformCredentialsSecretReference specifies the reference to the secret containing the terraform credentials
TerraformCredentialsSecretReference *v1.SecretReference `json:"terraformCredentialsSecretReference,omitempty"`
// TerraformRCConfigMapReference specifies the reference to a config map containing the terraform registry configuration
TerraformRCConfigMapReference *v1.SecretReference `json:"terraformRCConfigMapReference,omitempty"`
// TerraformCredentialsHelperConfigMapReference specifies the reference to a configmap containing the terraform registry credentials helper
TerraformCredentialsHelperConfigMapReference *v1.SecretReference `json:"terraformCredentialsHelperConfigMapReference,omitempty"`
}
// BaseConfigurationSpec defines the common fields of a ConfigurationSpec
@ -71,6 +84,11 @@ type BaseConfigurationSpec struct {
// ConfigurationStatus defines the observed state of Configuration
type ConfigurationStatus struct {
// observedGeneration is the most recent generation observed for this Configuration. It corresponds to the
// Configuration's generation, which is updated on mutation by the API Server.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
Apply ConfigurationApplyStatus `json:"apply,omitempty"`
Destroy ConfigurationDestroyStatus `json:"destroy,omitempty"`
}
@ -108,6 +126,7 @@ type Backend struct {
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="STATE",type="string",JSONPath=".status.apply.state"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:resource:shortName={conf,terraform-conf}
type Configuration struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2021 The KubeVela Authors.
@ -23,6 +22,7 @@ package v1beta1
import (
crossplane_runtime "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
@ -176,6 +176,26 @@ func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) {
**out = **in
}
in.BaseConfigurationSpec.DeepCopyInto(&out.BaseConfigurationSpec)
if in.GitCredentialsSecretReference != nil {
in, out := &in.GitCredentialsSecretReference, &out.GitCredentialsSecretReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformCredentialsSecretReference != nil {
in, out := &in.TerraformCredentialsSecretReference, &out.TerraformCredentialsSecretReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformRCConfigMapReference != nil {
in, out := &in.TerraformRCConfigMapReference, &out.TerraformRCConfigMapReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformCredentialsHelperConfigMapReference != nil {
in, out := &in.TerraformCredentialsHelperConfigMapReference, &out.TerraformCredentialsHelperConfigMapReference
*out = new(v1.SecretReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec.

View File

@ -17,40 +17,38 @@ limitations under the License.
package v1beta2
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
state "github.com/oam-dev/terraform-controller/api/types"
apitypes "github.com/oam-dev/terraform-controller/api/types"
types "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
)
// ConfigurationSpec defines the desired state of Configuration
type ConfigurationSpec struct {
// JSON is the Terraform JSON syntax configuration.
// Deprecated: after v0.3.1, use HCL instead.
JSON string `json:"JSON,omitempty"`
// HCL is the Terraform HCL type configuration
HCL string `json:"hcl,omitempty"`
// Remote is a git repo which contains hcl files. Currently, only public git repos are supported.
Remote string `json:"remote,omitempty"`
// GitRef is the git branch or tag or commit hash to checkout. Only used when Remote is specified.
GitRef apitypes.GitRef `json:"gitRef,omitempty"`
// +kubebuilder:pruning:PreserveUnknownFields
Variable *runtime.RawExtension `json:"variable,omitempty"`
// Backend stores the state in a Kubernetes secret with locking done using a Lease resource.
// TODO(zzxwill) If a backend exists in HCL/JSON, this can be optional. Currently, if Backend is not set by users, it
// still will set by the controller, ignoring the settings in HCL/JSON backend
// Backend describes the Terraform backend configuration.
// This field is needed if the users use a git repo to provide the hcl files or
// want to use their custom Terraform backend (instead of the default kubernetes backend type).
// Notice: This field may cause two backend blocks in the final Terraform module and make the executor job failed.
// So, please make sure that there are no backend configurations in your inline hcl code or the git repo.
Backend *Backend `json:"backend,omitempty"`
// Path is the sub-directory of remote git repository.
Path string `json:"path,omitempty"`
BaseConfigurationSpec `json:",inline"`
}
// BaseConfigurationSpec defines the common fields of a ConfigurationSpec
type BaseConfigurationSpec struct {
// WriteConnectionSecretToReference specifies the namespace and name of a
// Secret to which any connection details for this managed resource should
// be written. Connection details frequently include the endpoint, username,
@ -60,54 +58,118 @@ type BaseConfigurationSpec struct {
// ProviderReference specifies the reference to Provider
ProviderReference *types.Reference `json:"providerRef,omitempty"`
// +kubebuilder:pruning:PreserveUnknownFields
JobEnv *runtime.RawExtension `json:"JobEnv,omitempty"`
// InlineCredentials specifies the credentials in spec.HCl field as below.
// provider "aws" {
// region = "us-west-2"
// access_key = "my-access-key"
// secret_key = "my-secret-key"
// }
// Or indicates a Terraform module or configuration don't need credentials at all, like provider `random`
InlineCredentials bool `json:"inlineCredentials,omitempty"`
// DeleteResource will determine whether provisioned cloud resources will be deleted when CR is deleted
// +kubebuilder:default:=true
DeleteResource bool `json:"deleteResource,omitempty"`
DeleteResource *bool `json:"deleteResource,omitempty"`
// Region is cloud provider's region. It will override the region in the region field of ProviderReference
Region string `json:"customRegion,omitempty"`
// ForceDelete will force delete Configuration no matter which state it is or whether it has provisioned some resources
// It will help delete Configuration in unexpected cases.
ForceDelete *bool `json:"forceDelete,omitempty"`
// GitCredentialsSecretReference specifies the reference to the secret containing the git credentials
GitCredentialsSecretReference *v1.SecretReference `json:"gitCredentialsSecretReference,omitempty"`
// TerraformCredentialsSecretReference specifies the reference to the secret containing the terraform credentials and terraform registry details
TerraformCredentialsSecretReference *v1.SecretReference `json:"terraformCredentialsSecretReference,omitempty"`
// TerraformRCConfigMapReference specifies the reference to a config map containing the terraform registry configuration
TerraformRCConfigMapReference *v1.SecretReference `json:"terraformRCConfigMapReference,omitempty"`
// TerraformCredentialsHelperConfigMapReference specifies the reference to a configmap containing the terraform registry credentials helper
TerraformCredentialsHelperConfigMapReference *v1.SecretReference `json:"terraformCredentialsHelperConfigMapReference,omitempty"`
}
// ConfigurationStatus defines the observed state of Configuration
type ConfigurationStatus struct {
// observedGeneration is the most recent generation observed for this Configuration. It corresponds to the
// Configuration's generation, which is updated on mutation by the API Server.
// If ObservedGeneration equals Generation, and State is Available, the value of Outputs is latest
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
Apply ConfigurationApplyStatus `json:"apply,omitempty"`
Destroy ConfigurationDestroyStatus `json:"destroy,omitempty"`
}
// ConfigurationApplyStatus is the status for Configuration apply
type ConfigurationApplyStatus struct {
State state.ConfigurationState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
Outputs map[string]Property `json:"outputs,omitempty"`
State apitypes.ConfigurationState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
Outputs map[string]Property `json:"outputs,omitempty"`
// Region is the region for the cloud resources created by this Configuration. If spec.region is not empty, it's the
// value of it. Otherwise, it's the value of spec.providerReference.region.
Region string `json:"region,omitempty"`
}
// ConfigurationDestroyStatus is the status for Configuration destroy
type ConfigurationDestroyStatus struct {
State state.ConfigurationState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
State apitypes.ConfigurationState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}
// Property is the property for an output
type Property struct {
Value string `json:"value,omitempty"`
Type string `json:"type,omitempty"`
}
// Backend stores the state in a Kubernetes secret with locking done using a Lease resource.
// Backend describes the Terraform backend configuration
type Backend struct {
// SecretSuffix used when creating secrets. Secrets will be named in the format: tfstate-{workspace}-{secretSuffix}
SecretSuffix string `json:"secretSuffix,omitempty"`
// InClusterConfig Used to authenticate to the cluster from inside a pod. Only `true` is allowed
InClusterConfig bool `json:"inClusterConfig,omitempty"`
// Inline allows users to use raw hcl code to specify their Terraform backend
Inline string `json:"inline,omitempty"`
// BackendType indicates which backend type to use. This field is needed for custom backend configuration.
// +kubebuilder:validation:Enum=kubernetes;s3
BackendType string `json:"backendType,omitempty"`
// Kubernetes is needed for the Terraform `kubernetes` backend type.
Kubernetes *KubernetesBackendConf `json:"kubernetes,omitempty"`
// S3 is needed for the Terraform `s3` backend type.
S3 *S3BackendConf `json:"s3,omitempty"`
}
// KubernetesBackendConf defines all options supported by the Terraform `kubernetes` backend type.
// You can refer to https://www.terraform.io/language/settings/backends/kubernetes for the usage of each option.
type KubernetesBackendConf struct {
SecretSuffix string `json:"secret_suffix" hcl:"secret_suffix"`
Namespace *string `json:"namespace,omitempty" hcl:"namespace"`
}
// S3BackendConf defines all options supported by the Terraform `s3` backend type.
// You can refer to https://www.terraform.io/language/settings/backends/s3 for the usage of each option.
type S3BackendConf struct {
// Region is optional, default to the AWS_DEFAULT_REGION in the credentials of the provider
Region *string `json:"region,omitempty" hcl:"region"`
Bucket string `json:"bucket" hcl:"bucket"`
Key string `json:"key" hcl:"key"`
}
// +kubebuilder:object:root=true
// Configuration is the Schema for the configurations API
//+kubebuilder:storageversion
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="STATE",type="string",JSONPath=".status.apply.state"
// +kubebuilder:printcolumn:name="APPLY",type="string",JSONPath=".status.apply.state"
// +kubebuilder:printcolumn:name="DESTROY",type="string",JSONPath=".status.destroy.state"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
type Configuration struct {
metav1.TypeMeta `json:",inline"`

View File

@ -1,5 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2021 The KubeVela Authors.
@ -23,12 +22,23 @@ package v1beta2
import (
crossplane_runtime "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
if in.Kubernetes != nil {
in, out := &in.Kubernetes, &out.Kubernetes
*out = new(KubernetesBackendConf)
(*in).DeepCopyInto(*out)
}
if in.S3 != nil {
in, out := &in.S3, &out.S3
*out = new(S3BackendConf)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
@ -41,31 +51,6 @@ func (in *Backend) DeepCopy() *Backend {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BaseConfigurationSpec) DeepCopyInto(out *BaseConfigurationSpec) {
*out = *in
if in.WriteConnectionSecretToReference != nil {
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
*out = new(crossplane_runtime.SecretReference)
**out = **in
}
if in.ProviderReference != nil {
in, out := &in.ProviderReference, &out.ProviderReference
*out = new(crossplane_runtime.Reference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseConfigurationSpec.
func (in *BaseConfigurationSpec) DeepCopy() *BaseConfigurationSpec {
if in == nil {
return nil
}
out := new(BaseConfigurationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Configuration) DeepCopyInto(out *Configuration) {
*out = *in
@ -165,6 +150,7 @@ func (in *ConfigurationList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) {
*out = *in
out.GitRef = in.GitRef
if in.Variable != nil {
in, out := &in.Variable, &out.Variable
*out = new(runtime.RawExtension)
@ -173,9 +159,53 @@ func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) {
if in.Backend != nil {
in, out := &in.Backend, &out.Backend
*out = new(Backend)
(*in).DeepCopyInto(*out)
}
if in.WriteConnectionSecretToReference != nil {
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
*out = new(crossplane_runtime.SecretReference)
**out = **in
}
if in.ProviderReference != nil {
in, out := &in.ProviderReference, &out.ProviderReference
*out = new(crossplane_runtime.Reference)
**out = **in
}
if in.JobEnv != nil {
in, out := &in.JobEnv, &out.JobEnv
*out = new(runtime.RawExtension)
(*in).DeepCopyInto(*out)
}
if in.DeleteResource != nil {
in, out := &in.DeleteResource, &out.DeleteResource
*out = new(bool)
**out = **in
}
if in.ForceDelete != nil {
in, out := &in.ForceDelete, &out.ForceDelete
*out = new(bool)
**out = **in
}
if in.GitCredentialsSecretReference != nil {
in, out := &in.GitCredentialsSecretReference, &out.GitCredentialsSecretReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformCredentialsSecretReference != nil {
in, out := &in.TerraformCredentialsSecretReference, &out.TerraformCredentialsSecretReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformRCConfigMapReference != nil {
in, out := &in.TerraformRCConfigMapReference, &out.TerraformRCConfigMapReference
*out = new(v1.SecretReference)
**out = **in
}
if in.TerraformCredentialsHelperConfigMapReference != nil {
in, out := &in.TerraformCredentialsHelperConfigMapReference, &out.TerraformCredentialsHelperConfigMapReference
*out = new(v1.SecretReference)
**out = **in
}
in.BaseConfigurationSpec.DeepCopyInto(&out.BaseConfigurationSpec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec.
@ -205,6 +235,26 @@ func (in *ConfigurationStatus) DeepCopy() *ConfigurationStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesBackendConf) DeepCopyInto(out *KubernetesBackendConf) {
*out = *in
if in.Namespace != nil {
in, out := &in.Namespace, &out.Namespace
*out = new(string)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesBackendConf.
func (in *KubernetesBackendConf) DeepCopy() *KubernetesBackendConf {
if in == nil {
return nil
}
out := new(KubernetesBackendConf)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Property) DeepCopyInto(out *Property) {
*out = *in
@ -219,3 +269,23 @@ func (in *Property) DeepCopy() *Property {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *S3BackendConf) DeepCopyInto(out *S3BackendConf) {
*out = *in
if in.Region != nil {
in, out := &in.Region, &out.Region
*out = new(string)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new S3BackendConf.
func (in *S3BackendConf) DeepCopy() *S3BackendConf {
if in == nil {
return nil
}
out := new(S3BackendConf)
in.DeepCopyInto(out)
return out
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
apiVersion: v1
name: terraform-controller
version: 0.2.8
version: 0.1.0
description: A Kubernetes Terraform controller
home: https://github.com/oam-dev/terraform-controller
appVersion: "0.3.0"
appVersion: 0.1.0

View File

@ -1,11 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.0
creationTimestamp: null
controller-gen.kubebuilder.io/version: v0.16.5
name: configurations.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
@ -13,6 +11,9 @@ spec:
kind: Configuration
listKind: ConfigurationList
plural: configurations
shortNames:
- conf
- terraform-conf
singular: configuration
scope: Namespaced
versions:
@ -29,14 +30,19 @@ spec:
description: Configuration is the Schema for the configurations API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
@ -44,15 +50,14 @@ spec:
description: ConfigurationSpec defines the desired state of Configuration
properties:
JSON:
description: 'JSON is the Terraform JSON syntax configuration. Deprecated:
after v0.3.1, use HCL instead.'
description: |-
JSON is the Terraform JSON syntax configuration.
Deprecated: after v0.3.1, use HCL instead.
type: string
backend:
description: Backend stores the state in a Kubernetes secret with
locking done using a Lease resource. TODO(zzxwill) If a backend
exists in HCL/JSON, this can be optional. Currently, if Backend
is not set by users, it still will set by the controller, ignoring
the settings in HCL/JSON backend
description: |-
Backend stores the state in a Kubernetes secret with locking done using a Lease resource.
still will set by the controller, ignoring the settings in HCL/JSON backend
properties:
inClusterConfig:
description: InClusterConfig Used to authenticate to the cluster
@ -68,6 +73,20 @@ spec:
description: DeleteResource will determine whether provisioned cloud
resources will be deleted when CR is deleted
type: boolean
gitCredentialsSecretReference:
description: GitCredentialsSecretReference specifies the reference
to the secret containing the git credentials
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
hcl:
description: HCL is the Terraform HCL type configuration
type: string
@ -95,15 +114,58 @@ spec:
description: Remote is a git repo which contains hcl files. Currently,
only public git repos are supported.
type: string
terraformCredentialsHelperConfigMapReference:
description: TerraformCredentialsHelperConfigMapReference specifies
the reference to a configmap containing the terraform registry credentials
helper
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
terraformCredentialsSecretReference:
description: TerraformCredentialsSecretReference specifies the reference
to the secret containing the terraform credentials
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
terraformRCConfigMapReference:
description: TerraformRCConfigMapReference specifies the reference
to a config map containing the terraform registry configuration
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
variable:
type: object
x-kubernetes-preserve-unknown-fields: true
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
description: |-
WriteConnectionSecretToReference specifies the namespace and name of a
Secret to which any connection details for this managed resource should
be written. Connection details frequently include the endpoint, username,
and password required to connect to the managed resource.
properties:
name:
description: Name of the secret.
@ -148,6 +210,12 @@ spec:
description: A ConfigurationState represents the status of a resource
type: string
type: object
observedGeneration:
description: |-
observedGeneration is the most recent generation observed for this Configuration. It corresponds to the
Configuration's generation, which is updated on mutation by the API Server.
format: int64
type: integer
type: object
type: object
served: true
@ -156,7 +224,10 @@ spec:
status: {}
- additionalPrinterColumns:
- jsonPath: .status.apply.state
name: STATE
name: APPLY
type: string
- jsonPath: .status.destroy.state
name: DESTROY
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
@ -167,35 +238,77 @@ spec:
description: Configuration is the Schema for the configurations API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: ConfigurationSpec defines the desired state of Configuration
properties:
JSON:
description: 'JSON is the Terraform JSON syntax configuration. Deprecated:
after v0.3.1, use HCL instead.'
type: string
JobEnv:
type: object
x-kubernetes-preserve-unknown-fields: true
backend:
description: Backend stores the state in a Kubernetes secret with
locking done using a Lease resource. TODO(zzxwill) If a backend
exists in HCL/JSON, this can be optional. Currently, if Backend
is not set by users, it still will set by the controller, ignoring
the settings in HCL/JSON backend
description: |-
Backend describes the Terraform backend configuration.
This field is needed if the users use a git repo to provide the hcl files or
want to use their custom Terraform backend (instead of the default kubernetes backend type).
Notice: This field may cause two backend blocks in the final Terraform module and make the executor job failed.
So, please make sure that there are no backend configurations in your inline hcl code or the git repo.
properties:
backendType:
description: BackendType indicates which backend type to use.
This field is needed for custom backend configuration.
enum:
- kubernetes
- s3
type: string
inClusterConfig:
description: InClusterConfig Used to authenticate to the cluster
from inside a pod. Only `true` is allowed
type: boolean
inline:
description: Inline allows users to use raw hcl code to specify
their Terraform backend
type: string
kubernetes:
description: Kubernetes is needed for the Terraform `kubernetes`
backend type.
properties:
namespace:
type: string
secret_suffix:
type: string
required:
- secret_suffix
type: object
s3:
description: S3 is needed for the Terraform `s3` backend type.
properties:
bucket:
type: string
key:
type: string
region:
description: Region is optional, default to the AWS_DEFAULT_REGION
in the credentials of the provider
type: string
required:
- bucket
- key
type: object
secretSuffix:
description: 'SecretSuffix used when creating secrets. Secrets
will be named in the format: tfstate-{workspace}-{secretSuffix}'
@ -210,9 +323,46 @@ spec:
description: DeleteResource will determine whether provisioned cloud
resources will be deleted when CR is deleted
type: boolean
forceDelete:
description: |-
ForceDelete will force delete Configuration no matter which state it is or whether it has provisioned some resources
It will help delete Configuration in unexpected cases.
type: boolean
gitCredentialsSecretReference:
description: GitCredentialsSecretReference specifies the reference
to the secret containing the git credentials
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
gitRef:
description: GitRef is the git branch or tag or commit hash to checkout.
Only used when Remote is specified.
properties:
branch:
type: string
commit:
type: string
tag:
type: string
type: object
hcl:
description: HCL is the Terraform HCL type configuration
type: string
inlineCredentials:
description: "InlineCredentials specifies the credentials in spec.HCl
field as below.\n\tprovider \"aws\" {\n\t\tregion = \"us-west-2\"\n\t\taccess_key
= \"my-access-key\"\n\t\tsecret_key = \"my-secret-key\"\n\t}\nOr
indicates a Terraform module or configuration don't need credentials
at all, like provider `random`"
type: boolean
path:
description: Path is the sub-directory of remote git repository.
type: string
@ -233,15 +383,59 @@ spec:
description: Remote is a git repo which contains hcl files. Currently,
only public git repos are supported.
type: string
terraformCredentialsHelperConfigMapReference:
description: TerraformCredentialsHelperConfigMapReference specifies
the reference to a configmap containing the terraform registry credentials
helper
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
terraformCredentialsSecretReference:
description: TerraformCredentialsSecretReference specifies the reference
to the secret containing the terraform credentials and terraform
registry details
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
terraformRCConfigMapReference:
description: TerraformRCConfigMapReference specifies the reference
to a config map containing the terraform registry configuration
properties:
name:
description: name is unique within a namespace to reference a
secret resource.
type: string
namespace:
description: namespace defines the space within which the secret
name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
variable:
type: object
x-kubernetes-preserve-unknown-fields: true
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
description: |-
WriteConnectionSecretToReference specifies the namespace and name of a
Secret to which any connection details for this managed resource should
be written. Connection details frequently include the endpoint, username,
and password required to connect to the managed resource.
properties:
name:
description: Name of the secret.
@ -266,12 +460,15 @@ spec:
additionalProperties:
description: Property is the property for an output
properties:
type:
type: string
value:
type: string
type: object
type: object
region:
description: |-
Region is the region for the cloud resources created by this Configuration. If spec.region is not empty, it's the
value of it. Otherwise, it's the value of spec.providerReference.region.
type: string
state:
description: A ConfigurationState represents the status of a resource
type: string
@ -286,15 +483,16 @@ spec:
description: A ConfigurationState represents the status of a resource
type: string
type: object
observedGeneration:
description: |-
observedGeneration is the most recent generation observed for this Configuration. It corresponds to the
Configuration's generation, which is updated on mutation by the API Server.
If ObservedGeneration equals Generation, and State is Available, the value of Outputs is latest
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -1,11 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.0
creationTimestamp: null
controller-gen.kubebuilder.io/version: v0.16.5
name: providers.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
@ -29,14 +27,19 @@ spec:
description: Provider is the Schema for the providers API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
@ -47,8 +50,9 @@ spec:
description: Credentials required to authenticate to this provider.
properties:
secretRef:
description: A SecretRef is a reference to a secret key that contains
the credentials that must be used to connect to the provider.
description: |-
A SecretRef is a reference to a secret key that contains the credentials
that must be used to connect to the provider.
properties:
key:
description: The key to select.
@ -99,9 +103,3 @@ spec:
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -20,6 +20,11 @@ spec:
- name: terraform-controller
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
{{- if .Values.controllerNamespace }}
- --controller-namespace={{ .Values.controllerNamespace }}
{{- end }}
- --feature-gates=AllowDeleteProvisioningResource={{ .Values.featureGates.AllowDeleteProvisioningResource }}
env:
- name: CONTROLLER_NAMESPACE
valueFrom:
@ -35,4 +40,28 @@ spec:
value: {{ .Values.gitImage}}
- name: GITHUB_BLOCKED
value: {{ .Values.githubBlocked }}
{{ if .Values.jobBackoffLimit }}
- name: JOB_BACKOFF_LIMIT
value: {{ .Values.jobBackoffLimit }}
{{ end }}
{{ if .Values.jobNodeSelector }}
- name: JOB_NODE_SELECTOR
value: {{ .Values.jobNodeSelector }}
{{ end }}
{{ if .Values.resources.limits.cpu }}
- name: RESOURCES_LIMITS_CPU
value: {{ .Values.resources.limits.cpu }}
{{ end }}
{{ if .Values.resources.limits.memory }}
- name: RESOURCES_LIMITS_MEMORY
value: {{ .Values.resources.limits.memory }}
{{ end }}
{{ if .Values.resources.requests.cpu }}
- name: RESOURCES_REQUESTS_CPU
value: {{ .Values.resources.requests.cpu }}
{{ end }}
{{ if .Values.resources.requests.memory }}
- name: RESOURCES_REQUESTS_MEMORY
value: {{ .Values.resources.requests.memory }}
{{ end }}
serviceAccountName: tf-controller-service-account

View File

@ -2,14 +2,33 @@ replicaCount: 1
image:
repository: oamdev/terraform-controller
tag: 0.2.8
tag: latest
pullPolicy: Always
gitImage: alpine/git:latest
busyboxImage: busybox:latest
terraformImage: oamdev/docker-terraform:1.1.2
terraformImage: oamdev/docker-terraform:1.1.5
controllerNamespace: ""
# "{\"nat\": \"true\"}"
jobNodeSelector: ""
jobBackoffLimit: ""
resources:
limits:
cpu: "500m"
memory: "500Mi"
requests:
cpu: "250m"
memory: "250Mi"
backend:
namespace: vela-system
githubBlocked: "'false'"
featureGates:
# Enable the feature of allowing to delete a configuration whose cloud resources is not fully provisioned, or error happens
# This guarantees that the partial cloud resources will be deleted when the configuration is deleted
# Default value is true
AllowDeleteProvisioningResource: true

View File

@ -0,0 +1,172 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backend
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/oam-dev/terraform-controller/api/v1beta2"
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
backendTypeK8S = "kubernetes"
backendTypeS3 = "s3"
)
// Backend is an abstraction of what all backend types can do
type Backend interface {
// HCL can get the hcl code string
HCL() string
// GetTFStateJSON is used to get the Terraform state json from backend
GetTFStateJSON(ctx context.Context) ([]byte, error)
// CleanUp is used to clean up the backend when delete the configuration object
// For example, if the configuration use kubernetes backend, CleanUp will delete the backend secret
CleanUp(ctx context.Context) error
}
type backendInitFunc func(k8sClient client.Client, backendConf interface{}, credentials map[string]string) (Backend, error)
var backendInitFuncMap = map[string]backendInitFunc{
backendTypeK8S: newK8SBackend,
backendTypeS3: newS3Backend,
}
// ParseConfigurationBackend parses backend Conf from the v1beta2.Configuration
func ParseConfigurationBackend(configuration *v1beta2.Configuration, k8sClient client.Client, credentials map[string]string, controllerNSSpecified bool) (Backend, error) {
backend := configuration.Spec.Backend
var (
backendType string
backendConf interface{}
err error
)
switch {
case backend == nil || (backend.Inline == "" && backend.BackendType == ""):
// use the default k8s backend
return handleDefaultBackend(configuration, k8sClient, controllerNSSpecified)
case backend.Inline != "" && backend.BackendType != "":
return nil, errors.New("it's not allowed to set `spec.backend.inline` and `spec.backend.backendType` at the same time")
case backend.Inline != "":
// In this case, use the inline custom backend
backendType, backendConf, err = handleInlineBackendHCL(backend.Inline)
case backend.BackendType != "":
// In this case, use the explicit custom backend
// we don't change backend secret suffix to UID of configuration here.
// If backend specified, it's user's responsibility to set the right secret suffix, to avoid conflict.
backendType, backendConf, err = handleExplicitBackend(backend)
}
if err != nil {
return nil, err
}
initFunc := backendInitFuncMap[backendType]
if initFunc == nil {
return nil, fmt.Errorf("backend type (%s) is not supported", backendType)
}
return initFunc(k8sClient, backendConf, credentials)
}
func handleDefaultBackend(configuration *v1beta2.Configuration, k8sClient client.Client, controllerNSSpecified bool) (Backend, error) {
if configuration.Spec.Backend != nil {
if configuration.Spec.Backend.SecretSuffix == "" {
configuration.Spec.Backend.SecretSuffix = configuration.Name
}
configuration.Spec.Backend.InClusterConfig = true
} else {
configuration.Spec.Backend = &v1beta2.Backend{
SecretSuffix: configuration.Name,
InClusterConfig: true,
}
}
return newDefaultK8SBackend(configuration, k8sClient, controllerNSSpecified), nil
}
func handleInlineBackendHCL(hclCode string) (string, interface{}, error) {
type TerraformConfig struct {
Terraform struct {
Backend struct {
Name string `hcl:"name,label"`
Attrs hcl.Body `hcl:",remain"`
} `hcl:"backend,block"`
} `hcl:"terraform,block"`
}
hclFile, diags := hclparse.NewParser().ParseHCL([]byte(hclCode), "backend")
if diags.HasErrors() {
return "", nil, fmt.Errorf("there are syntax errors in the inline backend hcl code: %w", diags)
}
// try to parse hclFile to TerraformConfig or TerraformConfig.Terraform
config := &TerraformConfig{}
diags = gohcl.DecodeBody(hclFile.Body, nil, config)
if diags.HasErrors() || config.Terraform.Backend.Name == "" {
diags = gohcl.DecodeBody(hclFile.Body, nil, &config.Terraform)
if diags.HasErrors() || config.Terraform.Backend.Name == "" {
return "", nil, fmt.Errorf("the inline backend hcl code is not valid Terraform backend configuration: %w", diags)
}
}
backendType := config.Terraform.Backend.Name
var backendConf interface{}
switch strings.ToLower(backendType) {
case backendTypeK8S:
backendConf = &v1beta2.KubernetesBackendConf{}
case backendTypeS3:
backendConf = &v1beta2.S3BackendConf{}
default:
return "", nil, fmt.Errorf("backend type (%s) is not supported", backendType)
}
diags = gohcl.DecodeBody(config.Terraform.Backend.Attrs, nil, backendConf)
if diags.HasErrors() {
return "", nil, fmt.Errorf("the inline backend hcl code is not valid Terraform backend configuration: %w", diags)
}
return backendType, backendConf, nil
}
func handleExplicitBackend(backend *v1beta2.Backend) (string, interface{}, error) {
// check if is valid custom backend
backendType := backend.BackendType
// fetch backendConfValue using reflection
backendStructValue := reflect.ValueOf(backend)
if backendStructValue.Kind() == reflect.Ptr {
backendStructValue = backendStructValue.Elem()
}
backendField := backendStructValue.FieldByNameFunc(func(name string) bool {
return strings.EqualFold(name, backendType)
})
if backendField.Kind() != reflect.Ptr || backendField.IsNil() {
return "", nil, fmt.Errorf("there is no configuration for backendType %s", backend.BackendType)
}
return backendType, backendField.Interface(), nil
}

View File

@ -0,0 +1,346 @@
package backend
import (
"reflect"
"strings"
"testing"
"github.com/oam-dev/terraform-controller/api/v1beta2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestParseConfigurationBackend(t *testing.T) {
type args struct {
configuration *v1beta2.Configuration
credentials map[string]string
controllerNSSpecified bool
}
type want struct {
backend Backend
errMsg string
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "a",
Name: "secretref",
},
Data: map[string][]byte{
"access": []byte("access_key"),
},
}
configMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: "a",
Name: "configmapref",
},
Data: map[string]string{
"token": "token",
},
}
k8sClient := fake.NewClientBuilder().WithObjects(secret, configMap).Build()
testcases := []struct {
name string
args args
want want
}{
{
name: "backend is not nil, configuration is hcl",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{},
HCL: "image_id=123",
},
},
},
want: want{
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = ""
}
}
`,
SecretSuffix: "",
SecretNS: "",
},
},
},
{
name: "backend is nil, configuration is remote",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Remote: "https://github.com/a/b.git",
},
},
},
want: want{
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = ""
}
}
`,
SecretSuffix: "",
SecretNS: "",
},
},
},
{
name: "backend is not nil, use invalid(has syntax error) inline backend conf",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Inline: `
terraform {
`,
},
},
},
},
want: want{
errMsg: "there are syntax errors in the inline backend hcl code",
},
},
{
name: "backend is not nil, use invalid inline backend conf",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Inline: `
terraform {
}
`,
},
},
},
},
want: want{
errMsg: "the inline backend hcl code is not valid Terraform backend configuration",
},
},
{
name: "backend is not nil, use valid inline backend conf",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Inline: `
terraform {
backend "kubernetes" {
secret_suffix = ""
namespace = "vela-system"
}
}
`,
},
},
},
},
want: want{
errMsg: "",
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = "vela-system"
}
}
`,
SecretSuffix: "",
SecretNS: "vela-system",
},
},
},
{
name: "backend is not nil, use valid inline backend conf, should wrap",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Inline: `backend "kubernetes" {
secret_suffix = "tt"
namespace = "vela-system"
}`,
},
},
},
},
want: want{
errMsg: "",
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = "tt"
in_cluster_config = true
namespace = "vela-system"
}
}
`,
SecretSuffix: "tt",
SecretNS: "vela-system",
},
},
},
{
name: "backend is not nil, use explicit backend conf",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
BackendType: backendTypeK8S,
Kubernetes: &v1beta2.KubernetesBackendConf{
SecretSuffix: "suffix",
},
},
},
},
},
want: want{
errMsg: "",
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = "suffix"
in_cluster_config = true
namespace = ""
}
}
`,
SecretSuffix: "suffix",
SecretNS: "",
},
},
},
{
name: "backend is not nil, use explicit backend conf, no backendType",
args: args{
configuration: &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{Namespace: "a"},
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Kubernetes: &v1beta2.KubernetesBackendConf{
SecretSuffix: "suffix",
},
},
},
},
},
want: want{
errMsg: "",
backend: &K8SBackend{
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = "a"
}
}
`,
SecretSuffix: "",
SecretNS: "a",
},
},
},
{
name: "backend is not nil, use explicit backend conf, invalid backendType",
args: args{
configuration: &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{Namespace: "a"},
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
BackendType: backendTypeK8S,
},
},
},
},
want: want{
errMsg: "there is no configuration for backendType kubernetes",
},
},
{
name: "backend is not nil, use both inline and explicit",
args: args{
configuration: &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{Namespace: "a"},
Spec: v1beta2.ConfigurationSpec{
Backend: &v1beta2.Backend{
Inline: `kkk`,
BackendType: backendTypeK8S,
},
},
},
},
want: want{
errMsg: "it's not allowed to set `spec.backend.inline` and `spec.backend.backendType` at the same time",
},
},
{
name: "backend is nil, specify controller namespace, generate backend with legacy secret suffix",
args: args{
configuration: &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{Name: "name", Namespace: "ns", UID: "xxxx-xxxx"},
Spec: v1beta2.ConfigurationSpec{
Backend: nil,
},
},
controllerNSSpecified: true,
},
want: want{
backend: &K8SBackend{
LegacySecretSuffix: "name",
SecretNS: "ns",
SecretSuffix: "xxxx-xxxx",
Client: k8sClient,
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = "xxxx-xxxx"
in_cluster_config = true
namespace = "ns"
}
}
`,
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := ParseConfigurationBackend(tc.args.configuration, k8sClient, tc.args.credentials, tc.args.controllerNSSpecified)
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("ValidConfigurationObject() error = %v, wantErr %v", err, tc.want.errMsg)
return
}
if !reflect.DeepEqual(tc.want.backend, got) {
t.Errorf("\ngot %#v,\nwant %#v", got, tc.want.backend)
}
})
}
}

View File

@ -0,0 +1,199 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backend
import (
"context"
"fmt"
"os"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/terraform-controller/api/v1beta2"
"github.com/oam-dev/terraform-controller/controllers/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
terraformWorkspace = "default"
// TerraformStateNameInSecret is the key name to store Terraform state
TerraformStateNameInSecret = "tfstate"
// TFBackendSecret is the Secret name for Kubernetes backend
TFBackendSecret = "tfstate-%s-%s"
)
// K8SBackend is used to interact with the Terraform kubernetes backend
type K8SBackend struct {
// Client is used to interact with the Kubernetes apiServer
Client client.Client
// HCLCode stores the backend hcl code string
HCLCode string
// SecretSuffix is the suffix of the name of the Terraform backend secret
SecretSuffix string
// SecretNS is the namespace of the Terraform backend secret
SecretNS string
// LegacySecretSuffix is the same as SecretSuffix, but only used when `--controller-namespace` is specified
LegacySecretSuffix string
}
func newDefaultK8SBackend(configuration *v1beta2.Configuration, client client.Client, controllerNSSpecified bool) *K8SBackend {
ns := os.Getenv("TERRAFORM_BACKEND_NAMESPACE")
if ns == "" {
ns = configuration.GetNamespace()
}
var (
suffix = configuration.Spec.Backend.SecretSuffix
legacySuffix string
)
if controllerNSSpecified {
legacySuffix = suffix
suffix = string(configuration.GetUID())
}
hcl := renderK8SBackendHCL(suffix, ns)
return &K8SBackend{
Client: client,
HCLCode: hcl,
SecretSuffix: suffix,
SecretNS: ns,
LegacySecretSuffix: legacySuffix,
}
}
func newK8SBackend(k8sClient client.Client, backendConf interface{}, _ map[string]string) (Backend, error) {
conf, ok := backendConf.(*v1beta2.KubernetesBackendConf)
if !ok || conf == nil {
return nil, fmt.Errorf("invalid backendConf, want *v1beta2.KubernetesBackendConf, but got %#v", backendConf)
}
ns := ""
if conf.Namespace != nil {
ns = *conf.Namespace
} else {
ns = os.Getenv("TERRAFORM_BACKEND_NAMESPACE")
}
return &K8SBackend{
Client: k8sClient,
HCLCode: renderK8SBackendHCL(conf.SecretSuffix, ns),
SecretSuffix: conf.SecretSuffix,
SecretNS: ns,
}, nil
}
func renderK8SBackendHCL(suffix, ns string) string {
fmtStr := `
terraform {
backend "kubernetes" {
secret_suffix = "%s"
in_cluster_config = true
namespace = "%s"
}
}
`
return fmt.Sprintf(fmtStr, suffix, ns)
}
func (k *K8SBackend) secretName() string {
return fmt.Sprintf(TFBackendSecret, terraformWorkspace, k.SecretSuffix)
}
func (k *K8SBackend) legacySecretName() string {
return fmt.Sprintf(TFBackendSecret, terraformWorkspace, k.LegacySecretSuffix)
}
// GetTFStateJSON gets Terraform state json from the Terraform kubernetes backend
func (k *K8SBackend) GetTFStateJSON(ctx context.Context) ([]byte, error) {
var s = v1.Secret{}
// Try to get legacy secret first, if it doesn't exist, try to get new secret
err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &s)
if err != nil {
if err = k.Client.Get(ctx, client.ObjectKey{Name: k.secretName(), Namespace: k.SecretNS}, &s); err != nil {
return nil, errors.Wrap(err, "terraform state file backend secret is not generated")
}
}
tfStateData, ok := s.Data[TerraformStateNameInSecret]
if !ok {
return nil, fmt.Errorf("failed to get %s from Terraform State secret %s", TerraformStateNameInSecret, s.Name)
}
tfStateJSON, err := util.DecompressTerraformStateSecret(string(tfStateData))
if err != nil {
return nil, errors.Wrap(err, "failed to decompress state secret data")
}
return tfStateJSON, nil
}
// CleanUp will delete the Terraform kubernetes backend secret when deleting the configuration object
func (k *K8SBackend) CleanUp(ctx context.Context) error {
klog.InfoS("Deleting the legacy secret which stores Kubernetes backend", "Name", k.legacySecretName())
var kubernetesBackendSecret v1.Secret
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &kubernetesBackendSecret); err == nil {
if err := k.Client.Delete(ctx, &kubernetesBackendSecret); err != nil {
return err
}
}
klog.InfoS("Deleting the secret which stores Kubernetes backend", "Name", k.secretName())
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.secretName(), Namespace: k.SecretNS}, &kubernetesBackendSecret); err == nil {
if err := k.Client.Delete(ctx, &kubernetesBackendSecret); err != nil {
return err
}
}
return nil
}
// HCL returns the backend hcl code string
func (k *K8SBackend) HCL() string {
if k.LegacySecretSuffix != "" {
err := k.migrateLegacySecret()
if err != nil {
klog.ErrorS(err, "Failed to migrate legacy secret")
}
}
if k.HCLCode == "" {
k.HCLCode = renderK8SBackendHCL(k.SecretSuffix, k.SecretNS)
}
return k.HCLCode
}
// migrateLegacySecret will migrate the legacy secret to the new secret if the legacy secret exists
// This is needed when the --controller-namespace is specified and restart the controller
func (k *K8SBackend) migrateLegacySecret() error {
ctx := context.TODO()
s := v1.Secret{}
if err := k.Client.Get(ctx, client.ObjectKey{Name: k.legacySecretName(), Namespace: k.SecretNS}, &s); err == nil {
klog.InfoS("Migrating legacy secret to new secret", "LegacyName", k.legacySecretName(), "NewName", k.secretName(), "Namespace", k.SecretNS)
newSecret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: k.secretName(),
Namespace: k.SecretNS,
},
Data: s.Data,
}
err = k.Client.Create(ctx, &newSecret)
if err != nil {
return errors.Wrapf(err, "Fail to create new secret, Name: %s, Namespace: %s", k.secretName(), k.SecretNS)
} else if err = k.Client.Delete(ctx, &s); err != nil {
// Only delete the legacy secret if the new secret is successfully created
return errors.Wrapf(err, "Fail to delete legacy secret, Name: %s, Namespace: %s", k.legacySecretName(), k.SecretNS)
}
}
return nil
}

View File

@ -0,0 +1,274 @@
package backend
import (
"context"
"encoding/base64"
"reflect"
"testing"
"gotest.tools/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestK8SBackend_HCL(t *testing.T) {
type fields struct {
HCLCode string
SecretSuffix string
SecretNS string
}
k8sClient := fake.NewClientBuilder().Build()
tests := []struct {
name string
fields fields
want string
}{
{
name: "HCLCode is empty",
fields: fields{
SecretSuffix: "tt",
SecretNS: "ac",
},
want: `
terraform {
backend "kubernetes" {
secret_suffix = "tt"
in_cluster_config = true
namespace = "ac"
}
}
`,
},
{
name: "HCLCode is not empty",
fields: fields{
HCLCode: `
terraform {
backend "kubernetes" {
secret_suffix = "tt"
in_cluster_config = true
namespace = "ac"
}
}
`,
},
want: `
terraform {
backend "kubernetes" {
secret_suffix = "tt"
in_cluster_config = true
namespace = "ac"
}
}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &K8SBackend{
Client: k8sClient,
HCLCode: tt.fields.HCLCode,
SecretSuffix: tt.fields.SecretSuffix,
SecretNS: tt.fields.SecretNS,
}
if got := k.HCL(); got != tt.want {
t.Errorf("HCL() = %v, want %v", got, tt.want)
}
})
}
}
func TestK8SBackend_GetTFStateJSON(t *testing.T) {
const UID = "xxxx-xxxx"
type fields struct {
Client client.Client
HCLCode string
SecretSuffix string
SecretNS string
LegacySecretSuffix string
}
type args struct {
ctx context.Context
}
tfStateData, _ := base64.StdEncoding.DecodeString("H4sIAAAAAAAA/4SQzarbMBCF934KoXUdPKNf+1VKCWNp5AocO8hyaSl592KlcBd3cZfnHPHpY/52QshfXI68b3IS+tuVK5dCaS+P+8ci4TbcULb94JJplZPAFte8MS18PQrKBO8Q+xk59SHa1AMA9M4YmoN3FGJ8M/azPs96yElcCkLIsG+V8sblnqOc3uXlRuvZ0GxSSuiCRUYbw2gGHRFGPxitEgJYQDQ0a68I2ChNo1cAZJ2bR20UtW8bsv55NuJRS94W2erXe5X5QQs3A/FZ4fhJaOwUgZTVMRjto1HGpSGSQuuD955hdDDPcR6NY1ZpQJ/YwagTRAvBpsi8LXn7Pa1U+ahfWHX/zWThYz9L4Otg3390r+5fAAAA//8hmcuNuQEAAA==")
baseSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tfstate-default-a",
Namespace: "default",
},
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
TerraformStateNameInSecret: tfStateData,
},
}
k8sClient := fake.NewClientBuilder().WithObjects(baseSecret).Build()
k8sClient2 := fake.NewClientBuilder().Build()
tests := []struct {
name string
fields fields
args args
want []byte
wantErr bool
}{
{
name: "valid",
fields: fields{
Client: k8sClient,
HCLCode: "",
SecretSuffix: "a",
SecretNS: "default",
},
args: args{ctx: context.Background()},
want: tfStateJson,
},
{
name: "secret doesn't exist",
fields: fields{
Client: k8sClient2,
HCLCode: "",
SecretSuffix: "a",
SecretNS: "default",
},
args: args{ctx: context.Background()},
want: nil,
wantErr: true,
},
{
name: "got a legacy secret",
fields: fields{
Client: k8sClient,
HCLCode: "",
LegacySecretSuffix: "a",
SecretNS: "default",
SecretSuffix: UID,
},
want: tfStateJson,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &K8SBackend{
Client: tt.fields.Client,
HCLCode: tt.fields.HCLCode,
SecretSuffix: tt.fields.SecretSuffix,
SecretNS: tt.fields.SecretNS,
LegacySecretSuffix: tt.fields.LegacySecretSuffix,
}
got, err := k.GetTFStateJSON(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("GetTFStateJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetTFStateJSON() got = %v, want %v", got, tt.want)
t.Errorf("GetTFStateJSON() got = %s, want %s", got, tt.want)
}
})
}
}
func TestK8SBackend_CleanUp(t *testing.T) {
type fields struct {
Client client.Client
HCLCode string
SecretSuffix string
SecretNS string
}
type args struct {
ctx context.Context
}
secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tfstate-default-a",
Namespace: "default",
},
Type: v1.SecretTypeOpaque,
}
k8sClient := fake.NewClientBuilder().WithObjects(&secret).Build()
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "valid",
fields: fields{
Client: k8sClient,
SecretSuffix: "a",
SecretNS: "default",
},
args: args{ctx: context.Background()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &K8SBackend{
Client: tt.fields.Client,
HCLCode: tt.fields.HCLCode,
SecretSuffix: tt.fields.SecretSuffix,
SecretNS: tt.fields.SecretNS,
}
if err := k.CleanUp(tt.args.ctx); (err != nil) != tt.wantErr {
t.Errorf("CleanUp() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestMigrateLegacySecret(t *testing.T) {
secretNS := "default"
secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tfstate-default-a",
Namespace: secretNS,
},
Type: v1.SecretTypeOpaque,
}
k8sClient := fake.NewClientBuilder().WithObjects(&secret).Build()
fakeUID := "xxxx-xxxx"
k := &K8SBackend{
Client: k8sClient,
HCLCode: "",
SecretSuffix: fakeUID,
SecretNS: secretNS,
LegacySecretSuffix: "a",
}
err := k.migrateLegacySecret()
assert.NilError(t, err)
NoLegacySecK8sClient := fake.NewClientBuilder().Build()
k = &K8SBackend{
Client: NoLegacySecK8sClient,
HCLCode: "",
SecretSuffix: fakeUID,
SecretNS: secretNS,
LegacySecretSuffix: "a",
}
err = k.migrateLegacySecret()
assert.NilError(t, err)
}
var tfStateJson = []byte(`{
"version": 4,
"terraform_version": "1.0.2",
"serial": 2,
"lineage": "c35c8722-b2ef-cd6f-1111-755abc87acdd",
"outputs": {
"container_id":{
"value": "e5fff27c62e26dc9504d21980543f21161225ab483a1e534a98311a677b9453a",
"type": "string"
},
"image_id": {
"value": "sha256:d1a364dc548d5357f0da3268c888e1971bbdb957ee3f028fe7194f1d61c6fdeenginx:latest",
"type": "string"
}
},
"resources": []
}
`)

View File

@ -0,0 +1,163 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backend
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
awscredentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
"github.com/oam-dev/terraform-controller/api/v1beta2"
"github.com/oam-dev/terraform-controller/controllers/provider"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// S3Backend is used to interact with the Terraform s3 backend
type S3Backend struct {
client s3iface.S3API
Region string
Key string
Bucket string
}
func newS3Backend(_ client.Client, backendConf interface{}, credentials map[string]string) (Backend, error) {
conf, ok := backendConf.(*v1beta2.S3BackendConf)
if !ok || conf == nil {
return nil, fmt.Errorf("invalid backendConf, want *v1beta2.S3BackendConf, but got %#v", backendConf)
}
var region string
if conf.Region != nil && *conf.Region != "" {
region = *conf.Region
} else {
region = credentials[provider.EnvAWSDefaultRegion]
}
if region == "" {
return nil, errors.New("fail to get region when build s3 backend")
}
s3Backend := &S3Backend{
Region: region,
Key: conf.Key,
Bucket: conf.Bucket,
}
accessKey := credentials[provider.EnvAWSAccessKeyID]
secretKey := credentials[provider.EnvAWSSecretAccessKey]
sessionToken := credentials[provider.EnvAWSSessionToken]
if accessKey == "" || secretKey == "" {
return nil, errors.New("fail to get credentials when build s3 backend")
}
// build s3 client
sessionOpts := session.Options{
Config: aws.Config{
Credentials: awscredentials.NewStaticCredentials(accessKey, secretKey, sessionToken),
Region: aws.String(s3Backend.Region),
},
}
sess, err := session.NewSessionWithOptions(sessionOpts)
if err != nil {
return nil, fmt.Errorf("fial to build s3 backend: %w", err)
}
s3Backend.client = s3.New(sess)
// check if the bucket exists
if err := s3Backend.checkBucketExists(); err != nil {
return nil, err
}
return s3Backend, nil
}
func (s *S3Backend) checkBucketExists() error {
bucketListOutput, err := s.client.ListBuckets(&s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("fail to list bucket when check if the bucket(%s) exists: %w", s.Bucket, err)
}
for _, bucket := range bucketListOutput.Buckets {
if bucket.Name != nil && *bucket.Name == s.Bucket {
return nil
}
}
return fmt.Errorf("fail to get bucket (%s), please make sure the bucket exists and the provider credentials have access to the bucket", s.Bucket)
}
func (s *S3Backend) getObject() (*s3.GetObjectOutput, error) {
input := &s3.GetObjectInput{
Key: &s.Key,
Bucket: &s.Bucket,
}
return s.client.GetObject(input)
}
// GetTFStateJSON gets Terraform state json from the Terraform s3 backend
func (s *S3Backend) GetTFStateJSON(_ context.Context) ([]byte, error) {
output, err := s.getObject()
if err != nil {
return nil, err
}
defer func() { _ = output.Body.Close() }()
writer := bytes.NewBuffer(nil)
_, err = io.Copy(writer, output.Body)
if err != nil {
return nil, err
}
return writer.Bytes(), nil
}
// CleanUp will delete the s3 object which contains the Terraform state
func (s *S3Backend) CleanUp(_ context.Context) error {
_, err := s.getObject()
if err != nil {
// nolint:errorlint
if err, ok := err.(awserr.Error); ok && err.Code() == s3.ErrCodeNoSuchKey || err.Code() == s3.ErrCodeNoSuchBucket {
// the object is not found, no need to delete
return nil
}
return err
}
input := &s3.DeleteObjectInput{
Bucket: &s.Bucket,
Key: &s.Key,
}
_, err = s.client.DeleteObject(input)
return err
}
// HCL returns the backend hcl code string
func (s S3Backend) HCL() string {
fmtStr := `
terraform {
backend s3 {
bucket = "%s"
key = "%s"
region = "%s"
}
}
`
return fmt.Sprintf(fmtStr, s.Bucket, s.Key, s.Region)
}

View File

@ -0,0 +1,236 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backend
import (
"bytes"
"context"
"io/ioutil"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
"github.com/pkg/errors"
)
func TestS3Backend_HCL(t *testing.T) {
type fields struct {
Region string
Key string
Bucket string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "normal",
fields: fields{
Region: "a",
Key: "b",
Bucket: "c",
},
want: `
terraform {
backend s3 {
bucket = "c"
key = "b"
region = "a"
}
}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := S3Backend{
Region: tt.fields.Region,
Key: tt.fields.Key,
Bucket: tt.fields.Bucket,
}
if got := s.HCL(); got != tt.want {
t.Errorf("HCL() = %v, want %v", got, tt.want)
}
})
}
}
type mockNoSuchBucketError struct {
}
func (err mockNoSuchBucketError) Error() string {
return ""
}
func (err mockNoSuchBucketError) Message() string {
return ""
}
func (err mockNoSuchBucketError) OrigErr() error {
return errors.New(s3.ErrCodeNoSuchBucket)
}
func (err mockNoSuchBucketError) Code() string {
return s3.ErrCodeNoSuchBucket
}
type mockNoSuchKeyError struct {
}
func (err mockNoSuchKeyError) Error() string {
return ""
}
func (err mockNoSuchKeyError) Message() string {
return ""
}
func (err mockNoSuchKeyError) OrigErr() error {
return errors.New(s3.ErrCodeNoSuchKey)
}
func (err mockNoSuchKeyError) Code() string {
return s3.ErrCodeNoSuchKey
}
type mockS3Client struct {
s3iface.S3API
}
func (s *mockS3Client) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
var resp string
switch {
case *(input.Bucket) == "a" && *(input.Key) == "a":
resp = "test_a"
case *(input.Bucket) == "a" && *(input.Key) == "c":
return nil, mockNoSuchKeyError{}
case *(input.Bucket) == "b":
return nil, mockNoSuchBucketError{}
}
if resp != "" {
body := ioutil.NopCloser(bytes.NewBuffer([]byte(resp)))
return &s3.GetObjectOutput{Body: body}, nil
}
return nil, nil
}
func (s *mockS3Client) DeleteObject(input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
switch {
case *(input.Bucket) == "a" && *(input.Key) == "a":
return &s3.DeleteObjectOutput{}, nil
}
return nil, nil
}
func TestS3Backend_GetTFStateJSON(t *testing.T) {
type fields struct {
Key string
Bucket string
}
type args struct {
in0 context.Context
}
tests := []struct {
name string
fields fields
args args
want []byte
wantErr bool
}{
{
name: "bucket exists, key exists",
fields: fields{
Key: "a",
Bucket: "a",
},
want: []byte("test_a"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &S3Backend{
client: &mockS3Client{},
Key: tt.fields.Key,
Bucket: tt.fields.Bucket,
}
got, err := s.GetTFStateJSON(tt.args.in0)
if (err != nil) != tt.wantErr {
t.Errorf("GetTFStateJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetTFStateJSON() got = %v, want %v", got, tt.want)
}
})
}
}
func TestS3Backend_CleanUp(t *testing.T) {
type fields struct {
Key string
Bucket string
}
type args struct {
in0 context.Context
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "no such bucket",
fields: fields{
Key: "a",
Bucket: "b",
},
},
{
name: "no such key",
fields: fields{
Key: "c",
Bucket: "a",
},
},
{
name: "bucket exists, key exists",
fields: fields{
Key: "a",
Bucket: "a",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &S3Backend{
client: &mockS3Client{},
Key: tt.fields.Key,
Bucket: tt.fields.Bucket,
}
if err := s.CleanUp(tt.args.in0); (err != nil) != tt.wantErr {
t.Errorf("CleanUp() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -9,12 +9,15 @@ import (
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/terraform-controller/api/types"
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/oam-dev/terraform-controller/api/v1beta2"
"github.com/oam-dev/terraform-controller/controllers/features"
"github.com/oam-dev/terraform-controller/controllers/provider"
)
@ -32,17 +35,14 @@ const (
const errGitHubBlockedNotBoolean = "the value of githubBlocked is not a boolean"
// ValidConfigurationObject will validate a Configuration
func ValidConfigurationObject(configuration *v1beta1.Configuration) (types.ConfigurationType, error) {
json := configuration.Spec.JSON
func ValidConfigurationObject(configuration *v1beta2.Configuration) (types.ConfigurationType, error) {
hcl := configuration.Spec.HCL
remote := configuration.Spec.Remote
switch {
case json == "" && hcl == "" && remote == "":
return "", errors.New("spec.JSON, spec.HCL or spec.Remote should be set")
case json != "" && hcl != "", json != "" && remote != "", hcl != "" && remote != "":
return "", errors.New("spec.JSON, spec.HCL and/or spec.Remote cloud not be set at the same time")
case json != "":
return types.ConfigurationJSON, nil
case hcl == "" && remote == "":
return "", errors.New("spec.HCL or spec.Remote should be set")
case hcl != "" && remote != "":
return "", errors.New("spec.HCL and spec.Remote cloud not be set at the same time")
case hcl != "":
return types.ConfigurationHCL, nil
case remote != "":
@ -51,38 +51,6 @@ func ValidConfigurationObject(configuration *v1beta1.Configuration) (types.Confi
return "", nil
}
// RenderConfiguration will compose the Terraform configuration with hcl/json and backend
func RenderConfiguration(configuration *v1beta1.Configuration, terraformBackendNamespace string, configurationType types.ConfigurationType) (string, error) {
if configuration.Spec.Backend != nil {
if configuration.Spec.Backend.SecretSuffix == "" {
configuration.Spec.Backend.SecretSuffix = configuration.Name
}
configuration.Spec.Backend.InClusterConfig = true
} else {
configuration.Spec.Backend = &v1beta1.Backend{
SecretSuffix: configuration.Name,
InClusterConfig: true,
}
}
backendTF, err := RenderTemplate(configuration.Spec.Backend, terraformBackendNamespace)
if err != nil {
return "", errors.Wrap(err, "failed to prepare Terraform backend configuration")
}
switch configurationType {
case types.ConfigurationJSON:
return configuration.Spec.JSON, nil
case types.ConfigurationHCL:
completedConfiguration := configuration.Spec.HCL
completedConfiguration += "\n" + backendTF
return completedConfiguration, nil
case types.ConfigurationRemote:
return backendTF, nil
default:
return "", errors.New("Unsupported Configuration Type")
}
}
// SetRegion will set the region for Configuration
func SetRegion(ctx context.Context, k8sClient client.Client, namespace, name string, providerObj *v1beta1.Provider) (string, error) {
configuration, err := Get(ctx, k8sClient, apitypes.NamespacedName{Namespace: namespace, Name: name})
@ -98,13 +66,13 @@ func SetRegion(ctx context.Context, k8sClient client.Client, namespace, name str
}
// Update will update the Configuration
func Update(ctx context.Context, k8sClient client.Client, configuration *v1beta1.Configuration) error {
func Update(ctx context.Context, k8sClient client.Client, configuration *v1beta2.Configuration) error {
return k8sClient.Update(ctx, configuration)
}
// Get will get the Configuration
func Get(ctx context.Context, k8sClient client.Client, namespacedName apitypes.NamespacedName) (v1beta1.Configuration, error) {
configuration := &v1beta1.Configuration{}
func Get(ctx context.Context, k8sClient client.Client, namespacedName apitypes.NamespacedName) (v1beta2.Configuration, error) {
configuration := &v1beta2.Configuration{}
if err := k8sClient.Get(ctx, namespacedName, configuration); err != nil {
if kerrors.IsNotFound(err) {
klog.ErrorS(err, "unable to fetch Configuration", "NamespacedName", namespacedName)
@ -115,18 +83,29 @@ func Get(ctx context.Context, k8sClient client.Client, namespacedName apitypes.N
}
// IsDeletable will check whether the Configuration can be deleted immediately
// If deletable, it means no external cloud resources are provisioned
func IsDeletable(ctx context.Context, k8sClient client.Client, configuration *v1beta1.Configuration) (bool, error) {
providerRef := GetProviderNamespacedName(*configuration)
providerObj, err := provider.GetProviderFromConfiguration(ctx, k8sClient, providerRef.Namespace, providerRef.Name)
if err != nil {
return false, err
}
// allow Configuration to delete when the Provider doesn't exist or is not ready, which means external cloud resources are
// not provisioned at all
if providerObj == nil || providerObj.Status.State == types.ProviderIsNotReady {
// If deletable, it means
// - feature gate AllowDeleteProvisioningResource is enabled
// - no external cloud resources are provisioned
// - it's in force-delete state
func IsDeletable(ctx context.Context, k8sClient client.Client, configuration *v1beta2.Configuration) (bool, error) {
if feature.DefaultFeatureGate.Enabled(features.AllowDeleteProvisioningResource) {
return true, nil
}
if configuration.Spec.ForceDelete != nil && *configuration.Spec.ForceDelete {
return true, nil
}
if !configuration.Spec.InlineCredentials {
providerRef := GetProviderNamespacedName(*configuration)
providerObj, err := provider.GetProviderFromConfiguration(ctx, k8sClient, providerRef.Namespace, providerRef.Name)
if err != nil {
return false, err
}
// allow Configuration to delete when the Provider doesn't exist or is not ready, which means external cloud resources are
// not provisioned at all
if providerObj == nil || providerObj.Status.State == types.ProviderIsNotReady || configuration.Status.Apply.State == types.TerraformInitError {
return true, nil
}
}
if configuration.Status.Apply.State == types.ConfigurationProvisioningAndChecking {
warning := fmt.Sprintf("Destroy could not complete and needs to wait for Provision to complete first: %s", types.MessageCloudResourceProvisioningAndChecking)
@ -142,7 +121,7 @@ func ReplaceTerraformSource(remote string, githubBlockedStr string) string {
klog.InfoS("Whether GitHub is blocked", "githubBlocked", githubBlockedStr)
githubBlocked, err := strconv.ParseBool(githubBlockedStr)
if err != nil {
klog.Warningf(errGitHubBlockedNotBoolean, err)
klog.Warningf("%s: %v", errGitHubBlockedNotBoolean, err)
return remote
}
klog.InfoS("Parsed GITHUB_BLOCKED env", "githubBlocked", githubBlocked)
@ -171,7 +150,7 @@ func ReplaceTerraformSource(remote string, githubBlockedStr string) string {
}
// GetProviderNamespacedName will get the provider namespaced name
func GetProviderNamespacedName(configuration v1beta1.Configuration) *crossplane.Reference {
func GetProviderNamespacedName(configuration v1beta2.Configuration) *crossplane.Reference {
if configuration.Spec.ProviderReference != nil {
return configuration.Spec.ProviderReference
}

View File

@ -2,10 +2,10 @@ package configuration
import (
"context"
"github.com/stretchr/testify/assert"
"strings"
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -14,11 +14,12 @@ import (
"github.com/oam-dev/terraform-controller/api/types"
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/oam-dev/terraform-controller/api/v1beta2"
)
func TestValidConfigurationObject(t *testing.T) {
type args struct {
configuration *v1beta1.Configuration
configuration *v1beta2.Configuration
}
type want struct {
configurationType types.ConfigurationType
@ -33,8 +34,8 @@ func TestValidConfigurationObject(t *testing.T) {
{
name: "hcl",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
HCL: "abc",
},
},
@ -46,8 +47,8 @@ func TestValidConfigurationObject(t *testing.T) {
{
name: "remote",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Remote: "def",
},
},
@ -56,24 +57,11 @@ func TestValidConfigurationObject(t *testing.T) {
configurationType: types.ConfigurationRemote,
},
},
{
name: "json",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
JSON: "abc",
},
},
},
want: want{
configurationType: types.ConfigurationJSON,
},
},
{
name: "remote and hcl are set",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
HCL: "abc",
Remote: "def",
},
@ -81,19 +69,19 @@ func TestValidConfigurationObject(t *testing.T) {
},
want: want{
configurationType: "",
errMsg: "spec.JSON, spec.HCL and/or spec.Remote cloud not be set at the same time",
errMsg: "spec.HCL and spec.Remote cloud not be set at the same time",
},
},
{
name: "remote and hcl are not set",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{},
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{},
},
},
want: want{
configurationType: "",
errMsg: "spec.JSON, spec.HCL or spec.Remote should be set",
errMsg: "spec.HCL or spec.Remote should be set",
},
},
}
@ -113,99 +101,6 @@ func TestValidConfigurationObject(t *testing.T) {
}
func TestRenderConfiguration(t *testing.T) {
type args struct {
configuration *v1beta1.Configuration
ns string
configurationType types.ConfigurationType
}
type want struct {
cfg string
errMsg string
}
testcases := []struct {
name string
args args
want want
}{
{
name: "backend is not nil, configuration is hcl",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
Backend: &v1beta1.Backend{},
HCL: "abc",
},
},
ns: "vela-system",
configurationType: types.ConfigurationHCL,
},
want: want{
cfg: `abc
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = "vela-system"
}
}
`,
},
},
{
name: "backend is nil, configuration is remote",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{
Remote: "https://github.com/a/b.git",
},
},
ns: "vela-system",
configurationType: types.ConfigurationRemote,
},
want: want{
cfg: `
terraform {
backend "kubernetes" {
secret_suffix = ""
in_cluster_config = true
namespace = "vela-system"
}
}
`,
},
},
{
name: "backend is nil, configuration is not supported",
args: args{
configuration: &v1beta1.Configuration{
Spec: v1beta1.ConfigurationSpec{},
},
ns: "vela-system",
},
want: want{
errMsg: "Unsupported Configuration Type",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := RenderConfiguration(tc.args.configuration, tc.args.ns, tc.args.configurationType)
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("ValidConfigurationObject() error = %v, wantErr %v", err, tc.want.errMsg)
return
}
if got != tc.want.cfg {
t.Errorf("ValidConfigurationObject() = %v, want %v", got, tc.want.cfg)
}
})
}
}
func TestReplaceTerraformSource(t *testing.T) {
testcases := []struct {
remote string
@ -257,7 +152,8 @@ func TestReplaceTerraformSource(t *testing.T) {
func TestIsDeletable(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
_ = v1beta1.AddToScheme(s)
_ = v1beta2.AddToScheme(s)
provider2 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
@ -281,7 +177,7 @@ func TestIsDeletable(t *testing.T) {
k8sClient3 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
k8sClient4 := fake.NewClientBuilder().Build()
configuration := &v1beta1.Configuration{
configuration := &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
},
@ -290,9 +186,34 @@ func TestIsDeletable(t *testing.T) {
Name: "default",
Namespace: "default",
}
configuration.Spec.InlineCredentials = false
defaultConfiguration := &v1beta2.Configuration{}
defaultConfiguration.Spec.InlineCredentials = false
provisioningConfiguration := &v1beta2.Configuration{
Status: v1beta2.ConfigurationStatus{
Apply: v1beta2.ConfigurationApplyStatus{
State: types.ConfigurationProvisioningAndChecking,
},
},
}
provisioningConfiguration.Spec.InlineCredentials = false
readyConfiguration := &v1beta2.Configuration{
Status: v1beta2.ConfigurationStatus{
Apply: v1beta2.ConfigurationApplyStatus{
State: types.Available,
},
},
}
readyConfiguration.Spec.InlineCredentials = false
inlineConfiguration := &v1beta2.Configuration{}
inlineConfiguration.Spec.InlineCredentials = true
type args struct {
configuration *v1beta1.Configuration
configuration *v1beta2.Configuration
k8sClient client.Client
}
type want struct {
@ -308,7 +229,7 @@ func TestIsDeletable(t *testing.T) {
name: "provider is not found",
args: args{
k8sClient: k8sClient1,
configuration: &v1beta1.Configuration{},
configuration: defaultConfiguration,
},
want: want{
deletable: true,
@ -318,7 +239,7 @@ func TestIsDeletable(t *testing.T) {
name: "provider is not ready, use default providerRef",
args: args{
k8sClient: k8sClient2,
configuration: &v1beta1.Configuration{},
configuration: defaultConfiguration,
},
want: want{
deletable: true,
@ -337,14 +258,8 @@ func TestIsDeletable(t *testing.T) {
{
name: "configuration is provisioning",
args: args{
k8sClient: k8sClient3,
configuration: &v1beta1.Configuration{
Status: v1beta1.ConfigurationStatus{
Apply: v1beta1.ConfigurationApplyStatus{
State: types.ConfigurationProvisioningAndChecking,
},
},
},
k8sClient: k8sClient3,
configuration: provisioningConfiguration,
},
want: want{
errMsg: "Destroy could not complete and needs to wait for Provision to complete first",
@ -353,14 +268,8 @@ func TestIsDeletable(t *testing.T) {
{
name: "configuration is ready",
args: args{
k8sClient: k8sClient3,
configuration: &v1beta1.Configuration{
Status: v1beta1.ConfigurationStatus{
Apply: v1beta1.ConfigurationApplyStatus{
State: types.Available,
},
},
},
k8sClient: k8sClient3,
configuration: readyConfiguration,
},
want: want{},
},
@ -368,12 +277,22 @@ func TestIsDeletable(t *testing.T) {
name: "failed to get provider",
args: args{
k8sClient: k8sClient4,
configuration: &v1beta1.Configuration{},
configuration: defaultConfiguration,
},
want: want{
errMsg: "failed to get Provider object",
},
},
{
name: "no provider is needed",
args: args{
k8sClient: k8sClient4,
configuration: inlineConfiguration,
},
want: want{
deletable: false,
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
@ -394,24 +313,24 @@ func TestIsDeletable(t *testing.T) {
func TestSetRegion(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
_ = v1beta2.AddToScheme(s)
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
configuration1 := v1beta1.Configuration{
configuration1 := v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
Namespace: "default",
},
Spec: v1beta1.ConfigurationSpec{},
Spec: v1beta2.ConfigurationSpec{},
}
configuration1.Spec.Region = "xxx"
assert.Nil(t, k8sClient.Create(ctx, &configuration1))
configuration2 := v1beta1.Configuration{
configuration2 := v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "def",
Namespace: "default",
},
Spec: v1beta1.ConfigurationSpec{},
Spec: v1beta2.ConfigurationSpec{},
}
assert.Nil(t, k8sClient.Create(ctx, &configuration2))

View File

@ -17,28 +17,13 @@ limitations under the License.
package configuration
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"text/template"
"github.com/Masterminds/sprig/v3"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/terraform-controller/api/v1beta1"
)
var backendTF = `
terraform {
backend "kubernetes" {
secret_suffix = "{{.SecretSuffix}}"
in_cluster_config = {{.InClusterConfig}}
namespace = "{{.Namespace}}"
}
}
`
// RawExtension2Map will convert rawExtension to map
// This function is copied from oam-dev/kubevela
func RawExtension2Map(raw *runtime.RawExtension) (map[string]interface{}, error) {
@ -57,32 +42,6 @@ func RawExtension2Map(raw *runtime.RawExtension) (map[string]interface{}, error)
return ret, nil
}
type backendVars struct {
SecretSuffix string
InClusterConfig bool
Namespace string
}
// RenderTemplate renders Backend template
func RenderTemplate(backend *v1beta1.Backend, namespace string) (string, error) {
tmpl, err := template.New("backend").Funcs(template.FuncMap(sprig.FuncMap())).Parse(backendTF)
if err != nil {
return "", err
}
templateVars := backendVars{
SecretSuffix: backend.SecretSuffix,
InClusterConfig: backend.InClusterConfig,
Namespace: namespace,
}
var wr bytes.Buffer
err = tmpl.Execute(&wr, templateVars)
if err != nil {
return "", err
}
return wr.String(), nil
}
// Interface2String converts an interface{} type to string
func Interface2String(v interface{}) (string, error) {
var value string
@ -96,11 +55,11 @@ func Interface2String(v interface{}) (string, error) {
case bool:
value = strconv.FormatBool(v)
default:
valuejson, err := json.Marshal(v)
valueJSON, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("cloud not convert %v to string", v)
}
value = string(valuejson)
value = string(valueJSON)
}
return value, nil
}

View File

@ -96,7 +96,7 @@ func TestRawExtension2Map2(t *testing.T) {
},
},
want: want{
errMessage: "invalid character 'x' looking for beginning of value",
errMessage: "cannot convert RawExtension with unrecognized content type to unstructured",
},
}}
for name, tc := range cases {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
/*
Copyright 2023 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package features
import (
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/component-base/featuregate"
)
const (
AllowDeleteProvisioningResource featuregate.Feature = "AllowDeleteProvisioningResource"
)
var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
AllowDeleteProvisioningResource: {Default: false, PreRelease: featuregate.Alpha},
}
func init() {
runtime.Must(feature.DefaultMutableFeatureGate.Add(defaultFeatureGates))
}

View File

@ -0,0 +1,60 @@
package container
import (
"fmt"
"github.com/oam-dev/terraform-controller/api/types"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func (a *Assembler) ApplyContainer(executionType types.TerraformExecutionType, resourceQuota types.ResourceQuota) v1.Container {
c := v1.Container{
Name: types.TerraformContainerName,
Image: a.TerraformImage,
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{
"bash",
"-c",
fmt.Sprintf("terraform %s -lock=false -auto-approve", executionType),
},
VolumeMounts: []v1.VolumeMount{
{
Name: a.Name,
MountPath: types.WorkingVolumeMountPath,
},
{
Name: types.InputTFConfigurationVolumeName,
MountPath: types.InputTFConfigurationVolumeMountPath,
},
},
Env: a.Envs,
}
if resourceQuota.ResourcesLimitsCPU != "" || resourceQuota.ResourcesLimitsMemory != "" ||
resourceQuota.ResourcesRequestsCPU != "" || resourceQuota.ResourcesRequestsMemory != "" {
resourceRequirements := v1.ResourceRequirements{}
if resourceQuota.ResourcesLimitsCPU != "" || resourceQuota.ResourcesLimitsMemory != "" {
resourceRequirements.Limits = map[v1.ResourceName]resource.Quantity{}
if resourceQuota.ResourcesLimitsCPU != "" {
resourceRequirements.Limits["cpu"] = resourceQuota.ResourcesLimitsCPUQuantity
}
if resourceQuota.ResourcesLimitsMemory != "" {
resourceRequirements.Limits["memory"] = resourceQuota.ResourcesLimitsMemoryQuantity
}
}
if resourceQuota.ResourcesRequestsCPU != "" || resourceQuota.ResourcesLimitsMemory != "" {
resourceRequirements.Requests = map[v1.ResourceName]resource.Quantity{}
if resourceQuota.ResourcesRequestsCPU != "" {
resourceRequirements.Requests["cpu"] = resourceQuota.ResourcesRequestsCPUQuantity
}
if resourceQuota.ResourcesRequestsMemory != "" {
resourceRequirements.Requests["memory"] = resourceQuota.ResourcesRequestsMemoryQuantity
}
}
c.Resources = resourceRequirements
}
return c
}

View File

@ -0,0 +1,80 @@
package container
import (
"github.com/oam-dev/terraform-controller/api/types"
v1 "k8s.io/api/core/v1"
)
// Assembler helps to assemble the init containers
type Assembler struct {
Name string
GitCredential bool
TerraformCredential bool
TerraformRC bool
TerraformCredentialsHelper bool
TerraformImage string
BusyboxImage string
GitImage string
Git types.Git
Envs []v1.EnvVar
}
func NewAssembler(name string) *Assembler {
return &Assembler{Name: name}
}
func (a *Assembler) GitCredReference(ptr *v1.SecretReference) *Assembler {
if ptr != nil {
a.GitCredential = true
}
return a
}
func (a *Assembler) TerraformCredReference(ptr *v1.SecretReference) *Assembler {
if ptr != nil {
a.TerraformCredential = true
}
return a
}
func (a *Assembler) TerraformRCReference(ptr *v1.SecretReference) *Assembler {
if ptr != nil {
a.TerraformRC = true
}
return a
}
func (a *Assembler) TerraformCredentialsHelperReference(ptr *v1.SecretReference) *Assembler {
if ptr != nil {
a.TerraformCredentialsHelper = true
}
return a
}
func (a *Assembler) SetTerraformImage(image string) *Assembler {
a.TerraformImage = image
return a
}
func (a *Assembler) SetBusyboxImage(image string) *Assembler {
a.BusyboxImage = image
return a
}
func (a *Assembler) SetGitImage(image string) *Assembler {
a.GitImage = image
return a
}
func (a *Assembler) SetGit(git types.Git) *Assembler {
a.Git = git
return a
}
func (a *Assembler) SetEnvs(envs []v1.EnvVar) *Assembler {
a.Envs = envs
return a
}

View File

@ -0,0 +1,85 @@
package container
import (
"fmt"
"path/filepath"
"github.com/oam-dev/terraform-controller/api/types"
v1 "k8s.io/api/core/v1"
)
const GitContainerName = "git-configuration"
// GitContainer will clone the git repository, and copy the files to the working directory
func (a *Assembler) GitContainer() v1.Container {
mounts := []v1.VolumeMount{
{
Name: a.Name,
MountPath: types.WorkingVolumeMountPath,
},
{
Name: types.BackendVolumeName,
MountPath: types.BackendVolumeMountPath,
},
}
if a.GitCredential {
mounts = append(mounts,
v1.VolumeMount{
Name: types.GitAuthConfigVolumeName,
MountPath: types.GitAuthConfigVolumeMountPath,
})
}
command := a.getCloneCommand()
return v1.Container{
Name: GitContainerName,
Image: a.GitImage,
ImagePullPolicy: v1.PullIfNotPresent,
Command: command,
VolumeMounts: mounts,
}
}
func (a *Assembler) getCloneCommand() []string {
var cmd string
hclPath := filepath.Join(types.BackendVolumeMountPath, a.Git.Path)
copyCommand := fmt.Sprintf("cp -r %s/* %s", hclPath, types.WorkingVolumeMountPath)
checkoutCommand := ""
checkoutObject := getCheckoutObj(a.Git.Ref)
if checkoutObject != "" {
checkoutCommand = fmt.Sprintf("git checkout %s", checkoutObject)
}
cloneCommand := fmt.Sprintf("git clone %s %s", a.Git.URL, types.BackendVolumeMountPath)
// Check for git credentials, mount the SSH known hosts and private key, add private key into the SSH authentication agent
if a.GitCredential {
sshCommand := fmt.Sprintf("eval `ssh-agent` && ssh-add %s/%s", types.GitAuthConfigVolumeMountPath, v1.SSHAuthPrivateKey)
cloneCommand = fmt.Sprintf("%s && %s", sshCommand, cloneCommand)
}
cmd = cloneCommand
if checkoutCommand != "" {
cmd = fmt.Sprintf("%s && %s", cmd, checkoutCommand)
}
cmd = fmt.Sprintf("%s && %s", cmd, copyCommand)
command := []string{
"sh",
"-c",
cmd,
}
return command
}
func getCheckoutObj(ref types.GitRef) string {
if ref.Commit != "" {
return ref.Commit
} else if ref.Tag != "" {
return ref.Tag
}
return ref.Branch
}

View File

@ -0,0 +1,47 @@
package container
import (
"testing"
"github.com/oam-dev/terraform-controller/api/types"
)
func Test_getCheckoutObj(t *testing.T) {
tests := []struct {
name string
ref types.GitRef
want string
}{
{
name: "only branch",
ref: types.GitRef{
Branch: "feature",
},
want: "feature",
},
{
name: "tag take precedence over branch",
ref: types.GitRef{
Branch: "feature",
Tag: "v1.0.0",
},
want: "v1.0.0",
},
{
name: "commit take precedence over tag",
ref: types.GitRef{
Branch: "feature",
Tag: "v1.0.0",
Commit: "123456",
},
want: "123456",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getCheckoutObj(tt.ref); got != tt.want {
t.Errorf("getCheckoutObj() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,53 @@
package container
import (
"github.com/oam-dev/terraform-controller/api/types"
v1 "k8s.io/api/core/v1"
)
// InitContainer will run terraform init
func (a *Assembler) InitContainer() v1.Container {
mounts := []v1.VolumeMount{
{
Name: a.Name,
MountPath: types.WorkingVolumeMountPath,
},
}
if a.TerraformCredential {
mounts = append(mounts,
v1.VolumeMount{
Name: types.TerraformCredentialsConfigVolumeName,
MountPath: types.TerraformCredentialsConfigVolumeMountPath,
})
}
if a.TerraformRC {
mounts = append(mounts,
v1.VolumeMount{
Name: types.TerraformRCConfigVolumeName,
MountPath: types.TerraformRCConfigVolumeMountPath,
})
}
if a.TerraformCredentialsHelper {
mounts = append(mounts,
v1.VolumeMount{
Name: types.TerraformCredentialsHelperConfigVolumeName,
MountPath: types.TerraformCredentialsHelperConfigVolumeMountPath,
})
}
c := v1.Container{
Name: types.TerraformInitContainerName,
Image: a.TerraformImage,
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{
"sh",
"-c",
"terraform init",
},
VolumeMounts: mounts,
Env: a.Envs,
}
return c
}

View File

@ -0,0 +1,36 @@
package container
import (
"fmt"
"github.com/oam-dev/terraform-controller/api/types"
v1 "k8s.io/api/core/v1"
)
const InputContainerName = "prepare-input-terraform-configurations"
// InputContainer prepare input .tf files, copy them to the working directory
func (a *Assembler) InputContainer() v1.Container {
mounts := []v1.VolumeMount{
{
Name: a.Name,
MountPath: types.WorkingVolumeMountPath,
},
{
Name: types.InputTFConfigurationVolumeName,
MountPath: types.InputTFConfigurationVolumeMountPath,
},
}
return v1.Container{
Name: InputContainerName,
Image: a.BusyboxImage,
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{
"sh",
"-c",
fmt.Sprintf("cp %s/* %s", types.InputTFConfigurationVolumeMountPath, types.WorkingVolumeMountPath),
},
VolumeMounts: mounts,
}
}

View File

@ -0,0 +1,97 @@
package process
import (
"github.com/oam-dev/terraform-controller/api/types"
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/oam-dev/terraform-controller/api/v1beta2"
tfcfg "github.com/oam-dev/terraform-controller/controllers/configuration"
"github.com/oam-dev/terraform-controller/controllers/configuration/backend"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// LegacySubResources if user specify ControllerNamespace when re-staring controller, there are some sub-resources like Secret
// and ConfigMap that are in the namespace of the Configuration. We need to GC these sub-resources when Configuration is deleted.
type LegacySubResources struct {
// Namespace is the namespace of the Configuration, also the namespace of the sub-resources.
Namespace string
ApplyJobName string
DestroyJobName string
ConfigurationCMName string
VariableSecretName string
}
// TFConfigurationMeta is all the metadata of a Configuration
type TFConfigurationMeta struct {
Name string
Namespace string
ControllerNamespace string
ConfigurationType types.ConfigurationType
CompleteConfiguration string
Git types.Git
ConfigurationChanged bool
EnvChanged bool
ConfigurationCMName string
ApplyJobName string
DestroyJobName string
Envs []v1.EnvVar
ProviderReference *crossplane.Reference
VariableSecretName string
VariableSecretData map[string][]byte
DeleteResource bool
Region string
Credentials map[string]string
JobEnv map[string]interface{}
GitCredentialsSecretReference *v1.SecretReference
TerraformCredentialsSecretReference *v1.SecretReference
TerraformRCConfigMapReference *v1.SecretReference
TerraformCredentialsHelperConfigMapReference *v1.SecretReference
Backend backend.Backend
// JobNodeSelector Expose the node selector of job to the controller level
JobNodeSelector map[string]string
// TerraformImage is the Terraform image which can run `terraform init/plan/apply`
TerraformImage string
BusyboxImage string
GitImage string
// BackoffLimit specifies the number of retries to mark the Job as failed
BackoffLimit int32
// ResourceQuota series Variables are for Setting Compute Resources required by this container
ResourceQuota types.ResourceQuota
LegacySubResources LegacySubResources
ControllerNSSpecified bool
K8sClient client.Client
}
// TFState is Terraform State
type TFState struct {
Outputs map[string]TfStateProperty `json:"outputs"`
}
// TfStateProperty is the tf state property for an output
type TfStateProperty struct {
Value interface{} `json:"value,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// ToProperty converts TfStateProperty type to Property
func (tp *TfStateProperty) ToProperty() (v1beta2.Property, error) {
var (
property v1beta2.Property
err error
)
sv, err := tfcfg.Interface2String(tp.Value)
if err != nil {
return property, errors.Wrapf(err, "failed to convert value %s of terraform state outputs to string", tp.Value)
}
property = v1beta2.Property{
Value: sv,
}
return property, err
}

View File

@ -0,0 +1,757 @@
package process
import (
"context"
"encoding/json"
"fmt"
"os"
"reflect"
"github.com/oam-dev/terraform-controller/controllers/process/container"
"github.com/pkg/errors"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/terraform-controller/api/types"
"github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/oam-dev/terraform-controller/api/v1beta2"
tfcfg "github.com/oam-dev/terraform-controller/controllers/configuration"
"github.com/oam-dev/terraform-controller/controllers/configuration/backend"
"github.com/oam-dev/terraform-controller/controllers/provider"
"github.com/oam-dev/terraform-controller/controllers/util"
)
type Option func(spec v1beta2.Configuration, meta *TFConfigurationMeta)
// ControllerNamespaceOption will set the controller namespace for TFConfigurationMeta
func ControllerNamespaceOption(controllerNamespace string) Option {
return func(configuration v1beta2.Configuration, meta *TFConfigurationMeta) {
if controllerNamespace == "" {
return
}
uid := string(configuration.GetUID())
// @step: since we are using a single namespace to run these, we must ensure the names
// are unique across the namespace
meta.KeepLegacySubResourceMetas()
meta.ApplyJobName = uid + "-" + string(types.TerraformApply)
meta.DestroyJobName = uid + "-" + string(types.TerraformDestroy)
meta.ConfigurationCMName = fmt.Sprintf(types.TFInputConfigMapName, uid)
meta.VariableSecretName = fmt.Sprintf(types.TFVariableSecret, uid)
meta.ControllerNamespace = controllerNamespace
meta.ControllerNSSpecified = true
}
}
// New will create a new TFConfigurationMeta to process the configuration
func New(req ctrl.Request, configuration v1beta2.Configuration, k8sClient client.Client, option ...Option) *TFConfigurationMeta {
var meta = &TFConfigurationMeta{
ControllerNamespace: req.Namespace,
Namespace: req.Namespace,
Name: req.Name,
ConfigurationCMName: fmt.Sprintf(types.TFInputConfigMapName, req.Name),
VariableSecretName: fmt.Sprintf(types.TFVariableSecret, req.Name),
ApplyJobName: req.Name + "-" + string(types.TerraformApply),
DestroyJobName: req.Name + "-" + string(types.TerraformDestroy),
K8sClient: k8sClient,
}
jobNodeSelectorStr := os.Getenv("JOB_NODE_SELECTOR")
if jobNodeSelectorStr != "" {
err := json.Unmarshal([]byte(jobNodeSelectorStr), &meta.JobNodeSelector)
if err != nil {
klog.Warningf("the value of JobNodeSelector is not a json string: %v", err)
}
}
// githubBlocked mark whether GitHub is blocked in the cluster
githubBlockedStr := os.Getenv("GITHUB_BLOCKED")
if githubBlockedStr == "" {
githubBlockedStr = "false"
}
meta.Git.URL = tfcfg.ReplaceTerraformSource(configuration.Spec.Remote, githubBlockedStr)
if configuration.Spec.Path == "" {
meta.Git.Path = "."
} else {
meta.Git.Path = configuration.Spec.Path
}
if configuration.Spec.DeleteResource != nil {
meta.DeleteResource = *configuration.Spec.DeleteResource
} else {
meta.DeleteResource = true
}
if !configuration.Spec.InlineCredentials {
meta.ProviderReference = tfcfg.GetProviderNamespacedName(configuration)
}
if configuration.Spec.GitCredentialsSecretReference != nil {
meta.GitCredentialsSecretReference = configuration.Spec.GitCredentialsSecretReference
}
if configuration.Spec.TerraformCredentialsSecretReference != nil {
meta.TerraformCredentialsSecretReference = configuration.Spec.TerraformCredentialsSecretReference
}
if configuration.Spec.TerraformRCConfigMapReference != nil {
meta.TerraformRCConfigMapReference = configuration.Spec.TerraformRCConfigMapReference
}
if configuration.Spec.TerraformCredentialsHelperConfigMapReference != nil {
meta.TerraformCredentialsHelperConfigMapReference = configuration.Spec.TerraformCredentialsHelperConfigMapReference
}
for _, opt := range option {
opt(configuration, meta)
}
return meta
}
func (meta *TFConfigurationMeta) ValidateSecretAndConfigMap(ctx context.Context, k8sClient client.Client) error {
secretConfigMapToCheck := []struct {
ref *v1.SecretReference
notFoundState types.ConfigurationState
isSecret bool
neededKeys []string
errKey string
}{
{
ref: meta.GitCredentialsSecretReference,
notFoundState: types.InvalidGitCredentialsSecretReference,
isSecret: true,
neededKeys: []string{types.GitCredsKnownHosts, v1.SSHAuthPrivateKey},
errKey: "git credentials",
},
{
ref: meta.TerraformCredentialsSecretReference,
notFoundState: types.InvalidTerraformCredentialsSecretReference,
isSecret: true,
neededKeys: []string{types.TerraformCredentials},
errKey: "terraform credentials",
},
{
ref: meta.TerraformRCConfigMapReference,
notFoundState: types.InvalidTerraformRCConfigMapReference,
isSecret: false,
neededKeys: []string{types.TerraformRegistryConfig},
errKey: "terraformrc configuration",
},
{
ref: meta.TerraformCredentialsHelperConfigMapReference,
notFoundState: types.InvalidTerraformCredentialsHelperConfigMapReference,
isSecret: false,
neededKeys: []string{},
errKey: "terraform credentials helper",
},
}
for _, check := range secretConfigMapToCheck {
if check.ref != nil {
var object metav1.Object
var err error
object, err = GetSecretOrConfigMap(ctx, k8sClient, check.isSecret, check.ref, check.neededKeys, check.errKey)
if object == nil {
msg := string(check.notFoundState)
if err != nil {
msg = err.Error()
}
if updateStatusErr := meta.UpdateApplyStatus(ctx, k8sClient, check.notFoundState, msg); updateStatusErr != nil {
return errors.Wrap(updateStatusErr, msg)
}
return errors.New(msg)
}
// fix: The configmap or secret that the pod restricts from mounting must be in the same namespace as the pod,
// otherwise the volume mount will fail.
if object.GetNamespace() != meta.ControllerNamespace {
objectKind := "ConfigMap"
if check.isSecret {
objectKind = "Secret"
}
msg := fmt.Sprintf("Invalid %s '%s/%s', whose namespace '%s' is different from the Configuration, cannot mount the volume,"+
" you can fix this issue by creating the Secret/ConfigMap in the '%s' namespace.",
objectKind, object.GetNamespace(), object.GetName(), meta.ControllerNamespace, meta.ControllerNamespace)
if updateStatusErr := meta.UpdateApplyStatus(ctx, k8sClient, check.notFoundState, msg); updateStatusErr != nil {
return errors.Wrap(updateStatusErr, msg)
}
return errors.New(msg)
}
}
}
return nil
}
func (meta *TFConfigurationMeta) UpdateApplyStatus(ctx context.Context, k8sClient client.Client, state types.ConfigurationState, message string) error {
var configuration v1beta2.Configuration
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.Name, Namespace: meta.Namespace}, &configuration); err == nil {
configuration.Status.Apply = v1beta2.ConfigurationApplyStatus{
State: state,
Message: message,
Region: meta.Region,
}
configuration.Status.ObservedGeneration = configuration.Generation
if state == types.Available {
outputs, err := meta.getTFOutputs(ctx, k8sClient, configuration)
if err != nil {
klog.InfoS("Failed to get outputs", "error", err)
configuration.Status.Apply = v1beta2.ConfigurationApplyStatus{
State: types.GeneratingOutputs,
Message: types.ErrGenerateOutputs + ": " + err.Error(),
}
} else {
configuration.Status.Apply.Outputs = outputs
}
}
return k8sClient.Status().Update(ctx, &configuration)
}
return nil
}
func (meta *TFConfigurationMeta) UpdateDestroyStatus(ctx context.Context, k8sClient client.Client, state types.ConfigurationState, message string) error {
var configuration v1beta2.Configuration
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.Name, Namespace: meta.Namespace}, &configuration); err == nil {
configuration.Status.Destroy = v1beta2.ConfigurationDestroyStatus{
State: state,
Message: message,
}
return k8sClient.Status().Update(ctx, &configuration)
}
return nil
}
func (meta *TFConfigurationMeta) AssembleAndTriggerJob(ctx context.Context, k8sClient client.Client, executionType types.TerraformExecutionType) error {
// apply rbac
if err := createTerraformExecutorServiceAccount(ctx, k8sClient, meta.ControllerNamespace, types.ServiceAccountName); err != nil {
return err
}
if err := util.CreateTerraformExecutorClusterRoleBinding(ctx, k8sClient, meta.ControllerNamespace, fmt.Sprintf("%s-%s", meta.ControllerNamespace, types.ClusterRoleName), types.ServiceAccountName); err != nil {
return err
}
job := meta.assembleTerraformJob(executionType)
return k8sClient.Create(ctx, job)
}
// UpdateTerraformJobIfNeeded will set deletion finalizer to the Terraform job if its envs are changed, which will result in
// deleting the job. Finally, a new Terraform job will be generated
func (meta *TFConfigurationMeta) UpdateTerraformJobIfNeeded(ctx context.Context, k8sClient client.Client, job batchv1.Job) error {
// if either one changes, delete the job
if meta.EnvChanged || meta.ConfigurationChanged {
klog.InfoS("about to delete job", "Name", job.Name, "Namespace", job.Namespace)
var j batchv1.Job
if err := k8sClient.Get(ctx, client.ObjectKey{Name: job.Name, Namespace: job.Namespace}, &j); err == nil {
if deleteErr := k8sClient.Delete(ctx, &job, client.PropagationPolicy(metav1.DeletePropagationBackground)); deleteErr != nil {
return deleteErr
}
}
var s v1.Secret
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.VariableSecretName, Namespace: meta.ControllerNamespace}, &s); err == nil {
if deleteErr := k8sClient.Delete(ctx, &s); deleteErr != nil {
return deleteErr
}
}
}
return nil
}
func (meta *TFConfigurationMeta) assembleTerraformJob(executionType types.TerraformExecutionType) *batchv1.Job {
var (
initContainers []v1.Container
parallelism int32 = 1
completions int32 = 1
)
executorVolumes := meta.assembleExecutorVolumes()
assembler := container.NewAssembler(meta.Name).
TerraformCredReference(meta.TerraformCredentialsSecretReference).
TerraformRCReference(meta.TerraformRCConfigMapReference).
TerraformCredentialsHelperReference(meta.TerraformCredentialsHelperConfigMapReference).
GitCredReference(meta.GitCredentialsSecretReference).
SetGit(meta.Git).
SetBusyboxImage(meta.BusyboxImage).
SetTerraformImage(meta.TerraformImage).
SetGitImage(meta.GitImage).
SetEnvs(meta.Envs)
initContainers = append(initContainers, assembler.InputContainer())
if meta.Git.URL != "" {
initContainers = append(initContainers, assembler.GitContainer())
}
initContainers = append(initContainers, assembler.InitContainer())
applyContainer := assembler.ApplyContainer(executionType, meta.ResourceQuota)
name := meta.ApplyJobName
if executionType == types.TerraformDestroy {
name = meta.DestroyJobName
}
return &batchv1.Job{
TypeMeta: metav1.TypeMeta{
Kind: "Job",
APIVersion: "batch/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: meta.ControllerNamespace,
},
Spec: batchv1.JobSpec{
Parallelism: &parallelism,
Completions: &completions,
BackoffLimit: &meta.BackoffLimit,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
// This annotation will prevent istio-proxy sidecar injection in the pods
// as having the sidecar would have kept the Job in `Running` state and would
// not transition to `Completed`
"sidecar.istio.io/inject": "false",
},
},
Spec: v1.PodSpec{
// InitContainer will copy Terraform configuration files to working directory and create Terraform
// state file directory in advance
InitContainers: initContainers,
// Container terraform-executor will first copy predefined terraform.d to working directory, and
// then run terraform init/apply.
Containers: []v1.Container{applyContainer},
ServiceAccountName: types.ServiceAccountName,
Volumes: executorVolumes,
RestartPolicy: v1.RestartPolicyOnFailure,
NodeSelector: meta.JobNodeSelector,
},
},
},
}
}
func (meta *TFConfigurationMeta) assembleExecutorVolumes() []v1.Volume {
workingVolume := v1.Volume{Name: meta.Name}
workingVolume.EmptyDir = &v1.EmptyDirVolumeSource{}
inputTFConfigurationVolume := meta.createConfigurationVolume()
tfBackendVolume := meta.createTFBackendVolume()
executorVolumes := []v1.Volume{workingVolume, inputTFConfigurationVolume, tfBackendVolume}
secretOrConfigMapReferences := []struct {
ref *v1.SecretReference
volumeName string
isSecret bool
}{
{
ref: meta.GitCredentialsSecretReference,
volumeName: types.GitAuthConfigVolumeName,
isSecret: true,
},
{
ref: meta.TerraformCredentialsSecretReference,
volumeName: types.TerraformCredentialsConfigVolumeName,
isSecret: true,
},
{
ref: meta.TerraformRCConfigMapReference,
volumeName: types.TerraformRCConfigVolumeName,
isSecret: false,
},
{
ref: meta.TerraformCredentialsHelperConfigMapReference,
volumeName: types.TerraformCredentialsHelperConfigVolumeName,
isSecret: false,
},
}
for _, ref := range secretOrConfigMapReferences {
if ref.ref != nil {
executorVolumes = append(executorVolumes, meta.createSecretOrConfigMapVolume(ref.isSecret, ref.ref.Name, ref.volumeName))
}
}
return executorVolumes
}
func (meta *TFConfigurationMeta) createConfigurationVolume() v1.Volume {
inputCMVolumeSource := v1.ConfigMapVolumeSource{}
inputCMVolumeSource.Name = meta.ConfigurationCMName
inputTFConfigurationVolume := v1.Volume{Name: types.InputTFConfigurationVolumeName}
inputTFConfigurationVolume.ConfigMap = &inputCMVolumeSource
return inputTFConfigurationVolume
}
func (meta *TFConfigurationMeta) createTFBackendVolume() v1.Volume {
gitVolume := v1.Volume{Name: types.BackendVolumeName}
gitVolume.EmptyDir = &v1.EmptyDirVolumeSource{}
return gitVolume
}
func (meta *TFConfigurationMeta) createSecretOrConfigMapVolume(isSecret bool, secretOrConfigMapReferenceName string, volumeName string) v1.Volume {
var defaultMode int32 = 0400
volume := v1.Volume{Name: volumeName}
if isSecret {
volumeSource := v1.SecretVolumeSource{}
volumeSource.SecretName = secretOrConfigMapReferenceName
volumeSource.DefaultMode = &defaultMode
volume.Secret = &volumeSource
} else {
volumeSource := v1.ConfigMapVolumeSource{}
volumeSource.Name = secretOrConfigMapReferenceName
volumeSource.DefaultMode = &defaultMode
volume.ConfigMap = &volumeSource
}
return volume
}
func (meta *TFConfigurationMeta) KeepLegacySubResourceMetas() {
meta.LegacySubResources.Namespace = meta.Namespace
meta.LegacySubResources.ApplyJobName = meta.ApplyJobName
meta.LegacySubResources.DestroyJobName = meta.DestroyJobName
meta.LegacySubResources.ConfigurationCMName = meta.ConfigurationCMName
meta.LegacySubResources.VariableSecretName = meta.VariableSecretName
}
func (meta *TFConfigurationMeta) GetApplyJob(ctx context.Context, k8sClient client.Client, job *batchv1.Job) error {
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.LegacySubResources.ApplyJobName, Namespace: meta.LegacySubResources.Namespace}, job); err == nil {
klog.InfoS("Found legacy apply job", "Configuration", fmt.Sprintf("%s/%s", meta.Name, meta.Namespace),
"Job", fmt.Sprintf("%s/%s", meta.LegacySubResources.Namespace, meta.LegacySubResources.ApplyJobName))
return nil
}
err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.ApplyJobName, Namespace: meta.ControllerNamespace}, job)
return err
}
// RenderConfiguration will compose the Terraform configuration with hcl/json and backend
func (meta *TFConfigurationMeta) RenderConfiguration(configuration *v1beta2.Configuration, configurationType types.ConfigurationType) (string, backend.Backend, error) {
backendInterface, err := backend.ParseConfigurationBackend(configuration, meta.K8sClient, meta.Credentials, meta.ControllerNSSpecified)
if err != nil {
return "", nil, errors.Wrap(err, "failed to prepare Terraform backend configuration")
}
switch configurationType {
case types.ConfigurationHCL:
completedConfiguration := configuration.Spec.HCL
completedConfiguration += "\n" + backendInterface.HCL()
return completedConfiguration, backendInterface, nil
case types.ConfigurationRemote:
return backendInterface.HCL(), backendInterface, nil
default:
return "", nil, errors.New("Unsupported Configuration Type")
}
}
func (meta *TFConfigurationMeta) IsTFStateGenerated(ctx context.Context) bool {
// 1. exist backend
if meta.Backend == nil {
return false
}
// 2. and exist tfstate file
_, err := meta.Backend.GetTFStateJSON(ctx)
return err == nil
}
//nolint:funlen
func (meta *TFConfigurationMeta) getTFOutputs(ctx context.Context, k8sClient client.Client, configuration v1beta2.Configuration) (map[string]v1beta2.Property, error) {
var tfStateJSON []byte
var err error
if meta.Backend != nil {
tfStateJSON, err = meta.Backend.GetTFStateJSON(ctx)
if err != nil {
return nil, err
}
}
var tfState TFState
if err := json.Unmarshal(tfStateJSON, &tfState); err != nil {
return nil, err
}
outputs := make(map[string]v1beta2.Property)
for k, v := range tfState.Outputs {
property, err := v.ToProperty()
if err != nil {
return outputs, err
}
outputs[k] = property
}
writeConnectionSecretToReference := configuration.Spec.WriteConnectionSecretToReference
if writeConnectionSecretToReference == nil || writeConnectionSecretToReference.Name == "" {
return outputs, nil
}
name := writeConnectionSecretToReference.Name
ns := writeConnectionSecretToReference.Namespace
if ns == "" {
ns = types.DefaultNamespace
}
data := make(map[string][]byte)
for k, v := range outputs {
data[k] = []byte(v.Value)
}
var gotSecret v1.Secret
configurationName := configuration.ObjectMeta.Name
if err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: ns}, &gotSecret); err != nil {
if kerrors.IsNotFound(err) {
var secret = v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
Labels: map[string]string{
"terraform.core.oam.dev/created-by": "terraform-controller",
"terraform.core.oam.dev/owned-by": configurationName,
"terraform.core.oam.dev/owned-namespace": configuration.Namespace,
},
},
TypeMeta: metav1.TypeMeta{Kind: "Secret"},
Data: data,
}
err = k8sClient.Create(ctx, &secret)
if kerrors.IsAlreadyExists(err) {
return nil, fmt.Errorf("secret(%s) already exists", name)
} else if err != nil {
return nil, err
}
}
} else {
// check the owner of this secret
labels := gotSecret.ObjectMeta.Labels
ownerName := labels["terraform.core.oam.dev/owned-by"]
ownerNamespace := labels["terraform.core.oam.dev/owned-namespace"]
if (ownerName != "" && ownerName != configurationName) ||
(ownerNamespace != "" && ownerNamespace != configuration.Namespace) {
errMsg := fmt.Sprintf(
"configuration(namespace: %s ; name: %s) cannot update secret(namespace: %s ; name: %s) whose owner is configuration(namespace: %s ; name: %s)",
configuration.Namespace, configurationName,
gotSecret.Namespace, name,
ownerNamespace, ownerName,
)
klog.ErrorS(err, "fail to update backend secret")
return nil, errors.New(errMsg)
}
gotSecret.Data = data
if err := k8sClient.Update(ctx, &gotSecret); err != nil {
return nil, err
}
}
return outputs, nil
}
func (meta *TFConfigurationMeta) PrepareTFVariables(configuration *v1beta2.Configuration) error {
var (
envs []v1.EnvVar
data = map[string][]byte{}
)
if configuration == nil {
return errors.New("configuration is nil")
}
if !configuration.Spec.InlineCredentials && meta.ProviderReference == nil {
return errors.New("The referenced provider could not be retrieved")
}
tfVariable, err := getTerraformJSONVariable(configuration.Spec.Variable)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to get Terraform JSON variables from Configuration Variables %v", configuration.Spec.Variable))
}
for k, v := range tfVariable {
envValue, err := tfcfg.Interface2String(v)
if err != nil {
return err
}
data[k] = []byte(envValue)
}
if !configuration.Spec.InlineCredentials && meta.Credentials == nil {
return errors.New(provider.ErrCredentialNotRetrieved)
}
for k, v := range meta.Credentials {
data[k] = []byte(v)
}
for k, v := range meta.JobEnv {
envValue, err := tfcfg.Interface2String(v)
if err != nil {
return err
}
data[k] = []byte(envValue)
}
for k := range data {
valueFrom := &v1.EnvVarSource{SecretKeyRef: &v1.SecretKeySelector{Key: k}}
valueFrom.SecretKeyRef.Name = meta.VariableSecretName
envs = append(envs, v1.EnvVar{Name: k, ValueFrom: valueFrom})
}
meta.Envs = envs
meta.VariableSecretData = data
return nil
}
// GetCredentials will get credentials from secret of the Provider
func (meta *TFConfigurationMeta) GetCredentials(ctx context.Context, k8sClient client.Client, providerObj *v1beta1.Provider) error {
region, err := tfcfg.SetRegion(ctx, k8sClient, meta.Namespace, meta.Name, providerObj)
if err != nil {
return err
}
credentials, err := provider.GetProviderCredentials(ctx, k8sClient, providerObj, region)
if err != nil {
return err
}
if credentials == nil {
return errors.New(provider.ErrCredentialNotRetrieved)
}
meta.Credentials = credentials
meta.Region = region
return nil
}
func (meta *TFConfigurationMeta) createOrUpdateConfigMap(ctx context.Context, k8sClient client.Client, data map[string]string) error {
var gotCM v1.ConfigMap
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.ConfigurationCMName, Namespace: meta.ControllerNamespace}, &gotCM); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
cm := v1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "ConfigMap"},
ObjectMeta: metav1.ObjectMeta{
Name: meta.ConfigurationCMName,
Namespace: meta.ControllerNamespace,
},
Data: data,
}
if err := k8sClient.Create(ctx, &cm); err != nil {
return errors.Wrap(err, "failed to create TF configuration ConfigMap")
}
return nil
}
if !reflect.DeepEqual(gotCM.Data, data) {
gotCM.Data = data
return errors.Wrap(k8sClient.Update(ctx, &gotCM), "failed to update TF configuration ConfigMap")
}
return nil
}
func (meta *TFConfigurationMeta) prepareTFInputConfigurationData() map[string]string {
var dataName string
switch meta.ConfigurationType {
case types.ConfigurationHCL:
dataName = types.TerraformHCLConfigurationName
case types.ConfigurationRemote:
dataName = "terraform-backend.tf"
}
data := map[string]string{dataName: meta.CompleteConfiguration, "kubeconfig": ""}
return data
}
// StoreTFConfiguration will store Terraform configuration to ConfigMap
func (meta *TFConfigurationMeta) StoreTFConfiguration(ctx context.Context, k8sClient client.Client) error {
data := meta.prepareTFInputConfigurationData()
return meta.createOrUpdateConfigMap(ctx, k8sClient, data)
}
// CheckWhetherConfigurationChanges will check whether configuration is changed
func (meta *TFConfigurationMeta) CheckWhetherConfigurationChanges(ctx context.Context, k8sClient client.Client, configurationType types.ConfigurationType) error {
switch configurationType {
case types.ConfigurationHCL:
var cm v1.ConfigMap
if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.ConfigurationCMName, Namespace: meta.ControllerNamespace}, &cm); err != nil {
if kerrors.IsNotFound(err) {
return nil
}
return err
}
meta.ConfigurationChanged = cm.Data[types.TerraformHCLConfigurationName] != meta.CompleteConfiguration
if meta.ConfigurationChanged {
klog.InfoS("Configuration HCL changed", "ConfigMap", cm.Data[types.TerraformHCLConfigurationName],
"RenderedCompletedConfiguration", meta.CompleteConfiguration)
}
return nil
case types.ConfigurationRemote:
meta.ConfigurationChanged = false
return nil
default:
return errors.New("unsupported configuration type, only HCL or Remote is supported")
}
}
func GetSecretOrConfigMap(ctx context.Context, k8sClient client.Client, isSecret bool, ref *v1.SecretReference, neededKeys []string, errKey string) (metav1.Object, error) {
secret := &v1.Secret{}
configMap := &v1.ConfigMap{}
var err error
// key to determine if it is a secret or config map
var typeKey string
if isSecret {
namespacedName := client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}
err = k8sClient.Get(ctx, namespacedName, secret)
typeKey = "secret"
} else {
namespacedName := client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}
err = k8sClient.Get(ctx, namespacedName, configMap)
typeKey = "configmap"
}
errMsg := fmt.Sprintf("Failed to get %s %s", errKey, typeKey)
if err != nil {
klog.ErrorS(err, errMsg, "Name", ref.Name, "Namespace", ref.Namespace)
return nil, errors.Wrap(err, errMsg)
}
for _, key := range neededKeys {
var keyErr bool
if isSecret {
if _, ok := secret.Data[key]; !ok {
keyErr = true
}
} else {
if _, ok := configMap.Data[key]; !ok {
keyErr = true
}
}
if keyErr {
keyErr := errors.Errorf("'%s' not in %s %s", key, errKey, typeKey)
return nil, keyErr
}
}
if isSecret {
return secret, nil
}
return configMap, nil
}
func createTerraformExecutorServiceAccount(ctx context.Context, k8sClient client.Client, namespace, serviceAccountName string) error {
var serviceAccount = v1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: namespace,
},
}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: serviceAccountName, Namespace: namespace}, &v1.ServiceAccount{}); err != nil {
if kerrors.IsNotFound(err) {
if err := k8sClient.Create(ctx, &serviceAccount); err != nil {
return errors.Wrap(err, "failed to create ServiceAccount for Terraform executor")
}
}
}
return nil
}
func getTerraformJSONVariable(tfVariables *runtime.RawExtension) (map[string]interface{}, error) {
variables, err := tfcfg.RawExtension2Map(tfVariables)
if err != nil {
return nil, err
}
var environments = make(map[string]interface{})
for k, v := range variables {
environments[fmt.Sprintf("TF_VAR_%s", k)] = v
}
return environments, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,20 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envAWSAccessKeyID = "AWS_ACCESS_KEY_ID"
envAWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
envAWSDefaultRegion = "AWS_DEFAULT_REGION"
envAWSSessionToken = "AWS_SESSION_TOKEN"
// EnvAWSAccessKeyID is the name of the AWS_ACCESS_KEY_ID env
EnvAWSAccessKeyID = "AWS_ACCESS_KEY_ID"
// EnvAWSSecretAccessKey is the name of the AWS_SECRET_ACCESS_KEY env
EnvAWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
// EnvAWSDefaultRegion is the name of the AWS_DEFAULT_REGION env
EnvAWSDefaultRegion = "AWS_DEFAULT_REGION"
// EnvAWSSessionToken is the name of the AWS_SESSION_TOKEN env
EnvAWSSessionToken = "AWS_SESSION_TOKEN"
)
// AWSCredentials are credentials for AWS
@ -27,9 +31,9 @@ func getAWSCredentials(secretData []byte, name, namespace, region string) (map[s
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envAWSAccessKeyID: ak.AWSAccessKeyID,
envAWSSecretAccessKey: ak.AWSSecretAccessKey,
envAWSSessionToken: ak.AWSSessionToken,
envAWSDefaultRegion: region,
EnvAWSAccessKeyID: ak.AWSAccessKeyID,
EnvAWSSecretAccessKey: ak.AWSSecretAccessKey,
EnvAWSSessionToken: ak.AWSSessionToken,
EnvAWSDefaultRegion: region,
}, nil
}

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -35,6 +35,7 @@ const (
ucloud CloudProvider = "ucloud"
custom CloudProvider = "custom"
baidu CloudProvider = "baidu"
huawei CloudProvider = "huawei"
)
const (
@ -43,15 +44,16 @@ const (
envAlicloudRegion = "ALICLOUD_REGION"
envAliCloudStsToken = "ALICLOUD_SECURITY_TOKEN"
errConvertCredentials = "failed to convert the credentials of Secret from Provider"
errCredentialValid = "Credentials are not valid"
errConvertCredentials = "failed to convert the credentials of Secret from Provider"
errCredentialValid = "Credentials are not valid"
ErrCredentialNotRetrieved = "Credentials are not retrieved from referenced Provider"
)
// AlibabaCloudCredentials are credentials for Alibaba Cloud
type AlibabaCloudCredentials struct {
AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `yaml:"accessKeySecret"`
SecurityToken string `yaml:"securityToken"`
AccessKeyID string `yaml:"accessKeyID" json:"accessKeyID,omitempty"`
AccessKeySecret string `yaml:"accessKeySecret" json:"accessKeySecret,omitempty"`
SecurityToken string `yaml:"securityToken" json:"securityToken,omitempty"`
}
// GetProviderCredentials gets provider credentials by cloud provider name
@ -89,7 +91,7 @@ func GetProviderCredentials(ctx context.Context, k8sClient client.Client, provid
envAliCloudStsToken: ak.SecurityToken,
}, nil
case string(ucloud):
return getUCloudCredentials(secretData, name, namespace)
return getUCloudCredentials(secretData, name, namespace, region)
case string(aws):
return getAWSCredentials(secretData, name, namespace, region)
case string(gcp):
@ -106,6 +108,8 @@ func GetProviderCredentials(ctx context.Context, k8sClient client.Client, provid
return getCustomCredentials(secretData, name, namespace)
case string(baidu):
return getBaiduCloudCredentials(secretData, name, namespace, region)
case string(huawei):
return getHuaWeiCloudCredentials(secretData, name, namespace, region)
default:
errMsg := "unsupported provider"
klog.InfoS(errMsg, "Provider", provider.Spec.Provider)

View File

@ -7,11 +7,10 @@ import (
"testing"
. "github.com/agiledragon/gomonkey/v2"
"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
"github.com/go-yaml/yaml"
"github.com/google/go-cmp/cmp"
"github.com/jinzhu/copier"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -109,10 +108,8 @@ func TestGetProviderCredentials(t *testing.T) {
}
assert.Nil(t, k8sClient1.Create(ctx, secret))
patches := ApplyMethod(reflect.TypeOf(&sts.Client{}), "GetCallerIdentity", func(_ *sts.Client, request *sts.GetCallerIdentityRequest) (response *sts.GetCallerIdentityResponse, err error) {
response = nil
err = nil
return
patches := ApplyFunc(checkAlibabaCloudCredentials, func(region, accessKeyID, accessKeySecret, stsToken string) error {
return nil
})
defer patches.Reset()
@ -380,7 +377,7 @@ func TestGetProviderCredentials(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := GetProviderCredentials(ctx, tt.args.k8sClient, &tt.args.provider, tt.args.region)
if tt.errMsg != "" && err != nil && !strings.Contains(err.Error(), tt.errMsg) {
t.Errorf("GetProviderCredentials() error = %v, wantErr %v", err, err.Error())
t.Errorf("GetProviderCredentials() error = %q, wantErr %q", err, err.Error())
return
}
if !reflect.DeepEqual(got, tt.want) {
@ -777,7 +774,7 @@ func TestGetProviderCredentials4UCloud(t *testing.T) {
creds, _ := yaml.Marshal(&UCloudCredentials{
PublicKey: "a",
PrivateKey: "b",
Region: "bj",
Region: "bj1",
ProjectID: "c",
})
secret := &v1.Secret{
@ -1048,10 +1045,10 @@ func TestGetProviderCredentials4AWS(t *testing.T) {
region: "bj",
},
want: map[string]string{
envAWSAccessKeyID: "a",
envAWSSecretAccessKey: "b",
envAWSSessionToken: "c",
envAWSDefaultRegion: "bj",
EnvAWSAccessKeyID: "a",
EnvAWSSecretAccessKey: "b",
EnvAWSSessionToken: "c",
EnvAWSDefaultRegion: "bj",
},
},
{
@ -1254,3 +1251,104 @@ func TestGetProviderCredentials4Custom(t *testing.T) {
})
}
}
func TestGetProviderCredentials4HuaweiCloud(t *testing.T) {
ctx := context.TODO()
k8sClient := fake.NewClientBuilder().Build()
creds, _ := yaml.Marshal(&HuaWeiCloudCredentials{
AccessKey: "a",
SecretKey: "b",
})
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Data: map[string][]byte{
"credentials": creds,
},
Type: v1.SecretTypeOpaque,
}
assert.Nil(t, k8sClient.Create(ctx, secret))
provider := v1beta1.Provider{
Spec: v1beta1.ProviderSpec{
Provider: string(huawei),
Credentials: v1beta1.ProviderCredentials{
Source: "Secret",
SecretRef: &types.SecretKeySelector{
SecretReference: types.SecretReference{
Name: "default",
Namespace: "default",
},
Key: "credentials",
},
},
},
}
secret2 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "wrong-data",
Namespace: "default",
},
Data: map[string][]byte{
"credentials": []byte("xxx"),
},
Type: v1.SecretTypeOpaque,
}
k8sClient2 := fake.NewClientBuilder().Build()
assert.Nil(t, k8sClient2.Create(ctx, secret2))
var badProvider v1beta1.Provider
copier.CopyWithOption(&badProvider, &provider, copier.Option{DeepCopy: true})
badProvider.Spec.Credentials.SecretRef.Name = "wrong-data"
type args struct {
provider v1beta1.Provider
region string
k8sClient client.Client
}
tests := []struct {
name string
args args
want map[string]string
errMsg string
}{
{
name: "provider",
args: args{
k8sClient: k8sClient,
provider: provider,
region: "cn-north-4",
},
want: map[string]string{
envHuaWeiCloudAccessKey: "a",
envHuaWeiCloudSecretKey: "b",
envHuaWeiCloudRegion: "cn-north-4",
},
},
{
name: "provider with wrong data",
args: args{
k8sClient: k8sClient2,
provider: badProvider,
region: "xxx",
},
errMsg: errConvertCredentials,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetProviderCredentials(ctx, tt.args.k8sClient, &tt.args.provider, tt.args.region)
if tt.errMsg != "" && err != nil && !strings.Contains(err.Error(), tt.errMsg) {
t.Errorf("GetProviderCredentials() error = %v, wantErr %v", err, err.Error())
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetProviderCredentials() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -0,0 +1,32 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envHuaWeiCloudRegion = "HW_REGION_NAME"
envHuaWeiCloudAccessKey = "HW_ACCESS_KEY"
envHuaWeiCloudSecretKey = "HW_SECRET_KEY"
)
// HuaWeiCloudCredentials are credentials for Huawei Cloud
type HuaWeiCloudCredentials struct {
AccessKey string `yaml:"accessKey"`
SecretKey string `yaml:"secretKey"`
}
func getHuaWeiCloudCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var hwc HuaWeiCloudCredentials
if err := yaml.Unmarshal(secretData, &hwc); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envHuaWeiCloudAccessKey: hwc.AccessKey,
envHuaWeiCloudSecretKey: hwc.SecretKey,
envHuaWeiCloudRegion: region,
}, nil
}

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
@ -21,7 +21,7 @@ type UCloudCredentials struct {
ProjectID string `yaml:"projectID"`
}
func getUCloudCredentials(secretData []byte, name, namespace string) (map[string]string, error) {
func getUCloudCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var ak UCloudCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
@ -30,7 +30,7 @@ func getUCloudCredentials(secretData []byte, name, namespace string) (map[string
return map[string]string{
envUCloudPublicKey: ak.PublicKey,
envUCloudPrivateKey: ak.PrivateKey,
envUCloudRegion: ak.Region,
envUCloudRegion: region,
envUCloudProjectID: ak.ProjectID,
}, nil
}

View File

@ -0,0 +1,53 @@
package provider
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetUCloudCredentials(t *testing.T) {
tests := []struct {
name string
ns string
secretData []byte
region string
wanted map[string]string
}{
{
name: "ucloud1",
ns: "qa",
region: "cn-bj2",
secretData: []byte("publicKey: xxxx1\nprivateKey: xxxx2\nregion: test1\nprojectID: test1"),
wanted: map[string]string{
envUCloudPrivateKey: "xxxx2",
envUCloudProjectID: "test1",
envUCloudPublicKey: "xxxx1",
envUCloudRegion: "cn-bj2",
},
},
{
name: "ucloud1",
ns: "qa",
region: "",
secretData: []byte("publicKey: xxxx1\nprivateKey: xxxx2\nregion: test1\nprojectID: test1"),
wanted: map[string]string{
envUCloudPrivateKey: "xxxx2",
envUCloudProjectID: "test1",
envUCloudPublicKey: "xxxx1",
envUCloudRegion: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m, err := getUCloudCredentials(tt.secretData, tt.name, tt.ns, tt.region)
assert.Nil(t, err)
if !reflect.DeepEqual(tt.wanted, m) {
t.Errorf("getUCloudCredentials got = %v, wanted %v", m, tt.wanted)
}
})
}
}

View File

@ -1,8 +1,8 @@
package provider
import (
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)

View File

@ -21,6 +21,7 @@ import (
"fmt"
"github.com/go-logr/logr"
crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
@ -61,26 +62,36 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
return ctrl.Result{}, err
}
if _, err := providercred.GetProviderCredentials(ctx, r.Client, &provider, provider.Spec.Region); err != nil {
provider.Status.State = types.ProviderIsNotReady
provider.Status.Message = fmt.Sprintf("%s: %s", errGetCredentials, err.Error())
klog.ErrorS(err, errGetCredentials, "Provider", req.NamespacedName)
if updateErr := r.Status().Update(ctx, &provider); updateErr != nil {
klog.ErrorS(updateErr, errSettingStatus, "Provider", req.NamespacedName)
return ctrl.Result{}, errors.Wrap(updateErr, errSettingStatus)
err := func() error {
switch provider.Spec.Credentials.Source {
case crossplanetypes.CredentialsSourceInjectedIdentity:
break
case crossplanetypes.CredentialsSourceSecret:
_, err := providercred.GetProviderCredentials(ctx, r.Client, &provider, provider.Spec.Region)
return err
default:
return errors.Errorf("unsupported credentials source: %s", provider.Spec.Credentials.Source)
}
return ctrl.Result{}, errors.Wrap(err, errGetCredentials)
return nil
}()
if err != nil {
klog.ErrorS(err, errGetCredentials, "Provider", req.NamespacedName)
provider.Status.Message = fmt.Sprintf("%s: %s", errGetCredentials, err.Error())
provider.Status = terraformv1beta1.ProviderStatus{State: types.ProviderIsNotReady}
} else {
provider.Status.Message = "Provider ready"
provider.Status = terraformv1beta1.ProviderStatus{State: types.ProviderIsReady}
}
provider.Status = terraformv1beta1.ProviderStatus{
State: types.ProviderIsReady,
}
if updateErr := r.Status().Update(ctx, &provider); updateErr != nil {
klog.ErrorS(updateErr, errSettingStatus, "Provider", req.NamespacedName)
return ctrl.Result{}, errors.Wrap(updateErr, errSettingStatus)
}
return ctrl.Result{}, nil
return ctrl.Result{}, err
}
// SetupWithManager setups with a manager

View File

@ -3,19 +3,19 @@ package controllers
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"strings"
"testing"
. "github.com/agiledragon/gomonkey/v2"
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@ -30,7 +30,7 @@ func TestReconcile(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
v1.AddToScheme(s)
r1.Client = fake.NewClientBuilder().WithScheme(s).Build()
r1.Client = fake.NewClientBuilder().WithScheme(s).WithStatusSubresource(&v1beta1.Provider{}).Build()
r2 := &ProviderReconciler{}
provider2 := &v1beta1.Provider{
@ -69,7 +69,7 @@ func TestReconcile(t *testing.T) {
Type: v1.SecretTypeOpaque,
}
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).Build()
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).WithStatusSubresource(&v1beta1.Provider{}).Build()
r3 := &ProviderReconciler{}
provider3 := &v1beta1.Provider{
@ -92,7 +92,33 @@ func TestReconcile(t *testing.T) {
},
}
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).WithStatusSubresource(&v1beta1.Provider{}).Build()
r4 := &ProviderReconciler{}
provider4 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{Source: "InjectedIdentity"},
Provider: "aws",
},
}
r4.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider4).WithStatusSubresource(&v1beta1.Provider{}).Build()
r5 := &ProviderReconciler{}
provider5 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{Source: "Invalid"},
Provider: "aws",
},
}
r5.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider5).WithStatusSubresource(&v1beta1.Provider{}).Build()
type args struct {
req reconcile.Request
@ -130,21 +156,39 @@ func TestReconcile(t *testing.T) {
want: want{},
},
{
name: "Provider is found, but the secret is not available",
name: "Provider is found but the secret is not available",
args: args{
req: req,
r: r3,
},
want: want{
errMsg: errGetCredentials,
errMsg: `failed to get the Secret from Provider: secrets "abc" not found`,
},
},
{
name: "Provider is using source injected identity",
args: args{
req: req,
r: r4,
},
want: want{},
},
{
name: "Provider source is invalid",
args: args{
req: req,
r: r5,
},
want: want{
errMsg: `unsupported credentials source: Invalid`,
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if _, err := tc.args.r.Reconcile(ctx, tc.args.req); (tc.want.errMsg != "") &&
!strings.Contains(err.Error(), tc.want.errMsg) {
_, err := tc.args.r.Reconcile(ctx, tc.args.req)
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
}
})
@ -194,7 +238,7 @@ func TestReconcileProviderIsReadyButFailedToUpdateStatus(t *testing.T) {
Type: v1.SecretTypeOpaque,
}
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).Build()
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).WithStatusSubresource(&v1beta1.Provider{}).Build()
patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
switch obj.(type) {
@ -277,7 +321,7 @@ func TestReconcile3(t *testing.T) {
},
}
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
r3.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).WithStatusSubresource(&v1beta1.Provider{}).Build()
patches := ApplyFunc(apiutil.GVKForObject, func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
switch obj.(type) {

View File

@ -18,15 +18,17 @@ package controllers
import (
"path/filepath"
"reflect"
"testing"
. "github.com/agiledragon/gomonkey/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
// +kubebuilder:scaffold:imports
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
@ -39,12 +41,20 @@ var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func init() {
patches := NewPatches()
patches.ApplyMethod(reflect.TypeOf(&envtest.Environment{}), "Start", func(_ *envtest.Environment) (*rest.Config, error) {
return &rest.Config{}, nil
})
patches.ApplyMethod(reflect.TypeOf(&envtest.Environment{}), "Stop", func(_ *envtest.Environment) error {
return nil
})
}
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
RunSpecs(t, "Controller Suite")
}
var _ = BeforeSuite(func() {

View File

@ -5,30 +5,55 @@ import (
"context"
"fmt"
"io"
"regexp"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"github.com/oam-dev/terraform-controller/api/types"
)
func getPodLog(ctx context.Context, client kubernetes.Interface, namespace, jobName, containerName string) (string, error) {
func getPods(ctx context.Context, client kubernetes.Interface, namespace, jobName string) (*v1.PodList, error) {
label := fmt.Sprintf("job-name=%s", jobName)
pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: label})
if err != nil || pods == nil || len(pods.Items) == 0 {
if err != nil {
klog.InfoS("pods are not found", "Label", label, "Error", err)
return "", nil
return nil, err
}
return pods, nil
}
func getPodLog(ctx context.Context, client kubernetes.Interface, namespace, jobName, containerName, initContainerName string) (types.Stage, string, error) {
var (
targetContainer = containerName
stage = types.ApplyStage
)
pods, err := getPods(ctx, client, namespace, jobName)
if err != nil || pods == nil || len(pods.Items) == 0 {
klog.V(4).InfoS("pods are not found", "PodName", jobName, "Namepspace", namespace, "Error", err)
return stage, "", nil
}
pod := pods.Items[0]
// Here are two cases for Pending phase: 1) init container `terraform init` is not finished yet, 2) pod is not ready yet.
if pod.Status.Phase == v1.PodPending {
return "", nil
for _, c := range pod.Status.InitContainerStatuses {
if c.Name == initContainerName && !c.Ready {
targetContainer = initContainerName
stage = types.InitStage
break
}
}
}
req := client.CoreV1().Pods(namespace).GetLogs(pod.Name, &v1.PodLogOptions{Container: containerName})
req := client.CoreV1().Pods(namespace).GetLogs(pod.Name, &v1.PodLogOptions{Container: targetContainer})
logs, err := req.Stream(ctx)
if err != nil {
return "", err
return stage, "", err
}
defer func(logs io.ReadCloser) {
err := logs.Close()
@ -37,7 +62,14 @@ func getPodLog(ctx context.Context, client kubernetes.Interface, namespace, jobN
}
}(logs)
return flushStream(logs, pod.Name)
log, err := flushStream(logs, pod.Name)
if err != nil {
return stage, "", err
}
// To learn how it works, please refer to https://github.com/zzxwill/terraform-log-stripper.
strippedLog := stripColor(log)
return stage, strippedLog, nil
}
func flushStream(rc io.ReadCloser, podName string) (string, error) {
@ -50,3 +82,9 @@ func flushStream(rc io.ReadCloser, podName string) (string, error) {
klog.V(4).Info("pod logs", "Pod", podName, "Logs", logContent)
return logContent, nil
}
func stripColor(log string) string {
var re = regexp.MustCompile(`\x1b\[[0-9;]*m`)
str := re.ReplaceAllString(log, "")
return str
}

View File

@ -19,17 +19,21 @@ import (
"k8s.io/client-go/kubernetes/typed/core/v1/fake"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/flowcontrol"
"github.com/oam-dev/terraform-controller/api/types"
)
func TestGetPodLog(t *testing.T) {
ctx := context.Background()
type args struct {
client kubernetes.Interface
namespace string
name string
containerName string
client kubernetes.Interface
namespace string
name string
containerName string
initContainerName string
}
type want struct {
state types.Stage
log string
errMsg string
}
@ -78,24 +82,25 @@ func TestGetPodLog(t *testing.T) {
{
name: "Pod is available, but no logs",
args: args{
client: k8sClientSet,
namespace: "default",
name: "j1",
containerName: "terraform-executor",
client: k8sClientSet,
namespace: "default",
name: "j1",
containerName: "terraform-executor",
initContainerName: "terraform-init",
},
want: want{
errMsg: "can not be accept",
errMsg: "client rate limiter Wait returned an error: can not be accept",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := getPodLog(ctx, tc.args.client, tc.args.namespace, tc.args.name, tc.args.containerName)
if tc.want.errMsg != "" {
state, got, err := getPodLog(ctx, tc.args.client, tc.args.namespace, tc.args.name, tc.args.containerName, tc.args.initContainerName)
if tc.want.errMsg != "" || err != nil {
assert.EqualError(t, err, tc.want.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.want.log, got)
assert.Equal(t, tc.want.state, state)
}
})
}
@ -136,3 +141,40 @@ func TestFlushStream(t *testing.T) {
})
}
}
func TestStripColor(t *testing.T) {
type args struct {
log string
}
type want struct {
newLog string
}
var testcases = map[string]struct {
args args
want want
}{
"without color": {
args: args{
log: "abc",
},
want: want{
newLog: "abc",
},
},
"with color": {
args: args{
log: `Failed`,
},
want: want{
newLog: "Failed",
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
got := stripColor(tc.args.log)
assert.Equal(t, tc.want.newLog, got)
})
}
}

View File

@ -12,21 +12,22 @@ import (
)
// GetTerraformStatus will get Terraform execution status
func GetTerraformStatus(ctx context.Context, namespace, jobName, containerName string) (types.ConfigurationState, error) {
klog.InfoS("checking Terraform execution status", "Namespace", namespace, "Job", jobName)
func GetTerraformStatus(ctx context.Context, jobNamespace, jobName, containerName, initContainerName string) (types.ConfigurationState, error) {
klog.InfoS("checking Terraform init and execution status", "Namespace", jobNamespace, "Job", jobName)
clientSet, err := client.Init()
if err != nil {
klog.ErrorS(err, "failed to init clientSet")
return types.ConfigurationProvisioningAndChecking, err
}
logs, err := getPodLog(ctx, clientSet, namespace, jobName, containerName)
// check the stage of the pod
stage, logs, err := getPodLog(ctx, clientSet, jobNamespace, jobName, containerName, initContainerName)
if err != nil {
klog.ErrorS(err, "failed to get pod logs")
return types.ConfigurationProvisioningAndChecking, err
}
success, state, errMsg := analyzeTerraformLog(logs)
success, state, errMsg := analyzeTerraformLog(logs, stage)
if success {
return state, nil
}
@ -34,7 +35,8 @@ func GetTerraformStatus(ctx context.Context, namespace, jobName, containerName s
return state, errors.New(errMsg)
}
func analyzeTerraformLog(logs string) (bool, types.ConfigurationState, string) {
// analyzeTerraformLog will analyze the logs of Terraform apply pod, returns true if check is ok.
func analyzeTerraformLog(logs string, stage types.Stage) (bool, types.ConfigurationState, string) {
lines := strings.Split(logs, "\n")
for i, line := range lines {
if strings.Contains(line, "31mError:") {
@ -42,8 +44,14 @@ func analyzeTerraformLog(logs string) (bool, types.ConfigurationState, string) {
if strings.Contains(errMsg, "Invalid Alibaba Cloud region") {
return false, types.InvalidRegion, errMsg
}
return false, types.ConfigurationApplyFailed, errMsg
switch stage {
case types.InitStage:
return false, types.TerraformInitError, errMsg
case types.ApplyStage:
return false, types.ConfigurationApplyFailed, errMsg
}
}
}
return true, types.ConfigurationProvisioningAndChecking, ""
}

View File

@ -49,7 +49,7 @@ func TestGetTerraformStatus(t *testing.T) {
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
state, err := GetTerraformStatus(ctx, tc.args.namespace, tc.args.name, tc.args.containerName)
state, err := GetTerraformStatus(ctx, tc.args.name, tc.args.namespace, tc.args.containerName, "")
if tc.want.errMsg != "" {
assert.EqualError(t, err, tc.want.errMsg)
} else {
@ -92,7 +92,7 @@ func TestGetTerraformStatus2(t *testing.T) {
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
state, err := GetTerraformStatus(ctx, tc.args.namespace, tc.args.name, tc.args.containerName)
state, err := GetTerraformStatus(ctx, tc.args.name, tc.args.namespace, tc.args.containerName, "")
if tc.want.errMsg != "" {
assert.Contains(t, err.Error(), tc.want.errMsg)
} else {
@ -143,7 +143,7 @@ func TestAnalyzeTerraformLog(t *testing.T) {
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
success, state, errMsg := analyzeTerraformLog(tc.args.logs)
success, state, errMsg := analyzeTerraformLog(tc.args.logs, types.ApplyStage)
if tc.want.errMsg != "" {
assert.Contains(t, errMsg, tc.want.errMsg)
} else {

View File

@ -9,7 +9,8 @@ import (
func TestDecompressTerraformStateSecret(t *testing.T) {
type args struct {
data string
data string
needDecode bool
}
type want struct {
raw string
@ -23,7 +24,8 @@ func TestDecompressTerraformStateSecret(t *testing.T) {
{
name: "decompress terraform state secret",
args: args{
data: "H4sIAAAAAAAA/0SMwa7CIBBF9/0KMutH80ArDb9ijKHDYEhqMQO4afrvBly4POfc3H0QAt7EOaYNrDj/NS7E7ELi5/1XQI3/o4beM3F0K1ihO65xI/egNsLThLPRWi6agkR/CVIppaSZJrfgbBx6//1ItbxqyWDFfnTBlFNlpKaut+EYPgEAAP//xUXpvZsAAAA=",
data: "H4sIAAAAAAAA/0SMwa7CIBBF9/0KMutH80ArDb9ijKHDYEhqMQO4afrvBly4POfc3H0QAt7EOaYNrDj/NS7E7ELi5/1XQI3/o4beM3F0K1ihO65xI/egNsLThLPRWi6agkR/CVIppaSZJrfgbBx6//1ItbxqyWDFfnTBlFNlpKaut+EYPgEAAP//xUXpvZsAAAA=",
needDecode: true,
},
want: want{
raw: `{
@ -37,14 +39,26 @@ func TestDecompressTerraformStateSecret(t *testing.T) {
`,
},
},
{
name: "bad data",
args: args{
data: "abc",
},
want: want{
errMsg: "EOF",
},
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
state, err := base64.StdEncoding.DecodeString(tt.args.data)
assert.NoError(t, err)
got, err := DecompressTerraformStateSecret(string(state))
if tt.want.errMsg != "" {
if tt.args.needDecode {
state, err := base64.StdEncoding.DecodeString(tt.args.data)
assert.NoError(t, err)
tt.args.data = string(state)
}
got, err := DecompressTerraformStateSecret(tt.args.data)
if tt.want.errMsg != "" || err != nil {
assert.Contains(t, err.Error(), tt.want.errMsg)
} else {
assert.Equal(t, tt.want.raw, string(got))

View File

@ -1,18 +1,17 @@
package controllers
package util
import (
"context"
"fmt"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func createTerraformExecutorClusterRole(ctx context.Context, k8sClient client.Client, clusterRoleName string) error {
func CreateTerraformExecutorClusterRole(ctx context.Context, k8sClient client.Client, clusterRoleName string) error {
var clusterRole = rbacv1.ClusterRole{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
@ -44,7 +43,7 @@ func createTerraformExecutorClusterRole(ctx context.Context, k8sClient client.Cl
return nil
}
func createTerraformExecutorClusterRoleBinding(ctx context.Context, k8sClient client.Client, namespace, clusterRoleName, serviceAccountName string) error {
func CreateTerraformExecutorClusterRoleBinding(ctx context.Context, k8sClient client.Client, namespace, clusterRoleName, serviceAccountName string) error {
var crbName = fmt.Sprintf("%s-tf-executor-clusterrole-binding", namespace)
var clusterRoleBinding = rbacv1.ClusterRoleBinding{
TypeMeta: metav1.TypeMeta{
@ -77,24 +76,3 @@ func createTerraformExecutorClusterRoleBinding(ctx context.Context, k8sClient cl
}
return nil
}
func createTerraformExecutorServiceAccount(ctx context.Context, k8sClient client.Client, namespace, serviceAccountName string) error {
var serviceAccount = v1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: namespace,
},
}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: serviceAccountName, Namespace: namespace}, &v1.ServiceAccount{}); err != nil {
if kerrors.IsNotFound(err) {
if err := k8sClient.Create(ctx, &serviceAccount); err != nil {
return errors.Wrap(err, "failed to create ServiceAccount for Terraform executor")
}
}
}
return nil
}

View File

@ -0,0 +1,56 @@
package util
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
rbacv1 "k8s.io/api/rbac/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
)
var (
env envtest.Environment
k8sClient client.Client
)
var _ = BeforeSuite(func() {
env = envtest.Environment{}
cfg, err := env.Start()
Expect(err).To(BeNil())
Expect(cfg).ToNot(BeNil())
k8sClient, err = client.New(cfg, client.Options{})
Expect(err).To(BeNil())
})
var _ = AfterSuite(func() {
err := env.Stop()
Expect(err).To(BeNil())
})
var _ = Describe("Utils", func() {
roleName := "default-tf-executor-clusterrole"
It("CreateTerraformExecutorClusterRole", func() {
err := CreateTerraformExecutorClusterRole(context.TODO(), k8sClient, roleName)
Expect(err).To(BeNil())
// Get and examine the role
role := &rbacv1.ClusterRole{}
err = k8sClient.Get(context.TODO(), client.ObjectKey{
Name: roleName,
}, role)
Expect(err).To(BeNil())
Expect(len(role.Rules)).To(Equal(2))
Expect(role.Rules[0].Resources).To(Equal([]string{"secrets"}))
Expect(role.Rules[0].Verbs).To(Equal([]string{"get", "list", "create", "update", "delete"}))
Expect(role.Rules[1].Resources).To(Equal([]string{"leases"}))
Expect(role.Rules[1].Verbs).To(Equal([]string{"get", "create", "update", "delete"}))
})
It("CreateTerraformExecutorClusterRoleBinding", func() {
err := CreateTerraformExecutorClusterRoleBinding(context.TODO(), k8sClient, "default", roleName, "tf-executor-service-account")
Expect(err).To(BeNil())
})
})

View File

@ -0,0 +1,53 @@
package util
import (
"context"
"fmt"
"reflect"
"testing"
. "github.com/agiledragon/gomonkey/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/envtest"
)
func init() {
patches := ApplyMethod(reflect.TypeOf(&envtest.Environment{}), "Start", func(_ *envtest.Environment) (*rest.Config, error) {
return &rest.Config{}, nil
})
patches.ApplyMethod(reflect.TypeOf(&envtest.Environment{}), "Stop", func(_ *envtest.Environment) error {
return nil
})
patches.ApplyFunc(CreateTerraformExecutorClusterRole, func(ctx context.Context, c client.Client, name string) error {
role := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: name},
Rules: []rbacv1.PolicyRule{
{Resources: []string{"secrets"}, Verbs: []string{"get", "list", "create", "update", "delete"}},
{APIGroups: []string{"coordination.k8s.io"}, Resources: []string{"leases"}, Verbs: []string{"get", "create", "update", "delete"}},
},
}
return c.Create(ctx, role)
})
patches.ApplyFunc(CreateTerraformExecutorClusterRoleBinding, func(ctx context.Context, c client.Client, namespace, clusterRoleName, serviceAccountName string) error {
crb := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-tf-executor-clusterrole-binding", namespace), Namespace: namespace},
RoleRef: rbacv1.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: clusterRoleName},
Subjects: []rbacv1.Subject{{Kind: "ServiceAccount", Name: serviceAccountName, Namespace: namespace}},
}
return c.Create(ctx, crb)
})
patches.ApplyFunc(client.New, func(_ *rest.Config, _ client.Options) (client.Client, error) {
return fake.NewClientBuilder().Build(), nil
})
}
func TestUtils(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Utils Suite")
}

View File

@ -1,148 +0,0 @@
package e2e
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"golang.org/x/net/context"
"gotest.tools/assert"
kerrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"github.com/oam-dev/terraform-controller/controllers/client"
)
const (
backendSecretNamespace = "vela-system"
)
var (
testConfigurationsBasic = "examples/alibaba/eip/configuration_eip.yaml"
testConfigurationsRegression = []string{
"examples/alibaba/eip/configuration_eip.yaml",
"examples/alibaba/eip/configuration_eip_remote_in_another_namespace.yaml",
"examples/alibaba/eip/configuration_eip_remote_subdirectory.yaml",
// "examples/alibaba/rds/configuration_hcl_rds.yaml",
"examples/alibaba/oss/configuration_hcl_bucket.yaml",
}
)
func TestBasicConfiguration(t *testing.T) {
clientSet, err := client.Init()
assert.NilError(t, err)
ctx := context.Background()
klog.Info("1. Applying Configuration")
pwd, _ := os.Getwd()
configuration := filepath.Join(pwd, "..", testConfigurationsBasic)
cmd := fmt.Sprintf("kubectl apply -f %s", configuration)
err = exec.Command("bash", "-c", cmd).Start()
assert.NilError(t, err)
klog.Info("2. Checking Configuration status")
for i := 0; i < 60; i++ {
var fields []string
output, err := exec.Command("bash", "-c", "kubectl get configuration").Output()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
for i, line := range lines {
if i == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == "alibaba-eip" && fields[1] == Available {
goto continueCheck
}
}
if i == 59 {
t.Error("Configuration is not ready")
}
time.Sleep(time.Second * 5)
}
continueCheck:
klog.Info("3. Checking Configuration status")
klog.Info("- Checking ConfigMap which stores .tf")
_, err = clientSet.CoreV1().ConfigMaps("default").Get(ctx, "tf-alibaba-eip", v1.GetOptions{})
assert.NilError(t, err)
klog.Info("- Checking Secret which stores Backend")
_, err = clientSet.CoreV1().Secrets(backendSecretNamespace).Get(ctx, "tfstate-default-alibaba-eip", v1.GetOptions{})
assert.NilError(t, err)
klog.Info("- Checking Secret which stores outputs")
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, "eip-conn", v1.GetOptions{})
assert.NilError(t, err)
klog.Info("- Checking Secret which stores variables")
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, "variable-alibaba-eip", v1.GetOptions{})
assert.NilError(t, err)
klog.Info("4. Deleting Configuration")
cmd = fmt.Sprintf("kubectl delete -f %s", configuration)
err = exec.Command("bash", "-c", cmd).Start()
assert.NilError(t, err)
klog.Info("5. Checking Configuration is deleted")
for i := 0; i < 60; i++ {
var (
fields []string
existed bool
)
output, err := exec.Command("bash", "-c", "kubectl get configuration").Output()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
for j, line := range lines {
if j == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == "alibaba-eip" {
existed = true
}
}
if existed {
if i == 59 {
t.Error("Configuration is not ready")
}
time.Sleep(time.Second * 5)
continue
} else {
break
}
}
klog.Info("6. Checking Secrets and ConfigMap which should all be deleted")
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, "variable-alibaba-eip", v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, "eip-conn", v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
_, err = clientSet.CoreV1().Secrets(backendSecretNamespace).Get(ctx, "tfstate-default-alibaba-eip", v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
_, err = clientSet.CoreV1().ConfigMaps("default").Get(ctx, "tf-alibaba-eip", v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
}
func TestBasicConfigurationRegression(t *testing.T) {
var retryTimes = 120
klog.Info("0. Create namespace")
err := exec.Command("bash", "-c", "kubectl create ns abc").Start()
assert.NilError(t, err)
Regression(t, testConfigurationsRegression, retryTimes)
}

View File

@ -0,0 +1,127 @@
package controllernamespace
import (
"context"
"strings"
"time"
types2 "github.com/oam-dev/terraform-controller/api/types"
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
appv1 "k8s.io/api/apps/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
pkgClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"github.com/oam-dev/terraform-controller/api/v1beta2"
)
var _ = Describe("Restart with controller-namespace", func() {
const (
defaultNamespace = "default"
controllerNamespace = "terraform"
chartNamespace = "terraform"
)
var (
controllerDeployMeta = types.NamespacedName{Name: "terraform-controller", Namespace: chartNamespace}
)
ctx := context.Background()
// create k8s rest config
restConf, err := config.GetConfig()
Expect(err).NotTo(HaveOccurred())
k8sClient, err := pkgClient.New(restConf, pkgClient.Options{})
s := k8sClient.Scheme()
_ = v1beta2.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
configuration := &v1beta2.Configuration{
ObjectMeta: v1.ObjectMeta{
Name: "e2e-for-ctrl-ns",
Namespace: defaultNamespace,
},
Spec: v1beta2.ConfigurationSpec{
HCL: `
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
}`,
InlineCredentials: true,
WriteConnectionSecretToReference: &crossplane.SecretReference{
Name: "some-conn",
Namespace: defaultNamespace,
},
},
}
AfterEach(func() {
_ = k8sClient.Delete(ctx, configuration)
})
It("Restart with controller namespace", func() {
By("apply configuration without --controller-namespace", func() {
err = k8sClient.Create(ctx, configuration)
Expect(err).NotTo(HaveOccurred())
var cfg = &v1beta2.Configuration{}
Eventually(func() error {
err = k8sClient.Get(ctx, types.NamespacedName{Name: configuration.Name, Namespace: configuration.Namespace}, cfg)
if err != nil {
return err
}
if cfg.Status.Apply.State != types2.Available {
return errors.Errorf("configuration is not available, status now: %s", cfg.Status.Apply.State)
}
return nil
}, time.Second*60, time.Second*5).Should(Succeed())
})
By("restart controller with --controller-namespace", func() {
ctrlDeploy := appv1.Deployment{}
err = k8sClient.Get(ctx, controllerDeployMeta, &ctrlDeploy)
Expect(err).NotTo(HaveOccurred())
ctrlDeploy.Spec.Template.Spec.Containers[0].Args = append(ctrlDeploy.Spec.Template.Spec.Containers[0].Args, "--controller-namespace="+controllerNamespace)
err := k8sClient.Update(ctx, &ctrlDeploy)
Expect(err).NotTo(HaveOccurred())
Eventually(func() error {
err := k8sClient.Get(ctx, controllerDeployMeta, &ctrlDeploy)
if err != nil {
return err
}
if ctrlDeploy.Status.UnavailableReplicas == 1 {
return errors.New("controller is not updated")
}
return nil
}, time.Second*60, time.Second*5).Should(Succeed())
})
By("configuration should be still available", func() {
// wait about half minute to check configuration's state isn't changed
for i := 0; i < 30; i++ {
err := k8sClient.Get(ctx, types.NamespacedName{
Name: configuration.Name, Namespace: configuration.Namespace,
}, configuration)
Expect(err).NotTo(HaveOccurred())
time.Sleep(time.Second)
}
})
By("restore controller", func() {
ctrlDeploy := appv1.Deployment{}
err = k8sClient.Get(ctx, controllerDeployMeta, &ctrlDeploy)
Expect(err).NotTo(HaveOccurred())
cmds := make([]string, 0)
for _, cmd := range ctrlDeploy.Spec.Template.Spec.Containers[0].Args {
if !strings.HasPrefix(cmd, "--controller-namespace") {
cmds = append(cmds, cmd)
}
}
ctrlDeploy.Spec.Template.Spec.Containers[0].Args = cmds
err := k8sClient.Update(ctx, &ctrlDeploy)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -0,0 +1,14 @@
package controllernamespace_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestE2e(t *testing.T) {
RegisterFailHandler(Fail)
defer GinkgoRecover()
RunSpecs(t, "E2e Suite")
}

View File

@ -0,0 +1,459 @@
package normal
import (
"context"
"encoding/base64"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"text/template"
"time"
"gotest.tools/assert"
coreV1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"github.com/oam-dev/terraform-controller/controllers/client"
)
var (
testConfigurationsInlineCredentials = "examples/random/configuration_random.yaml"
testConfigurationsInlineCredentialsCustomBackendKubernetes = "examples/random/configuration_random_custom_backend_kubernetes.yaml"
testConfigurationsRegression = []string{
"examples/alibaba/eip/configuration_eip.yaml",
"examples/alibaba/eip/configuration_eip_remote_in_another_namespace.yaml",
"examples/alibaba/eip/configuration_eip_remote_subdirectory.yaml",
"examples/alibaba/oss/configuration_hcl_bucket.yaml",
}
testConfigurationsForceDelete = "examples/random/configuration_force_delete.yaml"
testConfigurationsGitCredsSecretReference = "examples/random/configuration_git_ssh.yaml"
testConfigurationDeleteProvisioningResources = "examples/random/configuration_delete_provisioning_resources.yaml"
chartNamespace = "terraform"
)
type ConfigurationAttr struct {
Name string
YamlPath string
TFConfigMapName string
BackendStateSecretName string
BackendStateSecretNS string
OutputsSecretName string
VariableSecretName string
}
type TestContext struct {
context.Context
Configuration *ConfigurationAttr
BackendSecretNamespace string
ClientSet *kubernetes.Clientset
}
type DoFunc = func(ctx *TestContext)
type Injector struct {
BeforeApplyConfiguration DoFunc
CheckConfiguration DoFunc
CleanUp DoFunc
// add more actions and check points if needed
}
func invoke(do DoFunc, ctx *TestContext) {
if do != nil {
do(ctx)
}
}
func testBase(t *testing.T, configuration ConfigurationAttr, injector Injector, useCustomBackend bool) {
klog.Infof("%s test begins……", configuration.Name)
waitConfigurationAvailable := func(ctx *TestContext) {
for i := 0; i < 60; i++ {
var fields []string
output, err := exec.Command("bash", "-c", "kubectl get configuration").CombinedOutput()
assert.NilError(t, err)
t.Log("get configuration\n", string(output))
lines := strings.Split(string(output), "\n")
for i, line := range lines {
if i == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == configuration.Name && fields[1] == Available {
return
}
}
output, err = exec.Command("bash", "-c", "kubectl get pod").CombinedOutput()
lines = strings.Split(string(output), "\n")
t.Log("get pod\n", string(output))
if i == 59 {
t.Error("Configuration is not ready, getting controller's log")
output, err = exec.Command("bash", "-c", "kubectl logs -n terraform deploy/terraform-controller").CombinedOutput()
assert.NilError(t, err)
t.Log(string(output))
}
time.Sleep(time.Second * 2)
}
}
backendSecretNamespace := configuration.BackendStateSecretNS
if backendSecretNamespace == "" {
backendSecretNamespace = os.Getenv("TERRAFORM_BACKEND_NAMESPACE")
if backendSecretNamespace == "" {
backendSecretNamespace = "vela-system"
}
}
clientSet, err := client.Init()
assert.NilError(t, err)
ctx := context.Background()
testCtx := &TestContext{
Context: ctx,
Configuration: &configuration,
BackendSecretNamespace: backendSecretNamespace,
ClientSet: clientSet,
}
defer invoke(injector.CleanUp, testCtx)
klog.Info("1. Applying Configuration")
invoke(injector.BeforeApplyConfiguration, testCtx)
pwd, _ := os.Getwd()
configuration.YamlPath = filepath.Join(pwd, "../..", configuration.YamlPath)
cmd := fmt.Sprintf("kubectl apply -f %s", configuration.YamlPath)
output, err := exec.Command("bash", "-c", cmd).CombinedOutput()
assert.NilError(t, err, string(output))
klog.Info("2. Checking Configuration status")
if injector.CheckConfiguration == nil {
injector.CheckConfiguration = waitConfigurationAvailable
}
invoke(injector.CheckConfiguration, testCtx)
klog.Info("3. Checking the status of Configs and Secrets")
klog.Info("- Checking ConfigMap which stores .tf")
_, err = clientSet.CoreV1().ConfigMaps("default").Get(ctx, configuration.TFConfigMapName, v1.GetOptions{})
assert.NilError(t, err)
if !useCustomBackend {
klog.Info("- Checking Secret which stores Backend")
_, err = clientSet.CoreV1().Secrets(backendSecretNamespace).Get(ctx, configuration.BackendStateSecretName, v1.GetOptions{})
assert.NilError(t, err)
}
if configuration.OutputsSecretName != "" {
klog.Info("- Checking Secret which stores outputs")
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, configuration.OutputsSecretName, v1.GetOptions{})
assert.NilError(t, err)
}
klog.Info("- Checking Secret which stores variables")
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, configuration.VariableSecretName, v1.GetOptions{})
assert.NilError(t, err)
klog.Info("4. Deleting Configuration")
cmd = fmt.Sprintf("kubectl delete -f %s", configuration.YamlPath)
output, err = exec.Command("bash", "-c", cmd).CombinedOutput()
assert.NilError(t, err, string(output))
klog.Info("5. Checking Configuration is deleted")
for i := 0; i < 60; i++ {
var (
fields []string
existed bool
)
output, err := exec.Command("bash", "-c", "kubectl get configuration").CombinedOutput()
assert.NilError(t, err, string(output))
lines := strings.Split(string(output), "\n")
for j, line := range lines {
if j == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == configuration.Name {
existed = true
}
}
if existed {
if i == 59 {
t.Error("Configuration is not deleted")
}
time.Sleep(time.Second * 5)
continue
} else {
break
}
}
klog.Info("6. Checking Secrets and ConfigMap which should all be deleted")
if configuration.OutputsSecretName != "" {
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, configuration.OutputsSecretName, v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
}
_, err = clientSet.CoreV1().Secrets("default").Get(ctx, configuration.VariableSecretName, v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
if !useCustomBackend {
_, err = clientSet.CoreV1().Secrets(backendSecretNamespace).Get(ctx, configuration.BackendStateSecretName, v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
}
_, err = clientSet.CoreV1().ConfigMaps("default").Get(ctx, configuration.TFConfigMapName, v1.GetOptions{})
assert.Equal(t, kerrors.IsNotFound(err), true)
klog.Infof("%s test ends……", configuration.Name)
}
func TestInlineCredentialsConfiguration(t *testing.T) {
configuration := ConfigurationAttr{
Name: "random-e2e",
YamlPath: testConfigurationsInlineCredentials,
TFConfigMapName: "tf-random-e2e",
BackendStateSecretName: "tfstate-default-random-e2e",
OutputsSecretName: "random-conn",
VariableSecretName: "variable-random-e2e",
}
testBase(t, configuration, Injector{}, false)
}
func TestInlineCredentialsConfigurationUseCustomBackendKubernetes(t *testing.T) {
configuration := ConfigurationAttr{
Name: "random-e2e-custom-backend-kubernetes",
YamlPath: testConfigurationsInlineCredentialsCustomBackendKubernetes,
BackendStateSecretName: "tfstate-default-custom-backend-kubernetes",
BackendStateSecretNS: "a",
TFConfigMapName: "tf-random-e2e-custom-backend-kubernetes",
OutputsSecretName: "random-conn-custom-backend-kubernetes",
VariableSecretName: "variable-random-e2e-custom-backend-kubernetes",
}
beforeApply := func(ctx *TestContext) {
output, err := exec.Command("bash", "-c", "kubectl create ns a").CombinedOutput()
if err != nil && !strings.Contains(string(output), "already exists") {
assert.NilError(t, err, string(output))
}
}
cleanUp := func(ctx *TestContext) {
output, err := exec.Command("bash", "-c", "kubectl delete ns a").CombinedOutput()
assert.NilError(t, err, string(output))
}
testBase(
t,
configuration,
Injector{
BeforeApplyConfiguration: beforeApply,
CleanUp: cleanUp,
},
true,
)
}
func TestForceDeleteConfiguration(t *testing.T) {
klog.Info("1. Applying Configuration whose hcl is not valid")
pwd, _ := os.Getwd()
configuration := filepath.Join(pwd, "..", testConfigurationsForceDelete)
cmd := fmt.Sprintf("kubectl apply -f %s", configuration)
err := exec.Command("bash", "-c", cmd).Start()
assert.NilError(t, err)
klog.Info("2. Deleting Configuration")
cmd = fmt.Sprintf("kubectl delete -f %s", configuration)
err = exec.Command("bash", "-c", cmd).Start()
assert.NilError(t, err)
klog.Info("5. Checking Configuration is deleted")
for i := 0; i < 60; i++ {
var (
fields []string
existed bool
)
output, err := exec.Command("bash", "-c", "kubectl get configuration").CombinedOutput()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
for j, line := range lines {
if j == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == "random-e2e-force-delete" {
existed = true
}
}
if existed {
if i == 59 {
t.Error("Configuration is not deleted")
}
time.Sleep(time.Second * 5)
continue
} else {
break
}
}
}
func TestGitCredentialsSecretReference(t *testing.T) {
configuration := ConfigurationAttr{
Name: "random-e2e-git-creds-secret-ref",
YamlPath: testConfigurationsGitCredsSecretReference,
TFConfigMapName: "tf-random-e2e-git-creds-secret-ref",
BackendStateSecretName: "tfstate-default-random-e2e-git-creds-secret-ref",
OutputsSecretName: "random-e2e-git-creds-secret-ref-conn",
VariableSecretName: "variable-random-e2e-git-creds-secret-ref",
}
clientSet, err := client.Init()
assert.NilError(t, err)
pwd, _ := os.Getwd()
gitServer := filepath.Join(pwd, "../..", "examples/git-credentials")
gitServerApplyCmd := fmt.Sprintf("kubectl apply -f %s", gitServer)
gitServerDeleteCmd := fmt.Sprintf("kubectl delete -f %s", gitServer)
gitSshAuthSecretYaml := filepath.Join(gitServer, "git-ssh-auth-secret.yaml")
beforeApply := func(ctx *TestContext) {
output, err := exec.Command("bash", "-c", gitServerApplyCmd).CombinedOutput()
assert.NilError(t, err, string(output))
klog.Info("- Checking git-server pod status")
for i := 0; i < 120; i++ {
serverReady := false
pushReady := false
pod, _ := clientSet.CoreV1().Pods("default").Get(ctx, "git-server", v1.GetOptions{})
conditions := pod.Status.Conditions
var index int
for count, condition := range conditions {
index = count
if condition.Status == "True" && condition.Type == coreV1.PodReady {
klog.Info("- pod=git-server ", condition.Type, "=", condition.Status)
break
}
}
if conditions[index].Status == "True" && conditions[index].Type == coreV1.PodReady {
serverReady = true
}
job, _ := clientSet.BatchV1().Jobs("default").Get(ctx, "git-push", v1.GetOptions{})
if job.Status.Succeeded == 1 {
pushReady = true
}
if serverReady && pushReady {
break
}
if i == 119 {
t.Error("git-server pod is not running")
}
time.Sleep(10 * time.Second)
}
getKnownHostsCmd := "kubectl exec pod/git-server -- ssh-keyscan git-server"
knownHosts, err := exec.Command("bash", "-c", getKnownHostsCmd).CombinedOutput()
assert.NilError(t, err)
gitSshAuthSecretTmpl := filepath.Join(gitServer, "templates/git-ssh-auth-secret.tmpl")
tmpl := template.Must(template.ParseFiles(gitSshAuthSecretTmpl))
gitSshAuthSecretYamlFile, err := os.Create(gitSshAuthSecretYaml)
assert.NilError(t, err)
err = tmpl.Execute(gitSshAuthSecretYamlFile, base64.StdEncoding.EncodeToString(knownHosts))
assert.NilError(t, err)
err = exec.Command("bash", "-c", gitServerApplyCmd).Run()
assert.NilError(t, err)
}
cleanUp := func(ctx *TestContext) {
err = exec.Command("bash", "-c", gitServerDeleteCmd).Run()
assert.NilError(t, err)
os.Remove(gitSshAuthSecretYaml)
}
testBase(
t,
configuration,
Injector{
BeforeApplyConfiguration: beforeApply,
CleanUp: cleanUp,
},
false,
)
}
func TestAllowDeleteProvisioningResoruce(t *testing.T) {
configuration := ConfigurationAttr{
Name: "random-e2e-delete-provisioning-resources",
YamlPath: testConfigurationDeleteProvisioningResources,
TFConfigMapName: "tf-random-e2e-delete-provisioning-resources",
BackendStateSecretName: "tfstate-default-random-e2e-delete-provisioning-resources",
// won't generate output at all
OutputsSecretName: "",
VariableSecretName: "variable-random-e2e-delete-provisioning-resources",
}
checkConfigurationIsProvisioning := func(ctx *TestContext) {
for i := 0; i < 20; i++ {
cfgProvisioning := false
backendExist := false
klog.Info("Check configuration is provisioning")
var fields []string
output, err := exec.Command("bash", "-c", "kubectl get configuration").CombinedOutput()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
for i, line := range lines {
if i == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 3 && fields[0] == configuration.Name && fields[1] == Provisioning {
cfgProvisioning = true
}
}
_, err = ctx.ClientSet.CoreV1().Secrets(ctx.BackendSecretNamespace).Get(ctx, configuration.BackendStateSecretName, v1.GetOptions{})
if err == nil {
backendExist = true
}
if cfgProvisioning && backendExist {
return
}
if i == 119 {
t.Error("Configuration is not ready")
}
time.Sleep(time.Second * 5)
}
}
testBase(
t,
configuration,
Injector{
CheckConfiguration: checkConfigurationIsProvisioning,
},
false,
)
}
//func TestBasicConfigurationRegression(t *testing.T) {
// var retryTimes = 120
//
// klog.Info("0. Create namespace")
// err := exec.Command("bash", "-c", "kubectl create ns abc").Start()
// assert.NilError(t, err)
//
// Regression(t, testConfigurationsRegression, retryTimes)
//}

View File

@ -1,4 +1,4 @@
package e2e
package normal
import (
"fmt"
@ -15,6 +15,7 @@ import (
// Available is the available status of Configuration
const Available = "Available"
const Provisioning = "ProvisioningAndChecking"
// Regression test for the e2e.
func Regression(t *testing.T, testcases []string, retryTimes int) {
@ -103,8 +104,7 @@ deletion:
time.Sleep(time.Second * 5)
continue
} else {
break
}
break
}
}

View File

@ -0,0 +1,65 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: alibaba-eip-inline
spec:
hcl: |
provider "alicloud" {
access_key = var.access_key
secret_key = var.secret_key
region = var.region
}
resource "alicloud_eip" "this" {
bandwidth = var.bandwidth
address_name = var.address_name
}
variable "address_name" {
description = "Name to be used on all resources as prefix. Default to 'TF-Module-EIP'."
default = "TF-Module-EIP"
type = string
}
variable "bandwidth" {
description = "Maximum bandwidth to the elastic public network, measured in Mbps (Mega bit per second)."
type = number
default = 1
}
variable "access_key" {
description = "Access Key ID of the Alibaba Cloud account."
type = string
}
variable "secret_key" {
description = "Access Key Secret of the Alibaba Cloud account."
type = string
}
variable "region" {
description = "Region of the Alibaba Cloud account."
type = string
default = "cn-beijing"
}
output "EIP_ADDRESS" {
description = "The elastic ip address."
value = alicloud_eip.this.ip_address
}
output "NAME" {
value = var.address_name
}
variable:
access_key: xxx
secret_key: yyy
inlineCredentials: true
writeConnectionSecretToRef:
name: eip-e2e-inline
namespace: default

View File

@ -1,37 +0,0 @@
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: alibaba-oss-bucket-json
spec:
JSON: |
{
"resource": {
"alicloud_oss_bucket": {
"bucket-acl": {
"bucket": "${var.bucket}",
"acl": "${var.acl}"
}
}
},
"output": {
"BUCKET_NAME": {
"value": "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
}
},
"variable": {
"bucket": {
"default": "poc"
},
"acl": {
"default": "private"
}
}
}
variable:
bucket: "vela-website"
acl: "private"
writeConnectionSecretToRef:
name: oss-conn
namespace: default

View File

@ -0,0 +1,42 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: alibaba-oss-bucket-hcl-backend-example
spec:
hcl: |
resource "alicloud_oss_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "BUCKET_NAME" {
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
}
variable "bucket" {
description = "OSS bucket name"
default = "loheagn-terraform-controller-2"
type = string
}
variable "acl" {
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
default = "private"
type = string
}
backend:
inline: |
terraform {
backend "kubernetes" {
secret_suffix = "a"
}
}
variable:
bucket: "terraform-controller-20220523"
acl: "private"
writeConnectionSecretToRef:
name: oss-conn
namespace: default

View File

@ -0,0 +1,40 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: alibaba-oss-bucket-hcl-backend-example
spec:
hcl: |
resource "alicloud_oss_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "BUCKET_NAME" {
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
}
variable "bucket" {
description = "OSS bucket name"
default = "loheagn-terraform-controller-2"
type = string
}
variable "acl" {
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
default = "private"
type = string
}
backend:
backendType: kubernetes
kubernetes:
secret_suffix: 'ali-oss'
namespace: 'terraform'
variable:
bucket: "terraform-controller-20220523"
acl: "private"
writeConnectionSecretToRef:
name: oss-conn
namespace: default

View File

@ -0,0 +1,28 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: aws-s3
spec:
remote: https://github.com/kubevela-contrib/terraform-modules.git
path: aws/s3
variable:
bucket: "vela-website-aws-20220628"
acl: "private"
backend:
backendType: s3
s3:
region: us-east-1
bucket: tf-controller-test
key: example.state
deleteResource: true
providerRef:
name: aws
namespace: default
writeConnectionSecretToRef:
name: s3-conn
namespace: default

View File

@ -0,0 +1,86 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: simple-terraform-module
namespace: default
data:
main.tf: |
resource "tls_private_key" "private_key" {
algorithm = var.algorithm
}
outputs.tf: |
output "openssh_private_key" {
value = nonsensitive(tls_private_key.private_key.private_key_pem)
}
variables.tf: |
variable "algorithm" {
description = "Encryption algorithm for the private key"
type = string
}
---
apiVersion: batch/v1
kind: Job
metadata:
name: git-push
namespace: default
labels:
name: git-push
spec:
template:
spec:
restartPolicy: OnFailure
initContainers:
- name: wait-for-git-server
image: alpine/git
command:
- sh
- -c
- >-
set -x &&
until nc -vzw 2 git-server 22; do sleep 10; done
volumeMounts:
- mountPath: /usr/.ssh
name: ssh-keys
containers:
- name: git-push
image: alpine/git
command:
- sh
- -c
- >-
set -x &&
eval $(ssh-agent) &&
ssh-add /usr/.ssh/id_rsa &&
mkdir /root/.ssh &&
ssh-keyscan git-server >> /root/.ssh/known_hosts &&
mkdir /usr/simple-terraform-module &&
cd /usr/simple-terraform-module &&
cp /simple-terraform-module/*.tf . &&
git config --global init.defaultBranch master &&
git init &&
git config user.name "john.doe" &&
git config user.email "john@doe.com" &&
git add . &&
git commit -m "initial commit" &&
git remote add origin git@git-server:simple-terraform-module.git &&
git push --set-upstream origin master
volumeMounts:
- mountPath: /usr/.ssh
name: ssh-keys
- mountPath: /simple-terraform-module
name: simple-terraform-module
resources:
limits:
memory: "128Mi"
cpu: "0.1"
volumes:
- name: ssh-keys
secret:
secretName: ssh-keys
items:
- key: id_rsa
path: id_rsa
defaultMode: 0400
- name: simple-terraform-module
configMap:
name: simple-terraform-module

View File

@ -0,0 +1,85 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: init-git-server-script
namespace: default
data:
init-git-server.sh: |
#!/bin/sh
set -x
mkdir -p ~/.ssh
chmod 0700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 0600 ~/.ssh/authorized_keys
mkdir simple-terraform-module.git
git config --global init.defaultBranch master &>/dev/null
git init --bare simple-terraform-module.git
---
apiVersion: v1
kind: Pod
metadata:
name: git-server
namespace: default
labels:
name: git-server
spec:
containers:
- name: git-server
image: ubuntu:22.04
command:
- sh
- -c
- >-
apt update &&
apt install git openssh-server -y &&
mkdir /var/run/sshd &&
useradd -r -m -U -d /home/git -s /bin/bash git &&
su - git -c /tmp/scripts/init-git-server.sh &&
cat ~/.ssh/authorized_keys >> /home/git/.ssh/authorized_keys &&
/usr/sbin/sshd -D
lifecycle:
preStop:
exec:
command:
- sh
- -c
- service ssh stop
volumeMounts:
- mountPath: /root/.ssh
name: ssh-keys
- mountPath: /tmp/scripts
name: init-git-server
ports:
- containerPort: 22
readinessProbe:
tcpSocket:
port: 22
initialDelaySeconds: 30
resources:
limits:
memory: "500Mi"
cpu: "0.2"
volumes:
- name: ssh-keys
secret:
secretName: ssh-keys
items:
- key: id_rsa.pub
path: authorized_keys
- name: init-git-server
configMap:
name: init-git-server-script
defaultMode: 0555
---
apiVersion: v1
kind: Service
metadata:
name: git-server
namespace: default
spec:
ports:
- port: 22
protocol: TCP
targetPort: 22
selector:
name: git-server

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: ssh-keys
namespace: default
type: Opaque
data:
id_rsa: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBbCtDTXIvbXl4eDlrNnJoUTlvbXhEQk1ZemYvMTJwTisvb24zSjJTTTVQUjhndFdOCk1mL2taWmdGQWNHYk55T3o5emI4MFVqTWNKY2lCNXdzNFVBcTJEa2ZzU090dllQbW5Nand2dUJId3p3cmpBcEgKT1ZVN1ozbjQ2VUpya2crUkVoWElVbGRYcXQzellOSllWZXF3MHhTOTBpNFNyR1RLS3VrR1FGQVh1L3Q5YzBERApDNHFGZXk1UXlQYndxMWFhN01uVDlqRHZvdWQxMjhtUnZoSzNSQkxZQjE3SURIQ3o4bURQNllkYVJJLys5MHdTCkh3c0dTdk44K3FtbTdzazFkdkJ2RkhNTllnaDl2S09mbDNuQitOOWlNQ2pOV1BvMFdBZkl1VHVCU0ozMnVHZ3UKdkxSZFJocXVpWlAyenpraEhpSnUzdHlVWDRWWHp2TXMwdSsyY1FJREFRQUJBb0lCQVFDS2ZVZk1iM1NGL2lxWApuZHExOUhocytqejBHeUtrWFRyQUFDNU96WEZzbFVPMFNlYW1ZU0J6UTF2TmJpMks2aE9BcWJOL1kxS0ltRWQvCmlQbWpyRTlsT3pHYTVWM1lJaDUzZVFPT0NoVm1BY2Z6dXF1WHJCQ3ZHcG5PbWJKZFRiU0xPVEdoWStPYyt5YWkKY3l2NXJEZnhRa2lWRDA0WHhSQlVjSWd5dk5Ybm90UHcwbmVEVHhISDN5N3VWL0tLRE9YUjVWdmZ3RzJJelpzRQp0UUNtejRRUHNRYUpLOGdMYk1BTC8xTmcwV0oxUjI0UFhlMzlid3ZqRytnMC9MYmFrcFB4YTJ2Tjc1VmZScEJXCjdaYVd3L2ZGWStwYjdjdENCSGVFQ3E1T1I1L0lOWGJuVEFUMHJKTlM0eEdEeUR6am9hOGtGamR0bGU0bmdKU2YKaG1wbFdxa0JBb0dCQU1Md3pZRDBEMEhxTWswZ3VuWlNsTDBhK3ZzdFF5S1VwOURvTHNSRnFNeEgrakt3c1J4eApWaE1nVzQrTEpSenllV0lPOWJWajkrZUY1SUxsQXo3WithQVNYdjVVdHlVSzJyM1I4N3FCcjFTMDhjS3hlYi9hCjVualBHbkl6aEVTOHNXRExmS2N6MkhYMnF5MWlsL1NNdloyQ2JZMllLYVk4ekNrU1JDQ2FIS2ZGQW9HQkFNZHkKdWhmN29DK0Z6dUtoanUzQ3l0aU1EZGhybDVhWVBmdWxWTHNlamxYR1ZxWU1nZDRXNzg5QTdCbUJZMERzdmh6RwprRnZ0bWhzUEw3RGhEdUZ2bGtvdnVkS25XbTJvUjZqbDFGNkZWSGxzN3lIS3FTQ2R2bjdXL0ZhZSsxL1loOFFSCk8vR2lBY1c2UTJuMHhVOTBVVmwvMnlVZGhHakVKSGN2S3hCcnpCSzlBb0dCQUpqWjdaanVSVVJlMlFBbTZHMjgKaE1uZWJPc2o2MThqQm83RWIxOFFhN0Y1d3BHYWY5VVlmUEJVVDlhMnVPd0FwL0tlWGtUVFZOK2gyYkpVMVgyagp0cHF2clBKcEJJMnovQjRZa0s0dDM0ZGd0YXYrTXNPZlpWVld0cHJURUNSQmZDZTBobElvVWRMMURmVnhPRXJWClRCeEQxNWpOdGVLV0MxTXM4bVJKMHF3dEFvR0FNbFUrcDJ6RitSaEFwS3IyNGdQRm95NTlFLy9iQ3BNekdUMloKQzN2am1idnJCQTZsKzRFNFZjcGhpdkkvTlJSSnlnTkdUUnpDUmsvbnpqQ055OUNZVWZLSFo2VDZTakFzblhBYQp6eHZBdk1BRC9UZ2l4R3RxdHFIVW5wdVNmcGFyZEl5UTN5THVaWkxqRG10S0hBb1R1WTF0cFlrMGNDZ0h0OWc4CmV2RnBWOVVDZ1lBejlmMk9ISzNVR254dDZOOXM0ZVpUak9TVDRSWUVzR0JOZ1pSV0JVc0pHMkRJdkE2Sks3T3QKc3hwTWwzYUpLVWFCWmswS3FReVlrNTkrRTRkeis4UFZjVmN1bGoxQUtYRVR1a3BQaUU3ZFhlTXpjaUxSNUt4YwpvVmFoWVFXSlg0N016aFVTMWxJSVFJcmlsN0JTNmFnbTlnV2NoVkpIVGNHYVY1ZFZBdmI2VXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
id_rsa.pub: c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFDWDRJeXYrYkxISDJUcXVGRDJpYkVNRXhqTi8vWGFrMzcraWZjblpJems5SHlDMVkweC8rUmxtQVVCd1pzM0k3UDNOdnpSU014d2x5SUhuQ3poUUNyWU9SK3hJNjI5ZythY3lQQys0RWZEUEN1TUNrYzVWVHRuZWZqcFFtdVNENUVTRmNoU1YxZXEzZk5nMGxoVjZyRFRGTDNTTGhLc1pNb3E2UVpBVUJlNyszMXpRTU1MaW9WN0xsREk5dkNyVnByc3lkUDJNTytpNTNYYnlaRytFcmRFRXRnSFhzZ01jTFB5WU0vcGgxcEVqLzczVEJJZkN3Wks4M3o2cWFidXlUVjI4RzhVY3cxaUNIMjhvNStYZWNINDMySXdLTTFZK2pSWUI4aTVPNEZJbmZhNGFDNjh0RjFHR3E2SmsvYlBPU0VlSW03ZTNKUmZoVmZPOHl6Uzc3WngK

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: git-ssh-auth
namespace: default
type: kubernetes.io/ssh-auth
data:
ssh-privatekey: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBbCtDTXIvbXl4eDlrNnJoUTlvbXhEQk1ZemYvMTJwTisvb24zSjJTTTVQUjhndFdOCk1mL2taWmdGQWNHYk55T3o5emI4MFVqTWNKY2lCNXdzNFVBcTJEa2ZzU090dllQbW5Nand2dUJId3p3cmpBcEgKT1ZVN1ozbjQ2VUpya2crUkVoWElVbGRYcXQzellOSllWZXF3MHhTOTBpNFNyR1RLS3VrR1FGQVh1L3Q5YzBERApDNHFGZXk1UXlQYndxMWFhN01uVDlqRHZvdWQxMjhtUnZoSzNSQkxZQjE3SURIQ3o4bURQNllkYVJJLys5MHdTCkh3c0dTdk44K3FtbTdzazFkdkJ2RkhNTllnaDl2S09mbDNuQitOOWlNQ2pOV1BvMFdBZkl1VHVCU0ozMnVHZ3UKdkxSZFJocXVpWlAyenpraEhpSnUzdHlVWDRWWHp2TXMwdSsyY1FJREFRQUJBb0lCQVFDS2ZVZk1iM1NGL2lxWApuZHExOUhocytqejBHeUtrWFRyQUFDNU96WEZzbFVPMFNlYW1ZU0J6UTF2TmJpMks2aE9BcWJOL1kxS0ltRWQvCmlQbWpyRTlsT3pHYTVWM1lJaDUzZVFPT0NoVm1BY2Z6dXF1WHJCQ3ZHcG5PbWJKZFRiU0xPVEdoWStPYyt5YWkKY3l2NXJEZnhRa2lWRDA0WHhSQlVjSWd5dk5Ybm90UHcwbmVEVHhISDN5N3VWL0tLRE9YUjVWdmZ3RzJJelpzRQp0UUNtejRRUHNRYUpLOGdMYk1BTC8xTmcwV0oxUjI0UFhlMzlid3ZqRytnMC9MYmFrcFB4YTJ2Tjc1VmZScEJXCjdaYVd3L2ZGWStwYjdjdENCSGVFQ3E1T1I1L0lOWGJuVEFUMHJKTlM0eEdEeUR6am9hOGtGamR0bGU0bmdKU2YKaG1wbFdxa0JBb0dCQU1Md3pZRDBEMEhxTWswZ3VuWlNsTDBhK3ZzdFF5S1VwOURvTHNSRnFNeEgrakt3c1J4eApWaE1nVzQrTEpSenllV0lPOWJWajkrZUY1SUxsQXo3WithQVNYdjVVdHlVSzJyM1I4N3FCcjFTMDhjS3hlYi9hCjVualBHbkl6aEVTOHNXRExmS2N6MkhYMnF5MWlsL1NNdloyQ2JZMllLYVk4ekNrU1JDQ2FIS2ZGQW9HQkFNZHkKdWhmN29DK0Z6dUtoanUzQ3l0aU1EZGhybDVhWVBmdWxWTHNlamxYR1ZxWU1nZDRXNzg5QTdCbUJZMERzdmh6RwprRnZ0bWhzUEw3RGhEdUZ2bGtvdnVkS25XbTJvUjZqbDFGNkZWSGxzN3lIS3FTQ2R2bjdXL0ZhZSsxL1loOFFSCk8vR2lBY1c2UTJuMHhVOTBVVmwvMnlVZGhHakVKSGN2S3hCcnpCSzlBb0dCQUpqWjdaanVSVVJlMlFBbTZHMjgKaE1uZWJPc2o2MThqQm83RWIxOFFhN0Y1d3BHYWY5VVlmUEJVVDlhMnVPd0FwL0tlWGtUVFZOK2gyYkpVMVgyagp0cHF2clBKcEJJMnovQjRZa0s0dDM0ZGd0YXYrTXNPZlpWVld0cHJURUNSQmZDZTBobElvVWRMMURmVnhPRXJWClRCeEQxNWpOdGVLV0MxTXM4bVJKMHF3dEFvR0FNbFUrcDJ6RitSaEFwS3IyNGdQRm95NTlFLy9iQ3BNekdUMloKQzN2am1idnJCQTZsKzRFNFZjcGhpdkkvTlJSSnlnTkdUUnpDUmsvbnpqQ055OUNZVWZLSFo2VDZTakFzblhBYQp6eHZBdk1BRC9UZ2l4R3RxdHFIVW5wdVNmcGFyZEl5UTN5THVaWkxqRG10S0hBb1R1WTF0cFlrMGNDZ0h0OWc4CmV2RnBWOVVDZ1lBejlmMk9ISzNVR254dDZOOXM0ZVpUak9TVDRSWUVzR0JOZ1pSV0JVc0pHMkRJdkE2Sks3T3QKc3hwTWwzYUpLVWFCWmswS3FReVlrNTkrRTRkeis4UFZjVmN1bGoxQUtYRVR1a3BQaUU3ZFhlTXpjaUxSNUt4YwpvVmFoWVFXSlg0N016aFVTMWxJSVFJcmlsN0JTNmFnbTlnV2NoVkpIVGNHYVY1ZFZBdmI2VXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
known_hosts: {{ . }}

View File

@ -0,0 +1,34 @@
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: huawei-vpc-hcl
spec:
hcl: |
terraform {
required_providers {
huaweicloud = {
source = "huaweicloud/huaweicloud"
version = "~> 1.26.0"
}
}
}
resource "huaweicloud_vpc" "myvpc" {
name = "myvpc"
cidr = "192.168.0.0/16"
}
resource "huaweicloud_vpc_subnet" "mysubnet" {
name = "mysubnet"
cidr = "192.168.0.0/16"
gateway_ip = "192.168.0.1"
//dns is required for cce node installing
primary_dns = "100.125.1.250"
secondary_dns = "100.125.21.250"
vpc_id = huaweicloud_vpc.myvpc.id
}
writeConnectionSecretToRef:
name: huaweicloud-vpc-conn
namespace: default

View File

@ -0,0 +1,13 @@
apiVersion: terraform.core.oam.dev/v1beta1
kind: Provider
metadata:
name: default
spec:
provider: huawei
region: cn-north-4
credentials:
source: Secret
secretRef:
namespace: vela-system
name: huawei-account-creds
key: credentials

View File

@ -0,0 +1,18 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: random-e2e-delete-provisioning-resources
spec:
hcl: |
resource "random_id" "server" {
byte_length = 8
}
resource "random_id" "error" {
byte_length = -1
}
inlineCredentials: true
writeConnectionSecretToRef:
name: random-conn
namespace: default

View File

@ -0,0 +1,20 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: random-e2e-force-delete
spec:
hcl: |
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
inlineCredentials: true
forceDelete: true
writeConnectionSecretToRef:
name: random-conn
namespace: default

View File

@ -0,0 +1,15 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: random-e2e-git-creds-secret-ref
spec:
inlineCredentials: true
remote: git@git-server:simple-terraform-module.git
variable:
algorithm: RSA
writeConnectionSecretToRef:
name: random-e2e-git-creds-secret-ref-conn
gitCredentialsSecretReference:
name: git-ssh-auth
namespace: default

View File

@ -0,0 +1,19 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: random-e2e
spec:
hcl: |
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
}
inlineCredentials: true
writeConnectionSecretToRef:
name: random-conn
namespace: default

View File

@ -0,0 +1,25 @@
apiVersion: terraform.core.oam.dev/v1beta2
kind: Configuration
metadata:
name: random-e2e-custom-backend-kubernetes
spec:
hcl: |
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
}
backend:
backendType: kubernetes
kubernetes:
secret_suffix: custom-backend-kubernetes
namespace: a
inlineCredentials: true
writeConnectionSecretToRef:
name: random-conn-custom-backend-kubernetes
namespace: default

View File

@ -0,0 +1,26 @@
resource "aws_s3_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "RESOURCE_IDENTIFIER" {
description = "The identifier of the resource"
value = aws_s3_bucket.bucket-acl.bucket_domain_name
}
output "BUCKET_NAME" {
value = aws_s3_bucket.bucket-acl.bucket_domain_name
description = "The name of the S3 bucket"
}
variable "bucket" {
description = "S3 bucket name"
default = "vela-website-2022-0614"
type = string
}
variable "acl" {
description = "S3 bucket ACL"
default = "private"
type = string
}

View File

@ -0,0 +1,3 @@
#vAWS VPC
Based on the Terraform template in https://github.com/terraform-aws-modules/terraform-aws-vpc

View File

@ -0,0 +1,88 @@
resource "aws_vpc" "this" {
count = var.create_vpc ? 1 : 0
cidr_block = var.cidr
instance_tenancy = var.instance_tenancy
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
enable_classiclink = var.enable_classiclink
enable_classiclink_dns_support = var.enable_classiclink_dns_support
assign_generated_ipv6_cidr_block = var.enable_ipv6
tags = merge(
{ "Name" = var.name },
var.tags,
var.vpc_tags,
)
}
output "vpc_id" {
description = "The ID of the VPC"
value = try(aws_vpc.this[0].id, "")
}
output "vpc_arn" {
description = "The ARN of the VPC"
value = try(aws_vpc.this[0].arn, "")
}
output "vpc_cidr_block" {
description = "The CIDR block of the VPC"
value = try(aws_vpc.this[0].cidr_block, "")
}
output "default_security_group_id" {
description = "The ID of the security group created by default on VPC creation"
value = try(aws_vpc.this[0].default_security_group_id, "")
}
output "default_network_acl_id" {
description = "The ID of the default network ACL"
value = try(aws_vpc.this[0].default_network_acl_id, "")
}
output "default_route_table_id" {
description = "The ID of the default route table"
value = try(aws_vpc.this[0].default_route_table_id, "")
}
output "vpc_instance_tenancy" {
description = "Tenancy of instances spin up within VPC"
value = try(aws_vpc.this[0].instance_tenancy, "")
}
output "vpc_enable_dns_support" {
description = "Whether or not the VPC has DNS support"
value = try(aws_vpc.this[0].enable_dns_support, "")
}
output "vpc_enable_dns_hostnames" {
description = "Whether or not the VPC has DNS hostname support"
value = try(aws_vpc.this[0].enable_dns_hostnames, "")
}
output "vpc_main_route_table_id" {
description = "The ID of the main route table associated with this VPC"
value = try(aws_vpc.this[0].main_route_table_id, "")
}
output "vpc_ipv6_association_id" {
description = "The association ID for the IPv6 CIDR block"
value = try(aws_vpc.this[0].ipv6_association_id, "")
}
output "vpc_ipv6_cidr_block" {
description = "The IPv6 CIDR block"
value = try(aws_vpc.this[0].ipv6_cidr_block, "")
}
output "vpc_owner_id" {
description = "The ID of the AWS account that owns the VPC"
value = try(aws_vpc.this[0].owner_id, "")
}
output "name" {
description = "The name of the VPC specified as argument to this module"
value = var.name
}

View File

@ -0,0 +1,65 @@
variable "create_vpc" {
description = "Controls if VPC should be created (it affects almost all resources)"
type = bool
default = true
}
variable "name" {
description = "Name to be used on all the resources as identifier"
type = string
default = ""
}
variable "cidr" {
description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overridden"
type = string
default = "10.0.0.0/16"
}
variable "enable_ipv6" {
description = "Requests an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC. You cannot specify the range of IP addresses, or the size of the CIDR block."
type = bool
default = false
}
variable "instance_tenancy" {
description = "A tenancy option for instances launched into the VPC"
type = string
default = "default"
}
variable "enable_dns_hostnames" {
description = "Should be true to enable DNS hostnames in the VPC"
type = bool
default = false
}
variable "enable_dns_support" {
description = "Should be true to enable DNS support in the VPC"
type = bool
default = true
}
variable "enable_classiclink" {
description = "Should be true to enable ClassicLink for the VPC. Only valid in regions and accounts that support EC2 Classic."
type = bool
default = null
}
variable "enable_classiclink_dns_support" {
description = "Should be true to enable ClassicLink DNS Support for the VPC. Only valid in regions and accounts that support EC2 Classic."
type = bool
default = null
}
variable "tags" {
description = "A map of tags to add to all resources"
type = map(string)
default = {}
}
variable "vpc_tags" {
description = "Additional tags for the VPC"
type = map(string)
default = {}
}

View File

@ -0,0 +1,16 @@
terraform {
backend "s3" {
bucket = "tf-state-poc-0608"
key = "sss"
region = "us-east-1"
}
}
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
}

View File

@ -0,0 +1,7 @@
resource "random_id" "server" {
byte_length = 8
}
output "random_id" {
value = random_id.server.hex
}

View File

@ -495,7 +495,7 @@ es-connection Opaque 1 7m37s
## KubeVela Terraform Addon
Terraform Controller is well integrated in [KuberVela](https://github.com/oam-dev/kubevela) as a Terraform addon. Enabling
Terraform Controller is well integrated in [KubeVela](https://github.com/kubevela/kubevela) as a Terraform addon. Enabling
Terraform addon and Terraform provider addon is the simplest way.
- Install Terraform Controller
@ -519,4 +519,4 @@ $ vela addon enable terraform-alibaba ALICLOUD_ACCESS_KEY=<xxx> ALICLOUD_SECRET_
- Provision and Consume cloud resources
Try to provision and consume cloud resources by KubeVela [Cli](https://kubevela.io/docs/end-user/components/cloud-services/provider-and-consume-cloud-services) or [VelaUX](https://kubevela.io/docs/next/tutorials/consume-cloud-services).
Try to provision and consume cloud resources by KubeVela [Cli](https://kubevela.io/docs/end-user/components/cloud-services/provider-and-consume-cloud-services) or [VelaUX](https://kubevela.io/docs/next/tutorials/consume-cloud-services).

View File

@ -3,7 +3,7 @@ package gitee
import (
"testing"
"github.com/oam-dev/terraform-controller/e2e"
"github.com/oam-dev/terraform-controller/e2e/normal"
)
var (
@ -16,5 +16,5 @@ var (
func TestGiteeConfigurationRegression(t *testing.T) {
var retryTimes = 240
e2e.Regression(t, giteeConfigurationsRegression, retryTimes)
normal.Regression(t, giteeConfigurationsRegression, retryTimes)
}

Some files were not shown because too many files have changed in this diff Show More