Compare commits

...

543 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 7ad83496cc
Merge pull request #265 from zzxwill/region
Change json tag of spec.Region to `customRegion` in Configuration
2022-03-08 11:48:26 +08:00
Zheng Xi Zhou f06cbc9ba9 Change json tag of spec.Region to `customRegion` in Configuration
Fix issue https://github.com/oam-dev/kubevela/issues/3384

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-07 23:08:44 +08:00
Zheng Xi Zhou 7760da7b7d
Merge pull request #264 from zzxwill/ut
Add Unit testcases
2022-03-07 10:28:06 +08:00
Zheng Xi Zhou 0abf57b90d add ut for provider controller
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-06 00:08:02 +08:00
Zheng Xi Zhou 69770e775a Add Unit testcases
Added uts

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-05 23:10:55 +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
Zheng Xi Zhou 97b3f998fd
Merge pull request #256 from anoop2811/master
fix: Disable istio-proxy injection to the job pods
2022-03-01 13:06:39 +08:00
Anoop Gopalakrishnan bf2fec0439 fix: Disable istio-proxy injection to the job pods
Signed-off-by: Anoop Gopalakrishnan <anoop2811@aol.in>
2022-02-28 20:27:19 -08:00
Zheng Xi Zhou f1046aecd5
Merge pull request #253 from anoop2811/master
fix: Filter logs from container for terraform jobs
2022-02-28 22:16:56 +08:00
Zheng Xi Zhou ee08fb30c2
Refine contributing doc (#251)
Add steps on how to contriubting code.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-28 13:05:03 +08:00
Anoop Gopalakrishnan 9899306882 fix: Filter logs from container for terraform jobs
Signed-off-by: Anoop Gopalakrishnan <anoop2811@aol.in>
2022-02-27 19:35:49 -08:00
Zheng Xi Zhou 3a418fbd8c
Add UTs (#252)
* Add UT

Added more unit-tests

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-28 10:32:30 +08:00
Anoop Gopalakrishnan 2a0aa6f22c fix: Filter logs from container for terraform jobs
Signed-off-by: Anoop Gopalakrishnan <anoop2811@aol.in>
2022-02-27 17:56:35 -08:00
Zheng Xi Zhou 4a8cafc034
Merge pull request #236 from evanli18/master
feat: support object,list(object) variable types
2022-02-25 14:06:12 +08:00
Zheng Xi Zhou 117756c596
Merge pull request #248 from zzxwill/tencent-gcp 2022-02-20 09:36:48 +08:00
lowkeyrd adac29beb9
Merge pull request #249 from zzxwill/getting-start-doc
Update getting-started and contribution doc
2022-02-18 17:47:43 +08:00
Zheng Xi Zhou f9a6b98f30 Update getting-started and contribution doc
Per the requests from two community users, updated the docs.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-18 17:43:27 +08:00
Zheng Xi Zhou a74faed581 Add Terraform Configuration examples
Add TF examples for GCP and Tecent Cloud

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-18 17:06:11 +08:00
Zheng Xi Zhou 11a0e90fbd
Merge pull request #245 from zzxwill/go
Upgrade go from 1.16 to 1.17
2022-02-18 16:51:33 +08:00
Zheng Xi Zhou b069fc57e4 update go-version in build script
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-18 16:47:12 +08:00
Zheng Xi Zhou ba53a52f96 Upgrade go from 1.16 to 1.17
Upgraded go version to 1.17

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-18 16:02:57 +08:00
Zheng Xi Zhou 6d3a06d4f6
Merge pull request #243 from zzxwill/baidu-provider
Add examples for Baidu provider
2022-02-15 15:15:30 +08:00
Zheng Xi Zhou 83f30c3255 fix ut
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-14 20:58:24 +08:00
Zheng Xi Zhou 0def153271 Add examples for Baidu provider
Added some examples and simplify the keys of the secret
which is used to store Baidu Cloud credentials.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-14 20:45:13 +08:00
Zheng Xi Zhou 4c52a3d7ec
Merge pull request #242 from zzxwill/ut
Add UT
2022-02-14 14:27:45 +08:00
Zheng Xi Zhou 2a98b2400e
Code refactor (#241)
Also added some unit tests

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-14 10:15:14 +08:00
Zheng Xi Zhou 400f76cae6 add more tests
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-13 23:06:55 +08:00
Zheng Xi Zhou ed3156f0d5 Add UT
Added some unit tests

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-13 20:30:45 +08:00
Zheng Xi Zhou 89491b3319
Support Baidu Cloud (#240)
Supported Baidu Cloud per user requests from Youzan and Baidu

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-02-10 19:30:12 +08:00
Evan Li c231477d77 feat: support object,list(object) variable types
Closes: #233
Signed-off-by: Evan Li <evan.li97@outlook.com>
2022-01-27 19:27:46 +08:00
Tianxin Dong 698b15215d
Merge pull request #235 from zzxwill/release0.3.4
Upgrade docker terraform image
2022-01-26 19:57:11 +08:00
Zheng Xi Zhou a4cc7138a9
Merge pull request #232 from evanli18/master
style: optimize log output
2022-01-26 19:56:02 +08:00
Zheng Xi Zhou f0e8f5438a Upgrade docker terraform image
Detailed: https://github.com/oam-dev/docker-terraform/releases/tag/v1.1.2

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-26 19:44:46 +08:00
Evan Li 01e3ac0047 style: optimize log output
- replace zap with klog
- set job log level to 4
- ignore NotFound

Closes: #229
Signed-off-by: Evan Li <evan.li97@outlook.com>
2022-01-25 11:00:45 +08:00
Zheng Xi Zhou d71caf00a0
Merge pull request #231 from zzxwill/go-report
Fix go report issues
2022-01-24 11:08:10 +08:00
Zheng Xi Zhou 6e447df9e5 Add unit testcases
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-23 23:54:09 +08:00
Zheng Xi Zhou bf0846cded Fix go report issues
Fixed the issues of go-report

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-23 23:12:47 +08:00
Zheng Xi Zhou f20db7aaed
Merge pull request #230 from zzxwill/unit-test
Fix unit-test workflow name
2022-01-23 22:59:50 +08:00
Zheng Xi Zhou f28b19f428 Fix unit-test workflow name
Updated the name for unit-test workflow

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-23 22:48:56 +08:00
Zheng Xi Zhou 19693758dc
Add some status stickers (#227)
Added Go report card, Docker pulls and code coverage stickers

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-20 09:47:09 +08:00
Zheng Xi Zhou 04d281602f
Merge pull request #228 from zzxwill/mysql
Update Tencent Cloud mysql .tf and Configuration
2022-01-19 19:45:57 +08:00
Zheng Xi Zhou 2eb61811f8 Update Tencent Cloud mysql .tf and Configuration
Updated mysql .tf and Configurations. Added some outputs in them.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-19 19:43:09 +08:00
Zheng Xi Zhou 37c7bd4a0f
Update Tecent cloud Terraform hcl and Configuration (#226)
Updated the Terraform HCL and Configuration for Tencent Cloud.
Also add a makefile target to make it convenient to set Provider

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-19 13:40:39 +08:00
Yue Wang dd2de6ed04
support provisioning tencent cloud resources (#133)
* support provisioning tencent cloud resources

Signed-off-by: royyuewang <royyuewang@tencent.com>
Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-19 10:21:56 +08:00
Zheng Xi Zhou 2d71501327
Merge pull request #225 from zzxwill/contribution 2022-01-18 12:56:58 +08:00
Zheng Xi Zhou 517109328e Use Markdown table to format the contributions
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-18 11:17:55 +08:00
Zheng Xi Zhou c0bb8bb10f Add contributors who support cloud providers
Added github handlers who support cloud providers

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-18 11:06:07 +08:00
Zheng Xi Zhou e43a363414
Code refator (#224)
* Code refator

Refatored code and add unit-tests

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

* cover 100% of go files

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-17 13:27:01 +08:00
Zheng Xi Zhou dea5e9bb3b
Fix: allow Configuration to delete when Provider is not ready (#222)
* Fix: allow Configuration to delete when Provider is not ready

When the provider doesn't exist or is not ready, allow the deletion
of the Configuration

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-14 10:30:31 +08:00
Zheng Xi Zhou 9a6ba96281
Correct `make alibaba-credentials` target (#221)
`echo -e` doesn't work on macOS. Terraform controller comes with KubeVela, so
users has namespace `vela-system` created in the cluster. Removed the line of
creating the namespace

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-14 09:09:31 +08:00
Zheng Xi Zhou 7b04905d6f
Set default value for environment names (#223)
* Set default name for environment names

If an environment name is empty, set the default name. This will be
consistent with old release, and is friendly for local development

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

* add ut

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-12 22:42:04 +08:00
Zheng Xi Zhou 0d58fe6426
Make provider status consistent with that of Configuration (#220)
* Make provider status consistent with that of Configuration

Used the same method to check to validate provider in Provider
controller and Configuration controller. Specifically, the ak/sk
is also validated in Provider controller

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

* add ut

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-12 14:58:24 +08:00
Tianxin Dong f62453bfec
Merge pull request #218 from zzxwill/deprecate-json
Deprecate spec.JSON in Configuration
2022-01-10 17:47:15 +08:00
Zheng Xi Zhou d69c1e107c Deprecate spec.JSON in Configuration
Deprecated spec.JSON and only support HCL in Configuration, as few
users use JSON in Configuration

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-10 17:40:24 +08:00
Zheng Xi Zhou dddf76e4a3
Merge pull request #215 from evanli18/master
Add custom provider
2022-01-06 14:48:45 +08:00
Evan Li 45be0dbf64 Add custom provider
Signed-off-by: Evan Li <evan.li97@outlook.com>
2022-01-06 10:27:15 +08:00
Tianxin Dong f80a5e04e0
Merge pull request #216 from zzxwill/release1.1.0
Add random provider in Terraform image
2022-01-05 17:09:30 +08:00
Zheng Xi Zhou 5432c70c8b Add random provider in Terraform image
Added another random provider in Terraform docker image

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-05 17:01:20 +08:00
Zheng Xi Zhou 6461ef97cc
Fix: update the architecture (#214)
Updated the acchitecture of Terraform Controller

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-05 13:48:47 +08:00
Zheng Xi Zhou 0a45c215bb
Merge pull request #213 from zzxwill/gitee-fix
Fix: correct the rules of converting github to gitee
2021-12-29 17:36:44 +08:00
Zheng Xi Zhou 5a34ef4ad1 Fix: correct the rules of converting github to gitee
Correct one rule of converting github to gitee

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-29 17:34:12 +08:00
Zheng Xi Zhou e1e2fe3643
Merge pull request #212 from oam-dev/fix-release
Image tag is not correctly set
2021-12-29 14:07:12 +08:00
Zheng Xi Zhou 3119422504 Image tag is not correctly set
The image tag is not correctly set.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-29 14:05:45 +08:00
Zheng Xi Zhou 086b42f5b6
Merge pull request #211 from oam-dev/fix-release
Fix the image tag required by Chart
2021-12-29 12:06:17 +08:00
Zheng Xi Zhou b5d7d5e6c8 Fix the image tag required by Chart
Fixed the image tag by removing `v`.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-29 12:04:37 +08:00
Zheng Xi Zhou 54400b259e
Merge pull request #210 from oam-dev/fix-release
Fix chart build issue
2021-12-29 11:49:41 +08:00
Zheng Xi Zhou 121d84cbb7 Fix chart build issue
Fixed issues of building a chart

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-29 11:44:14 +08:00
Zheng Xi Zhou 6f831daacc
Merge pull request #209 from zzxwill/fix-release
Build docker first and then chart
2021-12-29 10:53:10 +08:00
Zheng Xi Zhou 7d4584fe60 Build docker first and then chart
Build docker image first before building a chart

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-29 10:43:24 +08:00
Zheng Xi Zhou bcbfebc202
Merge pull request #208 from zzxwill/fix-release
Fix Chart build workflow
2021-12-29 10:24:28 +08:00
Zheng Xi Zhou a3ad18c94c Fix Chart build workflow
Fixed the chart build process, and set the right boolean value

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 23:14:52 +08:00
Zheng Xi Zhou ac3d10bf58
Merge pull request #207 from zzxwill/chart-build
Fix automatic chart build workflow
2021-12-28 20:13:53 +08:00
Zheng Xi Zhou c1ecbfa789
Merge pull request #205 from zzxwill/github-blocked
Replace GitHub with Gitee when GitHub is blocked
2021-12-28 20:11:07 +08:00
Zheng Xi Zhou 939a4162bb Fix automatic chart build workflow
Fixed Github workflow for automatically chart build

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 20:09:30 +08:00
Zheng Xi Zhou 9a1c834360
Merge pull request #206 from zzxwill/auto-release
Automatically make new release
2021-12-28 19:35:01 +08:00
Zheng Xi Zhou 1011fa7ca1 Automatically make new release
Automatically build new image and make new chart for a new release

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 19:26:59 +08:00
Zheng Xi Zhou 9602e396e0 Replace GitHub with Gitee when GitHub is blocked
When Github is blocked, replace Github with Gitee where Terraform
modules are stored.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 16:42:04 +08:00
Zheng Xi Zhou 2fa4ca6f1c
Merge pull request #204 from zzxwill/gitee
Verification Gitee hosted Terraform modules
2021-12-28 16:41:32 +08:00
Zheng Xi Zhou dac19e1757 remove e2e
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 14:40:30 +08:00
Zheng Xi Zhou 9c7513c15b fix linting issues
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-28 12:11:39 +08:00
Zheng Xi Zhou 780711c0b1 Verification Gitee hosted Terraform modules
In order to help those who are blocked by GitHub, we have ported
Terraform Configuration/Module from GitHub to [Gitee](https://gitee.com/).
And we have to verify those in Gitee work as those in GitHub.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-27 20:38:40 +08:00
Zheng Xi Zhou f3842eca56
Merge pull request #202 from zzxwill/ut-coverage
Upload ut code coverage report
2021-12-27 19:27:01 +08:00
Zheng Xi Zhou d6677866d9 fix ut
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-27 18:08:14 +08:00
Zheng Xi Zhou 577d1c81c6 Upload ut code coverage report
Generated code coverage report when runing ut and uploaded it
to github action.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-27 18:05:43 +08:00
Zheng Xi Zhou b00cd14cab
Merge pull request #201 from zzxwill/gitee
Refactor e2e tests and upload e2e coverage
2021-12-27 16:20:53 +08:00
Zheng Xi Zhou 5bc3cc5ad3 Refactor e2e tests and upload e2e coverage
Merged Ginkgo and testing into one e2e tests. Also uploaded e2e
coverage to Github

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-27 15:06:00 +08:00
Zheng Xi Zhou aa9dbb38d2
Merge pull request #199 from wangwang/initContainer
Make initContainers of terraform-{apply,destroy}-job configurable (#158)
2021-12-27 12:09:49 +08:00
liuwangwang ddb1fbcd3d Make initContainers of terraform-{apply,destroy}-job configurable (#158)
Signed-off-by: liuwangwang <liuwangwang@youzan.com>
2021-12-27 11:25:59 +08:00
Zheng Xi Zhou 20f474448d
Merge pull request #197 from wangwang/fix-clusterrolebinding
Fix unproper clusterrolebinding settings
2021-12-20 20:19:35 +08:00
Wangwang LIU 1aedbd44ec Fix unproper clusterrolebinding settings
Signed-off-by: Wangwang LIU <iamcaiya@gmail.com>
2021-12-16 16:09:43 +08:00
Zheng Xi Zhou 1e009b6e42
Validate Alibaba credentials ak/sk/ststoken (#195)
Change the API to validate Alibaba credentials, including ak, sk
and ststoken.

This precious API needs RAM polocies and will hit the issue
https://github.com/oam-dev/kubevela/issues/2917

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-15 09:47:52 +08:00
Zheng Xi Zhou 3afac53eab
Merge pull request #193 from zzxwill/ststoken
Support validating stsToken
2021-12-09 11:12:52 +08:00
Zheng Xi Zhou 48014da258
Fix unproper serviceaccount settings (#194)
* Fix improper serviceaccount settings

When there are two applications, who have same application name, but
different namespace names, the second one's service account will use
the first one. But the clusterrole's namespace isn't in its namespace.
It will lack all authority to perform any operations when running
Terraform applying.

Fix #192

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-09 11:12:27 +08:00
Zheng Xi Zhou d52b25fdb1 Add testcases
Added a bad testcase for valiating ak/sk/stsToken

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-03 16:44:55 +08:00
Zheng Xi Zhou 56d06f6a1d Support validating stsToken
Besides validating ak/sk, support stsToken

Fix #191

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-12-03 16:44:55 +08:00
Zheng Xi Zhou 0d4c952d61
Merge pull request #189 from zzxwill/api
Upgrade api version
2021-11-30 20:11:33 +08:00
zzxwill 819bbb4c23 Upgrade api version
As api/v1beta1/configuration_types.go changes, upgrade api version

Signed-off-by: zzxwill <zzxwill@gmail.com>
2021-11-30 20:10:55 +08:00
Zheng Xi Zhou c07ccfa8d4
Merge pull request #188 from zzxwill/spec
Refactor: reorganize the spec of Configuration
2021-11-30 19:58:23 +08:00
zzxwill 3393a603cd Refactor: reorganize the spec of Configuration
Moved `path` out of baseConfiguration and add `writeConnectionSecretToRef`
in baseConfiguration, making baseConfiguration to be able to integrate by
KubeVela.

Signed-off-by: zzxwill <zzxwill@gmail.com>
2021-11-30 19:47:50 +08:00
Zheng Xi Zhou e4b5b34459
Merge pull request #187 from zzxwill/secret-label
Add a label to mark the secret
2021-11-29 19:35:42 +08:00
zzxwill 59dd7ea55a Add a label to mark the secret
Added a label "created-by"="terraform-controller" to mark the secret
was created by Terraform Controller
2021-11-29 15:43:32 +08:00
Zheng Xi Zhou ab1742653a
Merge pull request #186 from zzxwill/set-region
Set region to spec.region
2021-11-28 21:38:17 +08:00
zzxwill b5b8e5885a Set region to spec.region
If spec.region is not set, use the region of the referenced provider
2021-11-27 21:15:25 +08:00
Zheng Xi Zhou 9b9de98ca6
Merge pull request #184 from zzxwill/override-region
Override Region of a provider in Configuration
2021-11-22 14:46:58 +08:00
zzxwill 2bc83e3c79 Add a Configuration state `InvalidRegion`
Added another Configuration state `InvalidRegion`. For this state,
no need to continue checking
2021-11-21 23:57:00 +08:00
zzxwill 1141095fc8 Override Region of a provider in Configuration
Override Region of a provider, where cloud resources are to
provision in application level.

Fix https://github.com/oam-dev/kubevela/issues/2636
2021-11-21 23:11:10 +08:00
Zheng Xi Zhou 57a3abd2a6
Merge pull request #182 from zzxwill/e2e-enhance
Add more e2e tests
2021-11-21 16:43:30 +08:00
zzxwill fad4557e72 Checking secrets and configmaps on applying and deleting
Check whether configmaps and secrets and created when applying
configurations. Check whetheter configmaps and secrets are deleted
after deleting configurations
2021-11-21 16:33:37 +08:00
zzxwill f6ec2061da Add more e2e tests
Added more tests to check Configuration
2021-11-21 16:27:05 +08:00
Zheng Xi Zhou e2911d9227
Merge pull request #179 from zzxwill/backend
The Kubernetes backend should keep consistent
2021-11-21 16:25:54 +08:00
Zheng Xi Zhou badcce96a1 Address comments
Co-authored-by: bobbygbriggs <94778624+bobbygbriggs@users.noreply.github.com>
2021-11-21 16:11:48 +08:00
zzxwill 3a1f2be7a0 Fix ci issue
Delete blank lines
2021-11-21 15:54:20 +08:00
zzxwill 73b1f9b119 Add one one state `GeneratingOutputs`
Also adding the state message
2021-11-21 15:54:20 +08:00
zzxwill 6e56fa9f37 The Kubernetes backend should keep consistent
After implementing the feature https://github.com/oam-dev/kubevela/issues/2670,
the namespace of Kubernetes backend should keep consistent.
2021-11-21 15:54:20 +08:00
Zheng Xi Zhou ad4da74bba
Fix status `ProvisioningAndChecking` (#181)
The state of Configuration won't become Available if trying to
delete it before it becomes Available.

Fix #180
2021-11-21 15:52:23 +08:00
Zheng Xi Zhou 157754da9d
Fix: Target `lint` doesn't work (#183)
After the porting of #172, `make lint` doesn't work, as it cloud indentify
`OK. Fix the issue and linting issue for `controllers/configuration_controller.go`
2021-11-21 11:19:25 +08:00
Zheng Xi Zhou d19b104ab4
Clean up auxiliary stuffs (#178)
Clean up auxiliary stuffs, like ConfigMap to store .tf, Secrets to
store variables, connedtions and Kubernetes backend
2021-11-19 17:26:04 +08:00
Zheng Xi Zhou 3309f3d8e0
Fix: The secret generated by Terraform won't regenerate after it's manually deleted (#177)
Fix https://github.com/oam-dev/kubevela/issues/2693
2021-11-19 16:07:07 +08:00
Zheng Xi Zhou 4d7566b19b
Generate Job/Pod/ConfigMap/Secret in the namespace of a Configuration (#175)
* Generate Job/Pod/ConfigMap/Secret in the namespace of a Configuration

This feature will help to debug the Job in user-definied namespaces. Partly
fix https://github.com/oam-dev/kubevela/issues/2670

* grant rbac

* add comments for constants

* fix ci

* add more verbs for serviceaccount

* add lease authority
2021-11-19 15:01:50 +08:00
Zheng Xi Zhou 38061ec035
Merge pull request #174 from zzxwill/rbac
Clean RBAC
2021-11-18 17:37:09 +08:00
zzxwill 391b796b9a Clean RBAC
RBAC files are seperated and difficult to add or modify.
2021-11-18 17:04:51 +08:00
Zheng Xi Zhou 33b410ad3b
Merge pull request #172 from zzxwill/golangci-lint
Fix: stop installing golangci-lint each time
2021-11-16 20:26:51 +08:00
zzxwill a45d751d11 Fix: stop installing golangci-lint each time
When golangci-lint doesn't locate in $PATH, it will be installed in
$GOBIN every single time.
2021-11-15 17:14:36 +08:00
Zheng Xi Zhou 8d4f8900d4
Merge pull request #166 from zzxwill/override-region
Code refactor
2021-11-15 17:12:08 +08:00
zzxwill 3040697295 Fix license issue
Fix #167
2021-11-15 15:55:35 +08:00
zzxwill 96475f134a Code refactor
Compose `path`, `providerRef`, `deleteResource` to struct `BaseConfigurationSpec`.
Then KubeVela can use inline it in a easier way.
2021-11-12 16:22:02 +08:00
Zheng Xi Zhou 272ff8fb0f
Merge pull request #171 from zzxwill/tf-issue
Update ConfigMap when .tf is changed
2021-11-11 14:10:37 +08:00
zzxwill 608b4c49ba address comments 2021-11-10 16:40:16 +08:00
zzxwill 8ac935c7d4 fix ci issue 2021-11-10 15:42:59 +08:00
zzxwill 656a53eccd Update ConfigMap when .tf is changed
If the ConfigMap, which is used to store Terraform hcl, is changed,
update it, or keep it as it is.
2021-11-10 15:30:37 +08:00
Zheng Xi Zhou 4cda5066c8
Merge pull request #168 from zzxwill/docker-build
Build latest image for Github CI
2021-11-10 15:29:38 +08:00
zzxwill 62af617eba Build latest image for Github CI
Change the image tag to `latest` for e2e test in Github CI
2021-11-10 14:20:04 +08:00
Zheng Xi Zhou 6238a5f194
Set RemoteGit and DeleteResource after retrieving configuration (#170)
The initContainer cloud not be added to InitContainers as RemoteGit
peroperty isn't properly set.
2021-11-10 14:09:19 +08:00
qiaozp dbdf4b45cd
add redis definition (#165)
* remove cms

* remove tf

* fix
2021-11-08 16:53:12 +08:00
Zheng Xi Zhou e0f0e03b19
Merge pull request #164 from zzxwill/enhance-provider-check
Check Alibaba Cloud credentials
2021-11-08 10:47:51 +08:00
zzxwill 9d9dfaa7e5 Check Alibaba Cloud credentials
Call Alibaba Cloud API to check whether the credentials are valid
2021-11-08 10:45:16 +08:00
Zheng Xi Zhou 9028c291f4
Merge pull request #163 from zzxwill/provider-not-ready
Check provider's availability in precheck
2021-11-04 17:59:10 +08:00
zzxwill 6596d2179e Check provider's availability in precheck
A provider's availability should be checked in precheck. The credentials
is stored after the provider is available.
2021-11-04 15:58:05 +08:00
Zheng Xi Zhou 2d1ea1d92b
Merge pull request #162 from zzxwill/docker-terraform-0.1.9
Update Terraform alicloud provider version to 1.140.0
2021-11-03 16:55:23 +08:00
zzxwill dd33684c0b Update Terraform alicloud provider version to 1.140.0
In the job image, update Terraform alicloud provider version to 1.140.0
2021-11-03 14:55:21 +08:00
Zheng Xi Zhou cacb1838c2
Merge pull request #161 from zzxwill/new-line
"\n" won't covert to new line in Linux
2021-11-03 11:29:21 +08:00
zzxwill cbab621785 "\n" won't covert to new line in Linux
Added `-e` to make "\n" to convert to new line
2021-11-03 11:03:42 +08:00
Zheng Xi Zhou c640444ba4
Merge pull request #160 from zzxwill/action
Fix e2e issue
2021-11-03 10:39:58 +08:00
zzxwill 0103a3ce27 Fix e2e issue
Fix #146
2021-11-03 10:28:10 +08:00
Zheng Xi Zhou 8aa035a2b7
Merge pull request #153 from zzxwill/keepresource
Feature: support external cloud resources to keep when CR deleted
2021-11-02 10:45:30 +08:00
Zheng Xi Zhou 5a648a9756
Merge pull request #157 from zzxwill/bump0.2.7
Bump chart version to 0.2.7
2021-10-28 14:27:15 +08:00
zzxwill 61e21505ee Bump chart version to 0.2.7
In this release, no api is changed.
2021-10-28 14:26:18 +08:00
Zheng Xi Zhou 26c5520226
Merge pull request #156 from zzxwill/expose-terraform-docker
Make terraform docker be configurable
2021-10-28 14:20:45 +08:00
Zheng Xi Zhou 70e7072799
Merge pull request #148 from wangwang/ucloud-provider
Support UCloud Resources
2021-10-28 11:40:35 +08:00
zzxwill 4cd3452e0d format code 2021-10-28 11:09:19 +08:00
zzxwill c52a7ddb89 Make terraform docker be configurable
By setting chart values, users can set their customized docker terraform,
in which customized providers can be used.
2021-10-28 10:55:49 +08:00
Zheng Xi Zhou b92df0e29a
Merge pull request #154 from zzxwill/ci
Fix: two `install` exists in Makefile
2021-10-27 17:07:30 +08:00
zzxwill faa4f54bca Feature: support external cloud resources to keep when CR deleted
If a CR is deleted, an option is provided to users whether to keep
cloud resources.

Fix #136
2021-10-27 17:06:50 +08:00
zzxwill 402d92954e Fix: two `install` exists in Makefile
Rename the latter `install` to `install-Chart` in Makefile
2021-10-27 16:44:54 +08:00
Zheng Xi Zhou 137fa58d22
Merge pull request #151 from zzxwill/fix-revert
Support more variable types
2021-10-27 16:01:47 +08:00
zzxwill 585113b733 Support more variable types
Besides int/string typed variables, support list and map types
2021-10-27 15:44:48 +08:00
LIU Wangwang 7528b2df34 move uredis hcl to [kubevela-contrib](https://github.com/kubevela-contrib/terraform-modules/pull/1) 2021-10-27 14:51:32 +08:00
Zheng Xi Zhou 24bfe84ed4
Merge pull request #150 from zzxwill/input-tf
Fix: the first status of Configuration should not be `ConfigurationReloading`
2021-10-27 14:00:40 +08:00
zzxwill 4701a362d0 Fix: the first status of Configuration should not be `ConfigurationReloading`
By storing the Configuration's HCL at the very begining to avoid the
first status of Confiugration to be `ConfigurationReloading`
2021-10-27 13:37:14 +08:00
Zheng Xi Zhou 81adbd2d71
Merge pull request #149 from oam-dev/revert-145-terraform-inputs
Revert "Support more variable types"
2021-10-27 11:47:23 +08:00
Zheng Xi Zhou 51558b7dfb
Revert "Support more variable types" 2021-10-27 11:47:02 +08:00
LIU Wangwang b52ee3c4ed Support UCloud Resources 2021-10-26 19:26:03 +08:00
Zheng Xi Zhou 20e8b5f3f9
Merge pull request #145 from zzxwill/terraform-inputs
Support more variable types
2021-10-26 17:29:12 +08:00
zzxwill 5383cae133 add checking status logs 2021-10-26 15:33:05 +08:00
zzxwill fd7047bd3d Increase reconciling test time to 10 minutes
As rds provision test case is added which will take 5 minutes to
complete, increase the reconciling time
2021-10-26 15:18:33 +08:00
zzxwill efdee6a2d1 fix e2e 2021-10-26 15:07:38 +08:00
zzxwill e82b1e5b63 fix e2e 2021-10-26 14:43:54 +08:00
zzxwill 92777f00ac Commit float64 to string
Int type string in runtime.RawExtension changed to `float64`. So change
it to string for Terraform environment variables
2021-10-26 14:36:28 +08:00
zzxwill c05265ea72 add one more e2e case 2021-10-26 14:36:28 +08:00
zzxwill eea968fc96 fix CI issues 2021-10-26 14:36:28 +08:00
Zheng Xi Zhou cd396e6a10 Store vairalbes in secret and check whether they are changed 2021-10-26 14:36:26 +08:00
zzxwill 427b07d638 get environment from secret 2021-10-26 14:20:49 +08:00
zzxwill a4434d222d Support more variable types
Besides int/string typed variables, support list and map types
2021-10-26 14:20:49 +08:00
Zheng Xi Zhou 6eb3f33f1e
Merge pull request #144 from zzxwill/ack-outputs
Add `CLUSTER_ID` in the outputs of Alibaba Cloud ACK
2021-10-25 22:47:27 +08:00
Zheng Xi Zhou 7e5c753da2
Merge pull request #143 from zzxwill/recreate-job
Set Configuration.status
2021-10-25 22:19:04 +08:00
zzxwill 16468a4dda Add `CLUSTER_ID` in the outputs of Alibaba Cloud ACK
Added another output in the outputs of ACK dedicated kubernetes
2021-10-22 20:16:15 +08:00
zzxwill e59563fadf Set Configuration.status
Don't base on other status of the Configuration as it's not relibale.
Set the current status directly

Fix #126
2021-10-22 17:41:15 +08:00
Zheng Xi Zhou a0fbc60853
Merge pull request #142 from zzxwill/out-of-cluster
For local development environment, will use local ~/.kube/config file
2021-10-22 15:54:46 +08:00
zzxwill 678e9105d6 For local development environment, will use local ~/.kube/config file
In production environment, will use InClusterConfig. But it's not
convenient for local development.
2021-10-22 15:45:07 +08:00
Zheng Xi Zhou a1c685dfc8
Merge pull request #139 from zzxwill/rds-public-allocation
Enable to allocate public connection string
2021-10-20 14:47:29 +08:00
Zheng Xi Zhou 91a80d2a4c
Merge pull request #138 from zzxwill/bump0.2.5
Bump chart version
2021-10-20 14:44:09 +08:00
zzxwill 01ade07075 update variable description 2021-10-20 12:25:17 +08:00
zzxwill ea8dc7f3b6 Enable to allocate public connectiong string
Also expose the connection string
2021-10-20 12:20:54 +08:00
zzxwill a770621611 Bump chart version
- Tag a new release
- Update contributing docs
- Move .tf to centerizied hcl repo and make Configuration looks simple
2021-10-19 14:57:32 +08:00
Zheng Xi Zhou d6c0e11010
Merge pull request #135 from zzxwill/sub-directory
Support remote git subdirectory
2021-10-18 19:53:07 +08:00
Zheng Xi Zhou d2fd01829d
Merge pull request #137 from zzxwill/azure-issue
Fix azure .tf issue
2021-10-18 18:04:52 +08:00
zzxwill e6f39466fb Fix azure .tf issue
Also fix status setting issues
2021-10-18 17:54:04 +08:00
zzxwill 4b32d544ce add a test case for Configuration with a remote path 2021-10-18 16:12:26 +08:00
zzxwill 08075c332f Support remote git subdirectory
Support sub-folder of a git repo to indicate the hcl location

Fix #124
2021-10-18 16:09:36 +08:00
Zheng Xi Zhou a6fd98dbd1
Merge pull request #134 from chivalryq/add-redis-def
Feat: add aliyun redis configuration and .tf
2021-10-18 15:48:17 +08:00
qiaozp e3de03d33e Feat: add aliyun redis configuration and .tf
Feat: add aliyun redis configuration and .tf
2021-10-18 14:58:33 +08:00
Zheng Xi Zhou cb32d9612b
Merge pull request #132 from zzxwill/e2e
Add e2e tests
2021-10-18 14:22:03 +08:00
zzxwill a50697d7c6 address comments 2021-10-18 12:03:31 +08:00
zzxwill 800a1bc8f1 fix ci
For ut, just run directory `controllers`
2021-10-18 12:03:31 +08:00
zzxwill 957b1843c7 Add testcases for Alibaba cloud 2021-10-18 12:03:31 +08:00
zzxwill 24698f8b70 Add e2e tests
Apply Configuration to verify cloud resources provisioning and
destroying
2021-10-18 12:03:31 +08:00
Zheng Xi Zhou d8a52817fb
Merge pull request #131 from zzxwill/appversion
Add app version in this chart
2021-10-18 12:02:59 +08:00
zzxwill fe7083544e Add app version in this chart
Add app version in the Chart.yaml to indicate the application version
2021-10-17 10:07:02 +08:00
Zheng Xi Zhou 4d83fbc6b9
Merge pull request #130 from zzxwill/log
Fetch terraform log
2021-10-17 09:30:05 +08:00
zzxwill 051b4ea2f5 check whether retrieved pod list is nil 2021-10-15 10:37:50 +08:00
zzxwill 7a29a5f548 Fetch terraform log
- Fetch Terraform log, analyaze it and set the status for Configuration
- Move syntax chekcing logics from controller to job
2021-10-14 17:23:41 +08:00
Zheng Xi Zhou 6241741343
Merge pull request #129 from zzxwill/skaffold
Apply Skaffold for development
2021-10-14 17:23:08 +08:00
zzxwill e4b1ca063c Apply Skaffold to development
Applied Skaffold to make local development more convienient, including
docker build and chart upgrade
2021-10-14 17:03:19 +08:00
Zheng Xi Zhou 7c206d5e6f
Merge pull request #128 from zzxwill/azure
Add outputs for Azure mariadb
2021-10-13 15:13:20 +08:00
zzxwill ff367cda32 Add outputs for Azure mariadb
Update both .hcl and Configuration for Azure mariadb database
2021-10-13 14:52:06 +08:00
Zheng Xi Zhou eadf32c095
Merge pull request #122 from zzxwill/release022
Bump chart version
2021-10-12 12:17:35 +08:00
zzxwill d151111c38 Bump chart version
Remove aws/azure provider in the Terraform apply job image
2021-10-12 11:43:35 +08:00
Zheng Xi Zhou 125700bb2c
Merge pull request #121 from zzxwill/doc
Add CONTRIBUTING doc and refine getting-start doc
2021-10-09 10:51:22 +08:00
zzxwill 772ccd7f84 Add CONTRIBUTING doc and refine getting-start doc
Encourage users to adopt KubeVela to provision and consume cloud
resources. And add contributing doc
2021-10-08 20:06:39 +08:00
Zheng Xi Zhou 2ecd5ad9ed
Merge pull request #120 from zzxwill/upgrade
Support v1.20+
2021-10-08 19:23:41 +08:00
zzxwill a96d5d41eb upgrade controller-gen to v0.6.0 per the suggestion from https://github.com/oam-dev/kubevela/issues/1730 2021-10-08 19:16:41 +08:00
zzxwill cfc23def0b Support v1.20+
Support Kubernetes v1.21 and v1.22.
Fix #91
2021-10-08 17:58:41 +08:00
Zheng Xi Zhou e9284fd307
Merge pull request #118 from zzxwill/bump0.2.1
Bump chart version to 0.2.1
2021-09-30 17:24:52 +08:00
zzxwill 99b806588a Bump chart version to 0.2.1 2021-09-30 17:24:12 +08:00
Zheng Xi Zhou f3677c82c1
Merge pull request #117 from zzxwill/provider
Support Configuration to reference a provider with a namespace
2021-09-30 16:30:28 +08:00
zzxwill 4faebf9d27 add an example to set provider's name in Configurtion 2021-09-30 16:14:44 +08:00
zzxwill 40adcfd6fd Support Configuration to reference a provider with a namespace
The referenced Provider can have a namespace. With this feature, different
cloud providers can have different provider respectively
2021-09-30 16:13:35 +08:00
Zheng Xi Zhou 58256572bf
Merge pull request #115 from zzxwill/azure-examples
Refine Azure hcl and Configuration examples
2021-09-30 10:53:38 +08:00
zzxwill 474b04ed6f Refine Azure hcl and Configuration examples
Added type/description for hcl variables
2021-09-30 10:37:35 +08:00
Zheng Xi Zhou 7041720158
Delete tf-validate-3316748360 directory 2021-09-29 15:06:10 +08:00
Zheng Xi Zhou 1d3831b28a
Merge pull request #112 from FogDong/feat-state
Fix: disable the default state when the state is specified
2021-09-24 16:34:09 +08:00
FogDong f8fbe3c900 Feat: tidy the go mod 2021-09-23 14:40:36 +08:00
FogDong 026fb5505c Fix: disable the default state when the state is specified 2021-09-23 11:34:38 +08:00
Zheng Xi Zhou 05358df33b
Merge pull request #114 from zzxwill/release0.2.0
bump chart version
2021-09-23 11:26:01 +08:00
zzxwill 7afa8dc336 bump chart version 2021-09-23 11:25:26 +08:00
Zheng Xi Zhou cd6204043c
Merge pull request #110 from zzxwill/remote-hcl
Support remote git repository
2021-09-23 11:19:42 +08:00
zzxwill cc5abfcdcb fix ci issue 2021-09-23 00:00:29 +08:00
zzxwill 25c37776d8 Add one more initcontainer to store backend for git repo hcls 2021-09-22 23:11:28 +08:00
zzxwill 35dc7a4b64 Support remote git repository
Supprt remote git repository, in which HCLs are stored.
2021-09-22 22:45:52 +08:00
Zheng Xi Zhou 5eecad2157
Merge pull request #113 from zzxwill/refactor-code
Code refactor
2021-09-22 22:42:50 +08:00
zzxwill b89b411e39 Code refactor
Store all Configuration releated information in TFConfigurationMeta
2021-09-22 20:59:06 +08:00
Zheng Xi Zhou af7cf3eff1
Merge pull request #108 from zzxwill/logging
Fix syntax checking issue
2021-09-14 18:06:26 +08:00
zzxwill 788f36e2fd Ignore file write permissions warnning 2021-09-14 16:50:22 +08:00
zzxwill 54b3afbbfe fix ci 2021-09-14 16:47:43 +08:00
zzxwill 41e42d6930 Fix syntax checking issue
- add `terraform` and `git` binary in this controlle image
- Logging old and new Configuration hcl/json when it changed
- bump version
2021-09-14 16:35:59 +08:00
Zheng Xi Zhou c1afac4553
Reorg the directory Terraform native examples (#106)
* Reorg the directory Terraform native examples

Put each .tf file in a single diretory, which makes it convinient
to run `terraform apply/destroy` for a signle cloud resource

* change the string type expression
2021-09-11 17:52:24 +08:00
Zheng Xi Zhou 28fd8981d0
Support Alibaba Cloud EIP (#107)
Add Alibaa cloud eip .tf and Configuration example
2021-09-11 17:50:44 +08:00
Zheng Xi Zhou 0847a28e84
Merge pull request #104 from zzxwill/refactor
Refactor terraform apply/destroy
2021-09-09 10:38:49 +08:00
Zheng Xi Zhou abd1e9bf27
Merge pull request #105 from zzxwill/image
Update controller image
2021-09-09 10:35:20 +08:00
zzxwill a66e139f6d Update controller image
Updated controller image:
- add random provider
- update provider alibaba
2021-09-08 19:26:29 +08:00
zzxwill f6353e0050 Refactor terraform apply/destroy
Added apply and destroy status for configuration object
2021-09-08 18:04:52 +08:00
Zheng Xi Zhou 74e2524e21
Merge pull request #103 from oam-dev/revert-102-refactor
Revert "Refactor terraform apply/destroy"
2021-09-08 17:57:15 +08:00
Zheng Xi Zhou 8a915bf062
Revert "Refactor terraform apply/destroy" 2021-09-08 17:56:54 +08:00
Zheng Xi Zhou 3dd40b652b
Merge pull request #102 from zzxwill/refactor
Refactor terraform apply/destroy
2021-09-08 17:56:16 +08:00
zzxwill c7d7deb928 Refactor terraform apply/destroy
Added apply and destroy status for configuration object
2021-09-08 14:30:40 +08:00
Zheng Xi Zhou 12461a0410
Merge pull request #99 from zzxwill/syntax
Add hcl/json syntax validation ability before executing terraform
2021-09-06 19:55:58 +08:00
zzxwill be27a6e56d add terraform windows binary 2021-09-06 19:46:44 +08:00
zzxwill fb7e0ab3ad upgrade golangci-lint version 2021-09-06 17:41:47 +08:00
zzxwill 9c8b51e5af fix lint issue by upgrading golang version 2021-09-06 17:23:31 +08:00
zzxwill eae780ebf3 fix the issue of unit-test for abs issue 2021-09-06 17:10:35 +08:00
zzxwill 540f5bb691 Add hcl/json syntax validation ability before executing terraform
Check the syntax for hcl/json before creating a job to running
terraform init && apply
2021-09-06 16:28:35 +08:00
Zheng Xi Zhou e5ba5c3232
Merge pull request #97 from zzxwill/update-configuration
Support configuration.spec.hcl update
2021-09-06 14:09:20 +08:00
zzxwill ddabfdf926 add syntax error in the configuration hcl 2021-09-03 21:29:30 +08:00
zzxwill 372a45b1ef Support configuration.spec.hcl/json update
When hcl/json of Configuration.spec is updated, the terraform exectution
job will be deleted and recrated.
2021-09-03 18:16:30 +08:00
Zheng Xi Zhou 5b6bb21f95
Merge pull request #98 from zzxwill/credential
Check credentials and set status in Provider controller
2021-09-03 15:28:01 +08:00
zzxwill 68d38c09bf address comments
Co-authored-by: Tianxin Dong <dongtianxin.tx@alibaba-inc.com>
2021-09-03 15:02:25 +08:00
zzxwill 348503fb55 add colunmn `age` 2021-09-03 14:43:21 +08:00
zzxwill 61ff8b8ed6 add column `state` 2021-09-03 14:42:03 +08:00
zzxwill f2cbb8399d Check credentials and set status in Provider controller
In provider controller, validate credentials and set the status
in Provider controller
2021-09-03 14:38:13 +08:00
Zheng Xi Zhou 891ddf2018
Update terraform docker image (#95)
In base image, a new provider `random` is added per the request from
users
2021-09-02 21:29:47 +08:00
Zheng Xi Zhou ea35baa1a4
Add Github Action golang-lint (#93)
* Add Github Action golang-lint

Added golang-lint to check lint issues

* fix lint issues

* upgrade golangci-lint version to include more linters

* use make lint to lint code
2021-09-02 14:22:16 +08:00
Zheng Xi Zhou 0b4923c602
Merge pull request #92 from zzxwill/docs
Update Installation doc
2021-08-30 16:06:01 +08:00
zzxwill ebeac324f9 Update Installation doc
Install Terraform controller from Chart repository
2021-08-30 11:53:24 +08:00
Zheng Xi Zhou 7630ff03fb
Merge pull request #89 from zzxwill/chart-name
Rename chart name
2021-08-27 14:21:36 +08:00
zzxwill 18873399fe Rename chart name
Removed `-chart` from the chart name
2021-08-27 11:58:18 +08:00
Zheng Xi Zhou b9a76ea202
Merge pull request #85 from zzxwill/release0.1.17
Bump chart version
2021-08-25 12:41:20 +08:00
zzxwill 120000b1fe Bump chart version
Bump chart version to mark the support for Elastic Cloud
2021-08-25 12:26:51 +08:00
Zheng Xi Zhou 9d6273c808
Merge pull request #86 from zzxwill/unit-test
Fix unit-test issue
2021-08-25 12:26:04 +08:00
zzxwill dfb72a37dc Fix unit-test issue
- You are passing a Done channel to a test node to test asynchronous behavior.
This is deprecated in Ginkgo V2.  Your test will run synchronously and
the timeout will be ignored.
- Change the way to install kubebuilder
2021-08-25 12:22:08 +08:00
Zheng Xi Zhou 778649c9f8
Merge pull request #79 from mattkirby/add_ec_support
Add EC support to Terraform controller
2021-08-22 13:04:32 +08:00
kirby@puppetlabs.com 649302696f Add missing terraform block before required_providers 2021-08-20 09:27:00 -07:00
kirby@puppetlabs.com 44eeeb20b1 Update example EC to make it usable
This change updates the example EC configuration so it is possible to
use. Without this change typos and bugs prevented it from being usable.
2021-08-19 16:31:41 -07:00
mattkirby 3cf7b566fb
Update getting-started.md
Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-08-17 10:25:36 -07:00
kirby@puppetlabs.com 514fe62b0d Add EC to supported providers
This change adds EC to the supported providers section of the main readme. Additionally, getting-started is updated for EC to include documentation to generating API keys.
2021-08-16 12:55:21 -07:00
mattkirby 8b4c919b11
Update getting-started.md
Replace "EC" with "Elastic Cloud" for clarity.

Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2021-08-16 10:55:30 -07:00
kirby@puppetlabs.com d96d243327 Add EC support to Terraform controller
This change adds support for the EC module in terraform-controller. Without this change the EC provider for Elastic Cloud cannot be used via the terraform-controller because it does not know how to find secrets.
2021-08-06 16:14:55 -07:00
Zheng Xi Zhou 044bcc6494
Merge pull request #77 from yangsoon/fix
Remove connectionSecret's OwnerReferences
2021-08-04 11:36:59 +08:00
yangsoon 0a552fe393 rm connectionSecret's OwnerReferences 2021-08-03 22:54:35 +08:00
Jianbo Sun d648a5a89d
don't specify version by replace in mode (#75) 2021-07-27 16:56:46 +08:00
Zheng Xi Zhou 2f0027664e
Support AWS temporary Session Token (#70)
For production-grade security it's recommended to deploy AWS
resources using temporary credentials.

Fix #69
2021-07-22 15:11:21 +08:00
Zheng Xi Zhou 0d08af35da
Executive permissions are granted (#74)
* Executive permissions are granted (#5)

Executive permissions are granted to all Terraform providers
in the base image, so update the image.

Fix https://github.com/oam-dev/terraform-controller/issues/72

* code format
2021-07-22 15:10:51 +08:00
Zheng Xi Zhou 4f8becbe2f
Remove not supported instance type for ACK (#73)
Removed not supported instance type for CS ACK and put alibaba Terraform
configuration in different folders which can help users to provision a
single cloud resource.
2021-07-22 15:10:26 +08:00
Zheng Xi Zhou 19823f8769
Merge pull request #66 from mauricio-sardinas/master
remove owner reference for job
2021-07-22 11:29:57 +08:00
Zheng Xi Zhou 585c6a6843
Merge pull request #67 from zzxwill/more-outputs
make more outputs for ack and make a new release
2021-07-19 20:18:05 +08:00
zzxwill ab68043314 add new release 2021-07-19 11:42:20 +08:00
zzxwill 46068dfe68 Make more outputs for ack
Make more outputs like `cluster_ca_cert`, `client_cert` for ack
2021-07-19 10:19:24 +08:00
Mauricio Sardinas e0513d159b
remove owner reference for job
when the configuration is outside of the namespace of the job in this case the controllerNamespace,  the owner reference causes de job to be garbage collected and continuosly be recreated
2021-07-16 20:37:05 -05:00
Zheng Xi Zhou 63d9f92c06
Merge pull request #63 from mauricio-sardinas/master
reduce job restarts and pull images
2021-07-16 22:16:25 +08:00
Zheng Xi Zhou ec6859dddc
Merge pull request #65 from zzxwill/ack-example
ACK: output cluster name and KubeConfig
2021-07-16 13:59:30 +08:00
Zheng Xi Zhou 55fe203cd9 output cluster name and KubeConfig 2021-07-16 13:49:12 +08:00
Zheng Xi Zhou dc6bad0421
Merge pull request #64 from zzxwill/unlock-terraform
Unlock terraform state and hack kubeconfig for Alibaba ACK
2021-07-16 13:11:23 +08:00
zzxwill 1aa97c66c7 Unlock terraform state and hack kubeconfig for Alibaba ACK
Fix issue `Error acquiring the state lock`, and add a file kubeconfig
for Alibaba ack to generate a kubeconfig file
2021-07-16 11:09:19 +08:00
Mauricio Sardinas a855e4dab9
change pull policy to PullIfNotPresent
reduce the number of pull of images to avoid reaching docker hub limit
2021-07-15 20:09:59 -05:00
Mauricio Sardinas c15ef4d04b
remove ttlSecondsAfterFinished from job
causes infinite restarts and apply of terraform
2021-07-15 20:05:05 -05:00
Zheng Xi Zhou 93babfbef5
Merge pull request #62 from zzxwill/release0.1.12
New release v0.1.12
2021-07-15 17:34:53 +08:00
zzxwill f0a9ee39bb New reelase v0.1.12 2021-07-15 17:33:54 +08:00
Zheng Xi Zhou b085fb7165
Merge pull request #61 from zzxwill/update
Support Configuration update operation
2021-07-15 17:21:16 +08:00
Zheng Xi Zhou f4c2d8fbb7 Support Configuration update operation
If an Configuration is updated, the backend job
will be deleted and a new one will be regeneated,
which results in the update of Configuration object
2021-07-13 21:08:29 +08:00
Zheng Xi Zhou 8be5164acb
Support Terraform binary v1.0 (#57)
Fix #39
2021-07-09 15:41:08 +08:00
Zheng Xi Zhou 12445907af
Update getting-started when using stsToken (#59) 2021-07-09 15:40:43 +08:00
Zheng Xi Zhou a6fe392ef0
Merge pull request #55 from zzxwill/release-0.1.11
update chart version number
2021-07-08 22:34:38 +08:00
Zheng Xi Zhou ce9e8532f8 update chart version number 2021-07-08 22:18:42 +08:00
Zheng Xi Zhou b999d6f833
Support ACK provisioning (#54) 2021-07-08 18:04:16 +08:00
Zheng Xi Zhou 44a06f0ada
Support Alibaba Cloud StsToken (#50)
Implemented the support of Environment ALICLOUD_SECURITY_TOKEN to
provisio cloud resources in Alibaba Cloud.
2021-07-08 13:41:44 +08:00
Zheng Xi Zhou 4b3112cd47
Merge pull request #45 from lewis-od/service-account
Service account
2021-06-25 20:25:09 +08:00
Lewis O'Driscoll f7bb5c22d1 Rename velaNamespace to credentialsNamespace 2021-06-23 15:46:11 +01:00
Lewis O'Driscoll 32ecae62c1 Remove redundant namespace in controller template 2021-06-23 12:50:08 +01:00
Lewis O'Driscoll 354efe2a4d Apply principle of least privilege 2021-06-22 18:44:22 +01:00
Lewis O'Driscoll c0285789bc Begin implementing POLP; Provision ancilliary resources in same namespace as controller 2021-06-21 19:51:05 +01:00
Lewis O'Driscoll f47ab097df Add service-account for executor and refactor job creation 2021-06-21 14:23:03 +01:00
Lewis O'Driscoll 888eb0f6d7 Add terraform-service-account 2021-06-21 14:23:03 +01:00
Zheng Xi Zhou 6d1232dfd1
Merge pull request #43 from lewis-od/list-configmaps
Add list configmaps permission to tf-controller-role
2021-06-21 10:42:27 +08:00
Zheng Xi Zhou 2594758ddb
Merge pull request #44 from zzxwill/cve
Bump github.com/onsi/gomega to v1.12.0
2021-06-21 10:33:38 +08:00
Zheng Xi Zhou fa74ed965f Bump github.com/onsi/gomega to v1.12.0
Fix security issue:

golang: crypto/ssh: crafted authentication request can lead to nil pointer dereference
A nil pointer dereference in the golang.org/x/crypto/ssh component through v0.0.0-20201203163018-be400aefbc4c for Go allows remote attackers to cause a denial of service against SSH servers.
2021-06-21 10:27:35 +08:00
Lewis O'Driscoll 5cef223549 Add list configmaps permission to tf-controller-role 2021-06-20 18:43:27 +01:00
Zheng Xi Zhou 882dff3113
Merge pull request #35 from just-do1/deletion
update configuration deletion
2021-06-11 16:36:24 +08:00
just-do1 b77746161f Merge branch 'oam-dev:master' into deletion 2021-06-11 16:00:33 +08:00
Zheng Xi Zhou f74c4e8afb
Merge pull request #41 from zzxwill/format
Format file controllers/util/provider.go
2021-06-11 14:03:37 +08:00
Zheng Xi Zhou 7a5ab073de setup go for make reviewable 2021-06-11 09:52:15 +08:00
fandy a6b259335a remove configmapName 2021-06-11 08:56:56 +08:00
Zheng Xi Zhou 721ed02e4c upgrade go version in workflow 2021-06-10 18:16:14 +08:00
fandy 1dc1c4a69e fix the destroy job error 2021-06-10 17:52:34 +08:00
Zheng Xi Zhou a2e30f0d76 Format file
Fix the issue when running `make reviewable`
2021-06-10 15:14:08 +08:00
Zheng Xi Zhou 777c666572
Merge pull request #40 from zzxwill/release-019
Release 019
2021-06-10 14:21:59 +08:00
Zheng Xi Zhou 63ef1275d9 update READEM for cloud provider links, adjust content order 2021-06-10 14:21:12 +08:00
Zheng Xi Zhou 6067942e36 release new chart 2021-06-10 14:18:56 +08:00
Zheng Xi Zhou 1053681add
Merge pull request #37 from emanuelr93/master
Fix TF_CONTROLLER_ROLE
2021-06-10 06:45:10 +08:00
fandy 18addeeed8 update add/remove finalizer 2021-06-08 10:52:19 +08:00
fandy 94ca421560 remove crossplane 2021-06-08 08:18:01 +08:00
Emanuel Russo 509855a65c Merge branch 'master' of https://github.com/emanuelr93/terraform-controller 2021-06-07 10:27:21 +02:00
Emanuel Russo e934e2be5d fix tf_controller_role 2021-06-07 10:26:56 +02:00
fandy b37d1d2db1 update busybox image 2021-06-04 15:17:12 +08:00
fandy ef514618e0 update configuration deletion 2021-06-04 14:24:03 +08:00
Zheng Xi Zhou f03e9e9840
Merge pull request #34 from just-do1/vsphere
support vsphere
2021-06-04 11:37:11 +08:00
fandy 9648ab7a02 udpate vsphere link 2021-06-04 11:30:21 +08:00
fandy 3a8a060fe4 add link 2021-06-04 11:08:20 +08:00
fandy 3014e818e4 update doc 2021-06-04 10:54:22 +08:00
fandy de038829eb update name 2021-06-04 09:51:35 +08:00
fandy e067cacfb8 support vsphere 2021-06-04 09:46:13 +08:00
Zheng Xi Zhou 6bfea6285d
Merge pull request #30 from emanuelr93/master
Update getting-started.md. Fix typos and version
2021-05-31 19:26:46 +08:00
Emanuel Russo 24e654a641
Update getting-started.md. Fix typos and version
Update getting-started.md. Fix typos and version
2021-05-31 12:49:13 +02:00
Zheng Xi Zhou 26f133bff4
Merge pull request #29 from emanuelr93/master
Add GCP to README.md
2021-05-26 22:05:02 +08:00
Emanuel Russo cedaf7c235
Update README.md
Adding GCP to README
2021-05-26 15:59:25 +02:00
Zheng Xi Zhou 847dc1cde5
Merge pull request #27 from emanuelr93/master
Support GCP cloud resources provisioning
2021-05-26 21:52:33 +08:00
Emanuel Russo 1752f8a684 Add Apex to convert gcpCredentialsJson to string 2021-05-25 18:17:24 +02:00
Emanuel Russo a5867ac1e8 Fix typo 2021-05-25 11:12:39 +02:00
Emanuel Russo 267ac866c8 Add clarification about GCP Auth 2021-05-25 11:12:12 +02:00
Emanuel Russo 58c4a94a8b Remove wrong example 2021-05-25 11:07:33 +02:00
Emanuel Russo c6f3ea4f14 Add newline at end of files 2021-05-25 11:07:05 +02:00
Emanuel Russo 4bcc5d5223 Remove RBAC Apply 2021-05-25 11:00:54 +02:00
Emanuel Russo fd83d579b8 remove k alias for kubectl in getting-started.md file 2021-05-25 10:58:16 +02:00
Emanuel Russo c0cd5cd70c Fix typo 2021-05-25 10:07:50 +02:00
Emanuel Russo 804582c7b4
Merge branch 'master' into master 2021-05-25 10:01:51 +02:00
Zheng Xi Zhou a773e89d61
Support Azure cloud resources provisioning (#25)
* Support Azure cloud resources provisioning

Supported Azure provider, added docs and samples

* update README

* address comments
2021-05-25 15:57:10 +08:00
Emanuel Russo 6debdb1c04 Add GCP Support for Terraform Controller 2021-05-25 09:11:01 +02:00
Zheng Xi Zhou e867cb9bea
Merge pull request #24 from cnbailian/fix-typo
fix DESIGN.md typo
2021-05-21 19:37:01 +08:00
Zheng Xi Zhou 9ec005c926
Merge pull request #23 from zzxwill/golang-version
bump golang version to 1.16
2021-05-21 19:35:13 +08:00
cnbailian 0edfc35155 fix DESIGN.md typo 2021-05-21 15:59:17 +08:00
Zheng Xi Zhou a8beaa2802 bump golang version to 1.16 2021-05-21 10:46:15 +08:00
Zheng Xi Zhou a6d9c52811
Merge pull request #22 from zzxwill/master
bump chart version to 0.1.7
2021-05-20 16:51:02 +08:00
Zheng Xi Zhou 6a28def64a bump chart version to 0.1.7 2021-05-20 16:50:20 +08:00
Zheng Xi Zhou 5a99a11348
Merge pull request #21 from zzxwill/master
bump chart version
2021-05-20 16:49:20 +08:00
Zheng Xi Zhou c5e5dda487 bump chart version 2021-05-20 16:48:46 +08:00
Zheng Xi Zhou 30f1870a69
Merge pull request #20 from zzxwill/provider
Configuration can reference to Provider
2021-05-20 10:10:28 +08:00
Zheng Xi Zhou ae13027fb6 Configuration can reference to Provider
Fix #11
2021-05-20 10:04:33 +08:00
Zheng Xi Zhou 0fb9007d1d
Merge pull request #19 from zzxwill/pod
Delete orphaned pod after job is deleted
2021-05-20 09:56:49 +08:00
Zheng Xi Zhou c592958634 Delete orphaned pod after job is deleted
Use deletionOption DeletePropagationBackground tag to delete all
child Pods of a job when the job is about to be cleaned up.
2021-05-19 11:34:42 +08:00
Zheng Xi Zhou 964b58ff38
Merge pull request #18 from zzxwill/status-event
Add more status.state for Configuration
2021-05-13 14:14:46 +08:00
Zheng Xi Zhou 11e27bf311 change .status to be a subresource 2021-05-13 13:45:16 +08:00
Zheng Xi Zhou 58390ca56e Add more status.state for Configuration
Also add volume "state" for "kubectl get
Configurations"
2021-05-11 19:12:56 +08:00
Zheng Xi Zhou 1d003e80c3
Merge pull request #17 from zzxwill/master
Fix terraform tfstate issue
2021-04-27 15:33:55 +08:00
Zheng Xi Zhou 6e90b7a9af Fix terrafrom tfstate issue
Grant pod to manipulate leases of coordination.k8s.io
2021-04-27 15:30:48 +08:00
Zheng Xi Zhou f771c0fe5d Merge branch 'master' of https://github.com/oam-dev/terraform-controller 2021-04-27 12:04:35 +08:00
Zheng Xi Zhou 997a5f361f release new helm chart 2021-04-26 16:17:43 +08:00
Zheng Xi Zhou 41f1569faa
Merge pull request #16 from zzxwill/doc
Update README
2021-04-26 16:13:28 +08:00
Zheng Xi Zhou 9ec35bef60 Update README
Updated README doc per the release of helm chart, also split it
into getting started doc and design doc
2021-04-26 16:12:15 +08:00
Zheng Xi Zhou 05f6022a04
Merge pull request #13 from zzxwill/k8s-tf-state
Use Kubernetes backend to replace cm tf-state
2021-04-26 15:33:32 +08:00
Zheng Xi Zhou 3d6bf56916 update docs 2021-04-26 15:26:55 +08:00
Zheng Xi Zhou c08ae217b7 clean up tfstate related code 2021-04-26 15:26:55 +08:00
Zheng Xi Zhou fc6eccdac4 Use Kubernetes backend to replace cm tf-state
Fix #3
2021-04-26 15:24:08 +08:00
Zheng Xi Zhou bfcd8e23a3
Merge pull request #15 from zzxwill/install
Generate helm chart for the controller
2021-04-26 15:02:00 +08:00
Zheng Xi Zhou 0faacb9a73 make chart by also inlcuding rbac manifests 2021-04-26 15:01:26 +08:00
Zheng Xi Zhou 9fd5ca27fe Add a Helm chart template and build it by `make chart 2021-04-26 14:54:45 +08:00
Zheng Xi Zhou 9e53edc6a7 Generate helm chart for the controller
- docker image build/push
- generate crds in chart folder
- chart package
2021-04-26 14:54:44 +08:00
Zheng Xi Zhou 887dcd15cc
Merge pull request #14 from zzxwill/doc
Update information on docker oam-dev/docker-terraform
2021-04-25 19:54:34 +08:00
Zheng Xi Zhou 9581dfdc9f Update information on docker oam-dev/docker-terraform
Moved the docker image source from github.com/zzxwill/broadinstitute-docker-terraform
to github.com/oam-dev/docker-terraform
2021-04-25 15:27:20 +08:00
Zheng Xi Zhou d89c4c2507
Merge pull request #10 from zzxwill/examples
Address some corner and expand examples
2021-04-20 14:55:20 +08:00
Zheng Xi Zhou 950a99f2f0 Address some corner and expand examples
Cover configuration cases without variable and
writeConnectionSecretToRef. Also added and updated
some examples.
2021-04-19 21:05:13 +08:00
Zheng Xi Zhou 5261c74ac1
Merge pull request #9 from zzxwill/provider
Set SecretReference.namespace to omitempty
2021-04-15 21:44:19 +08:00
Zheng Xi Zhou cd233e8160 Set SecretReference.namespace to omitempty
In KubeVela, the namespace can be automatically set by vela core,
so set it optional for developer
2021-04-15 21:33:46 +08:00
212 changed files with 21815 additions and 1973 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

48
.github/workflows/chart.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: HelmChart
on:
push:
tags:
- "v*"
workflow_dispatch: {}
jobs:
publish-charts:
env:
HELM_CHART: chart/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.4.0
- name: Get the version
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
- name: Tag helm chart image
run: |
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: |
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

73
.github/workflows/e2e-test.yml vendored Normal file
View File

@ -0,0 +1,73 @@
name: E2E Test
on:
push:
branches:
- master
tags:
- v*
workflow_dispatch: {}
pull_request:
branches:
- master
env:
GO_VERSION: '1.23.8'
KIND_VERSION: 'v0.12.0'
jobs:
e2e-tests:
runs-on: ubuntu-22.04
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Setup Kind
uses: engineerd/setup-kind@v0.5.0
with:
version: ${{ env.KIND_VERSION }}
skipClusterCreation: true
- name: Setup Kind Cluster
run: |
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 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
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./e2e-coverage1.xml
flags: e2e

52
.github/workflows/format.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Format
on:
push:
branches:
- master
- release-*
workflow_dispatch: {}
pull_request:
branches:
- master
- release-*
env:
# Common versions
GO_VERSION: '1.23.8'
KIND_VERSION: 'v0.7.0'
jobs:
detect-noop:
runs-on: ubuntu-22.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@v3.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
paths_ignore: '["**.md", "**.png", "**.jpg"]'
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false
make-reviewable:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Make reviewable
run: make reviewable

View File

@ -1,95 +0,0 @@
name: Go
on:
push:
branches:
- master
- release-*
workflow_dispatch: {}
pull_request:
branches:
- master
- release-*
env:
# Common versions
GO_VERSION: '1.14'
GOLANGCI_VERSION: 'v1.39'
DOCKER_BUILDX_VERSION: 'v0.4.2'
KIND_VERSION: 'v0.7.0'
jobs:
detect-noop:
runs-on: ubuntu-20.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@v3.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
paths_ignore: '["**.md", "**.png", "**.jpg"]'
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false
unit-tests:
runs-on: ubuntu-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
submodules: true
- name: Cache Go Dependencies
uses: actions/cache@v2
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Install ginkgo
run: |
sudo apt-get install -y golang-ginkgo-dev
- name: Setup Kind Cluster
uses: engineerd/setup-kind@v0.5.0
with:
version: ${{ env.KIND_VERSION }}
- name: install Kubebuilder
uses: wonderflow/kubebuilder-action@v1.1
- name: Run Make test
run: make test
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.txt
flags: unittests
name: codecov-umbrella
make-reviewable:
runs-on: ubuntu-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Make reviewable
run: make reviewable

View File

@ -1,4 +1,4 @@
name: license
name: License
on:
push:
branches:

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

69
.github/workflows/unit-test.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Unit Test
on:
push:
branches:
- master
- release-*
tags:
- v*
workflow_dispatch: {}
pull_request:
branches:
- master
- release-*
env:
# Common versions
GO_VERSION: '1.23.8'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: golangci-lint
run: |
make lint
unit-tests:
runs-on: ubuntu-22.04
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
submodules: true
- name: Cache Go Dependencies
uses: actions/cache@v4
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Install Kubebuilder
run: |
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_linux_amd64.tar.gz
tar -zxf kubebuilder_2.3.2_linux_amd64.tar.gz
sudo mv kubebuilder_2.3.2_linux_amd64 /usr/local/kubebuilder
- name: Run Make test
run: make test
- name: Upload coverage report
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./ut-coverage1.xml
flags: unit

27
.gitignore vendored
View File

@ -1,6 +1,4 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
@ -10,27 +8,28 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
e2e-coverage1.xml
ut-coverage1.xml
# Dependency directories (remove the comment below to include it)
# vendor/
.idea/
.vscode/
.vela/
.DS_Store
examples/tf-native/alibaba/json/terraform.tfstate
examples/tf-native/alibaba/json/terraform.tfstate.backup
examples/tf-native/alibaba/json/.terraform
examples/tf-native/alibaba/json/.terraform.lock.hcl
# Terraform
terraform.tfstate
terraform.tfstate.backup
.terraform
.terraform.lock.hcl
terraform-controller-*
examples/tf-native/alibaba/cs/kubeconfig
examples/tf-native/alibaba/hcl/terraform.tfstate
examples/tf-native/alibaba/hcl/terraform.tfstate.backup
examples/tf-native/alibaba/hcl/.terraform
examples/tf-native/alibaba/hcl/.terraform.lock.hcl
bin/manager
examples/tf-native/aws/hcl/terraform.tfstate
examples/tf-native/aws/hcl/terraform.tfstate.backup
examples/tf-native/aws/hcl/.terraform
examples/tf-native/aws/hcl/.terraform.lock.hcl
# Secret for git server
examples/git-credentials/git-ssh-auth-secret.yaml

215
.golangci.yml Normal file
View File

@ -0,0 +1,215 @@
run:
timeout: 10m
skip-files:
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
formats: colored-line-number
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false
# [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
exclude-functions: fmt:.*,io/ioutil:^Read.*
exhaustive:
# indicates that switch statements are to be considered exhaustive if a
# 'default' case is present, even if all enum members aren't listed in the
# switch
default-signifies-exhaustive: true
govet:
# report about shadowed variables
check-shadowing: false
revive:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 32
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 5
lll:
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
makezero:
# Allow only slices initialized with a length of zero. Default is false.
always: false
linters:
enable:
- govet
- gocyclo
- gocritic
- goconst
- goimports
- gofmt # We enable this as well as goimports for its simplify mode.
- revive
- unconvert
- misspell
- nakedret
- staticcheck
- gosimple
- unused
presets:
- bugs
- unused
fast: false
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.
- path: _test(ing)?\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- exportloopref
- unparam
# Ease some gocritic warnings on test files.
- path: _test\.go
text: "(unnamedResult|exitAfterDefer)"
linters:
- gocritic
# These are performance optimisations rather than style issues per se.
# They warn when function arguments or range values copy a lot of memory
# rather than using a pointer.
- text: "(hugeParam|rangeValCopy):"
linters:
- gocritic
# This "TestMain should call os.Exit to set exit code" warning is not clever
# enough to notice that we call a helper method that calls os.Exit.
- text: "SA3000:"
linters:
- staticcheck
- text: "k8s.io/api/core/v1"
linters:
- goimports
# This is a "potential hardcoded credentials" warning. It's triggered by
# any variable with 'secret' in the same, and thus hits a lot of false
# positives in Kubernetes land where a Secret is an object type.
- text: "G101:"
linters:
- gosec
- gas
# This is an 'errors unhandled' warning that duplicates errcheck.
- text: "G104:"
linters:
- gosec
- gas
# The Azure AddToUserAgent method appends to the existing user agent string.
# It returns an error if you pass it an empty string lettinga you know the
# user agent did not change, making it more of a warning.
- text: \.AddToUserAgent
linters:
- errcheck
- text: "don't use an underscore"
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`.
# Default value for this option is true.
exclude-use-default: false
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
new: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

161
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,161 @@
# Contributing to Terraform Controller
Thanks for contributing to Terraform Controller!
## Prerequisites
- Go
Go version `>=1.17` is required.
- Helm Cli (optional)
Refer to [Helm official Doc](https://helm.sh/docs/intro/install/) to install `helm` Cli.
## How to start up the project
- Apply CRDs to a Kubernetes cluster
```shell
$ make install
go: creating new go.mod: module tmp
...
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 created
customresourcedefinition.apiextensions.k8s.io/providers.terraform.core.oam.dev created
```
- Run Terraform Controller
```shell
$ make run
go: creating new go.mod: module tmp
...
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 ./...
/Users/zhouzhengxi/go/bin/controller-gen "crd:trivialVersions=true" webhook paths="./..." output:crd:artifacts:config=chart/crds
go run ./main.go
I0227 12:15:32.890376 38818 request.go:668] Waited for 1.001811845s due to client-side throttling, not priority and fairness, request: GET:https://47.242.126.78:6443/apis/apigatewayv2.aws.crossplane.io/v1alpha1?timeout=32s
```
## An development example for Cloud Resources Management
Let's take Alibaba Cloud as an example.
### Apply Provider credentials
```shell
$ export ALICLOUD_ACCESS_KEY=xxx; export ALICLOUD_SECRET_KEY=yyy
```
If you'd like to use Alicloud Security Token Service, also export `ALICLOUD_SECURITY_TOKEN`.
```shell
$ export ALICLOUD_SECURITY_TOKEN=zzz
```
```
$ make alibaba
```
### Apply Terraform Configuration
Apply [OSS Terraform Configuration](./examples/alibaba/oss/configuration_hcl_bucket.yaml) to provision an Alibaba OSS bucket.
```shell
$ kubectl get configuration.terraform.core.oam.dev
NAME AGE
alibaba-oss 1h
$ kubectl get configuration.terraform.core.oam.dev alibaba-oss -o yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"terraform.core.oam.dev/v1beta1","kind":"Configuration","metadata":{"annotations":{},"name":"alibaba-oss","namespace":"default"},"spec":{"JSON":"{\n \"resource\": {\n \"alicloud_oss_bucket\": {\n \"bucket-acl\": {\n \"bucket\": \"${var.bucket}\",\n \"acl\": \"${var.acl}\"\n }\n }\n },\n \"output\": {\n \"BUCKET_NAME\": {\n \"value\": \"${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}\"\n }\n },\n \"variable\": {\n \"bucket\": {\n \"default\": \"poc\"\n },\n \"acl\": {\n \"default\": \"private\"\n }\n }\n}\n","variable":{"acl":"private","bucket":"vela-website"},"writeConnectionSecretToRef":{"name":"oss-conn","namespace":"default"}}}
creationTimestamp: "2021-04-02T08:17:08Z"
generation: 2
spec:
...
variable:
acl: private
bucket: vela-website
writeConnectionSecretToRef:
name: oss-conn
namespace: default
status:
outputs:
BUCKET_NAME:
type: string
value: vela-website.oss-cn-beijing.aliyuncs.com
state: provisioned
```
Use [ossutil cli](https://www.alibabacloud.com/help/en/doc-detail/207217.htm) to check whether OSS bucket is provisioned.
```shell
$ ossutil ls oss://
CreationTime Region StorageClass BucketName
2021-04-10 00:42:09 +0800 CST oss-cn-beijing Standard oss://vela-website
Bucket Number is: 1
0.146789(s) elapsed
```
- Check the generated connection secret
```shell
$ kubectl get secret oss-conn
NAME TYPE DATA AGE
oss-conn Opaque 1 2m41s
```
### Update Configuration
Change the OSS ACL to `public-read`.
```yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: alibaba-oss
spec:
...
variable:
...
acl: "public-read"
```
### Delete Configuration
Delete the configuration will destroy the OSS cloud resource.
```shell
$ kubectl delete configuration.terraform.core.oam.dev alibaba-oss
configuration.terraform.core.oam.dev "alibaba-oss" deleted
$ ossutil ls oss://
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
```

65
DESIGN.md Normal file
View File

@ -0,0 +1,65 @@
# Design
![](docs/resources/architecture.png)
## Components
### Provider
The `Provider` object is used to accept credentials from a Cloud provider, like Alibaba Cloud or AWS. For example, `ALICLOUD_ACCESS_KEY`,
`ALICLOUD_SECRET_KEY` from `Provider` will be used by `terraform init`.
This component is inspired by [Crossplane runtime](https://crossplane.io/), which can support various cloud providers.
### Configuration
The `Configuration` object is used to accept Terraform HCL/JSON configuration provisioning, updating and deletion. It covers
the whole lifecycle of a cloud resource.
- Configuration init component
This init component will retrieve HCL/JSON configuration from the object and store it to ConfigMap `aliyun-${ConfigurationName}-tf-input`.
During creation stage, it will mount the ConfigMap to a volume and copy the Terraform configuration file to the working directory.
During update stage, it will mount Terraform state file ConfigMap `aliyun-${ConfigurationName}-tf-state`, which will be generated
after a cloud resource is successfully provisioned, to the volume and copy it to the working directory.
This component is taken upon by container `pause`.
- Terraform configuration executor component
This executor component will perform `terraform init` and `terraform apply`. After a cloud resource is successfully provisioned,
Terraform state file will be generated.
This executor is job, which has the ability to retry and auto-recovery from failures.
It's taken upon by container oam-dev/docker-terraform:0.14.10, which is built from [oamdev/docker-terraform](https://github.com/oam-dev/docker-terraform.git).
- Terraform state file retriever
This component is relatively simple, which will monitor the generation of Terraform state file. Upon the state file is
generated, it will store the file content to ConfigMap `aliyun-${ConfigurationName}-tf-state`, which will be used during
`Configuration` update and deletion stage.
This component is taken upon by the container zzxwill/terraform-tfstate-retriever:v0.2, which built from [terraform-tfstate-retriever](https://github.com/zzxwill/terraform-tfstate-retriever).
## Technical alternatives
### Why taking Crossplane ProviderConfiguration as cloud credentials Provider?
As Terraform controller is intended to support various Cloud providers, like AWS, Azure, Alibaba Cloud, GCP, and VMWare.
Crossplane new `ProviderConfiguration` is known as it mature model for these cloud providers. By utilizing the model, this
controller can support various cloud providers at the very first day.
### Why choosing ConfigMap as the storage system over cloud shared disks or Object storage system?
By using ConfigMap to store terraform configuration files and generated state file will be a generic way for nearly all
Kubernetes clusters.
By using cloud shared volumes/Object Storage System(like Alibaba OSS, and AWS S3), it's straight forward as terraform
HCL/JSON configuration and generated state are files. But we have to adapt to various cloud providers with various storage
solution like cloud disk or OSS to Alibaba Cloud, s3 to AWS.
Here is a drawback for the choice: we have to grant the Pod in the Job to create ConfigMaps.

View File

@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.13 as builder
FROM golang:1.23-alpine as builder
WORKDIR /workspace
# Copy the Go Modules manifests
@ -19,9 +19,14 @@ 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 gcr.io/distroless/static:nonroot
FROM alpine
WORKDIR /
COPY --from=builder /workspace/manager .
USER nonroot:nonroot
#USER nonroot:nonroot
# COPY terraform binary
COPY bin/terraform /usr/bin/terraform
#RUN chmod +x /usr/bin/terraform
RUN apk add git
ENTRYPOINT ["/manager"]

201
Makefile
View File

@ -1,8 +1,15 @@
# Image URL to use all building/pushing image targets
IMG ?= controller:latest
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)
GREEN := $(shell printf "\033[32m")
CNone := $(shell printf "\033[0m")
OK = echo ${TIME} ${GREEN}[ OK ]${CNone}
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@ -15,7 +22,7 @@ all: manager
# Run tests
test: generate fmt vet manifests
go test ./... -coverprofile cover.out
go test -coverprofile=ut-coverage1.xml ./controllers/...
# Build manager binary
manager: generate fmt vet
@ -27,11 +34,11 @@ run: generate fmt vet manifests
# Install CRDs into a cluster
install: manifests
kustomize build config/crd | kubectl apply -f -
kubectl apply -f chart/crds
# Uninstall CRDs from a cluster
uninstall: manifests
kustomize build config/crd | kubectl delete -f -
kustomize build chart/crds | kubectl delete -f -
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
deploy: manifests
@ -40,7 +47,7 @@ deploy: manifests
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
$(CONTROLLER_GEN) $(CRD_OPTIONS) webhook paths="./..." output:crd:artifacts:config=chart/crds
# Run go fmt against code
fmt: goimports
@ -62,6 +69,10 @@ docker-build: test
docker-push:
docker push ${IMG}
# Make helm chart
chart: docker-build docker-push
helm package chart --destination .
# find or download controller-gen
# download controller-gen if necessary
controller-gen:
@ -71,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.2.5 ;\
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.5 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
@ -79,7 +90,7 @@ else
CONTROLLER_GEN=$(shell which controller-gen)
endif
GOLANGCILINT_VERSION ?= v1.31.0
GOLANGCILINT_VERSION ?= v1.60.1
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
HOSTARCH := $(shell uname -m)
ifeq ($(HOSTARCH),x86_64)
@ -87,16 +98,20 @@ HOSTARCH := amd64
endif
golangci:
ifeq (, $(shell which golangci-lint))
ifneq ($(shell which golangci-lint),)
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(shell which golangci-lint)
else ifeq (, $(shell which $(GOBIN)/golangci-lint))
@{ \
set -e ;\
echo 'installing golangci-lint-$(GOLANGCILINT_VERSION)' ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) $(GOLANGCILINT_VERSION) ;\
echo 'Install succeed' ;\
echo 'Successfully installed' ;\
}
GOLANGCILINT=$(GOBIN)/golangci-lint
else
GOLANGCILINT=$(shell which golangci-lint)
@$(OK) golangci-lint is already installed
GOLANGCILINT=$(GOBIN)/golangci-lint
endif
lint: golangci
@ -116,9 +131,169 @@ 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
GOIMPORTS=$(shell which goimports)
endif
endif
install-chart:
helm lint ./chart
helm upgrade --install --create-namespace --namespace terraform terraform-controller ./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"
exit 1
endif
ifeq (, $(ALICLOUD_SECRET_KEY))
@echo "Environment variable ALICLOUD_SECRET_KEY is not set"
exit 1
endif
echo "accessKeyID: ${ALICLOUD_ACCESS_KEY}\naccessKeySecret: ${ALICLOUD_SECRET_KEY}\nsecurityToken: ${ALICLOUD_SECURITY_TOKEN}" > alibaba-credentials.conf
kubectl create secret generic alibaba-account-creds -n vela-system --from-file=credentials=alibaba-credentials.conf
rm -f alibaba-credentials.conf
kubectl get secret -n vela-system alibaba-account-creds
alibaba-provider:
kubectl apply -f examples/alibaba/provider.yaml
alibaba: alibaba-credentials alibaba-provider
aws-credentials:
ifeq (, $(AWS_ACCESS_KEY_ID))
@echo "Environment variable AWS_ACCESS_KEY_ID is not set"
exit 1
endif
ifeq (, $(AWS_SECRET_ACCESS_KEY))
@echo "Environment variable AWS_SECRET_ACCESS_KEY is not set"
exit 1
endif
# refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs
echo "awsAccessKeyID: ${AWS_ACCESS_KEY_ID}\nawsSecretAccessKey: ${AWS_SECRET_ACCESS_KEY}\nawsSessionToken: ${AWS_SESSION_TOKEN}" > aws-credentials.conf
kubectl create secret generic aws-account-creds -n vela-system --from-file=credentials=aws-credentials.conf
rm -f aws-credentials.conf
aws-provider:
kubectl apply -f examples/aws/provider.yaml
aws: aws-credentials aws-provider
azure-credentials:
ifeq (, $(ARM_CLIENT_ID))
@echo "Environment variable ARM_CLIENT_ID is not set"
exit 1
endif
ifeq (, $(ARM_CLIENT_SECRET))
@echo "Environment variable ARM_CLIENT_SECRET is not set"
exit 1
endif
ifeq (, $(ARM_SUBSCRIPTION_ID))
@echo "Environment variable ARM_SUBSCRIPTION_ID is not set"
exit 1
endif
ifeq (, $(ARM_TENANT_ID))
@echo "Environment variable ARM_TENANT_ID is not set"
exit 1
endif
echo "armClientID: ${ARM_CLIENT_ID}\narmClientSecret: ${ARM_CLIENT_SECRET}\narmSubscriptionID: ${ARM_SUBSCRIPTION_ID}\narmTenantID: ${ARM_TENANT_ID}" > azure-credentials.conf
kubectl create secret generic azure-account-creds -n vela-system --from-file=credentials=azure-credentials.conf
rm -f azure-credentials.conf
azure-provider:
kubectl apply -f examples/azure/provider.yaml
azure: azure-credentials azure-provider
ucloud-credentials:
ifeq (, $(UCLOUD_PRIVATE_KEY))
@echo "Environment variable UCLOUD_PRIVATE_KEY is not set"
exit 1
endif
ifeq (, $(UCLOUD_PUBLIC_KEY))
@echo "Environment variable UCLOUD_PUBLIC_KEY is not set"
exit 1
endif
ifeq (, $(UCLOUD_PROJECT_ID))
@echo "Environment variable UCLOUD_PROJECT_ID is not set"
exit 1
endif
ifeq (, $(UCLOUD_REGION))
@echo "Environment variable UCLOUD_REGION is not set"
exit 1
endif
echo "publicKey: ${UCLOUD_PUBLIC_KEY}\nprivateKey: ${UCLOUD_PRIVATE_KEY}\nregion: ${UCLOUD_REGION}\nprojectID: ${UCLOUD_PROJECT_ID}" > ucloud-credentials.conf
kubectl create secret generic ucloud-account-creds -n vela-system --from-file=credentials=ucloud-credentials.conf
rm -f ucloud-credentials.conf
ucloud-provider:
kubectl apply -f examples/ucloud/provider.yaml
ucloud: ucloud-credentials ucloud-provider
custom-credentials:
echo "Token: mytoken" > custom-credentials.conf
kubectl create secret generic custom-account-creds -n vela-system --from-file=credentials=custom-credentials.conf
rm -f custom-credentials.conf
custom-provider:
kubectl apply -f examples/custom/provider.yaml
custom: custom-credentials custom-provider
configuration:
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
e2e: e2e-setup configuration
e2e-gitee:
go test -coverprofile=e2e-gitee-coverage1.xml -v ./gitee/...
tencent-credentials:
ifeq (, $(TENCENTCLOUD_SECRET_ID))
@echo "Environment variable TENCENTCLOUD_SECRET_ID is not set"
exit 1
endif
ifeq (, $(TENCENTCLOUD_SECRET_KEY))
@echo "Environment variable TENCENTCLOUD_SECRET_KEY is not set"
exit 1
endif
echo "secretID: ${TENCENTCLOUD_SECRET_ID}\nsecretKey: ${TENCENTCLOUD_SECRET_KEY}" > tencent-credentials.conf
kubectl create secret generic tencent-account-creds -n vela-system --from-file=credentials=tencent-credentials.conf
rm -f tencent-credentials.conf
kubectl get secret -n vela-system tencent-account-creds
tencent-provider:
kubectl apply -f examples/tencent/provider.yaml
tencent: tencent-credentials tencent-provider

462
README.md
View File

@ -1,6 +1,10 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/oam-dev/terraform-controller)](https://goreportcard.com/report/github.com/oam-dev/terraform-controller)
![Docker Pulls](https://img.shields.io/docker/pulls/oamdev/terraform-controller)
[![codecov](https://codecov.io/gh/oam-dev/terraform-controller/branch/master/graph/badge.svg)](https://codecov.io/gh/oam-dev/terraform-controller)
# Terraform Controller
Terraform Controller is a Kubernetes Controller for Terraform, which can address the requirement of [Using Terraform HCL as IaC module in KubeVela](https://github.com/oam-dev/kubevela/issues/698)
Terraform Controller is a Kubernetes Controller for Terraform.
![](docs/resources/architecture.png)
@ -8,452 +12,32 @@ Terraform Controller is a Kubernetes Controller for Terraform, which can address
## Supported Cloud Providers
- Alibaba Cloud
- AWS
| Cloud Provider | Contributor |
|----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|
| [Alibaba Cloud](https://www.alibabacloud.com/) | KubeVela team |
| [AWS](https://aws.amazon.com/) | KubeVela team |
| [Azure](https://portal.azure.com/) | KubeVela team |
| [Elastic Cloud](https://www.elastic.co/) | [@mattkirby](https://github.com/mattkirby) |
| [GCP](https://cloud.google.com/) | [@emanuelr93](https://github.com/emanuelr93) |
| [VMware vSphere](https://www.vmware.com/hk/products/vsphere.html) | [@just-do1](https://github.com/just-do1) |
| [UCloud](https://www.ucloud.cn/) | [@wangwang](https://github.com/wangwang) |
| [Custom](https://github.com/oam-dev/terraform-controller/blob/master/examples/custom/configuration_hcl_example.yaml) | [@evanli18](https://github.com/evanli18) |
| [Tencent Cloud](https://cloud.tencent.com/) | [@captainroy-hy](https://github.com/captainroy-hy) |
| [Baidu Cloud](https://cloud.baidu.com/) | KubeVela team |
## Supported Terraform Configuration
- HCL
- JSON
# Design
## Components
### Provider
The `Provider` object is used to accept credentials from a Cloud provider, like Alibaba Cloud or AWS. For example, `ALICLOUD_ACCESS_KEY`,
`ALICLOUD_SECRET_KEY` from `Provider` will be used by `terraform init`.
This component is inspired by [Crossplane runtime](https://crossplane.io/), which can support various cloud providers.
### Configuration
The `Configuration` object is used to accept Terraform HCL/JSON configuration provisioning, updating and deletion. It covers
the whole lifecycle of a cloud resource.
- Configuration init component
This init component will retrieve HCL/JSON configuration from the object and store it to ConfigMap `aliyun-${ConfigurationName}-tf-input`.
During creation stage, it will mount the ConfigMap to a volume and copy the Terraform configuration file to the working directory.
During update stage, it will mount Terraform state file ConfigMap `aliyun-${ConfigurationName}-tf-state`, which will be generated
after a cloud resource is successfully provisioned, to the volume and copy it to the working directory.
This component is taken upon by container `pause`.
- Terraform configuration executor component
This executor component will perform `terrform init` and `terraform apply`. After a cloud resource is successfully provisioned,
Terraform state file will be generated.
This executor is job, which has the ability to retry and auto-recovery from failures.
It's taken upon by container zzxwill/docker-terraform:0.14.10, which is built from [zzxwill/broadinstitute-docker-terraform](https://github.com/zzxwill/broadinstitute-docker-terraform.git).
- Terraform state file retriever
This component is relatively simple, which will monitor the generation of Terraform state file. Upon the state file is
generated, it will store the file content to ConfigMap `aliyun-${ConfigurationName}-tf-state`, which will be used during
`Configuration` update and deletion stage.
This component is taken upon by the container zzxwill/terraform-tfstate-retriever:v0.2, which built from [terraform-tfstate-retriever](https://github.com/zzxwill/terraform-tfstate-retriever).
## Technical alternatives
### Why taking Crossplane ProviderConfiguration as cloud credentials Provider?
As Terraform controller is intended to support various Cloud providers, like AWS, Azure, Alibaba Cloud, GCP, and VMWare.
Crossplane new `ProviderConfiguration` is known as it mature model for these cloud providers. By utilizing the model, this
controller can support various cloud providers at the very first day.
### Why choosing ConfigMap as the storage system over cloud shared disks or Object storage system?
By using ConfigMap to store terraform configuration files and generated state file will be a generic way for nearly all
Kubernetes clusters.
By using cloud shared volumes/Object Storage System(like Alibaba OSS, and AWS S3), it's straight forward as terraform
HCL/JSON configuration and generated state are files. But we have to adapt to various cloud providers with various storage
solution like cloud disk or OSS to Alibaba Cloud, s3 to AWS.
Here is a drawback for the choice: we have to grant the Pod in the Job to create ConfigMaps.
- JSON (Deprecated in v0.3.1, removed in v0.4.6)
# Get started
- Install the controller
See our [Getting Started](./getting-started.md) guide please.
## Alibaba Cloud
# Design
### Locally run Terraform Controller
Please refer to [Design](./DESIGN.md).
Get the codebase from [release v0.1-alpha.1](https://github.com/zzxwill/terraform-controller/releases/tag/v0.1-alpha.1),
and run it locally.
# Contributing
### Apply Provider configuration
```shell
$ export ALICLOUD_ACCESS_KEY=xxx; export ALICLOUD_SECRET_KEY=yyy
$ sh hack/prepare-alibaba-credentials.sh
$ kubectl get secret -n vela-system
NAME TYPE DATA AGE
alibaba-account-creds Opaque 1 11s
$ k apply -f examples/alibaba/provider.yaml
provider.terraform.core.oam.dev/default created
```
### Authenticate pods to create ConfigMaps
Terraform state file is essential to update or destroy cloud resources. After terraform execution completes, its state file
needs to be stored to a ConfigMap.
```shell
$ kubectl apply -f examples/rbac.yaml
clusterrole.rbac.authorization.k8s.io/tf-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/tf-binding created
```
### Apply Terraform Configuration
Apply Terraform configuration [configuration_hcl_oss.yaml](./examples/alibaba/configuration_hcl_oss.yaml) (JSON configuration [configuration_oss.yaml](./examples/alibaba/configuration_json_oss.yaml) is also supported) to provision an Alibaba OSS bucket.
```yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: aliyun-oss
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" {
default = "vela-website"
}
variable "acl" {
default = "private"
}
variable:
bucket: "vela-website"
acl: "private"
writeConnectionSecretToRef:
name: oss-conn
namespace: default
```
```shell
$ kubectl get configuration.terraform.core.oam.dev
NAME AGE
aliyun-oss 1h
$ kubectl get configuration.terraform.core.oam.dev aliyun-oss -o yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"terraform.core.oam.dev/v1beta1","kind":"Configuration","metadata":{"annotations":{},"name":"aliyun-oss","namespace":"default"},"spec":{"JSON":"{\n \"resource\": {\n \"alicloud_oss_bucket\": {\n \"bucket-acl\": {\n \"bucket\": \"${var.bucket}\",\n \"acl\": \"${var.acl}\"\n }\n }\n },\n \"output\": {\n \"BUCKET_NAME\": {\n \"value\": \"${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}\"\n }\n },\n \"variable\": {\n \"bucket\": {\n \"default\": \"poc\"\n },\n \"acl\": {\n \"default\": \"private\"\n }\n }\n}\n","variable":{"acl":"private","bucket":"vela-website"},"writeConnectionSecretToRef":{"name":"oss-conn","namespace":"default"}}}
creationTimestamp: "2021-04-02T08:17:08Z"
generation: 2
spec:
...
variable:
acl: private
bucket: vela-website
writeConnectionSecretToRef:
name: oss-conn
namespace: default
status:
outputs:
BUCKET_NAME:
type: string
value: vela-website.oss-cn-beijing.aliyuncs.com
state: provisioned
```
### Looking into Configuration (optional)
#### Watch the job to complete
```shell
$ kubectl get job
NAME COMPLETIONS DURATION AGE
aliyun-oss-apply 1/1 12s 94s
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
aliyun-oss-apply-5c8b6 0/2 Completed 0 111s
$ kubectl logs aliyun-oss-rllx4 terraform-executor
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/alicloud...
- Installing hashicorp/alicloud v1.119.1...
- Installed hashicorp/alicloud v1.119.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Warning: Additional provider information from registry
The remote registry returned warnings for
registry.terraform.io/hashicorp/alicloud:
- For users on Terraform 0.13 or greater, this provider has moved to
aliyun/alicloud. Please update your source in required_providers.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
alicloud_oss_bucket.bucket-acl: Creating...
alicloud_oss_bucket.bucket-acl: Creation complete after 3s [id=vela-website]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
BUCKET_NAME = "vela-website.oss-cn-beijing.aliyuncs.com"
```
OSS bucket is provisioned.
```shell
$ ossutil ls oss://
CreationTime Region StorageClass BucketName
2021-04-10 00:42:09 +0800 CST oss-cn-beijing Standard oss://vela-website
Bucket Number is: 1
0.146789(s) elapsed
```
#### Check whether Terraform state file is stored
```shell
$ kubectl get cm | grep aliyun-oss
aliyun-oss-tf-input 1 16m
aliyun-oss-tf-state 1 11m
$ kubectl get cm aliyun-oss-tf-state -o yaml
apiVersion: v1
data:
terraform.tfstate: |
{
"version": 4,
"terraform_version": "0.14.9",
"serial": 2,
"lineage": "61cbded2-6323-0f83-823d-9c40c000b91d",
"outputs": {
"BUCKET_NAME": {
"value": "vela-website.oss-cn-beijing.aliyuncs.com",
"type": "string"
}
},
"resources": [
{
"mode": "managed",
"type": "alicloud_oss_bucket",
"name": "bucket-acl",
"provider": "provider[\"registry.terraform.io/hashicorp/alicloud\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"acl": "private",
"bucket": "vela-website",
"cors_rule": [],
"creation_date": "2021-04-02",
"extranet_endpoint": "oss-cn-beijing.aliyuncs.com",
"force_destroy": false,
"id": "vela-website",
"intranet_endpoint": "oss-cn-beijing-internal.aliyuncs.com",
"lifecycle_rule": [],
"location": "oss-cn-beijing",
"logging": [],
"logging_isenable": null,
"owner": "1874279259696164",
"policy": "",
"redundancy_type": "LRS",
"referer_config": [],
"server_side_encryption_rule": [],
"storage_class": "Standard",
"tags": null,
"versioning": [],
"website": []
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}
kind: ConfigMap
metadata:
creationTimestamp: "2021-04-02T03:37:31Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:terraform.tfstate: {}
manager: terraform-tfstate-retriever
operation: Update
time: "2021-04-02T03:37:31Z"
name: aliyun-oss-tf-state
namespace: default
resourceVersion: "33145818"
selfLink: /api/v1/namespaces/default/configmaps/aliyun-oss-tf-state
uid: 762b1912-1f8f-428c-a4c7-2a7297375579
```
#### Check the generated connection secret
```shell
$ kubectl get secret oss-conn
NAME TYPE DATA AGE
oss-conn Opaque 1 2m41s
```
### Update Configuration
Change the OSS ACL to `public-read`.
```yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: aliyun-oss
spec:
JSON: |
..
variable:
...
acl: "public-read"
```
### Delete Configuration
Delete the configuration will destroy the OSS cloud resource.
```shell
$ kubectl delete configuration.terraform.core.oam.dev aliyun-oss
configuration.terraform.core.oam.dev "aliyun-oss" deleted
$ ossutil ls oss://
Bucket Number is: 0
0.030917(s) elapsed
```
## AWS
### Apply Provider configuration
```shell
$ export AWS_ACCESS_KEY_ID=xxx;export AWS_SECRET_ACCESS_KEY=yyy
$ sh hack/prepare-aws-credentials.sh
$ kubectl get secret -n vela-system
NAME TYPE DATA AGE
aws-account-creds Opaque 1 52s
$ k apply -f examples/aws/provider.yaml
provider.terraform.core.oam.dev/default created
$ kubectl apply -f examples/rbac.yaml
clusterrole.rbac.authorization.k8s.io/tf-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/tf-binding created
```
### Apply Terraform Configuration
Apply Terraform configuration [configuration_hcl_s3.yaml](./examples/aws/configuration_hcl_s3.yaml) to provision a s3 bucket.
```yaml
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: aws-s3
spec:
hcl: |
resource "aws_s3_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "BUCKET_NAME" {
value = aws_s3_bucket.bucket-acl.bucket_domain_name
}
variable "bucket" {
default = "vela-website"
}
variable "acl" {
default = "private"
}
variable:
bucket: "vela-website"
acl: "private"
writeConnectionSecretToRef:
name: s3-conn
namespace: default
```
```shell
$ kubectl get configuration.terraform.core.oam.dev
NAME AGE
aws-s3 6m48s
$ kubectl describe configuration.terraform.core.oam.dev aws-s3
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
...
Write Connection Secret To Ref:
Name: s3-conn
Namespace: default
Status:
Outputs:
BUCKET_NAME:
Type: string
Value: vela-website.s3.amazonaws.com
State: provisioned
$ kubectl get secret s3-conn
NAME TYPE DATA AGE
s3-conn Opaque 1 7m37s
$ aws s3 ls
2021-04-12 19:03:32 vela-website
```
This is the [contributing guide](./CONTRIBUTING.md). Looking forward to your contribution.

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

@ -50,5 +50,15 @@ type SecretReference struct {
Name string `json:"name"`
// Namespace of the secret.
Namespace string `json:"namespace"`
Namespace string `json:"namespace,omitempty"`
}
// A Reference to a named object.
type Reference struct {
// Name of the referenced object.
Name string `json:"name"`
// Namespace of the referenced object.
// +kubebuilder:default:=default
Namespace string `json:"namespace,omitempty"`
}

84
api/types/state.go Normal file
View File

@ -0,0 +1,84 @@
/*
Copyright 2019 The Crossplane 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 types
// A ConfigurationState represents the status of a resource
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"
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 (
// MessageDestroyJobNotCompleted is the message when Configuration deletion isn't completed
MessageDestroyJobNotCompleted = "Configuration deletion isn't completed"
// MessageApplyJobNotCompleted is the message when cloud resources are not created completed
MessageApplyJobNotCompleted = "cloud resources are not created completed"
// MessageCloudResourceProvisioningAndChecking is the message when cloud resource is being provisioned
MessageCloudResourceProvisioningAndChecking = "Cloud resources are being provisioned and provisioning status is checking..."
// ErrUpdateTerraformApplyJob means hitting an issue to update Terraform apply job
ErrUpdateTerraformApplyJob = "Hit an issue to update Terraform apply job"
// MessageCloudResourceDeployed means Cloud resources are deployed and ready to use
MessageCloudResourceDeployed = "Cloud resources are deployed and ready to use"
// MessageCloudResourceDestroying is the message when cloud resource is being destroyed
MessageCloudResourceDestroying = "Cloud resources is being destroyed..."
// ErrProviderNotFound means provider not found
ErrProviderNotFound = "provider not found"
// ErrProviderNotReady means provider object is not ready
ErrProviderNotReady = "Provider is not ready"
// ConfigurationReloadingAsHCLChanged means Configuration changed and needs reloading
ConfigurationReloadingAsHCLChanged = "Configuration's HCL has changed, and starts reloading"
// ConfigurationReloadingAsVariableChanged means Configuration changed and needs reloading
ConfigurationReloadingAsVariableChanged = "Configuration's variable has changed, and starts reloading"
// ErrGenerateOutputs means error to generate outputs
ErrGenerateOutputs = "Hit an issue to generate outputs"
)
// ProviderState is the type for Provider state
type ProviderState string
const (
// ProviderIsReady is the `ready` state
ProviderIsReady ProviderState = "ready"
// ProviderIsNotReady marks the state of a Provider is not ready
ProviderIsNotReady ProviderState = "ProviderNotReady"
)

42
api/types/terraform.go Normal file
View File

@ -0,0 +1,42 @@
package types
import "k8s.io/apimachinery/pkg/api/resource"
const (
// TerraformHCLConfigurationName is the file name for Terraform hcl Configuration
TerraformHCLConfigurationName = "main.tf"
)
// ConfigurationType is the type for Terraform Configuration
type ConfigurationType string
const (
// 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,45 +17,116 @@ 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"
state "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
// 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"`
// +kubebuilder:pruning:PreserveUnknownFields
Variable runtime.RawExtension `json:"variable"`
// Remote is a git repo which contains hcl files. Currently, only public git repos are supported.
Remote string `json:"remote,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 *Backend `json:"backend,omitempty"`
// Path is the sub-directory of remote git repository.
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
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,
// and password required to connect to the managed resource.
// +optional
WriteConnectionSecretToReference *types.SecretReference `json:"writeConnectionSecretToRef,omitempty"`
// ProviderReference specifies the reference to Provider
ProviderReference *types.Reference `json:"providerRef,omitempty"`
// DeleteResource will determine whether provisioned cloud resources will be deleted when CR is deleted
// +kubebuilder:default:=true
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:"region,omitempty"`
}
// ConfigurationStatus defines the observed state of Configuration
type ConfigurationStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
State string `json:"state,omitempty"`
Outputs map[string]Property `json:"outputs,omitempty"`
// 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"`
}
// 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"`
}
// ConfigurationDestroyStatus is the status for Configuration destroy
type ConfigurationDestroyStatus struct {
State state.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.
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"`
}
// +kubebuilder:object:root=true
// Configuration is the Schema for the configurations API
// +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

@ -17,9 +17,10 @@ limitations under the License.
package v1beta1
import (
"github.com/oam-dev/terraform-controller/api/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
)
// ProviderSpec defines the desired state of Provider.
@ -28,7 +29,7 @@ type ProviderSpec struct {
Provider string `json:"provider"`
// Region is cloud provider's region
Region string `json:"region"`
Region string `json:"region,omitempty"`
// Credentials required to authenticate to this provider.
Credentials ProviderCredentials `json:"credentials"`
@ -38,23 +39,26 @@ type ProviderSpec struct {
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=None;Secret;InjectedIdentity;Environment;Filesystem
Source types.CredentialsSource `json:"source"`
Source crossplanetypes.CredentialsSource `json:"source"`
// A SecretRef is a reference to a secret key that contains the credentials
// that must be used to connect to the provider.
// +optional
SecretRef *types.SecretKeySelector `json:"secretRef,omitempty"`
SecretRef *crossplanetypes.SecretKeySelector `json:"secretRef,omitempty"`
}
// ProviderStatus defines the observed state of Provider.
type ProviderStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
State types.ProviderState `json:"state,omitempty"`
Message string `json:"message,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="STATE",type="string",JSONPath=".status.state"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// Provider is the Schema for the providerconfigs API.
// Provider is the Schema for the providers API.
type Provider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@ -1,4 +1,4 @@
// +build !ignore_autogenerated
//go:build !ignore_autogenerated
/*
Copyright 2021 The KubeVela Authors.
@ -22,9 +22,50 @@ 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"
)
// 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
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
func (in *Backend) DeepCopy() *Backend {
if in == nil {
return nil
}
out := new(Backend)
in.DeepCopyInto(out)
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
@ -52,6 +93,43 @@ func (in *Configuration) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationApplyStatus) DeepCopyInto(out *ConfigurationApplyStatus) {
*out = *in
if in.Outputs != nil {
in, out := &in.Outputs, &out.Outputs
*out = make(map[string]Property, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationApplyStatus.
func (in *ConfigurationApplyStatus) DeepCopy() *ConfigurationApplyStatus {
if in == nil {
return nil
}
out := new(ConfigurationApplyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationDestroyStatus) DeepCopyInto(out *ConfigurationDestroyStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationDestroyStatus.
func (in *ConfigurationDestroyStatus) DeepCopy() *ConfigurationDestroyStatus {
if in == nil {
return nil
}
out := new(ConfigurationDestroyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationList) DeepCopyInto(out *ConfigurationList) {
*out = *in
@ -87,10 +165,35 @@ 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
in.Variable.DeepCopyInto(&out.Variable)
if in.WriteConnectionSecretToReference != nil {
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
*out = new(crossplane_runtime.SecretReference)
if in.Variable != nil {
in, out := &in.Variable, &out.Variable
*out = new(runtime.RawExtension)
(*in).DeepCopyInto(*out)
}
if in.Backend != nil {
in, out := &in.Backend, &out.Backend
*out = new(Backend)
**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
}
}
@ -108,13 +211,8 @@ func (in *ConfigurationSpec) DeepCopy() *ConfigurationSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationStatus) DeepCopyInto(out *ConfigurationStatus) {
*out = *in
if in.Outputs != nil {
in, out := &in.Outputs, &out.Outputs
*out = make(map[string]Property, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
in.Apply.DeepCopyInto(&out.Apply)
out.Destroy = in.Destroy
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationStatus.

View File

@ -0,0 +1,193 @@
/*
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 v1beta2
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
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 {
// 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 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"`
// 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.
// +optional
WriteConnectionSecretToReference *types.SecretReference `json:"writeConnectionSecretToRef,omitempty"`
// 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"`
// 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 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 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"`
}
// 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:subresource:status
// +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"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ConfigurationSpec `json:"spec,omitempty"`
Status ConfigurationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ConfigurationList contains a list of Configuration
type ConfigurationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Configuration `json:"items"`
}
func init() {
SchemeBuilder.Register(&Configuration{}, &ConfigurationList{})
}

View File

@ -0,0 +1,36 @@
/*
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 v1beta2 contains API Schema definitions for the terraform v1beta2 API group
// +kubebuilder:object:generate=true
// +groupName=terraform.core.oam.dev
package v1beta2
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "terraform.core.oam.dev", Version: "v1beta2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@ -0,0 +1,291 @@
//go:build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
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.
func (in *Backend) DeepCopy() *Backend {
if in == nil {
return nil
}
out := new(Backend)
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
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration.
func (in *Configuration) DeepCopy() *Configuration {
if in == nil {
return nil
}
out := new(Configuration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Configuration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationApplyStatus) DeepCopyInto(out *ConfigurationApplyStatus) {
*out = *in
if in.Outputs != nil {
in, out := &in.Outputs, &out.Outputs
*out = make(map[string]Property, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationApplyStatus.
func (in *ConfigurationApplyStatus) DeepCopy() *ConfigurationApplyStatus {
if in == nil {
return nil
}
out := new(ConfigurationApplyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationDestroyStatus) DeepCopyInto(out *ConfigurationDestroyStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationDestroyStatus.
func (in *ConfigurationDestroyStatus) DeepCopy() *ConfigurationDestroyStatus {
if in == nil {
return nil
}
out := new(ConfigurationDestroyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationList) DeepCopyInto(out *ConfigurationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Configuration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationList.
func (in *ConfigurationList) DeepCopy() *ConfigurationList {
if in == nil {
return nil
}
out := new(ConfigurationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ConfigurationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// 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)
(*in).DeepCopyInto(*out)
}
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
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec.
func (in *ConfigurationSpec) DeepCopy() *ConfigurationSpec {
if in == nil {
return nil
}
out := new(ConfigurationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigurationStatus) DeepCopyInto(out *ConfigurationStatus) {
*out = *in
in.Apply.DeepCopyInto(&out.Apply)
out.Destroy = in.Destroy
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationStatus.
func (in *ConfigurationStatus) DeepCopy() *ConfigurationStatus {
if in == nil {
return nil
}
out := new(ConfigurationStatus)
in.DeepCopyInto(out)
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
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Property.
func (in *Property) DeepCopy() *Property {
if in == nil {
return nil
}
out := new(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
}

1
bin/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*

Binary file not shown.

6
chart/Chart.yaml Normal file
View File

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

View File

@ -0,0 +1,498 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.5
name: configurations.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
names:
kind: Configuration
listKind: ConfigurationList
plural: configurations
shortNames:
- conf
- terraform-conf
singular: configuration
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.apply.state
name: STATE
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
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
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
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
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
from inside a pod. Only `true` is allowed
type: boolean
secretSuffix:
description: 'SecretSuffix used when creating secrets. Secrets
will be named in the format: tfstate-{workspace}-{secretSuffix}'
type: string
type: object
deleteResource:
default: true
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
path:
description: Path is the sub-directory of remote git repository.
type: string
providerRef:
description: ProviderReference specifies the reference to Provider
properties:
name:
description: Name of the referenced object.
type: string
namespace:
default: default
description: Namespace of the referenced object.
type: string
required:
- name
type: object
region:
description: Region is cloud provider's region. It will override the
region in the region field of ProviderReference
type: string
remote:
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.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
type: object
type: object
status:
description: ConfigurationStatus defines the observed state of Configuration
properties:
apply:
description: ConfigurationApplyStatus is the status for Configuration
apply
properties:
message:
type: string
outputs:
additionalProperties:
description: Property is the property for an output
properties:
type:
type: string
value:
type: string
type: object
type: object
state:
description: A ConfigurationState represents the status of a resource
type: string
type: object
destroy:
description: ConfigurationDestroyStatus is the status for Configuration
destroy
properties:
message:
type: string
state:
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
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- jsonPath: .status.apply.state
name: APPLY
type: string
- jsonPath: .status.destroy.state
name: DESTROY
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta2
schema:
openAPIV3Schema:
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
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
type: string
metadata:
type: object
spec:
description: ConfigurationSpec defines the desired state of Configuration
properties:
JobEnv:
type: object
x-kubernetes-preserve-unknown-fields: true
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}'
type: string
type: object
customRegion:
description: Region is cloud provider's region. It will override the
region in the region field of ProviderReference
type: string
deleteResource:
default: true
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
providerRef:
description: ProviderReference specifies the reference to Provider
properties:
name:
description: Name of the referenced object.
type: string
namespace:
default: default
description: Namespace of the referenced object.
type: string
required:
- name
type: object
remote:
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.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
type: object
type: object
status:
description: ConfigurationStatus defines the observed state of Configuration
properties:
apply:
description: ConfigurationApplyStatus is the status for Configuration
apply
properties:
message:
type: string
outputs:
additionalProperties:
description: Property is the property for an output
properties:
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
type: object
destroy:
description: ConfigurationDestroyStatus is the status for Configuration
destroy
properties:
message:
type: string
state:
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: {}

View File

@ -0,0 +1,105 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.5
name: providers.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
names:
kind: Provider
listKind: ProviderList
plural: providers
singular: provider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.state
name: STATE
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
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
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
type: string
metadata:
type: object
spec:
description: ProviderSpec defines the desired state of Provider.
properties:
credentials:
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.
properties:
key:
description: The key to select.
type: string
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- key
- name
type: object
source:
description: Source of the provider credentials.
enum:
- None
- Secret
- InjectedIdentity
- Environment
- Filesystem
type: string
required:
- source
type: object
provider:
description: Provider is the cloud service provider, like `alibaba`
type: string
region:
description: Region is cloud provider's region
type: string
required:
- credentials
- provider
type: object
status:
description: ProviderStatus defines the observed state of Provider.
properties:
message:
type: string
state:
description: ProviderState is the type for Provider state
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@ -0,0 +1,67 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: terraform-controller
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: terraform-controller
template:
metadata:
labels:
app: terraform-controller
app.kubernetes.io/name: {{ .Release.Name }}
app.kubernetes.io/part-of: kubevela
app.kubernetes.io/managed-by: helm
spec:
containers:
- 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:
fieldRef:
fieldPath: metadata.namespace
- name: TERRAFORM_IMAGE
value: {{ .Values.terraformImage}}
- name: TERRAFORM_BACKEND_NAMESPACE
value: {{ .Values.backend.namespace }}
- name: BUSYBOX_IMAGE
value: {{ .Values.busyboxImage}}
- name: GIT_IMAGE
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

@ -0,0 +1,96 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tf-controller-clusterrole
rules:
- apiGroups:
- ""
resources:
- "configmaps"
verbs:
- "list"
- "watch"
- "get"
- "create"
- "update"
- "watch"
- "delete"
# Required to write terraform outputs
- apiGroups:
- ""
resources:
- "secrets"
- "serviceaccounts"
verbs:
- "get"
- "list"
- "create"
- "update"
- "delete"
- "watch"
- "delete"
- apiGroups:
- "batch"
resources:
- "jobs"
verbs:
- "get"
- "list"
- "create"
- "update"
- "delete"
- "watch"
- apiGroups:
- ""
resources:
- "pods/log"
- "pods"
verbs:
- "get"
- "list"
- "create"
- "update"
- "delete"
- "watch"
- "delete"
- apiGroups:
- "terraform.core.oam.dev"
resources:
- "configurations"
- "providers"
- "providers/status"
- "configurations/status"
verbs:
- "get"
- "list"
- "create"
- "update"
- "delete"
- "watch"
- apiGroups:
- "rbac.authorization.k8s.io"
resources:
- "clusterroles"
- "clusterrolebindings"
verbs:
- "get"
- "list"
- "create"
- "update"
- "delete"
- "watch"
- apiGroups:
- "coordination.k8s.io"
resources:
- "leases"
verbs:
- "get"
- "create"
- "update"
- "delete"

View File

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tf-controller-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: tf-controller-clusterrole
subjects:
- kind: ServiceAccount
name: tf-controller-service-account
namespace: {{ .Release.Namespace }}

View File

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: tf-controller-service-account
namespace: {{ .Release.Namespace }}

34
chart/values.yaml Normal file
View File

@ -0,0 +1,34 @@
replicaCount: 1
image:
repository: oamdev/terraform-controller
tag: latest
pullPolicy: Always
gitImage: alpine/git:latest
busyboxImage: busybox:latest
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

9
codecov.yml Normal file
View File

@ -0,0 +1,9 @@
coverage:
status:
project:
default:
target: auto
threshold: 0.1%
patch:
default:
target: 80%

View File

@ -1,95 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: configurations.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
names:
kind: Configuration
listKind: ConfigurationList
plural: configurations
singular: configuration
scope: Namespaced
validation:
openAPIV3Schema:
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'
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'
type: string
metadata:
type: object
spec:
description: ConfigurationSpec defines the desired state of Configuration
properties:
JSON:
description: JSON is the Terraform JSON syntax configuration
type: string
hcl:
description: HCL is the Terraform HCL type configuration
type: string
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.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
- namespace
type: object
required:
- variable
type: object
status:
description: ConfigurationStatus defines the observed state of Configuration
properties:
outputs:
additionalProperties:
properties:
type:
type: string
value:
type: string
type: object
type: object
state:
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
of cluster Important: Run "make" to regenerate code after modifying
this file'
type: string
type: object
type: object
version: v1beta1
versions:
- name: v1beta1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -1,95 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: providers.terraform.core.oam.dev
spec:
group: terraform.core.oam.dev
names:
kind: Provider
listKind: ProviderList
plural: providers
singular: provider
scope: Namespaced
validation:
openAPIV3Schema:
description: Provider is the Schema for the providerconfigs 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'
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'
type: string
metadata:
type: object
spec:
description: ProviderSpec defines the desired state of Provider.
properties:
credentials:
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.
properties:
key:
description: The key to select.
type: string
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- key
- name
- namespace
type: object
source:
description: Source of the provider credentials.
enum:
- None
- Secret
- InjectedIdentity
- Environment
- Filesystem
type: string
required:
- source
type: object
provider:
description: Provider is the cloud service provider, like `alibaba`
type: string
region:
description: Region is cloud provider's region
type: string
required:
- credentials
- provider
- region
type: object
status:
description: ProviderStatus defines the observed state of Provider.
type: object
type: object
version: v1beta1
versions:
- name: v1beta1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -1,13 +1,13 @@
# permissions for end users to edit providerconfigs.
# permissions for end users to edit providers.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: providerconfig-editor-role
name: provider-editor-role
rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs
- providers
verbs:
- create
- delete
@ -19,6 +19,6 @@ rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs/status
- providers/status
verbs:
- get

View File

@ -1,13 +1,13 @@
# permissions for end users to view providerconfigs.
# permissions for end users to view providers.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: providerconfig-viewer-role
name: provider-viewer-role
rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs
- providers
verbs:
- get
- list
@ -15,6 +15,6 @@ rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs/status
- providers/status
verbs:
- get

View File

@ -4,7 +4,7 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: manager-role
name: tf-api-role
rules:
- apiGroups:
- terraform.core.oam.dev
@ -29,7 +29,7 @@ rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs
- providers
verbs:
- create
- delete
@ -41,7 +41,7 @@ rules:
- apiGroups:
- terraform.core.oam.dev
resources:
- providerconfigs/status
- providers/status
verbs:
- get
- patch

View File

@ -1,12 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: manager-role
subjects:
- kind: ServiceAccount
name: default
namespace: system

View File

@ -1,7 +0,0 @@
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
metadata:
name: configuration-sample
spec:
# Add fields here
foo: bar

View File

@ -1,7 +0,0 @@
apiVersion: terraform.core.oam.dev/v1beta1
kind: ProviderConfig
metadata:
name: providerconfig-sample
spec:
# Add fields here
foo: bar

View File

@ -0,0 +1,15 @@
package client
import (
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
// Init initializes the Go client set
func Init() (*kubernetes.Clientset, error) {
config, err := config.GetConfig()
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}

View File

@ -0,0 +1,86 @@
package client
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
func TestInit(t *testing.T) {
type args struct {
configFile string
}
type want struct {
errMsg string
}
pwd, err := os.Getwd()
assert.NoError(t, err)
kubeConfig := filepath.Join(pwd, "config")
assert.NoError(t, os.WriteFile(kubeConfig, []byte(""), 0400))
defer os.Remove(kubeConfig)
os.Setenv("KUBECONFIG", kubeConfig)
testcases := []struct {
name string
args args
want want
}{
{
name: "init",
args: args{},
want: want{
errMsg: "invalid configuration",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if _, err := Init(); tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("Init() error = %v, wantErr %v", err, tc.want.errMsg)
}
})
}
}
func TestInitWithWrongConfig(t *testing.T) {
type args struct {
configFile string
}
type want struct {
errMsg string
}
gomonkey.ApplyFunc(config.GetConfigWithContext, func(context string) (*rest.Config, error) {
return &rest.Config{}, nil
})
testcases := []struct {
name string
args args
want want
}{
{
name: "init",
args: args{},
want: want{},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if _, err := Init(); tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("Init() error = %v, wantErr %v", err, tc.want.errMsg)
}
})
}
}

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

@ -0,0 +1,161 @@
package configuration
import (
"context"
"fmt"
"strconv"
"strings"
"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"
)
const (
// GithubPrefix is the constant of GitHub domain
GithubPrefix = "https://github.com/"
// GithubKubeVelaContribPrefix is the prefix of GitHub repository of kubevela-contrib
GithubKubeVelaContribPrefix = "https://github.com/kubevela-contrib"
// GiteeTerraformSourceOrg is the Gitee organization of Terraform source
GiteeTerraformSourceOrg = "https://gitee.com/kubevela-terraform-source"
// GiteePrefix is the constant of Gitee domain
GiteePrefix = "https://gitee.com/"
)
const errGitHubBlockedNotBoolean = "the value of githubBlocked is not a boolean"
// ValidConfigurationObject will validate a Configuration
func ValidConfigurationObject(configuration *v1beta2.Configuration) (types.ConfigurationType, error) {
hcl := configuration.Spec.HCL
remote := configuration.Spec.Remote
switch {
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 != "":
return types.ConfigurationRemote, nil
}
return "", nil
}
// 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})
if err != nil {
return "", errors.Wrap(err, "failed to get configuration")
}
if configuration.Spec.Region != "" {
return configuration.Spec.Region, nil
}
configuration.Spec.Region = providerObj.Spec.Region
return providerObj.Spec.Region, Update(ctx, k8sClient, &configuration)
}
// Update will update the Configuration
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) (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)
}
return *configuration, err
}
return *configuration, nil
}
// IsDeletable will check whether the Configuration can be deleted immediately
// 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)
klog.Warning(warning)
return false, errors.New(warning)
}
return false, nil
}
// ReplaceTerraformSource will replace the Terraform source from GitHub to Gitee
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("%s: %v", errGitHubBlockedNotBoolean, err)
return remote
}
klog.InfoS("Parsed GITHUB_BLOCKED env", "githubBlocked", githubBlocked)
if !githubBlocked {
return remote
}
if remote == "" {
return ""
}
if strings.HasPrefix(remote, GithubPrefix) {
var repo string
if strings.HasPrefix(remote, GithubKubeVelaContribPrefix) {
repo = strings.Replace(remote, GithubPrefix, GiteePrefix, 1)
} else {
tmp := strings.Split(strings.Replace(remote, GithubPrefix, "", 1), "/")
if len(tmp) == 2 {
repo = GiteeTerraformSourceOrg + "/" + tmp[1]
}
}
klog.InfoS("New remote git", "Gitee", repo)
return repo
}
return remote
}
// GetProviderNamespacedName will get the provider namespaced name
func GetProviderNamespacedName(configuration v1beta2.Configuration) *crossplane.Reference {
if configuration.Spec.ProviderReference != nil {
return configuration.Spec.ProviderReference
}
return &crossplane.Reference{
Name: provider.DefaultName,
Namespace: provider.DefaultNamespace,
}
}

View File

@ -0,0 +1,397 @@
package configuration
import (
"context"
"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"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"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 *v1beta2.Configuration
}
type want struct {
configurationType types.ConfigurationType
errMsg string
}
testcases := []struct {
name string
args args
want want
}{
{
name: "hcl",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
HCL: "abc",
},
},
},
want: want{
configurationType: types.ConfigurationHCL,
},
},
{
name: "remote",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
Remote: "def",
},
},
},
want: want{
configurationType: types.ConfigurationRemote,
},
},
{
name: "remote and hcl are set",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{
HCL: "abc",
Remote: "def",
},
},
},
want: want{
configurationType: "",
errMsg: "spec.HCL and spec.Remote cloud not be set at the same time",
},
},
{
name: "remote and hcl are not set",
args: args{
configuration: &v1beta2.Configuration{
Spec: v1beta2.ConfigurationSpec{},
},
},
want: want{
configurationType: "",
errMsg: "spec.HCL or spec.Remote should be set",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := ValidConfigurationObject(tc.args.configuration)
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.configurationType {
t.Errorf("ValidConfigurationObject() = %v, want %v", got, tc.want.configurationType)
}
})
}
}
func TestReplaceTerraformSource(t *testing.T) {
testcases := []struct {
remote string
githubBlocked string
expected string
}{
{
remote: "",
expected: "",
githubBlocked: "xxx",
},
{
remote: "https://github.com/kubevela-contrib/terraform-modules.git",
expected: "https://github.com/kubevela-contrib/terraform-modules.git",
githubBlocked: "false",
},
{
remote: "https://github.com/kubevela-contrib/terraform-modules.git",
expected: "https://gitee.com/kubevela-contrib/terraform-modules.git",
githubBlocked: "true",
},
{
remote: "https://github.com/abc/terraform-modules.git",
expected: "https://gitee.com/kubevela-terraform-source/terraform-modules.git",
githubBlocked: "true",
},
{
remote: "abc",
githubBlocked: "true",
expected: "abc",
},
{
remote: "",
githubBlocked: "true",
expected: "",
},
}
for _, tc := range testcases {
t.Run(tc.remote, func(t *testing.T) {
actual := ReplaceTerraformSource(tc.remote, tc.githubBlocked)
if actual != tc.expected {
t.Errorf("expected %s, got %s", tc.expected, actual)
}
})
}
}
func TestIsDeletable(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
_ = v1beta1.AddToScheme(s)
_ = v1beta2.AddToScheme(s)
provider2 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Status: v1beta1.ProviderStatus{
State: types.ProviderIsNotReady,
},
}
provider3 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Status: v1beta1.ProviderStatus{
State: types.ProviderIsReady,
},
}
k8sClient1 := fake.NewClientBuilder().WithScheme(s).Build()
k8sClient2 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider2).Build()
k8sClient3 := fake.NewClientBuilder().WithScheme(s).WithObjects(provider3).Build()
k8sClient4 := fake.NewClientBuilder().Build()
configuration := &v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
},
}
configuration.Spec.ProviderReference = &crossplane.Reference{
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 *v1beta2.Configuration
k8sClient client.Client
}
type want struct {
deletable bool
errMsg string
}
testcases := []struct {
name string
args args
want want
}{
{
name: "provider is not found",
args: args{
k8sClient: k8sClient1,
configuration: defaultConfiguration,
},
want: want{
deletable: true,
},
},
{
name: "provider is not ready, use default providerRef",
args: args{
k8sClient: k8sClient2,
configuration: defaultConfiguration,
},
want: want{
deletable: true,
},
},
{
name: "provider is not ready, providerRef is set in configuration spec",
args: args{
k8sClient: k8sClient2,
configuration: configuration,
},
want: want{
deletable: true,
},
},
{
name: "configuration is provisioning",
args: args{
k8sClient: k8sClient3,
configuration: provisioningConfiguration,
},
want: want{
errMsg: "Destroy could not complete and needs to wait for Provision to complete first",
},
},
{
name: "configuration is ready",
args: args{
k8sClient: k8sClient3,
configuration: readyConfiguration,
},
want: want{},
},
{
name: "failed to get provider",
args: args{
k8sClient: k8sClient4,
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) {
got, err := IsDeletable(ctx, tc.args.k8sClient, tc.args.configuration)
if err != nil {
if !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("IsDeletable() error = %v, wantErr %v", err, tc.want.errMsg)
return
}
}
if got != tc.want.deletable {
t.Errorf("IsDeletable() = %v, want %v", got, tc.want.deletable)
}
})
}
}
func TestSetRegion(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
_ = v1beta2.AddToScheme(s)
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
configuration1 := v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
Namespace: "default",
},
Spec: v1beta2.ConfigurationSpec{},
}
configuration1.Spec.Region = "xxx"
assert.Nil(t, k8sClient.Create(ctx, &configuration1))
configuration2 := v1beta2.Configuration{
ObjectMeta: metav1.ObjectMeta{
Name: "def",
Namespace: "default",
},
Spec: v1beta2.ConfigurationSpec{},
}
assert.Nil(t, k8sClient.Create(ctx, &configuration2))
provider := &v1beta1.Provider{
Spec: v1beta1.ProviderSpec{
Region: "yyy",
},
}
type args struct {
namespace string
name string
}
type want struct {
region string
errMsg string
}
testcases := map[string]struct {
args args
want want
}{
"configuration is available, region is set": {
args: args{
namespace: "default",
name: "abc",
},
want: want{
region: "xxx",
},
},
"configuration is available, region is not set": {
args: args{
namespace: "default",
name: "def",
},
want: want{
region: "yyy",
},
},
"configuration isn't available": {
args: args{
namespace: "default",
name: "ghi",
},
want: want{
errMsg: "failed to get configuration",
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
region, err := SetRegion(ctx, k8sClient, tc.args.namespace, tc.args.name, provider)
if tc.want.errMsg != "" && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("SetRegion() error = %v, wantErr %v", err, tc.want.errMsg)
}
if region != tc.want.region {
t.Errorf("SetRegion() want = %s, got %s", tc.want.region, region)
}
})
}
}

View File

@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package util
package configuration
import (
"encoding/json"
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/runtime"
)
@ -37,5 +39,27 @@ func RawExtension2Map(raw *runtime.RawExtension) (map[string]interface{}, error)
if err != nil {
return nil, err
}
return ret, err
return ret, nil
}
// Interface2String converts an interface{} type to string
func Interface2String(v interface{}) (string, error) {
var value string
switch v := v.(type) {
case string:
value = v
case int:
value = strconv.Itoa(v)
case float64:
value = fmt.Sprint(v)
case bool:
value = strconv.FormatBool(v)
default:
valueJSON, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("cloud not convert %v to string", v)
}
value = string(valueJSON)
}
return value, nil
}

View File

@ -0,0 +1,187 @@
package configuration
import (
"testing"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"
)
func TestRawExtension2Map(t *testing.T) {
type want struct {
result interface{}
err error
}
type Spec struct {
Variable *runtime.RawExtension `json:"variable,omitempty"`
}
cases := map[string]struct {
variable string
want want
}{
"StringType": {
variable: `
Variable:
k: Will
`,
want: want{
result: "Will",
err: nil,
},
},
"ListType1": {
variable: `
Variable:
k: ["Will", "Catherine"]
`,
want: want{
result: []interface{}{"Will", "Catherine"},
err: nil,
},
},
"ListType2": {
variable: `
Variable:
k:
- "Will"
- "Catherine"
`,
want: want{
result: []interface{}{"Will", "Catherine"},
err: nil,
},
},
"nil": {
want: want{
result: nil,
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
var spec Spec
err := yaml.Unmarshal([]byte(tc.variable), &spec)
assert.NilError(t, err)
result, err := RawExtension2Map(spec.Variable)
if tc.want.err != nil {
assert.Error(t, err, tc.want.err.Error())
} else {
assert.Equal(t, tc.want.err, err)
assert.DeepEqual(t, tc.want.result, result["k"])
}
})
}
}
func TestRawExtension2Map2(t *testing.T) {
type args struct {
raw *runtime.RawExtension
}
type want struct {
errMessage string
}
cases := map[string]struct {
args args
want want
}{
"bad raw": {
args: args{
raw: &runtime.RawExtension{
Raw: []byte("xxx"),
},
},
want: want{
errMessage: "cannot convert RawExtension with unrecognized content type to unstructured",
},
}}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := RawExtension2Map(tc.args.raw)
if tc.want.errMessage != "" {
assert.Error(t, err, tc.want.errMessage)
} else {
assert.NilError(t, err)
}
})
}
}
func TestInterface2String(t *testing.T) {
type want struct {
result string
err error
}
cases := map[string]struct {
variable interface{}
want want
}{
"StringType": {
variable: "Will",
want: want{
result: "Will",
err: nil,
},
},
"IntType": {
variable: 123,
want: want{
result: "123",
err: nil,
},
},
"BoolType": {
variable: true,
want: want{
result: "true",
err: nil,
},
},
"NumberType": {
variable: 1024.1,
want: want{
result: "1024.1",
err: nil,
},
},
"ListType1": {
variable: []interface{}{"Will", "Catherine"},
want: want{
result: `["Will","Catherine"]`,
err: nil,
},
},
"ListType2": {
variable: []interface{}{123, 456},
want: want{
result: `[123,456]`,
err: nil,
},
},
"ObjectType": {
variable: struct{ Name string }{"Terraform"},
want: want{
result: `{"Name":"Terraform"}`,
err: nil,
},
},
"ListObjectType": {
variable: []struct{ Name string }{{"Terraform"}, {"OAM"}, {"Vela"}},
want: want{
result: `[{"Name":"Terraform"},{"Name":"OAM"},{"Name":"Vela"}]`,
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
result, err := Interface2String(tc.variable)
assert.Equal(t, tc.want.err, err)
assert.DeepEqual(t, tc.want.result, result)
})
}
}

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

@ -0,0 +1,39 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
// 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
type AWSCredentials struct {
AWSAccessKeyID string `yaml:"awsAccessKeyID"`
AWSSecretAccessKey string `yaml:"awsSecretAccessKey"`
AWSSessionToken string `yaml:"awsSessionToken"`
}
func getAWSCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var ak AWSCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
EnvAWSAccessKeyID: ak.AWSAccessKeyID,
EnvAWSSecretAccessKey: ak.AWSSecretAccessKey,
EnvAWSSessionToken: ak.AWSSessionToken,
EnvAWSDefaultRegion: region,
}, nil
}

View File

@ -0,0 +1,36 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envARMClientID = "ARM_CLIENT_ID"
envARMClientSecret = "ARM_CLIENT_SECRET"
envARMSubscriptionID = "ARM_SUBSCRIPTION_ID"
envARMTenantID = "ARM_TENANT_ID"
)
// AzureCredentials are credentials for Azure
type AzureCredentials struct {
ARMClientID string `yaml:"armClientID"`
ARMClientSecret string `yaml:"armClientSecret"`
ARMSubscriptionID string `yaml:"armSubscriptionID"`
ARMTenantID string `yaml:"armTenantID"`
}
func getAzureCredentials(secretData []byte, name, namespace string) (map[string]string, error) {
var cred AzureCredentials
if err := yaml.Unmarshal(secretData, &cred); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envARMClientID: cred.ARMClientID,
envARMClientSecret: cred.ARMClientSecret,
envARMSubscriptionID: cred.ARMSubscriptionID,
envARMTenantID: cred.ARMTenantID,
}, nil
}

View File

@ -0,0 +1,32 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envBaiduAccessKey = "BAIDUCLOUD_ACCESS_KEY"
envBaiduSecretKey = "BAIDUCLOUD_SECRET_KEY"
envBaiduRegion = "BAIDUCLOUD_REGION"
)
// BaiduCloudCredentials are credentials for Baidu Cloud
type BaiduCloudCredentials struct {
KeyBaiduAccessKey string `yaml:"accessKey"`
KeyBaiduSecretKey string `yaml:"secretKey"`
}
func getBaiduCloudCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var ak BaiduCloudCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envBaiduAccessKey: ak.KeyBaiduAccessKey,
envBaiduSecretKey: ak.KeyBaiduSecretKey,
envBaiduRegion: region,
}, nil
}

View File

@ -0,0 +1,168 @@
package provider
import (
"context"
"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/terraform-controller/api/v1beta1"
)
const (
// DefaultName is the name of Provider object
DefaultName = "default"
// DefaultNamespace is the namespace of Provider object
DefaultNamespace = "default"
)
// CloudProvider is a type for mark a Cloud Provider
type CloudProvider string
const (
alibaba CloudProvider = "alibaba"
aws CloudProvider = "aws"
gcp CloudProvider = "gcp"
tencent CloudProvider = "tencent"
azure CloudProvider = "azure"
vsphere CloudProvider = "vsphere"
ec CloudProvider = "ec"
ucloud CloudProvider = "ucloud"
custom CloudProvider = "custom"
baidu CloudProvider = "baidu"
huawei CloudProvider = "huawei"
)
const (
envAlicloudAcessKey = "ALICLOUD_ACCESS_KEY"
envAlicloudSecretKey = "ALICLOUD_SECRET_KEY"
envAlicloudRegion = "ALICLOUD_REGION"
envAliCloudStsToken = "ALICLOUD_SECURITY_TOKEN"
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" 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
func GetProviderCredentials(ctx context.Context, k8sClient client.Client, provider *v1beta1.Provider, region string) (map[string]string, error) {
switch provider.Spec.Credentials.Source {
case "Secret":
var secret v1.Secret
secretRef := provider.Spec.Credentials.SecretRef
name := secretRef.Name
namespace := secretRef.Namespace
if err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, &secret); err != nil {
errMsg := "failed to get the Secret from Provider"
klog.ErrorS(err, errMsg, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errMsg)
}
secretData, ok := secret.Data[secretRef.Key]
if !ok {
return nil, errors.Errorf("in the provider %s, the key %s not found in the referenced secret %s", provider.Name, secretRef.Key, name)
}
switch provider.Spec.Provider {
case string(alibaba):
var ak AlibabaCloudCredentials
if err := yaml.Unmarshal(secret.Data[secretRef.Key], &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
if err := checkAlibabaCloudCredentials(region, ak.AccessKeyID, ak.AccessKeySecret, ak.SecurityToken); err != nil {
klog.ErrorS(err, errCredentialValid)
return nil, errors.Wrap(err, errCredentialValid)
}
return map[string]string{
envAlicloudAcessKey: ak.AccessKeyID,
envAlicloudSecretKey: ak.AccessKeySecret,
envAlicloudRegion: region,
envAliCloudStsToken: ak.SecurityToken,
}, nil
case string(ucloud):
return getUCloudCredentials(secretData, name, namespace, region)
case string(aws):
return getAWSCredentials(secretData, name, namespace, region)
case string(gcp):
return getGCPCredentials(secretData, name, namespace, region)
case string(tencent):
return getTencentCloudCredentials(secretData, name, namespace, region)
case string(azure):
return getAzureCredentials(secretData, name, namespace)
case string(vsphere):
return getVSphereCredentials(secretData, name, namespace)
case string(ec):
return getECCloudCredentials(secretData, name, namespace)
case string(custom):
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)
return nil, errors.New(errMsg)
}
default:
errMsg := "the credentials type is not supported."
err := errors.New(errMsg)
klog.ErrorS(err, "", "CredentialType", provider.Spec.Credentials.Source)
return nil, err
}
}
// GetProviderFromConfiguration gets provider object from Configuration
// Returns:
// 1) (nil, err): hit an issue to find the provider
// 2) (nil, nil): provider not found
// 3) (provider, nil): provider found
func GetProviderFromConfiguration(ctx context.Context, k8sClient client.Client, namespace, name string) (*v1beta1.Provider, error) {
var provider = &v1beta1.Provider{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, provider); err != nil {
if kerrors.IsNotFound(err) {
return nil, nil
}
errMsg := "failed to get Provider object"
klog.ErrorS(err, errMsg, "Name", name)
return nil, errors.Wrap(err, errMsg)
}
return provider, nil
}
// checkAlibabaCloudProvider checks if the credentials from the provider are valid
func checkAlibabaCloudCredentials(region string, accessKeyID, accessKeySecret, stsToken string) error {
var (
client *sts.Client
err error
)
if stsToken != "" {
client, err = sts.NewClientWithStsToken(region, accessKeyID, accessKeySecret, stsToken)
} else {
client, err = sts.NewClientWithAccessKey(region, accessKeyID, accessKeySecret)
}
if err != nil {
return err
}
request := sts.CreateGetCallerIdentityRequest()
request.Scheme = "https"
_, err = client.GetCallerIdentity(request)
if err != nil {
errMsg := "Alibaba Cloud credentials are invalid"
klog.ErrorS(err, errMsg)
return errors.Wrap(err, errMsg)
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
// CustomCredentials are credentials for custom (you self)
type CustomCredentials map[string]string
func getCustomCredentials(secretData []byte, name, namespace string) (map[string]string, error) {
var ck = make(CustomCredentials)
if err := yaml.Unmarshal(secretData, &ck); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return ck, nil
}

View File

@ -0,0 +1,27 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envECApiKey = "EC_API_KEY"
)
// ECCredentials are credentials for Elastic CLoud
type ECCredentials struct {
ECApiKey string `yaml:"ecApiKey"`
}
func getECCloudCredentials(secretData []byte, name, namespace string) (map[string]string, error) {
var ak ECCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envECApiKey: ak.ECApiKey,
}, nil
}

View File

@ -0,0 +1,32 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envGCPCredentialsJSON = "GOOGLE_CREDENTIALS"
envGCPRegion = "GOOGLE_REGION"
envGCPProject = "GOOGLE_PROJECT"
)
// GCPCredentials are credentials for GCP
type GCPCredentials struct {
GCPCredentialsJSON string `yaml:"gcpCredentialsJSON"`
GCPProject string `yaml:"gcpProject"`
}
func getGCPCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var ak GCPCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envGCPCredentialsJSON: ak.GCPCredentialsJSON,
envGCPProject: ak.GCPProject,
envGCPRegion: region,
}, nil
}

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

@ -0,0 +1,32 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envQCloudSecretID = "TENCENTCLOUD_SECRET_ID"
envQCloudSecretKey = "TENCENTCLOUD_SECRET_KEY"
envQCloudRegion = "TENCENTCLOUD_REGION"
)
// TencentCloudCredentials are credentials for Tencent Cloud
type TencentCloudCredentials struct {
SecretID string `yaml:"secretID"`
SecretKey string `yaml:"secretKey"`
}
func getTencentCloudCredentials(secretData []byte, name, namespace, region string) (map[string]string, error) {
var ak TencentCloudCredentials
if err := yaml.Unmarshal(secretData, &ak); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envQCloudSecretID: ak.SecretID,
envQCloudSecretKey: ak.SecretKey,
envQCloudRegion: region,
}, nil
}

View File

@ -0,0 +1,36 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envUCloudPrivateKey = "UCLOUD_PRIVATE_KEY"
envUCloudProjectID = "UCLOUD_PROJECT_ID"
envUCloudPublicKey = "UCLOUD_PUBLIC_KEY"
envUCloudRegion = "UCLOUD_REGION"
)
// UCloudCredentials are credentials for UCloud
type UCloudCredentials struct {
PublicKey string `yaml:"publicKey"`
PrivateKey string `yaml:"privateKey"`
Region string `yaml:"region"`
ProjectID string `yaml:"projectID"`
}
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)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envUCloudPublicKey: ak.PublicKey,
envUCloudPrivateKey: ak.PrivateKey,
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

@ -0,0 +1,36 @@
package provider
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
const (
envVSphereUser = "VSPHERE_USER"
envVSpherePassword = "VSPHERE_PASSWORD"
envVSphereServer = "VSPHERE_SERVER"
envVSphereAllowUnverifiedSSL = "VSPHERE_ALLOW_UNVERIFIED_SSL"
)
// VSphereCredentials are credentials for VSphere
type VSphereCredentials struct {
VSphereUser string `yaml:"vSphereUser"`
VSpherePassword string `yaml:"vSpherePassword"`
VSphereServer string `yaml:"vSphereServer"`
VSphereAllowUnverifiedSSL string `yaml:"vSphereAllowUnverifiedSSL,omitempty"`
}
func getVSphereCredentials(secretData []byte, name, namespace string) (map[string]string, error) {
var cred VSphereCredentials
if err := yaml.Unmarshal(secretData, &cred); err != nil {
klog.ErrorS(err, errConvertCredentials, "Name", name, "Namespace", namespace)
return nil, errors.Wrap(err, errConvertCredentials)
}
return map[string]string{
envVSphereUser: cred.VSphereUser,
envVSpherePassword: cred.VSpherePassword,
envVSphereServer: cred.VSphereServer,
envVSphereAllowUnverifiedSSL: cred.VSphereAllowUnverifiedSSL,
}, nil
}

View File

@ -18,14 +18,25 @@ package controllers
import (
"context"
"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"
"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"
terraformv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
providercred "github.com/oam-dev/terraform-controller/controllers/provider"
)
const (
errGetCredentials = "failed to get credentials from the cloud provider"
errSettingStatus = "failed to set status"
)
// ProviderReconciler reconciles a Provider object
@ -35,14 +46,15 @@ type ProviderReconciler struct {
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=terraform.core.oam.dev,resources=providerconfigs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=terraform.core.oam.dev,resources=providerconfigs/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=terraform.core.oam.dev,resources=providers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=terraform.core.oam.dev,resources=providers/status,verbs=get;update;patch
func (r *ProviderReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var ctx = context.Background()
_ = r.Log.WithValues("provider", req.NamespacedName)
// Reconcile will reconcile periodically
func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
klog.InfoS("reconciling Terraform Provider...", "NamespacedName", req.NamespacedName)
var provider terraformv1beta1.Provider
if err := r.Get(ctx, req.NamespacedName, &provider); err != nil {
if kerrors.IsNotFound(err) {
err = nil
@ -50,9 +62,39 @@ func (r *ProviderReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
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 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}
}
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{}, err
}
// SetupWithManager setups with a manager
func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&terraformv1beta1.Provider{}).

View File

@ -0,0 +1,421 @@
package controllers
import (
"context"
"fmt"
"strings"
"testing"
. "github.com/agiledragon/gomonkey/v2"
"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"
crossplanetypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/oam-dev/terraform-controller/controllers/provider"
)
func TestReconcile(t *testing.T) {
r1 := &ProviderReconciler{}
ctx := context.Background()
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
v1.AddToScheme(s)
r1.Client = fake.NewClientBuilder().WithScheme(s).WithStatusSubresource(&v1beta1.Provider{}).Build()
r2 := &ProviderReconciler{}
provider2 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{
Source: "Secret",
SecretRef: &crossplanetypes.SecretKeySelector{
SecretReference: crossplanetypes.SecretReference{
Name: "abc",
Namespace: "default",
},
Key: "credentials",
},
},
Provider: "aws",
},
}
creds, _ := yaml.Marshal(&provider.AWSCredentials{
AWSAccessKeyID: "a",
AWSSecretAccessKey: "b",
AWSSessionToken: "c",
})
secret2 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
Namespace: "default",
},
Data: map[string][]byte{
"credentials": creds,
},
Type: v1.SecretTypeOpaque,
}
r2.Client = fake.NewClientBuilder().WithScheme(s).WithObjects(secret2, provider2).WithStatusSubresource(&v1beta1.Provider{}).Build()
r3 := &ProviderReconciler{}
provider3 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{
Source: "Secret",
SecretRef: &crossplanetypes.SecretKeySelector{
SecretReference: crossplanetypes.SecretReference{
Name: "abc",
Namespace: "default",
},
Key: "credentials",
},
},
Provider: "aws",
},
}
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
r *ProviderReconciler
}
type want struct {
errMsg string
}
req := ctrl.Request{}
req.NamespacedName = types.NamespacedName{
Name: "aws",
Namespace: "default",
}
testcases := []struct {
name string
args args
want want
}{
{
name: "Provider is not found",
args: args{
req: req,
r: r1,
},
},
{
name: "Provider is found",
args: args{
req: req,
r: r2,
},
want: want{},
},
{
name: "Provider is found but the secret is not available",
args: args{
req: req,
r: r3,
},
want: want{
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) {
_, 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)
}
})
}
}
func TestReconcileProviderIsReadyButFailedToUpdateStatus(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
v1.AddToScheme(s)
r2 := &ProviderReconciler{}
provider2 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{
Source: "Secret",
SecretRef: &crossplanetypes.SecretKeySelector{
SecretReference: crossplanetypes.SecretReference{
Name: "abc",
Namespace: "default",
},
Key: "credentials",
},
},
Provider: "aws",
},
}
creds, _ := yaml.Marshal(&provider.AWSCredentials{
AWSAccessKeyID: "a",
AWSSecretAccessKey: "b",
AWSSessionToken: "c",
})
secret2 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "abc",
Namespace: "default",
},
Data: map[string][]byte{
"credentials": creds,
},
Type: v1.SecretTypeOpaque,
}
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) {
case *v1beta1.Provider:
p := obj.(*v1beta1.Provider)
if p.Status.State != "" {
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
}
}
return apiutilGVKForObject(obj, scheme)
})
defer patches.Reset()
type args struct {
req reconcile.Request
r *ProviderReconciler
}
type want struct {
errMsg string
}
req := ctrl.Request{}
req.NamespacedName = types.NamespacedName{
Name: "aws",
Namespace: "default",
}
testcases := []struct {
name string
args args
want want
}{
{
name: "Provider is found",
args: args{
req: req,
r: r2,
},
want: want{
errMsg: "failed to set status",
},
},
}
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) {
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
}
})
}
}
func TestReconcile3(t *testing.T) {
ctx := context.Background()
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
v1.AddToScheme(s)
r3 := &ProviderReconciler{}
provider3 := &v1beta1.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "aws",
Namespace: "default",
},
Spec: v1beta1.ProviderSpec{
Credentials: v1beta1.ProviderCredentials{
Source: "Secret",
SecretRef: &crossplanetypes.SecretKeySelector{
SecretReference: crossplanetypes.SecretReference{
Name: "abc",
Namespace: "default",
},
Key: "credentials",
},
},
Provider: errSettingStatus,
},
}
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) {
case *v1beta1.Provider:
p := obj.(*v1beta1.Provider)
if p.Status.State != "" {
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
}
}
return apiutilGVKForObject(obj, scheme)
})
defer patches.Reset()
type args struct {
req reconcile.Request
r *ProviderReconciler
}
type want struct {
errMsg string
}
req := ctrl.Request{}
req.NamespacedName = types.NamespacedName{
Name: "aws",
Namespace: "default",
}
testcases := []struct {
name string
args args
want want
}{
{
name: "Provider is found, but the secret is not available",
args: args{
req: req,
r: r3,
},
want: want{
errMsg: errSettingStatus,
},
},
}
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) {
t.Errorf("Reconcile() error = %v, wantErr %v", err, tc.want.errMsg)
}
})
}
}
func apiutilGVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
switch obj.(type) {
case *v1beta1.Provider:
p := obj.(*v1beta1.Provider)
if p.Status.State != "" {
return obj.GetObjectKind().GroupVersionKind(), errors.New("xxx")
}
}
// a copy implementation of `apiutil.GVKForObject`
_, isPartial := obj.(*metav1.PartialObjectMetadata) //nolint:ifshort
_, isPartialList := obj.(*metav1.PartialObjectMetadataList)
if isPartial || isPartialList {
// we require that the GVK be populated in order to recognize the object
gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 {
return schema.GroupVersionKind{}, runtime.NewMissingKindErr("unstructured object has no kind")
}
if len(gvk.Version) == 0 {
return schema.GroupVersionKind{}, runtime.NewMissingVersionErr("unstructured object has no version")
}
return gvk, nil
}
gvks, isUnversioned, err := scheme.ObjectKinds(obj)
if err != nil {
return schema.GroupVersionKind{}, err
}
if isUnversioned {
return schema.GroupVersionKind{}, fmt.Errorf("cannot create group-version-kind for unversioned type %T", obj)
}
if len(gvks) < 1 {
return schema.GroupVersionKind{}, fmt.Errorf("no group-version-kinds associated with type %T", obj)
}
if len(gvks) > 1 {
// this should only trigger for things like metav1.XYZ --
// normal versioned types should be fine
return schema.GroupVersionKind{}, fmt.Errorf(
"multiple group-version-kinds associated with type %T, refusing to guess at one", obj)
}
return gvks[0], nil
}

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,15 +41,23 @@ 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(done Done) {
var _ = BeforeSuite(func() {
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
@ -69,8 +79,6 @@ var _ = BeforeSuite(func(done Done) {
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {

View File

@ -0,0 +1,90 @@
package terraform
import (
"bytes"
"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 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 {
klog.InfoS("pods are not found", "Label", label, "Error", err)
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 {
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: targetContainer})
logs, err := req.Stream(ctx)
if err != nil {
return stage, "", err
}
defer func(logs io.ReadCloser) {
err := logs.Close()
if err != nil {
return
}
}(logs)
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) {
var buf = &bytes.Buffer{}
_, err := io.Copy(buf, rc)
if err != nil {
return "", err
}
logContent := buf.String()
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

@ -0,0 +1,180 @@
package terraform
import (
"context"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fakeclient "k8s.io/client-go/kubernetes/fake"
"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
initContainerName string
}
type want struct {
state types.Stage
log string
errMsg string
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "p1",
Namespace: "default",
Labels: map[string]string{
"job-name": "j1",
},
},
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
}
k8sClientSet := fakeclient.NewSimpleClientset(pod)
patches := gomonkey.ApplyMethod(reflect.TypeOf(&fake.FakePods{}), "GetLogs",
func(_ *fake.FakePods, _ string, _ *v1.PodLogOptions) *rest.Request {
rate := flowcontrol.NewFakeNeverRateLimiter()
restClient, _ := rest.NewRESTClient(
&url.URL{
Scheme: "http",
Host: "",
},
"",
rest.ClientContentConfig{},
rate,
http.DefaultClient)
r := rest.NewRequest(restClient)
r.Body([]byte("xxx"))
return r
})
defer patches.Reset()
var testcases = []struct {
name string
args args
want want
}{
{
name: "Pod is available, but no logs",
args: args{
client: k8sClientSet,
namespace: "default",
name: "j1",
containerName: "terraform-executor",
initContainerName: "terraform-init",
},
want: want{
errMsg: "client rate limiter Wait returned an error: can not be accept",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
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.Equal(t, tc.want.log, got)
assert.Equal(t, tc.want.state, state)
}
})
}
}
func TestFlushStream(t *testing.T) {
type args struct {
rc io.ReadCloser
name string
}
type want struct {
errMsg string
}
var testcases = []struct {
name string
args args
want want
}{
{
name: "Flush stream",
args: args{
rc: ioutil.NopCloser(strings.NewReader("xxx")),
name: "p1",
},
want: want{},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
logs, err := flushStream(tc.args.rc, tc.args.name)
if tc.want.errMsg != "" {
assert.Contains(t, err.Error(), tc.want.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, "xxx", logs)
}
})
}
}
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

@ -0,0 +1,57 @@
package terraform
import (
"context"
"strings"
"github.com/pkg/errors"
"k8s.io/klog/v2"
"github.com/oam-dev/terraform-controller/api/types"
"github.com/oam-dev/terraform-controller/controllers/client"
)
// GetTerraformStatus will get Terraform execution status
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
}
// 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, stage)
if success {
return state, nil
}
return state, errors.New(errMsg)
}
// 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:") {
errMsg := strings.Join(lines[i:], "\n")
if strings.Contains(errMsg, "Invalid Alibaba Cloud region") {
return false, types.InvalidRegion, 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

@ -0,0 +1,156 @@
package terraform
import (
"context"
"testing"
"github.com/agiledragon/gomonkey/v2"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"github.com/oam-dev/terraform-controller/api/types"
)
func TestGetTerraformStatus(t *testing.T) {
ctx := context.Background()
type args struct {
namespace string
name string
containerName string
}
type want struct {
state types.ConfigurationState
errMsg string
}
gomonkey.ApplyFunc(config.GetConfigWithContext, func(context string) (*rest.Config, error) {
return &rest.Config{}, nil
})
testcases := []struct {
name string
args args
want want
}{
{
name: "logs are not available",
args: args{
namespace: "default",
name: "test",
containerName: "terraform-executor",
},
want: want{
state: types.ConfigurationProvisioningAndChecking,
errMsg: "",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
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 {
assert.Equal(t, tc.want.state, state)
}
})
}
}
func TestGetTerraformStatus2(t *testing.T) {
ctx := context.Background()
type args struct {
namespace string
name string
containerName string
}
type want struct {
state types.ConfigurationState
errMsg string
}
gomonkey.ApplyFunc(config.GetConfigWithContext, func(context string) (*rest.Config, error) {
return nil, errors.New("failed to init clientSet")
})
testcases := []struct {
name string
args args
want want
}{
{
name: "failed to init clientSet",
args: args{},
want: want{
state: types.ConfigurationProvisioningAndChecking,
errMsg: "failed to init clientSet",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
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 {
assert.Equal(t, tc.want.state, state)
}
})
}
}
func TestAnalyzeTerraformLog(t *testing.T) {
type args struct {
logs string
}
type want struct {
success bool
state types.ConfigurationState
errMsg string
}
testcases := []struct {
name string
args args
want want
}{
{
name: "normal failed logs",
args: args{
logs: "31mError:",
},
want: want{
success: false,
state: types.ConfigurationApplyFailed,
errMsg: "31mError:",
},
},
{
name: "invalid region",
args: args{
logs: "31mError:\nInvalid Alibaba Cloud region",
},
want: want{
success: false,
state: types.InvalidRegion,
errMsg: "Invalid Alibaba Cloud region",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
success, state, errMsg := analyzeTerraformLog(tc.args.logs, types.ApplyStage)
if tc.want.errMsg != "" {
assert.Contains(t, errMsg, tc.want.errMsg)
} else {
assert.Equal(t, tc.want.success, success)
assert.Equal(t, tc.want.state, state)
}
})
}
}

View File

@ -0,0 +1,24 @@
package util
import (
"bytes"
"compress/gzip"
)
// DecompressTerraformStateSecret decompress the data of Terraform backend state secret
// Modified based on Hashicorp code base https://github.com/hashicorp/terraform/blob/fabdf0bea1fa2bf6a9d56cc3ea0f28242bf5e812/backend/remote-state/kubernetes/client.go#L355
// Licensed under Mozilla Public License 2.0
func DecompressTerraformStateSecret(data string) ([]byte, error) {
b := new(bytes.Buffer)
gz, err := gzip.NewReader(bytes.NewReader([]byte(data)))
if err != nil {
return nil, err
}
if _, err := b.ReadFrom(gz); err != nil {
return nil, err
}
if err := gz.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}

View File

@ -0,0 +1,68 @@
package util
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecompressTerraformStateSecret(t *testing.T) {
type args struct {
data string
needDecode bool
}
type want struct {
raw string
errMsg string
}
testcases := []struct {
name string
args args
want want
}{
{
name: "decompress terraform state secret",
args: args{
data: "H4sIAAAAAAAA/0SMwa7CIBBF9/0KMutH80ArDb9ijKHDYEhqMQO4afrvBly4POfc3H0QAt7EOaYNrDj/NS7E7ELi5/1XQI3/o4beM3F0K1ihO65xI/egNsLThLPRWi6agkR/CVIppaSZJrfgbBx6//1ItbxqyWDFfnTBlFNlpKaut+EYPgEAAP//xUXpvZsAAAA=",
needDecode: true,
},
want: want{
raw: `{
"version": 4,
"terraform_version": "1.0.2",
"serial": 2,
"lineage": "c35c8722-b2ef-cd6f-1111-755abc87acdd",
"outputs": {},
"resources": []
}
`,
},
},
{
name: "bad data",
args: args{
data: "abc",
},
want: want{
errMsg: "EOF",
},
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
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,101 +0,0 @@
package util
import (
"context"
"github.com/ghodss/yaml"
"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/v1beta1"
)
const (
// ProviderName is the name of Provider object
ProviderName = "default"
// ProviderNamespace is the namespace of Provider object
ProviderNamespace = "default"
)
type CloudProvider string
const (
Alibaba CloudProvider = "alibaba"
AWS CloudProvider = "aws"
)
const (
EnvAlicloudAcessKey = "ALICLOUD_ACCESS_KEY"
EnvAlicloudSecretKey = "ALICLOUD_SECRET_KEY"
EnvAlicloudRegion = "ALICLOUD_REGION"
EnvAWSAccessKeyID = "AWS_ACCESS_KEY_ID"
EnvAWSSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
EnvAWSDefaultRegion = "AWS_DEFAULT_REGION"
)
type AlibabaCloudCredentials struct {
AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `yaml:"accessKeySecret"`
}
type AWSCredentials struct {
AWSAccessKeyID string `yaml:"awsAccessKeyID"`
AWSSecretAccessKey string `yaml:"awsSecretAccessKey"`
}
func GetProviderCredentials(ctx context.Context, k8sClient client.Client) (map[string]string, error) {
var provider v1beta1.Provider
if err := k8sClient.Get(ctx, client.ObjectKey{Name: ProviderName, Namespace: ProviderNamespace}, &provider); err != nil {
errMsg := "failed to get Provider object"
klog.ErrorS(err, errMsg, "Name", ProviderName)
return nil, errors.Wrap(err, errMsg)
}
region := provider.Spec.Region
switch provider.Spec.Credentials.Source {
case "Secret":
var secret v1.Secret
secretRef := provider.Spec.Credentials.SecretRef
if err := k8sClient.Get(ctx, client.ObjectKey{Name: secretRef.Name, Namespace: secretRef.Namespace}, &secret); err != nil {
errMsg := "failed to get the Secret from Provider"
klog.ErrorS(err, errMsg, "Name", secretRef.Name, "Namespace", secretRef.Namespace)
return nil, errors.Wrap(err, errMsg)
}
switch provider.Spec.Provider {
case string(Alibaba):
var ak AlibabaCloudCredentials
if err := yaml.Unmarshal(secret.Data[secretRef.Key], &ak); err != nil {
errMsg := "failed to convert the credentials of Secret from Provider"
klog.ErrorS(err, errMsg, "Name", secretRef.Name, "Namespace", secretRef.Namespace)
return nil, errors.Wrap(err, errMsg)
}
return map[string]string{
EnvAlicloudAcessKey: ak.AccessKeyID,
EnvAlicloudSecretKey: ak.AccessKeySecret,
EnvAlicloudRegion: region,
}, nil
case string(AWS):
var ak AWSCredentials
if err := yaml.Unmarshal(secret.Data[secretRef.Key], &ak); err != nil {
errMsg := "failed to convert the credentials of Secret from Provider"
klog.ErrorS(err, errMsg, "Name", secretRef.Name, "Namespace", secretRef.Namespace)
return nil, errors.Wrap(err, errMsg)
}
return map[string]string{
EnvAWSAccessKeyID: ak.AWSAccessKeyID,
EnvAWSSecretAccessKey: ak.AWSSecretAccessKey,
EnvAWSDefaultRegion: region,
}, nil
}
default:
errMsg := "the credentials type is not supported."
err := errors.New(errMsg)
klog.ErrorS(err, "", "CredentialType", provider.Spec.Credentials.Source)
return nil, err
}
return nil, nil
}

78
controllers/util/rbac.go Normal file
View File

@ -0,0 +1,78 @@
package util
import (
"context"
"fmt"
"github.com/pkg/errors"
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 {
var clusterRole = rbacv1.ClusterRole{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "ClusterRole",
},
ObjectMeta: metav1.ObjectMeta{
Name: clusterRoleName,
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"get", "list", "create", "update", "delete"},
},
{
APIGroups: []string{"coordination.k8s.io"},
Resources: []string{"leases"},
Verbs: []string{"get", "create", "update", "delete"},
},
},
}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: clusterRoleName}, &rbacv1.ClusterRole{}); err != nil {
if kerrors.IsNotFound(err) {
if err := k8sClient.Create(ctx, &clusterRole); err != nil {
return errors.Wrap(err, "failed to create ClusterRole for Terraform executor")
}
}
}
return nil
}
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{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "ClusterRoleBinding",
},
ObjectMeta: metav1.ObjectMeta{
Name: crbName,
Namespace: namespace,
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: clusterRoleName,
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: serviceAccountName,
Namespace: namespace,
},
},
}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: crbName}, &rbacv1.ClusterRoleBinding{}); err != nil {
if kerrors.IsNotFound(err) {
if err := k8sClient.Create(ctx, &clusterRoleBinding); err != nil {
return errors.Wrap(err, "failed to create ClusterRoleBinding 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,30 +0,0 @@
package util
import (
"github.com/pkg/errors"
"github.com/oam-dev/terraform-controller/api/v1beta1"
)
type ConfigurationType string
const (
ConfigurationJSON ConfigurationType = "JSON"
ConfigurationHCL ConfigurationType = "HCL"
)
func ValidConfiguration(configuration v1beta1.Configuration) (ConfigurationType, string, error) {
json := configuration.Spec.JSON
hcl := configuration.Spec.HCL
switch {
case json == "" && hcl == "":
return "", "", errors.New("spec.JSON or spec.HCL should be set")
case json != "" && hcl != "":
return "", "", errors.New("spec.JSON and spec.HCL cloud not be set at the same time")
case json != "":
return ConfigurationJSON, json, nil
case hcl != "":
return ConfigurationHCL, hcl, nil
}
return "", "", errors.New("unknown issue")
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

After

Width:  |  Height:  |  Size: 660 KiB

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)
//}

110
e2e/normal/regression.go Normal file
View File

@ -0,0 +1,110 @@
package normal
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"gotest.tools/assert"
"k8s.io/klog/v2"
)
// 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) {
klog.Info("1. Applying Configuration")
pwd, _ := os.Getwd()
for _, p := range testcases {
configuration := filepath.Join(pwd, "..", p)
cmd := fmt.Sprintf("kubectl apply -f %s", configuration)
err := exec.Command("bash", "-c", cmd).Start() // #nosec
assert.NilError(t, err)
}
klog.Info("2. Checking Configurations status")
for i := 0; i < retryTimes; i++ {
var fields []string
output, err := exec.Command("bash", "-c", "kubectl get configuration -A").Output()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
// delete the last line which is empty
lines = lines[:len(lines)-1]
if len(lines) < len(testcases)+1 {
continue
}
var available = true
for i, line := range lines {
if i == 0 {
continue
}
fields = strings.Fields(line)
if len(fields) == 4 {
if fields[2] != Available {
available = false
t.Logf("Configuration %s is not available", fields[1])
break
}
}
}
if available {
goto deletion
}
if i == retryTimes-1 {
t.Error("Not all configurations are ready")
}
time.Sleep(time.Second * 5)
}
deletion:
klog.Info("3. Deleting Configuration")
for _, p := range testcases {
configuration := filepath.Join(pwd, "..", p)
cmd := fmt.Sprintf("kubectl delete -f %s", configuration)
err := exec.Command("bash", "-c", cmd).Start() // #nosec
assert.NilError(t, err)
}
klog.Info("4. Checking Configuration is deleted")
for i := 0; i < retryTimes; i++ {
var (
fields []string
existed bool
)
output, err := exec.Command("bash", "-c", "kubectl get configuration -A").Output()
assert.NilError(t, err)
lines := strings.Split(string(output), "\n")
for j, line := range lines {
if j == 0 {
continue
}
existed = true
fields = strings.Fields(line)
if len(fields) == 4 {
t.Logf("Retrying %d times. Configuration %s is deleting.", i+1, fields[1])
}
}
if existed {
if i == retryTimes-1 {
t.Error("Configuration are not deleted")
}
time.Sleep(time.Second * 5)
continue
}
break
}
}

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