├── .github ├── ISSUE_TEMPLATE │ ├── config.yaml │ ├── question.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── tag.yml │ └── promote.yml ├── charts └── oam-kubernetes-runtime │ ├── .gitignore │ ├── templates │ ├── traitdefinitions.yaml │ ├── scopedefinitions.yaml │ ├── workloaddefinitions.yaml │ ├── _helpers.tpl │ └── webhook.yaml │ ├── .helmignore │ ├── Chart.yaml │ ├── values.yaml.tmpl │ └── crds │ ├── core.oam.dev_scopedefinitions.yaml │ ├── core.oam.dev_traitdefinitions.yaml │ ├── core.oam.dev_workloaddefinitions.yaml │ └── core.oam.dev_manualscalertraits.yaml ├── legacy ├── charts │ └── oam-kubernetes-runtime-legacy │ │ ├── .gitignore │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ └── crds │ │ ├── core.oam.dev_scopedefinitions.yaml │ │ ├── core.oam.dev_traitdefinitions.yaml │ │ ├── core.oam.dev_workloaddefinitions.yaml │ │ └── core.oam.dev_manualscalertraits.yaml ├── convert │ └── main.go └── README.md ├── .gitmodules ├── hack ├── ssl │ ├── .gitignore │ ├── Makefile │ ├── README.md │ └── ssl.sh ├── linter-violation.tmpl └── boilerplate.go.txt ├── examples ├── containerized-workload │ ├── sample_scope_definition.yaml │ ├── sample_secret.yaml │ ├── sample_application_config.yaml │ ├── sample_component.yaml │ └── README.md ├── dependency │ ├── test.sh │ ├── definition.yaml │ ├── demo-with-conditions.yaml │ ├── demo-with-valuefrom.yaml │ ├── demo.yaml │ └── README.md ├── flight-tracker │ ├── definitions │ │ └── ingress-trait.yaml │ ├── components │ │ ├── tracker-flights-component.yaml │ │ ├── tracker-weather-component.yaml │ │ ├── tracker-quakes-component.yaml │ │ ├── tracker-ui-component.yaml │ │ ├── tracker-db-component.yaml │ │ └── tracker-data-component.yaml │ ├── README.md │ └── tracker-app-config.yaml ├── component-versioning │ ├── definition.yaml │ ├── sample_application_config.yaml │ ├── component-mutable-app.yaml │ ├── versioning-demo-app.yaml │ └── sample_component.yaml ├── webhook-enabled │ ├── app.yaml │ └── definition.yaml ├── typed-component │ ├── sample_component.yaml │ ├── definitions.yaml │ ├── sample_application_config.yaml │ └── README.md └── data-passing │ ├── definitions.yaml │ └── demo.yaml ├── test ├── e2e-test │ └── testdata │ │ └── revision │ │ ├── workload-def.yaml │ │ ├── trait-def-no-revision.yaml │ │ ├── trait-def.yaml │ │ ├── comp-v1.yaml │ │ ├── comp-v2.yaml │ │ └── app.yaml └── integration │ ├── doc.go │ ├── util.go │ └── builder.go ├── pkg ├── controller │ ├── v1alpha2 │ │ ├── core │ │ │ ├── traits │ │ │ │ ├── manualscalertrait │ │ │ │ │ └── manualscalertrait_controller_test.go │ │ │ │ └── doc.go │ │ │ ├── scopes │ │ │ │ ├── doc.go │ │ │ │ └── healthscope │ │ │ │ │ ├── standard.go │ │ │ │ │ └── standard_test.go │ │ │ └── workloads │ │ │ │ ├── doc.go │ │ │ │ └── containerizedworkload │ │ │ │ └── containerizedworkload_controller_helper.go │ │ ├── applicationconfiguration │ │ │ ├── dag.go │ │ │ ├── controllerhooks.go │ │ │ └── appconfig_suit_test.go │ │ └── setup.go │ ├── doc.go │ └── controller.go ├── webhook │ ├── README.md │ └── v1alpha2 │ │ ├── admit.go │ │ ├── applicationconfiguration │ │ ├── helper_test.go │ │ ├── suite_test.go │ │ └── helper.go │ │ └── component │ │ ├── component_suite_test.go │ │ └── validating_handler.go └── oam │ ├── labels.go │ ├── mock │ ├── mapper.go │ └── mocks.go │ ├── types.go │ ├── discoverymapper │ ├── mapper.go │ └── suit_test.go │ └── util │ └── test_utils.go ├── apis ├── apis.go ├── core │ ├── v1alpha2 │ │ ├── doc.go │ │ ├── core_trait_types.go │ │ ├── methods.go │ │ ├── core_scope_types.go │ │ └── register.go │ └── core.go └── generate.go ├── .gitignore ├── images └── oam-kubernetes-runtime │ ├── Makefile │ └── Dockerfile ├── OWNERS.md ├── Dockerfile ├── go.mod ├── design └── one-pager-podspecable-workload.md ├── cmd └── oam-kubernetes-runtime │ └── main.go └── DEVELOPMENT.md /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/.gitignore: -------------------------------------------------------------------------------- 1 | values.yaml 2 | values.yaml-e 3 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/.gitignore: -------------------------------------------------------------------------------- 1 | values.yaml 2 | values.yaml-e 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "build"] 2 | path = build 3 | url = https://github.com/upbound/build 4 | -------------------------------------------------------------------------------- /hack/ssl/.gitignore: -------------------------------------------------------------------------------- 1 | oam-kubernetes-runtime-webhook.csr 2 | oam-kubernetes-runtime-webhook.key 3 | oam-kubernetes-runtime-webhook.pem 4 | csr.conf -------------------------------------------------------------------------------- /examples/containerized-workload/sample_scope_definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: HealthScope 3 | metadata: 4 | name: example-health-scope 5 | spec: 6 | workloadRefs: [] 7 | -------------------------------------------------------------------------------- /examples/containerized-workload/sample_secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: mysecret 5 | type: Opaque 6 | data: 7 | username: YWRtaW4= 8 | password: MWYyZDFlMmU2N2Rm 9 | -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/workload-def.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: WorkloadDefinition 3 | metadata: 4 | name: bars.example.com 5 | spec: 6 | definitionRef: 7 | name: bars.example.com -------------------------------------------------------------------------------- /examples/dependency/test.sh: -------------------------------------------------------------------------------- 1 | kubectl delete -f examples/dependency/demo.yaml 2 | kubectl apply -f examples/dependency/definition.yaml 3 | kubectl create -f examples/dependency/demo.yaml 4 | go run cmd/oam-runtime/main.go 5 | -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/trait-def-no-revision.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: TraitDefinition 3 | metadata: 4 | name: bars.example.com 5 | spec: 6 | definitionRef: 7 | name: bars.example.com -------------------------------------------------------------------------------- /examples/flight-tracker/definitions/ingress-trait.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: TraitDefinition 3 | metadata: 4 | name: ingresses.extensions 5 | spec: 6 | definitionRef: 7 | name: ingresses.extensions -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Help wanted. 4 | title: "[Question]" 5 | labels: help wanted 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | -------------------------------------------------------------------------------- /hack/ssl/Makefile: -------------------------------------------------------------------------------- 1 | APP = oam-kubernetes-runtime-webhook 2 | NAMESPACE = oam-system 3 | 4 | .PHONY:cert 5 | cert: 6 | @./ssl.sh $(APP) $(NAMESPACE) 7 | 8 | .PHONY:clean 9 | clean: 10 | rm -vf *.key *.pem *.cert *.crt *.csr 11 | -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/trait-def.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: TraitDefinition 3 | metadata: 4 | name: bars.example.com 5 | spec: 6 | revisionEnabled: true 7 | definitionRef: 8 | name: bars.example.com -------------------------------------------------------------------------------- /hack/linter-violation.tmpl: -------------------------------------------------------------------------------- 1 | `{{violation.rule}}`: {{violation.message}} 2 | 3 | Refer to Crossplane's [coding style documentation](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#coding-style-and-linting) for more information. -------------------------------------------------------------------------------- /hack/ssl/README.md: -------------------------------------------------------------------------------- 1 | # SSL Script 2 | 3 | We are generating ssl certs and secrets here. 4 | 5 | 6 | References: [Writing a very basic kubernetes mutating admission webhook](https://medium.com/ovni/writing-a-very-basic-kubernetes-mutating-admission-webhook-398dbbcb63ec) 7 | 8 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/templates/traitdefinitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: TraitDefinition 3 | metadata: 4 | name: manualscalertraits.core.oam.dev 5 | spec: 6 | workloadRefPath: spec.workloadRef 7 | definitionRef: 8 | name: manualscalertraits.core.oam.dev -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/comp-v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: example-component 5 | namespace: component-versioning-test 6 | spec: 7 | workload: 8 | apiVersion: example.com/v1 9 | kind: Bar 10 | spec: 11 | key: v1 -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/comp-v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: example-component 5 | namespace: component-versioning-test 6 | spec: 7 | workload: 8 | apiVersion: example.com/v1 9 | kind: Bar 10 | spec: 11 | key: v2 -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/templates/scopedefinitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ScopeDefinition 3 | metadata: 4 | name: healthscopes.core.oam.dev 5 | spec: 6 | workloadRefsPath: spec.workloadRefs 7 | allowComponentOverlap: true 8 | definitionRef: 9 | name: healthscope.core.oam.dev -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller_test.go: -------------------------------------------------------------------------------- 1 | package manualscalertrait 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestManualscalertrait(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Manualscalertrait Suite") 13 | } 14 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/templates/workloaddefinitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: WorkloadDefinition 3 | metadata: 4 | name: containerizedworkloads.core.oam.dev 5 | spec: 6 | definitionRef: 7 | name: containerizedworkloads.core.oam.dev 8 | childResourceKinds: 9 | - apiVersion: apps/v1 10 | kind: Deployment 11 | - apiVersion: v1 12 | kind: Service -------------------------------------------------------------------------------- /examples/component-versioning/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: TraitDefinition 3 | metadata: 4 | name: simplerollouttraits.extend.oam.dev 5 | spec: 6 | revisionEnabled: true 7 | workloadRefPath: spec.workloadRef 8 | appliesToWorkloads: 9 | - core.oam.dev/v1alpha2.ContainerizedWorkload 10 | - deployments.apps 11 | definitionRef: 12 | name: simplerollouttraits.extend.oam.dev -------------------------------------------------------------------------------- /test/e2e-test/testdata/revision/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ApplicationConfiguration 3 | metadata: 4 | name: example-appconfig 5 | namespace: component-versioning-test 6 | spec: 7 | components: 8 | - componentName: example-component 9 | traits: 10 | - trait: 11 | apiVersion: example.com/v1 12 | kind: Bar 13 | spec: 14 | foo: bar -------------------------------------------------------------------------------- /examples/component-versioning/sample_application_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ApplicationConfiguration 3 | metadata: 4 | name: example-appconfig 5 | spec: 6 | components: 7 | - componentName: example-component 8 | traits: 9 | - trait: 10 | apiVersion: core.oam.dev/v1alpha2 11 | kind: ManualScalerTrait 12 | spec: 13 | replicaCount: 3 -------------------------------------------------------------------------------- /examples/component-versioning/component-mutable-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ApplicationConfiguration 3 | metadata: 4 | name: example-appconfig 5 | spec: 6 | components: 7 | - revisionName: example-component-v1 8 | traits: 9 | - trait: 10 | apiVersion: core.oam.dev/v1alpha2 11 | kind: ManualScalerTrait 12 | spec: 13 | replicaCount: 3 14 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /examples/component-versioning/versioning-demo-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ApplicationConfiguration 3 | metadata: 4 | name: example-appconfig-rollout 5 | spec: 6 | components: 7 | - componentName: example-component 8 | traits: 9 | - trait: 10 | apiVersion: extend.oam.dev/v1alpha2 11 | kind: SimpleRolloutTrait 12 | spec: 13 | replica: 6 14 | maxUnavailable: 2 15 | batch: 2 16 | -------------------------------------------------------------------------------- /pkg/webhook/README.md: -------------------------------------------------------------------------------- 1 | # Validation Rules 2 | 3 | The admission webhook validates ApplicationConfiguration spec according to the following rules. 4 | 5 | - RevisionName & ComponentName of component MUST be mutually exclusive. It's not allowed to assign both but one of them must be assigned. 6 | - If a component is versioning enabled (that means its revisionName is assigned or it contains any revisionEnabled trait), its workload `metadata.name` MUST NOT be assigned value nor overwritten by parameters. 7 | 8 | -------------------------------------------------------------------------------- /examples/webhook-enabled/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: foo 5 | spec: 6 | workload: 7 | type: fooworkload 8 | spec: 9 | key: workload 10 | 11 | --- 12 | apiVersion: core.oam.dev/v1alpha2 13 | kind: ApplicationConfiguration 14 | metadata: 15 | name: example-appconfig 16 | spec: 17 | components: 18 | - componentName: foo 19 | traits: 20 | - trait: 21 | name: footrait 22 | properties: 23 | key: trait -------------------------------------------------------------------------------- /examples/component-versioning/sample_component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: example-component 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | spec: 10 | containers: 11 | - name: wordpress 12 | image: wordpress:4.6.1-apache 13 | ports: 14 | - containerPort: 80 15 | name: wordpress 16 | env: 17 | - name: TEST_ENV 18 | value: test -------------------------------------------------------------------------------- /examples/typed-component/sample_component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: web-service-component 5 | spec: 6 | workload: 7 | type: web-service 8 | spec: 9 | containers: 10 | - name: wordpress 11 | image: wordpress:4.6.1-apache 12 | ports: 13 | - containerPort: 80 14 | name: wordpress 15 | env: 16 | - name: TEST_ENV 17 | value: test 18 | parameters: 19 | - name: image 20 | fieldPaths: 21 | - spec.containers[0].image -------------------------------------------------------------------------------- /examples/typed-component/definitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: WorkloadDefinition 3 | metadata: 4 | name: web-service 5 | spec: 6 | definitionRef: 7 | name: containerizedworkloads.core.oam.dev 8 | childResourceKinds: 9 | - apiVersion: apps/v1 10 | kind: Deployment 11 | - apiVersion: v1 12 | kind: Service 13 | --- 14 | apiVersion: core.oam.dev/v1alpha2 15 | kind: ScopeDefinition 16 | metadata: 17 | name: healthscopes.core.oam.dev 18 | spec: 19 | definitionRef: 20 | name: healthscopes.core.oam.dev 21 | workloadRefsPath: spec.workloadRefs 22 | allowComponentOverlap: true 23 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /pkg/controller/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | -------------------------------------------------------------------------------- /test/integration/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package integration 18 | -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: Tag 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release version (e.g. v0.1.0)' 8 | required: true 9 | message: 10 | description: 'Tag message' 11 | required: true 12 | 13 | jobs: 14 | create-tag: 15 | runs-on: ubuntu-18.04 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Create Tag 22 | uses: negz/create-tag@v1 23 | with: 24 | version: ${{ github.event.inputs.version }} 25 | message: ${{ github.event.inputs.message }} 26 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /apis/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package apis contains Kubernetes API groups 18 | package apis 19 | -------------------------------------------------------------------------------- /examples/dependency/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: foo.example.com 5 | spec: 6 | group: example.com 7 | names: 8 | kind: Foo 9 | listKind: FooList 10 | plural: foo 11 | singular: foo 12 | scope: Namespaced 13 | version: v1 14 | preserveUnknownFields: true 15 | --- 16 | apiVersion: core.oam.dev/v1alpha2 17 | kind: WorkloadDefinition 18 | metadata: 19 | name: foo.example.com 20 | spec: 21 | definitionRef: 22 | name: foo.example.com 23 | --- 24 | apiVersion: core.oam.dev/v1alpha2 25 | kind: TraitDefinition 26 | metadata: 27 | name: foo.example.com 28 | spec: 29 | definitionRef: 30 | name: foo.example.com 31 | -------------------------------------------------------------------------------- /examples/data-passing/definitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: foo.example.com 5 | spec: 6 | group: example.com 7 | names: 8 | kind: Foo 9 | listKind: FooList 10 | plural: foo 11 | singular: foo 12 | scope: Namespaced 13 | version: v1 14 | preserveUnknownFields: true 15 | --- 16 | apiVersion: core.oam.dev/v1alpha2 17 | kind: WorkloadDefinition 18 | metadata: 19 | name: foo.example.com 20 | spec: 21 | definitionRef: 22 | name: foo.example.com 23 | --- 24 | apiVersion: core.oam.dev/v1alpha2 25 | kind: TraitDefinition 26 | metadata: 27 | name: foo.example.com 28 | spec: 29 | definitionRef: 30 | name: foo.example.com 31 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/scopes/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package scopes provides scope related controllers. 18 | package scopes 19 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/traits/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package traits provides traits related controllers. 18 | package traits 19 | -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/admit.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2/applicationconfiguration" 5 | "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2/component" 6 | 7 | "sigs.k8s.io/controller-runtime/pkg/manager" 8 | ) 9 | 10 | // Add will be called in main and register all validation handlers 11 | func Add(mgr manager.Manager) error { 12 | if err := applicationconfiguration.RegisterValidatingHandler(mgr); err != nil { 13 | return err 14 | } 15 | applicationconfiguration.RegisterMutatingHandler(mgr) 16 | if err := component.RegisterMutatingHandler(mgr); err != nil { 17 | return err 18 | } 19 | component.RegisterValidatingHandler(mgr) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/workloads/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package workloads provides workloads related controllers. 18 | package workloads 19 | -------------------------------------------------------------------------------- /examples/containerized-workload/sample_application_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: ApplicationConfiguration 3 | metadata: 4 | name: example-appconfig 5 | spec: 6 | components: 7 | - componentName: example-component 8 | parameterValues: 9 | - name: image 10 | value: wordpress:php7.2 11 | traits: 12 | - trait: 13 | apiVersion: core.oam.dev/v1alpha2 14 | kind: ManualScalerTrait 15 | metadata: 16 | name: example-appconfig-trait 17 | spec: 18 | replicaCount: 3 19 | scopes: 20 | - scopeRef: 21 | apiVersion: core.oam.dev/v1alpha2 22 | kind: HealthScope 23 | name: example-health-scope 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: kind/feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 14 | 15 | **Describe the solution you'd like** 16 | 19 | 20 | **Describe alternatives you've considered** 21 | 24 | 25 | **Additional context** 26 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | main 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | /.cache 12 | /.work 13 | /_output 14 | /config/ 15 | /config 16 | cover.out 17 | /vendor 18 | /.vendor-new 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | # Dependency directories (remove the comment below to include it) 27 | vendor/ 28 | .idea 29 | .DS_Store 30 | bin/ 31 | tmp/ 32 | 33 | # Vscode files 34 | .vscode 35 | .devcontainer/ 36 | 37 | # Webhook certificates 38 | csr.conf 39 | oam-kubernetes-runtime-webhook.csr 40 | oam-kubernetes-runtime-webhook.key 41 | oam-kubernetes-runtime-webhook.pem 42 | 43 | # legacy chart 44 | charts/oam-kubernetes-runtime-legacy 45 | 46 | -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-flights-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: flights-api 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: flights-api 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: flights-api 16 | image: sonofjorel/rudr-flights-api:0.49 17 | env: 18 | - name: DATA_SERVICE_URI 19 | value: "foo" 20 | ports: 21 | - name: http 22 | containerPort: 3003 23 | protocol: TCP 24 | parameters: 25 | - name: dataUri 26 | description: uri for data-api pod 27 | required: true 28 | fieldPaths: 29 | - spec.containers[0].env[0].value -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-weather-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: weather-api 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: weather-api 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: weather-api 16 | image: sonofjorel/rudr-weather-api:0.49 17 | env: 18 | - name: DATA_SERVICE_URI 19 | value: "foo" 20 | ports: 21 | - name: http 22 | containerPort: 3015 23 | protocol: TCP 24 | parameters: 25 | - name: dataUri 26 | description: uri for data-api pod 27 | required: true 28 | fieldPaths: 29 | - spec.containers[0].env[0].value -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-quakes-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: quakes-api 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: quakes-api 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: quakes-api 16 | image: sonofjorel/rudr-quakes-api:0.49 17 | env: 18 | - name: DATA_SERVICE_URI 19 | value: "foo" 20 | ports: 21 | - name: http 22 | containerPort: 3012 23 | protocol: TCP 24 | parameters: 25 | - name: dataUri 26 | description: uri for data-api pod 27 | required: true 28 | fieldPaths: 29 | - spec.containers[0].env[0].value 30 | 31 | -------------------------------------------------------------------------------- /images/oam-kubernetes-runtime/Makefile: -------------------------------------------------------------------------------- 1 | # ==================================================================================== 2 | # Setup Project 3 | 4 | PLATFORMS := linux_amd64 linux_arm64 5 | include ../../build/makelib/common.mk 6 | 7 | # ==================================================================================== 8 | # Options 9 | IMAGE = $(BUILD_REGISTRY)/oam-kubernetes-runtime-$(ARCH) 10 | include ../../build/makelib/image.mk 11 | 12 | # ==================================================================================== 13 | # Targets 14 | 15 | img.build: 16 | @$(INFO) docker build $(IMAGE) 17 | @cp -r ../.. $(IMAGE_TEMP_DIR) || $(FAIL) 18 | @cp $(OUTPUT_DIR)/bin/$(OS)_$(ARCH)/oam-kubernetes-runtime $(IMAGE_TEMP_DIR) || $(FAIL) 19 | @cd $(IMAGE_TEMP_DIR) || $(FAIL) 20 | @docker build $(BUILD_ARGS) \ 21 | -t $(IMAGE) \ 22 | $(IMAGE_TEMP_DIR) || $(FAIL) 23 | @$(OK) docker build $(IMAGE) -------------------------------------------------------------------------------- /apis/core/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha2 contains resources relating to the Open Application Model. 18 | // See https://github.com/oam-dev/spec for more details. 19 | // +kubebuilder:object:generate=true 20 | // +groupName=core.oam.dev 21 | // +versionName=v1alpha2 22 | package v1alpha2 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: kind/bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 14 | 15 | **To Reproduce** 16 | 23 | 24 | **Expected behavior** 25 | 28 | 29 | **Screenshots** 30 | 33 | 34 | **Cluster information** 35 | 39 | 40 | **Additional context** 41 | 44 | -------------------------------------------------------------------------------- /images/oam-kubernetes-runtime/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.13 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY apis/ apis/ 14 | COPY pkg/ pkg/ 15 | COPY cmd/oam-kubernetes-runtime/main.go main.go 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o controller main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | FROM gcr.io/distroless/static:nonroot 23 | WORKDIR / 24 | COPY --from=builder /workspace/controller . 25 | USER nonroot:nonroot 26 | 27 | ENTRYPOINT ["/controller"] 28 | -------------------------------------------------------------------------------- /examples/containerized-workload/sample_component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: example-component 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | spec: 10 | containers: 11 | - name: wordpress 12 | image: wordpress:4.6.1-apache 13 | ports: 14 | - containerPort: 80 15 | name: wordpress 16 | env: 17 | - name: TEST_ENV 18 | value: test 19 | config: 20 | - path: /test/configfile/config 21 | value: test 22 | - path: /test/secretconfig 23 | fromSecret: 24 | name: mysecret 25 | key: password 26 | parameters: 27 | - name: instance-name 28 | fieldPaths: 29 | - metadata.name 30 | - name: image 31 | fieldPaths: 32 | - spec.containers[0].image 33 | -------------------------------------------------------------------------------- /OWNERS.md: -------------------------------------------------------------------------------- 1 | # OWNERS 2 | 3 | This page lists all maintainers for **this** repository. Each repository in the [Crossplane 4 | organization](https://github.com/crossplane/) will list their repository maintainers in their own 5 | `OWNERS.md` file. 6 | 7 | Please see the Crossplane 8 | [GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/master/GOVERNANCE.md) for governance 9 | guidelines and responsibilities for the steering committee and maintainers. 10 | 11 | ## Maintainers 12 | 13 | * Nic Cope ([negz](https://github.com/negz)) 14 | * Daniel Mangum ([hasheddan](https://github.com/hasheddan)) 15 | * Hongchao Deng ([hongchaodeng](https://github.com/hongchaodeng)) 16 | * Jianbo Sun ([wonderflow](https://github.com/wonderflow)) 17 | * Ryan Zhang ([ryanzhang-oss](https://github.com/ryanzhang-oss)) 18 | * Yue Wang ([captainroy-hy](https://github.com/captainroy-hy)) -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: oam-kubernetes-runtime 3 | description: A Helm chart for OAM Kubernetes Resources Controller 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | version: 0.0.1 18 | 19 | # This is the version number of the application being deployed. This version number should be 20 | # incremented each time you make changes to the application. 21 | appVersion: 0.1 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.13 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY apis/ apis/ 14 | COPY pkg/ pkg/ 15 | COPY cmd/oam-kubernetes-runtime/main.go main.go 16 | 17 | # Build 18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o controller main.go 19 | 20 | # Use distroless as minimal base image to package the manager binary 21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 22 | # oamdev/gcr.io-distroless-static:nonroot is syncd from gcr.io/distroless/static:nonroot as somewhere can't reach gcr.io 23 | FROM oamdev/gcr.io-distroless-static:nonroot 24 | WORKDIR / 25 | COPY --from=builder /workspace/controller . 26 | USER nonroot:nonroot 27 | 28 | ENTRYPOINT ["/controller"] 29 | -------------------------------------------------------------------------------- /examples/typed-component/sample_application_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: HealthScope 3 | metadata: 4 | name: example-health-scope 5 | spec: 6 | probe-timeout: 5 7 | probe-interval: 5 8 | workloadRefs: 9 | - apiVersion: core.oam.dev/v1alpha2 10 | kind: ContainerizedWorkload 11 | name: web-service-component 12 | --- 13 | apiVersion: core.oam.dev/v1alpha2 14 | kind: ApplicationConfiguration 15 | metadata: 16 | name: example-appconfig 17 | spec: 18 | components: 19 | - componentName: web-service-component 20 | parameterValues: 21 | - name: image 22 | value: wordpress:php7.2 23 | traits: 24 | - trait: 25 | apiVersion: core.oam.dev/v1alpha2 26 | kind: ManualScalerTrait 27 | metadata: 28 | name: example-appconfig-trait 29 | spec: 30 | replicaCount: 3 31 | scopes: 32 | - scopeRef: 33 | apiVersion: core.oam.dev/v1alpha2 34 | kind: HealthScope 35 | name: example-health-scope 36 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: oam-kubernetes-runtime-legacy 3 | description: A Helm chart for OAM Kubernetes Resources Controller, targeted on Kubernetes v1.14 and v1.15 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | version: 0.0.1 18 | 19 | # This is the version number of the application being deployed. This version number should be 20 | # incremented each time you make changes to the application. 21 | appVersion: 0.1 22 | -------------------------------------------------------------------------------- /legacy/convert/main.go: -------------------------------------------------------------------------------- 1 | package main // #nosec 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func main() { 13 | dir, err := os.Getwd() 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | if len(os.Args) > 1 { 18 | dir = os.Args[1] 19 | } 20 | err = filepath.Walk(dir, func(path string, info os.FileInfo, _ error) error { 21 | if info.IsDir() { 22 | return nil 23 | } 24 | /* #nosec */ 25 | data, err := ioutil.ReadFile(path) 26 | if err != nil { 27 | fmt.Fprintln(os.Stderr, "failed to read file", err) 28 | return err 29 | } 30 | newdata := strings.ReplaceAll(string(data), "x-kubernetes-embedded-resource: true", "") 31 | newdata = strings.ReplaceAll(newdata, "x-kubernetes-preserve-unknown-fields: true", "") 32 | newdata = strings.ReplaceAll(newdata, "default: false", "") 33 | /* #nosec */ 34 | if err = ioutil.WriteFile(path, []byte(newdata), 0644); err != nil { 35 | fmt.Fprintln(os.Stderr, "failed to write file:", err) 36 | return err 37 | } 38 | return nil 39 | }) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package controller 15 | 16 | import "time" 17 | 18 | // Args args used by controller 19 | type Args struct { 20 | // RevisionLimit is the maximum number of revisions that will be maintained. 21 | // The default value is 50. 22 | RevisionLimit int 23 | 24 | // ApplyOnceOnly indicates whether workloads and traits should be 25 | // affected if no spec change is made in the ApplicationConfiguration. 26 | ApplyOnceOnly bool 27 | 28 | // LongWait is controller next reconcile interval time 29 | LongWait time.Duration 30 | } 31 | -------------------------------------------------------------------------------- /examples/webhook-enabled/definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: foo.example.com 5 | spec: 6 | group: example.com 7 | names: 8 | kind: Foo 9 | listKind: FooList 10 | plural: foo 11 | singular: foo 12 | scope: Namespaced 13 | version: v1 14 | preserveUnknownFields: true 15 | --- 16 | apiVersion: core.oam.dev/v1alpha2 17 | kind: WorkloadDefinition 18 | metadata: 19 | name: fooworkload 20 | spec: 21 | definitionRef: 22 | name: foo.example.com 23 | --- 24 | apiVersion: core.oam.dev/v1alpha2 25 | kind: TraitDefinition 26 | metadata: 27 | name: footrait 28 | spec: 29 | definitionRef: 30 | name: foo.example.com 31 | 32 | --- 33 | apiVersion: rbac.authorization.k8s.io/v1 34 | kind: ClusterRole 35 | metadata: 36 | name: foo:system:aggregate-to-controller 37 | labels: 38 | rbac.oam.dev/aggregate-to-controller: "true" 39 | rules: 40 | - apiGroups: 41 | - example.com 42 | resources: 43 | - "*" 44 | verbs: 45 | - "*" 46 | - apiGroups: 47 | - apps 48 | resources: 49 | - controllerrevisions 50 | verbs: 51 | - "*" 52 | - apiGroups: 53 | - apiextensions.k8s.io 54 | resources: 55 | - customresourcedefinitions 56 | verbs: 57 | - "*" -------------------------------------------------------------------------------- /test/integration/util.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | /* 4 | Copyright 2020 The Crossplane Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package integration 20 | 21 | import ( 22 | "context" 23 | "time" 24 | ) 25 | 26 | func waitFor(ctx context.Context, interval time.Duration, check func() (bool, error)) error { 27 | ticker := time.NewTicker(interval) 28 | 29 | for { 30 | select { 31 | case <-ctx.Done(): 32 | return ctx.Err() 33 | case <-ticker.C: 34 | // check functions return both a boolean and error to demonstrate 35 | // whether an error has occurred and whether the function should 36 | // continue to be called. waitFor will only exit when the check 37 | // function returns true. 38 | done, err := check() 39 | if done { 40 | return err 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-ui-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: service-tracker-ui 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: web-ui 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: service-tracker-ui 16 | image: sonofjorel/rudr-web-ui:0.49 17 | env: 18 | - name: FLIGHT_API_ROOT 19 | value: "foo" 20 | - name: WEATHER_API_ROOT 21 | value: "foo" 22 | - name: QUAKES_API_ROOT 23 | value: "foo" 24 | ports: 25 | - name: http 26 | containerPort: 8080 27 | protocol: TCP 28 | parameters: 29 | - name: flightsUri 30 | description: uri for flights-api pod 31 | required: true 32 | fieldPaths: 33 | - spec.containers[0].env[0].value 34 | - name: weatherUri 35 | description: uri for weather-api pod 36 | required: true 37 | fieldPaths: 38 | - spec.containers[0].env[1].value 39 | - name: quakesUri 40 | description: uri for quakes-api pod 41 | required: true 42 | fieldPaths: 43 | - spec.containers[0].env[2].value 44 | 45 | -------------------------------------------------------------------------------- /apis/core/core.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package core contains Kubernetes API groups for core OAM resources. 18 | // Refer to https://github.com/oam-dev/spec for more details. 19 | package core 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime" 23 | 24 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 25 | ) 26 | 27 | func init() { 28 | // Register the types with the Scheme so the resources can map objects to GroupVersionKinds and back 29 | AddToSchemes = append(AddToSchemes, v1alpha2.SchemeBuilder.AddToScheme) 30 | } 31 | 32 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 33 | var AddToSchemes runtime.SchemeBuilder 34 | 35 | // AddToScheme adds all Resources to the Scheme 36 | func AddToScheme(s *runtime.Scheme) error { 37 | return AddToSchemes.AddToScheme(s) 38 | } 39 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/values.yaml.tmpl: -------------------------------------------------------------------------------- 1 | # Default values for oam-kubernetes-runtime. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | useWebhook: false 7 | applyOnceOnly: false 8 | image: 9 | repository: crossplane/oam-kubernetes-runtime 10 | tag: %%VERSION%% 11 | pullPolicy: Always 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | ingress: 17 | enabled: false 18 | 19 | serviceAccount: 20 | # Specifies whether a service account should be created 21 | create: true 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: 25 | 26 | podSecurityContext: {} 27 | # fsGroup: 2000 28 | 29 | securityContext: {} 30 | # capabilities: 31 | # drop: 32 | # - ALL 33 | # readOnlyRootFilesystem: true 34 | # runAsNonRoot: true 35 | # runAsUser: 1000 36 | 37 | webhookService: 38 | type: ClusterIP 39 | port: 9443 40 | 41 | resources: 42 | limits: 43 | cpu: 300m 44 | memory: 150Mi 45 | requests: 46 | cpu: 100m 47 | memory: 20Mi 48 | 49 | nodeSelector: {} 50 | 51 | tolerations: [] 52 | 53 | affinity: {} 54 | 55 | # certificate related to the webhook 56 | certificate: 57 | certificateName: serving-cert 58 | secretName: webhook-server-cert 59 | mountPath: /etc/k8s-webhook-certs 60 | caBundle: replace-me 61 | -------------------------------------------------------------------------------- /examples/dependency/demo-with-conditions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: dependency-clusterrole 5 | labels: 6 | rbac.oam.dev/aggregate-to-controller: "true" 7 | rules: 8 | - apiGroups: 9 | - example.com 10 | resources: 11 | - 'foo' 12 | verbs: 13 | - '*' 14 | --- 15 | apiVersion: core.oam.dev/v1alpha2 16 | kind: Component 17 | metadata: 18 | name: source 19 | spec: 20 | workload: 21 | apiVersion: example.com/v1 22 | kind: Foo 23 | metadata: 24 | name: source 25 | spec: 26 | key: test-value 27 | status: # comment this part to mimic dependency wait time. 28 | state: pending 29 | --- 30 | apiVersion: core.oam.dev/v1alpha2 31 | kind: Component 32 | metadata: 33 | name: sink 34 | spec: 35 | workload: 36 | apiVersion: example.com/v1 37 | kind: Foo 38 | metadata: 39 | name: sink 40 | --- 41 | apiVersion: core.oam.dev/v1alpha2 42 | kind: ApplicationConfiguration 43 | metadata: 44 | name: example-appconfig 45 | spec: 46 | components: 47 | - componentName: source 48 | dataOutputs: 49 | - name: example-key 50 | fieldPath: "spec.key" 51 | conditions: 52 | - op: eq 53 | value: running 54 | fieldPath: "status.state" 55 | - componentName: sink 56 | dataInputs: 57 | - valueFrom: 58 | dataOutputName: example-key 59 | toFieldPaths: 60 | - "spec.key" 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/crossplane/oam-kubernetes-runtime 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Azure/go-autorest/autorest v0.9.2 // indirect 7 | github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect 8 | github.com/crossplane/crossplane-runtime v0.10.0 9 | github.com/davecgh/go-spew v1.1.1 10 | github.com/evanphx/json-patch v4.5.0+incompatible 11 | github.com/ghodss/yaml v1.0.0 12 | github.com/go-logr/logr v0.1.0 13 | github.com/go-logr/zapr v0.1.1 // indirect 14 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect 15 | github.com/google/go-cmp v0.4.0 16 | github.com/gophercloud/gophercloud v0.6.0 // indirect 17 | github.com/kr/pretty v0.2.0 // indirect 18 | github.com/onsi/ginkgo v1.12.1 19 | github.com/onsi/gomega v1.10.1 20 | github.com/pkg/errors v0.9.1 21 | github.com/prometheus/client_golang v1.1.0 // indirect 22 | github.com/stretchr/testify v1.4.0 23 | github.com/tidwall/gjson v1.6.3 24 | go.uber.org/zap v1.10.0 25 | golang.org/x/tools v0.0.0-20200630223951-c138986dd9b9 // indirect 26 | google.golang.org/appengine v1.6.5 // indirect 27 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 28 | k8s.io/api v0.18.6 29 | k8s.io/apiextensions-apiserver v0.18.6 30 | k8s.io/apimachinery v0.18.6 31 | k8s.io/client-go v0.18.6 32 | k8s.io/klog v1.0.0 33 | k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 34 | k8s.io/kubectl v0.18.5 35 | k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 36 | sigs.k8s.io/controller-runtime v0.6.2 37 | sigs.k8s.io/controller-tools v0.2.4 38 | ) 39 | -------------------------------------------------------------------------------- /examples/dependency/demo-with-valuefrom.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: dependency-clusterrole 5 | labels: 6 | rbac.oam.dev/aggregate-to-controller: "true" 7 | rules: 8 | - apiGroups: 9 | - example.com 10 | resources: 11 | - 'foo' 12 | verbs: 13 | - '*' 14 | --- 15 | apiVersion: core.oam.dev/v1alpha2 16 | kind: Component 17 | metadata: 18 | name: source 19 | spec: 20 | workload: 21 | apiVersion: example.com/v1 22 | kind: Foo 23 | metadata: 24 | name: source 25 | spec: 26 | key: test-value 27 | status: 28 | state: pending 29 | --- 30 | apiVersion: core.oam.dev/v1alpha2 31 | kind: Component 32 | metadata: 33 | name: sink 34 | spec: 35 | workload: 36 | apiVersion: example.com/v1 37 | kind: Foo 38 | metadata: 39 | name: sink 40 | --- 41 | apiVersion: core.oam.dev/v1alpha2 42 | kind: ApplicationConfiguration 43 | metadata: 44 | name: example-appconfig 45 | labels: 46 | state: running 47 | spec: 48 | components: 49 | - componentName: source 50 | dataOutputs: 51 | - name: example-key 52 | fieldPath: "spec.key" 53 | conditions: 54 | - op: eq 55 | valueFrom: 56 | fieldPath: metadata.labels[state] 57 | fieldPath: "status.state" 58 | - componentName: sink 59 | dataInputs: 60 | - valueFrom: 61 | dataOutputName: example-key 62 | toFieldPaths: 63 | - "spec.key" 64 | -------------------------------------------------------------------------------- /examples/dependency/demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: dependency-clusterrole 5 | labels: 6 | rbac.oam.dev/aggregate-to-controller: "true" 7 | rules: 8 | - apiGroups: 9 | - example.com 10 | resources: 11 | - 'foo' 12 | verbs: 13 | - '*' 14 | --- 15 | apiVersion: core.oam.dev/v1alpha2 16 | kind: Component 17 | metadata: 18 | name: source 19 | spec: 20 | workload: 21 | apiVersion: example.com/v1 22 | kind: Foo 23 | metadata: 24 | name: source 25 | status: {} 26 | # Uncomment the following and apply again will make dependency satisfied. 27 | # status: 28 | # key: test 29 | # status: 30 | # key: 31 | # - name: a 32 | # value: aa 33 | # - name: b 34 | # value: bb 35 | --- 36 | apiVersion: core.oam.dev/v1alpha2 37 | kind: Component 38 | metadata: 39 | name: sink 40 | spec: 41 | workload: 42 | apiVersion: example.com/v1 43 | kind: Foo 44 | metadata: 45 | name: sink 46 | spec: 47 | key: 48 | - name: exist 49 | value: existtt 50 | --- 51 | apiVersion: core.oam.dev/v1alpha2 52 | kind: ApplicationConfiguration 53 | metadata: 54 | name: example-appconfig 55 | spec: 56 | components: 57 | - componentName: source 58 | dataOutputs: 59 | - name: example-key 60 | fieldPath: "status.key" 61 | - componentName: sink 62 | dataInputs: 63 | - valueFrom: 64 | dataOutputName: example-key 65 | toFieldPaths: 66 | - "spec.key" 67 | -------------------------------------------------------------------------------- /.github/workflows/promote.yml: -------------------------------------------------------------------------------- 1 | name: Promote 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Release version (e.g. v0.1.0)' 8 | required: true 9 | channel: 10 | description: 'Release channel' 11 | required: true 12 | default: 'alpha' 13 | 14 | env: 15 | # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run 16 | # a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether 17 | # credentials have been provided before trying to run steps that need them. 18 | DOCKER_USR: ${{ secrets.DOCKER_USR }} 19 | AWS_USR: ${{ secrets.AWS_USR }} 20 | 21 | jobs: 22 | promote-artifacts: 23 | runs-on: ubuntu-18.04 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | submodules: true 30 | 31 | - name: Fetch History 32 | run: git fetch --prune --unshallow 33 | 34 | - name: Login to Docker 35 | uses: docker/login-action@v1 36 | if: env.DOCKER_USR != '' 37 | with: 38 | username: ${{ secrets.DOCKER_USR }} 39 | password: ${{ secrets.DOCKER_PSW }} 40 | 41 | - name: Promote Artifacts in S3 and Docker Hub 42 | if: env.AWS_USR != '' && env.DOCKER_USR != '' 43 | run: make -j2 promote BRANCH_NAME=${GITHUB_REF##*/} 44 | env: 45 | VERSION: ${{ github.event.inputs.version }} 46 | CHANNEL: ${{ github.event.inputs.channel }} 47 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_USR }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PSW }} 49 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/applicationconfiguration/dag.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package applicationconfiguration 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | 22 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 23 | ) 24 | 25 | // dag is the dependency graph for an AppConfig. 26 | type dag struct { 27 | Sources map[string]*dagSource 28 | } 29 | 30 | // dagSource represents the object information with DataOutput 31 | type dagSource struct { 32 | // ObjectRef refers to the object this source come from. 33 | ObjectRef *corev1.ObjectReference 34 | 35 | Conditions []v1alpha2.ConditionRequirement 36 | } 37 | 38 | // newDAG creates a fresh new DAG. 39 | func newDAG() *dag { 40 | return &dag{ 41 | Sources: make(map[string]*dagSource), 42 | } 43 | } 44 | 45 | // AddSource adds a data output source into the DAG. 46 | func (d *dag) AddSource(sourceName string, ref *corev1.ObjectReference, m []v1alpha2.ConditionRequirement) { 47 | d.Sources[sourceName] = &dagSource{ 48 | ObjectRef: ref, 49 | Conditions: m, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/applicationconfiguration/controllerhooks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package applicationconfiguration 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/crossplane/crossplane-runtime/pkg/logging" 23 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 24 | 25 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 26 | ) 27 | 28 | // A ControllerHooks provide customized reconcile logic for an ApplicationConfiguration 29 | type ControllerHooks interface { 30 | Exec(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) 31 | } 32 | 33 | // ControllerHooksFn reconciles an ApplicationConfiguration 34 | type ControllerHooksFn func(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) 35 | 36 | // Exec the customized reconcile logic on the ApplicationConfiguration 37 | func (fn ControllerHooksFn) Exec(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, logger logging.Logger) (reconcile.Result, error) { 38 | return fn(ctx, ac, logger) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/setup.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | ctrl "sigs.k8s.io/controller-runtime" 21 | 22 | "github.com/crossplane/crossplane-runtime/pkg/logging" 23 | 24 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller" 25 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/applicationconfiguration" 26 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/scopes/healthscope" 27 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/traits/manualscalertrait" 28 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2/core/workloads/containerizedworkload" 29 | ) 30 | 31 | // Setup workload controllers. 32 | func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error { 33 | for _, setup := range []func(ctrl.Manager, controller.Args, logging.Logger) error{ 34 | applicationconfiguration.Setup, containerizedworkload.Setup, manualscalertrait.Setup, healthscope.Setup, 35 | } { 36 | if err := setup(mgr, args, l); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /apis/generate.go: -------------------------------------------------------------------------------- 1 | // +build generate 2 | 3 | /* 4 | Copyright 2019 The Crossplane Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // NOTE(negz): See the below link for details on what is happening here. 20 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 21 | 22 | // Remove existing CRDs 23 | //go:generate rm -rf ../charts/oam-kubernetes-runtime/crds 24 | 25 | // Generate deepcopy methodsets and CRD manifests 26 | //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:crdVersions=v1 output:artifacts:config=../charts/oam-kubernetes-runtime/crds 27 | 28 | // Generate legacy_support for K8s 1.12~1.15 versions CRD manifests 29 | //go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:trivialVersions=true output:artifacts:config=../legacy/charts/oam-kubernetes-runtime-legacy/crds 30 | //go:generate go run ../legacy/convert/main.go ../legacy/charts/oam-kubernetes-runtime-legacy/crds 31 | 32 | package apis 33 | 34 | import ( 35 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" //nolint:typecheck 36 | ) 37 | -------------------------------------------------------------------------------- /examples/containerized-workload/README.md: -------------------------------------------------------------------------------- 1 | # Custom Workload 2 | 3 | This is an example web application with a custom workload 4 | 5 | ## Run ApplicationConfiguration 6 | 7 | Install the components. 8 | 9 | ```bash 10 | $ kubectl apply -f . 11 | applicationconfiguration.core.oam.dev/example-appconfig created 12 | component.core.oam.dev/example-component created 13 | healthscope.core.oam.dev/example-health-scope created 14 | secret/mysecret created 15 | ``` 16 | 17 | ## Result 18 | 19 | An `example-component` is created (running Wordpress, which you will see on the corresponding `Service` endpoint): 20 | 21 | ``` 22 | $ kubectl get all 23 | NAME READY STATUS RESTARTS AGE 24 | pod/example-component-564b7b45fd-9lgvr 1/1 Running 0 46s 25 | pod/example-component-564b7b45fd-q4nxs 1/1 Running 0 46s 26 | pod/example-component-564b7b45fd-r9nbg 1/1 Running 0 47s 27 | 28 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 29 | service/example-component NodePort 10.109.59.180 80:31537/TCP 47s 30 | service/kubernetes ClusterIP 10.96.0.1 443/TCP 10d 31 | 32 | NAME READY UP-TO-DATE AVAILABLE AGE 33 | deployment.apps/example-component 3/3 3 3 47s 34 | 35 | NAME DESIRED CURRENT READY AGE 36 | replicaset.apps/example-component-564b7b45fd 3 3 3 47s 37 | ``` 38 | 39 | Wordpress will respond on port 80. There is a UI, but since there is no database it will not be fully functional. 40 | 41 | ## Clean up 42 | 43 | Clean resources. 44 | 45 | ```bash 46 | $ kubectl delete -f . 47 | ``` 48 | -------------------------------------------------------------------------------- /pkg/oam/labels.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package oam 18 | 19 | // Label key strings. 20 | // AppConfig controller will add these labels into workloads. 21 | const ( 22 | // LabelAppName records the name of AppConfig 23 | LabelAppName = "app.oam.dev/name" 24 | // LabelAppComponent records the name of Component 25 | LabelAppComponent = "app.oam.dev/component" 26 | // LabelAppComponentRevision records the revision name of Component 27 | LabelAppComponentRevision = "app.oam.dev/revision" 28 | // LabelOAMResourceType whether a CR is workload or trait 29 | LabelOAMResourceType = "app.oam.dev/resourceType" 30 | 31 | // WorkloadTypeLabel indicates the type of the workloadDefinition 32 | WorkloadTypeLabel = "workload.oam.dev/type" 33 | // TraitTypeLabel indicates the type of the traitDefinition 34 | TraitTypeLabel = "trait.oam.dev/type" 35 | ) 36 | 37 | const ( 38 | // ResourceTypeTrait mark this K8s Custom Resource is an OAM trait 39 | ResourceTypeTrait = "TRAIT" 40 | // ResourceTypeWorkload mark this K8s Custom Resource is an OAM workload 41 | ResourceTypeWorkload = "WORKLOAD" 42 | ) 43 | 44 | const ( 45 | // AnnotationAppGeneration records the generation of AppConfig 46 | AnnotationAppGeneration = "app.oam.dev/generation" 47 | ) 48 | -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-db-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: tracker-postgres-db 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: db-workload 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: postgres 16 | image: docker.io/postgres:9.6.17-alpine 17 | env: 18 | - name: POSTGRES_USER 19 | value: "postgres" 20 | - name: POSTGRES_PASSWORD 21 | value: "dbpassword" 22 | - name: POSTGRES_DB 23 | value: "hackfest" 24 | ports: 25 | - name: postgres 26 | containerPort: 5432 27 | protocol: TCP 28 | readinessProbe: 29 | exec: 30 | command: 31 | - echo 32 | failureThreshold: 6 33 | initialDelaySeconds: 5 34 | periodSeconds: 10 35 | successThreshold: 1 36 | timeoutSeconds: 5 37 | livenessProbe: 38 | exec: 39 | command: 40 | - echo 41 | failureThreshold: 6 42 | initialDelaySeconds: 30 43 | periodSeconds: 10 44 | successThreshold: 1 45 | timeoutSeconds: 5 46 | parameters: 47 | - name: dbuser 48 | description: database username 49 | required: false 50 | fieldPaths: 51 | - spec.containers[0].env[0].value 52 | - name: dbpassword 53 | description: database password 54 | required: false 55 | fieldPaths: 56 | - spec.containers[0].env[1].value 57 | - name: database 58 | description: new database 59 | required: false 60 | fieldPaths: 61 | - spec.containers[0].env[2].value -------------------------------------------------------------------------------- /legacy/README.md: -------------------------------------------------------------------------------- 1 | # Legacy Support 2 | 3 | Now lots of apps are still running on K8s clusters version v1.14, v1.15, while oam-k8s-runtime requires the minimum 4 | K8s version to be v1.16. 5 | 6 | Currently, the main block is OAM runtime use CRD v1, while these old K8s versions don't support CRD v1. 7 | So we generate v1beta1 CRD here for convenience. But we have no guarantee that oam-runtime will support the 8 | legacy k8s versions. 9 | 10 | Follow the instructions in [README](../README.md) to create a namespace like `oam-system` and add the OAM Kubernetes 11 | Runtime helm repo. 12 | 13 | ``` 14 | $ kubectl create namespace oam-system 15 | $ helm repo add crossplane-master https://charts.crossplane.io/master/ 16 | ``` 17 | 18 | Run the following command to install an OAM Kubernetes Runtime legacy chart. 19 | 20 | ``` 21 | $ helm install oam --namespace oam-system crossplane-master/oam-kubernetes-runtime-legacy --devel 22 | ``` 23 | 24 | If you'd like to install an older version of the legacy chart, use `helm search` to choose a proper chart version. 25 | ``` 26 | $ helm search repo oam-kubernetes-runtime-legacy --devel -l 27 | NAME CHART VERSION APP VERSION DESCRIPTION 28 | crossplane-master/oam-kubernetes-runtime-legacy 0.3...... 0.3...... A Helm chart for OAM Kubernetes Resources Contr 29 | crossplane-master/oam-kubernetes-runtime-legacy 0.3.1-5.g11e1894 0.3.1-5.g11e1894 A Helm chart for OAM Kubernetes Resources Contr 30 | crossplane-master/oam-kubernetes-runtime-legacy 0.3...... 0.3...... A Helm chart for OAM Kubernetes Resources Contr 31 | 32 | $ helm install oam --namespace oam-system crossplane-master/oam-kubernetes-runtime-legacy --version 0.3.1-5.g11e1894 --devel 33 | ``` 34 | 35 | Install the legacy chart as below if you want a nightly version. 36 | 37 | ``` 38 | $ helm install oam --namespace oam-system crossplane-master/oam-kubernetes-runtime-legacy --set image.tag=master --devel 39 | ``` 40 | -------------------------------------------------------------------------------- /examples/flight-tracker/README.md: -------------------------------------------------------------------------------- 1 | # Flight Tracker 2 | 3 | This is an example microservices application with a Javascript Web UI, a PostgreSQL database, and a series of API microservices. The idea is that various app developers would create Components for their corresponding apps. The overall config will add traits and allow the app to be fully deployed 4 | 5 | ![](https://github.com/oam-dev/samples/raw/master/2.ServiceTracker_App/service-tracker-diagram.jpg) 6 | 7 | > Full application original source here: https://github.com/chzbrgr71/service-tracker 8 | 9 | ## Apply Definition 10 | 11 | For this demo, it is necessary to create a `WorkloadDefinition` for the `ContainerizedWorkload` and `PostgreSQLInstance` workload types, and a `TraitDefinition` for the `ManualScalerTrait` type. 12 | 13 | ```bash 14 | $ kubectl apply -f definitions/ 15 | traitdefinition.core.oam.dev/ingresses.extensions created 16 | ``` 17 | 18 | ## Install Component 19 | 20 | Register the `Components`. 21 | 22 | ```bash 23 | $ kubectl apply -f components/ 24 | component.core.oam.dev/data-api created 25 | component.core.oam.dev/tracker-postgres-db created 26 | component.core.oam.dev/flights-api created 27 | component.core.oam.dev/quakes-api created 28 | component.core.oam.dev/service-tracker-ui created 29 | component.core.oam.dev/weather-api created 30 | ``` 31 | 32 | ## Run ApplicationConfiguration 33 | 34 | Install the `ApplicationConfiguration`. 35 | 36 | ```bash 37 | $ kubectl apply -f tracker-app-config.yaml 38 | secret/dbuser created 39 | applicationconfiguration.core.oam.dev/service-tracker created 40 | ``` 41 | 42 | ## Result 43 | 44 | Visit your browser, and open `http://{your-ingress-address}/web-ui` for the Service Tracker website. Refresh the data on the dashboard for each of the microservices. 45 | 46 | ![image](https://tvax2.sinaimg.cn/large/ad5fbf65ly1ggh2brro77j21hb0tan00.jpg) 47 | 48 | ## Clean up 49 | 50 | clean resource of `flight-tracke`. 51 | 52 | ```bash 53 | $ kubectl delete -f . 54 | $ kubectl delete -f components/ 55 | $ kubectl delete -f definitions/ 56 | ``` 57 | 58 | > More info, please see https://github.com/oam-dev/samples/tree/master/2.ServiceTracker_App -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/applicationconfiguration/helper_test.go: -------------------------------------------------------------------------------- 1 | package applicationconfiguration 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "k8s.io/apimachinery/pkg/util/intstr" 10 | 11 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 12 | ) 13 | 14 | func TestCheckParams(t *testing.T) { 15 | wlNameValue := "wlName" 16 | pName := "wlnameParam" 17 | wlParam := v1alpha2.ComponentParameter{ 18 | Name: pName, 19 | FieldPaths: []string{WorkloadNamePath}, 20 | } 21 | wlParamValue := v1alpha2.ComponentParameterValue{ 22 | Name: pName, 23 | Value: intstr.FromString(wlNameValue), 24 | } 25 | 26 | mockValue := "mockValue" 27 | mockPName := "mockParam" 28 | mockFieldPath := "a.b" 29 | mockParam := v1alpha2.ComponentParameter{ 30 | Name: mockPName, 31 | FieldPaths: []string{mockFieldPath}, 32 | } 33 | mockParamValue := v1alpha2.ComponentParameterValue{ 34 | Name: pName, 35 | Value: intstr.FromString(mockValue), 36 | } 37 | tests := []struct { 38 | caseName string 39 | cps []v1alpha2.ComponentParameter 40 | cpvs []v1alpha2.ComponentParameterValue 41 | expectResult bool 42 | expectParamValue string 43 | }{ 44 | { 45 | caseName: "get wokload name params and value", 46 | cps: []v1alpha2.ComponentParameter{wlParam}, 47 | cpvs: []v1alpha2.ComponentParameterValue{wlParamValue}, 48 | expectResult: false, 49 | expectParamValue: wlNameValue, 50 | }, 51 | { 52 | caseName: "not found workload name params", 53 | cps: []v1alpha2.ComponentParameter{mockParam}, 54 | cpvs: []v1alpha2.ComponentParameterValue{mockParamValue}, 55 | expectResult: true, 56 | expectParamValue: "", 57 | }, 58 | } 59 | 60 | for _, tc := range tests { 61 | func(t *testing.T) { 62 | result, pValue := checkParams(tc.cps, tc.cpvs) 63 | assert.Equal(t, result, tc.expectResult, 64 | fmt.Sprintf("test case: %v", tc.caseName)) 65 | assert.Equal(t, pValue, tc.expectParamValue, 66 | fmt.Sprintf("test case: %v", tc.caseName)) 67 | }(t) 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/scopes/healthscope/standard.go: -------------------------------------------------------------------------------- 1 | package healthscope 2 | 3 | import ( 4 | "context" 5 | 6 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 7 | "github.com/pkg/errors" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | kuberuntime "k8s.io/apimachinery/pkg/runtime" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | "k8s.io/apimachinery/pkg/types" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | ) 14 | 15 | var ( 16 | // PodSpecWorkload is a generic PaaS workload which adopts full K8s pod spec. 17 | // More details refer to oam-dev/kubevela 18 | podSpecWorkloadGVK = schema.GroupVersionKind{ 19 | Group: "standard.oam.dev", 20 | Version: "v1alpha1", 21 | Kind: "PodSpecWorkload", 22 | } 23 | ) 24 | 25 | // CheckPodSpecWorkloadHealth check health condition of podspecworkloads.standard.oam.dev 26 | func CheckPodSpecWorkloadHealth(ctx context.Context, c client.Client, ref runtimev1alpha1.TypedReference, namespace string) *WorkloadHealthCondition { 27 | if ref.GroupVersionKind() != podSpecWorkloadGVK { 28 | return nil 29 | } 30 | r := &WorkloadHealthCondition{ 31 | HealthStatus: StatusHealthy, 32 | TargetWorkload: ref, 33 | } 34 | workloadObj := unstructured.Unstructured{} 35 | workloadObj.SetGroupVersionKind(ref.GroupVersionKind()) 36 | if err := c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: ref.Name}, &workloadObj); err != nil { 37 | r.HealthStatus = StatusUnhealthy 38 | r.Diagnosis = errors.Wrap(err, errHealthCheck).Error() 39 | return r 40 | } 41 | r.ComponentName = getComponentNameFromLabel(&workloadObj) 42 | r.TargetWorkload.UID = workloadObj.GetUID() 43 | 44 | childRefsData, _, _ := unstructured.NestedSlice(workloadObj.Object, "status", "resources") 45 | childRefs := []runtimev1alpha1.TypedReference{} 46 | for _, v := range childRefsData { 47 | v := v.(map[string]interface{}) 48 | tmpChildRef := &runtimev1alpha1.TypedReference{} 49 | if err := kuberuntime.DefaultUnstructuredConverter.FromUnstructured(v, tmpChildRef); err != nil { 50 | r.HealthStatus = StatusUnhealthy 51 | r.Diagnosis = errors.Wrap(err, errHealthCheck).Error() 52 | } 53 | childRefs = append(childRefs, *tmpChildRef) 54 | } 55 | updateChildResourcesCondition(ctx, c, namespace, r, ref, childRefs) 56 | return r 57 | } 58 | -------------------------------------------------------------------------------- /apis/core/v1alpha2/core_trait_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 24 | ) 25 | 26 | var _ oam.Trait = &ManualScalerTrait{} 27 | 28 | // A ManualScalerTraitSpec defines the desired state of a ManualScalerTrait. 29 | type ManualScalerTraitSpec struct { 30 | // ReplicaCount of the workload this trait applies to. 31 | ReplicaCount int32 `json:"replicaCount"` 32 | 33 | // WorkloadReference to the workload this trait applies to. 34 | WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef"` 35 | } 36 | 37 | // A ManualScalerTraitStatus represents the observed state of a 38 | // ManualScalerTrait. 39 | type ManualScalerTraitStatus struct { 40 | runtimev1alpha1.ConditionedStatus `json:",inline"` 41 | } 42 | 43 | // +kubebuilder:object:root=true 44 | 45 | // A ManualScalerTrait determines how many replicas a workload should have. 46 | // +kubebuilder:resource:categories={crossplane,oam} 47 | // +kubebuilder:subresource:status 48 | type ManualScalerTrait struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ObjectMeta `json:"metadata,omitempty"` 51 | 52 | Spec ManualScalerTraitSpec `json:"spec,omitempty"` 53 | Status ManualScalerTraitStatus `json:"status,omitempty"` 54 | } 55 | 56 | // +kubebuilder:object:root=true 57 | 58 | // ManualScalerTraitList contains a list of ManualScalerTrait. 59 | type ManualScalerTraitList struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ListMeta `json:"metadata,omitempty"` 62 | Items []ManualScalerTrait `json:"items"` 63 | } 64 | -------------------------------------------------------------------------------- /examples/typed-component/README.md: -------------------------------------------------------------------------------- 1 | # Custom Workload 2 | 3 | This is an example web application with a custom workload 4 | 5 | ## Run ApplicationConfiguration 6 | 7 | Install the components. 8 | 9 | ```bash 10 | $ kubectl apply -f . 11 | workloaddefinition.core.oam.dev/web-service created 12 | scopedefinition.core.oam.dev/healthscopes.core.oam.dev created 13 | healthscope.core.oam.dev/example-health-scope created 14 | applicationconfiguration.core.oam.dev/example-appconfig created 15 | component.core.oam.dev/web-service-component created 16 | ``` 17 | 18 | 19 | > NOTE: The `oam-k8s-runtime` webhook is needed to enhance the workload in the `Component` definition. If you are running without the webhook you will need to add the following to the `spec.workload` in `sample_component.yaml`: 20 | > 21 | > apiVersion: core.oam.dev/v1alpha2 22 | > kind: ContainerizedWorkload 23 | 24 | 25 | ## Result 26 | 27 | A `web-service-component` is created (running Wordpress, which you will see on the corresponding `Service` endpoint): 28 | 29 | ``` 30 | $ kubectl get all 31 | NAME READY STATUS RESTARTS AGE 32 | pod/web-service-component-78fbdd6787-5nwh5 1/1 Running 0 2m19s 33 | pod/web-service-component-78fbdd6787-9gmfp 1/1 Running 0 2m20s 34 | pod/web-service-component-78fbdd6787-r652l 1/1 Running 0 2m21s 35 | 36 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 37 | service/kubernetes ClusterIP 10.96.0.1 443/TCP 10d 38 | service/web-service-component NodePort 10.99.171.93 80:30209/TCP 2m22s 39 | 40 | NAME READY UP-TO-DATE AVAILABLE AGE 41 | deployment.apps/web-service-component 3/3 3 3 2m22s 42 | 43 | NAME DESIRED CURRENT READY AGE 44 | replicaset.apps/web-service-component-6b4b8b57b7 0 0 0 2m22s 45 | replicaset.apps/web-service-component-78fbdd6787 3 3 3 2m21s 46 | ``` 47 | 48 | Wordpress will respond on port 80. There is a UI, but since there is no database it will not be fully functional. 49 | 50 | ## Clean up 51 | 52 | Clean resources. 53 | 54 | ```bash 55 | $ kubectl delete -f . 56 | ``` 57 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "oam-kubernetes-runtime.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | 10 | {{/* 11 | Create a default fully qualified app name. 12 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 13 | If release name contains chart name it will be used as a full name, otherwise, it will be append to the chart name 14 | */}} 15 | {{- define "oam-kubernetes-runtime.fullname" -}} 16 | {{- if .Values.fullnameOverride -}} 17 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 18 | {{- else -}} 19 | {{- $name := default .Chart.Name .Values.nameOverride -}} 20 | {{- if contains $name .Release.Name -}} 21 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 22 | {{- else -}} 23 | {{- printf "%s-%s" $name .Release.Name | trunc 63 | trimSuffix "-" -}} 24 | {{- end -}} 25 | {{- end -}} 26 | {{- end -}} 27 | 28 | {{/* 29 | Create chart name and version as used by the chart label. 30 | */}} 31 | {{- define "oam-kubernetes-runtime.chart" -}} 32 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 33 | {{- end -}} 34 | 35 | {{/* 36 | Use create namespace 37 | */}} 38 | {{- define "oam-kubernetes-runtime.createNamespace" -}} 39 | {{- if eq .Release.Namespace "default" -}} 40 | {{- false -}} 41 | {{- else -}} 42 | {{- true -}} 43 | {{- end -}} 44 | {{- end -}} 45 | 46 | {{/* 47 | Common labels 48 | */}} 49 | {{- define "oam-kubernetes-runtime.labels" -}} 50 | helm.sh/chart: {{ include "oam-kubernetes-runtime.chart" . }} 51 | {{ include "oam-kubernetes-runtime.selectorLabels" . }} 52 | {{- if .Chart.AppVersion }} 53 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 54 | {{- end }} 55 | app.kubernetes.io/managed-by: {{ .Release.Service }} 56 | {{- end -}} 57 | 58 | {{/* 59 | Selector labels 60 | */}} 61 | {{- define "oam-kubernetes-runtime.selectorLabels" -}} 62 | app.kubernetes.io/name: {{ include "oam-kubernetes-runtime.name" . }} 63 | app.kubernetes.io/instance: {{ .Release.Name }} 64 | {{- end -}} 65 | 66 | {{/* 67 | Create the name of the service account to use 68 | */}} 69 | {{- define "oam-kubernetes-runtime.serviceAccountName" -}} 70 | {{- if .Values.serviceAccount.create -}} 71 | {{ default (include "oam-kubernetes-runtime.fullname" .) .Values.serviceAccount.name }} 72 | {{- else -}} 73 | {{ default "default" .Values.serviceAccount.name }} 74 | {{- end -}} 75 | {{- end -}} 76 | -------------------------------------------------------------------------------- /hack/ssl/ssl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit 3 | 4 | export APP="${1:-oam-kubernetes-runtime-webhook}" 5 | export NAMESPACE="${2:-default}" 6 | export CSR_NAME="${APP}.${NAMESPACE}.svc" 7 | 8 | echo "... creating ${app}.key" 9 | openssl genrsa -out ${APP}.key 2048 10 | 11 | echo "... creating ${app}.csr" 12 | cat >csr.conf< /dev/null 2>&1 57 | if [ "$?" -eq 0 ]; then 58 | break 59 | fi 60 | if [[ $SECONDS -ge 60 ]]; then 61 | echo "[!] timed out waiting for csr" 62 | exit 1 63 | fi 64 | sleep 2 65 | done 66 | 67 | kubectl certificate approve ${CSR_NAME} 68 | 69 | SECONDS=0 70 | while true; do 71 | echo "... waiting for serverCert to be present in kubernetes" 72 | echo "kubectl get csr ${CSR_NAME} -o jsonpath='{.status.certificate}'" 73 | serverCert=$(kubectl get csr ${CSR_NAME} -o jsonpath='{.status.certificate}') 74 | if [[ $serverCert != "" ]]; then 75 | break 76 | fi 77 | if [[ $SECONDS -ge 60 ]]; then 78 | echo "[!] timed out waiting for serverCert" 79 | exit 1 80 | fi 81 | sleep 2 82 | done 83 | 84 | echo "... creating ${app}.pem cert file" 85 | echo "\$serverCert | openssl base64 -d -A -out ${APP}.pem" 86 | echo ${serverCert} | openssl base64 -d -A -out ${APP}.pem 87 | -------------------------------------------------------------------------------- /design/one-pager-podspecable-workload.md: -------------------------------------------------------------------------------- 1 | # Indicate Workload has PodSpec field 2 | 3 | - Owner: Jianbo Sun (@wonderflow) 4 | - Date: 10/09/2020 5 | - Status: Implemented 6 | 7 | ## Background 8 | 9 | Since we have added labels like [`workload.oam.dev/podspecable`](https://github.com/oam-dev/spec/blob/master/4.workload_definitions.md#labels) 10 | into OAM Spec to indicate the workload will contain 'podSpec' in its spec. 11 | 12 | In most famous workload like `Deployment`, `StatefulSet`, `Job`, the `podSpec` field will always be `spec.template.spec`. 13 | But it's hard to know which field is podSpec for K8s CRD. For example, we write a workload named [`PodSpecWorkload`](https://github.com/oam-dev/kubevela/blob/master/charts/vela-core/crds/standard.oam.dev_podspecworkloads.yaml). 14 | It has `podSpec` in `spec.podSpec`, this is different with `spec.template.spec`. In this case, we need a field to indicate 15 | which field is `podSpec`. 16 | 17 | ## Proposal 18 | 19 | So I propose we add a field 'podSpecPath' and a label `workload.oam.dev/podspecable: true` into WorkloadDefinition. 20 | 21 | The 'podSpecPath' field and the label are both optional fields, they could have following behaviors: 22 | 23 | ### No label and No `podSpecPath` field 24 | 25 | ``` 26 | apiVersion: core.oam.dev/v1alpha2 27 | kind: WorkloadDefinition 28 | metadata: 29 | name: webservice 30 | spec: 31 | definitionRef: 32 | name: podspecworkloads.standard.oam.dev 33 | ... 34 | ``` 35 | 36 | In this case, we can't do any podSpec assumption for this workload. 37 | 38 | ### label specified with No `podSpecPath` field 39 | 40 | ``` 41 | apiVersion: core.oam.dev/v1alpha2 42 | kind: WorkloadDefinition 43 | metadata: 44 | name: webservice 45 | labels: 46 | workload.oam.dev/podspecable: true 47 | spec: 48 | definitionRef: 49 | name: podspecworkloads.standard.oam.dev 50 | ... 51 | ``` 52 | 53 | In this case, we will always regard `spec.template.spec` as the `podSpecPath`. 54 | This will work for most famous workloads like K8s `Deployment`/`StatefulSet`/`Job` and many other CRD Objects like 55 | `OpenKruise CloneSet`/`Knative Service`. 56 | 57 | ### Has `podSpecPath` field 58 | 59 | ``` 60 | apiVersion: core.oam.dev/v1alpha2 61 | kind: WorkloadDefinition 62 | metadata: 63 | name: webservice 64 | labels: 65 | workload.oam.dev/podspecable: true 66 | spec: 67 | podSpecPath: "spec.podSpec" 68 | definitionRef: 69 | name: podspecworkloads.standard.oam.dev 70 | ... 71 | ``` 72 | 73 | If the `podSpecPath` is specified, the `workload.oam.dev/podspecable: true` label will always be automatically added. 74 | 75 | In this case, trait can use `podSpecPath` to get the field of podSpec. 76 | 77 | -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/component/component_suite_test.go: -------------------------------------------------------------------------------- 1 | package component_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 16 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 17 | 18 | "github.com/crossplane/oam-kubernetes-runtime/apis/core" 19 | ) 20 | 21 | var scheme = runtime.NewScheme() 22 | var crd crdv1.CustomResourceDefinition 23 | var reqResource metav1.GroupVersionResource 24 | var decoder *admission.Decoder 25 | 26 | func TestComponentWebHandler(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Component Web handler") 29 | } 30 | 31 | var _ = BeforeSuite(func(done Done) { 32 | By("Bootstrapping test environment") 33 | ctrl.SetLogger(zap.New(func(o *zap.Options) { 34 | o.Development = true 35 | o.DestWritter = os.Stdout 36 | })) 37 | By("Setup scheme") 38 | err := core.AddToScheme(scheme) 39 | Expect(err).Should(BeNil()) 40 | err = clientgoscheme.AddToScheme(scheme) 41 | Expect(err).Should(BeNil()) 42 | // the crd we will refer to 43 | crd = crdv1.CustomResourceDefinition{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "foo.example.com", 46 | Labels: map[string]string{"crd": "dependency"}, 47 | }, 48 | Spec: crdv1.CustomResourceDefinitionSpec{ 49 | Group: "example.com", 50 | Names: crdv1.CustomResourceDefinitionNames{ 51 | Kind: "Foo", 52 | ListKind: "FooList", 53 | Plural: "foo", 54 | Singular: "foo", 55 | }, 56 | Versions: []crdv1.CustomResourceDefinitionVersion{ 57 | { 58 | Name: "v1", 59 | Served: true, 60 | Storage: true, 61 | Schema: &crdv1.CustomResourceValidation{ 62 | OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 63 | Type: "object", 64 | Properties: map[string]crdv1.JSONSchemaProps{ 65 | "status": { 66 | Type: "object", 67 | Properties: map[string]crdv1.JSONSchemaProps{ 68 | "key": {Type: "string"}, 69 | }, 70 | }, 71 | }, 72 | }, 73 | }, 74 | }, 75 | }, 76 | Scope: crdv1.NamespaceScoped, 77 | }, 78 | } 79 | By("Prepare for the admission resource") 80 | reqResource = metav1.GroupVersionResource{Group: "core.oam.dev", Version: "v1alpha2", Resource: "components"} 81 | By("Prepare for the admission decoder") 82 | decoder, err = admission.NewDecoder(scheme) 83 | Expect(err).Should(BeNil()) 84 | By("Finished test bootstrap") 85 | close(done) 86 | }) 87 | -------------------------------------------------------------------------------- /pkg/oam/mock/mapper.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/discoverymapper" 5 | 6 | "k8s.io/apimachinery/pkg/api/meta" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | ) 9 | 10 | var _ discoverymapper.DiscoveryMapper = &DiscoveryMapper{} 11 | 12 | // nolint 13 | type GetMapper func() (meta.RESTMapper, error) 14 | 15 | // nolint 16 | type Refresh func() (meta.RESTMapper, error) 17 | 18 | // nolint 19 | type RESTMapping func(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) 20 | 21 | // nolint 22 | type KindsFor func(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) 23 | 24 | // NewMockDiscoveryMapper for unit test only 25 | func NewMockDiscoveryMapper() *DiscoveryMapper { 26 | return &DiscoveryMapper{ 27 | MockRESTMapping: NewMockRESTMapping(""), 28 | MockKindsFor: NewMockKindsFor(""), 29 | } 30 | } 31 | 32 | // NewMockRESTMapping for unit test only 33 | func NewMockRESTMapping(resource string) RESTMapping { 34 | return func(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { 35 | return &meta.RESTMapping{Resource: schema.GroupVersionResource{Resource: resource, Version: versions[0], Group: gk.Group}}, nil 36 | } 37 | } 38 | 39 | // NewMockKindsFor for unit test only 40 | func NewMockKindsFor(kind string, version ...string) KindsFor { 41 | return func(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { 42 | if len(kind) <= 1 { 43 | return []schema.GroupVersionKind{{Version: input.Version, Group: input.Group, Kind: kind}}, nil 44 | } 45 | var ss []schema.GroupVersionKind 46 | for _, v := range version { 47 | gvk := schema.GroupVersionKind{Version: v, Group: input.Group, Kind: kind} 48 | if input.Version != "" && input.Version == v { 49 | return []schema.GroupVersionKind{gvk}, nil 50 | } 51 | ss = append(ss, gvk) 52 | } 53 | return ss, nil 54 | } 55 | } 56 | 57 | // DiscoveryMapper for unit test only, use GetMapper and refresh will panic 58 | type DiscoveryMapper struct { 59 | MockGetMapper GetMapper 60 | MockRefresh Refresh 61 | MockRESTMapping RESTMapping 62 | MockKindsFor KindsFor 63 | } 64 | 65 | // GetMapper for mock 66 | func (m *DiscoveryMapper) GetMapper() (meta.RESTMapper, error) { 67 | return m.MockGetMapper() 68 | } 69 | 70 | // Refresh for mock 71 | func (m *DiscoveryMapper) Refresh() (meta.RESTMapper, error) { 72 | return m.MockRefresh() 73 | } 74 | 75 | // RESTMapping for mock 76 | func (m *DiscoveryMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { 77 | return m.MockRESTMapping(gk, versions...) 78 | } 79 | 80 | // KindsFor for mock 81 | func (m *DiscoveryMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { 82 | return m.MockKindsFor(input) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/oam/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package oam contains miscellaneous OAM helper types. 18 | package oam 19 | 20 | import ( 21 | "context" 22 | 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "k8s.io/apimachinery/pkg/runtime/schema" 26 | 27 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 28 | ) 29 | 30 | // ScopeKind contains the type metadata for a kind of an OAM scope resource. 31 | type ScopeKind schema.GroupVersionKind 32 | 33 | // TraitKind contains the type metadata for a kind of an OAM trait resource. 34 | type TraitKind schema.GroupVersionKind 35 | 36 | // WorkloadKind contains the type metadata for a kind of an OAM workload resource. 37 | type WorkloadKind schema.GroupVersionKind 38 | 39 | // A Conditioned may have conditions set or retrieved. Conditions are typically 40 | // indicate the status of both a resource and its reconciliation process. 41 | type Conditioned interface { 42 | SetConditions(c ...runtimev1alpha1.Condition) 43 | GetCondition(runtimev1alpha1.ConditionType) runtimev1alpha1.Condition 44 | } 45 | 46 | // A WorkloadReferencer may reference an OAM workload. 47 | type WorkloadReferencer interface { 48 | GetWorkloadReference() runtimev1alpha1.TypedReference 49 | SetWorkloadReference(runtimev1alpha1.TypedReference) 50 | } 51 | 52 | // A WorkloadsReferencer may reference an OAM workload. 53 | type WorkloadsReferencer interface { 54 | GetWorkloadReferences() []runtimev1alpha1.TypedReference 55 | AddWorkloadReference(runtimev1alpha1.TypedReference) 56 | } 57 | 58 | // A Finalizer manages the finalizers on the resource. 59 | type Finalizer interface { 60 | AddFinalizer(ctx context.Context, obj Object) error 61 | RemoveFinalizer(ctx context.Context, obj Object) error 62 | } 63 | 64 | // An Object is a Kubernetes object. 65 | type Object interface { 66 | metav1.Object 67 | runtime.Object 68 | } 69 | 70 | // A Trait is a type of OAM trait. 71 | type Trait interface { 72 | Object 73 | 74 | Conditioned 75 | WorkloadReferencer 76 | } 77 | 78 | // A Scope is a type of OAM scope. 79 | type Scope interface { 80 | Object 81 | 82 | Conditioned 83 | WorkloadsReferencer 84 | } 85 | 86 | // A Workload is a type of OAM workload. 87 | type Workload interface { 88 | Object 89 | 90 | Conditioned 91 | } 92 | -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/applicationconfiguration/suite_test.go: -------------------------------------------------------------------------------- 1 | package applicationconfiguration 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 16 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 17 | 18 | "github.com/crossplane/oam-kubernetes-runtime/apis/core" 19 | ) 20 | 21 | var scheme = runtime.NewScheme() 22 | var crd crdv1.CustomResourceDefinition 23 | var reqResource metav1.GroupVersionResource 24 | var decoder *admission.Decoder 25 | 26 | func TestApplicationConfigurationWebHandler(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "ApplicationConfiguration Web handler") 29 | } 30 | 31 | var _ = BeforeSuite(func(done Done) { 32 | By("Bootstrapping test environment") 33 | ctrl.SetLogger(zap.New(func(o *zap.Options) { 34 | o.Development = true 35 | o.DestWritter = os.Stdout 36 | })) 37 | By("Setup scheme") 38 | err := core.AddToScheme(scheme) 39 | Expect(err).Should(BeNil()) 40 | err = clientgoscheme.AddToScheme(scheme) 41 | Expect(err).Should(BeNil()) 42 | // the crd we will refer to 43 | crd = crdv1.CustomResourceDefinition{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "foo.example.com", 46 | }, 47 | Spec: crdv1.CustomResourceDefinitionSpec{ 48 | Group: "example.com", 49 | Names: crdv1.CustomResourceDefinitionNames{ 50 | Kind: "Foo", 51 | ListKind: "FooList", 52 | Plural: "foo", 53 | Singular: "foo", 54 | }, 55 | Versions: []crdv1.CustomResourceDefinitionVersion{ 56 | { 57 | Name: "v1", 58 | Served: true, 59 | Storage: true, 60 | Schema: &crdv1.CustomResourceValidation{ 61 | OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 62 | Type: "object", 63 | Properties: map[string]crdv1.JSONSchemaProps{ 64 | "spec": { 65 | Type: "object", 66 | Properties: map[string]crdv1.JSONSchemaProps{ 67 | "key": {Type: "string"}, 68 | }, 69 | }, 70 | "status": { 71 | Type: "object", 72 | Properties: map[string]crdv1.JSONSchemaProps{ 73 | "key": {Type: "string"}, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | }, 81 | Scope: crdv1.NamespaceScoped, 82 | }, 83 | } 84 | By("Prepare for the admission resource") 85 | reqResource = metav1.GroupVersionResource{Group: "core.oam.dev", Version: "v1alpha2", Resource: "applicationconfigurations"} 86 | By("Prepare for the admission decoder") 87 | decoder, err = admission.NewDecoder(scheme) 88 | Expect(err).Should(BeNil()) 89 | By("Finished test bootstrap") 90 | close(done) 91 | }) 92 | -------------------------------------------------------------------------------- /examples/flight-tracker/tracker-app-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: dbuser 5 | type: Opaque 6 | data: 7 | endpoint: ZGItd29ya2xvYWQ= 8 | password: ZGJwYXNzd29yZA== 9 | username: cG9zdGdyZXM= 10 | --- 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | kind: ClusterRole 13 | metadata: 14 | name: flight-tracker-clusterrole 15 | labels: 16 | rbac.oam.dev/aggregate-to-controller: "true" 17 | rules: 18 | - apiGroups: 19 | - extensions 20 | resources: 21 | - 'ingresses' 22 | verbs: 23 | - '*' 24 | --- 25 | apiVersion: core.oam.dev/v1alpha2 26 | kind: ApplicationConfiguration 27 | metadata: 28 | name: service-tracker 29 | spec: 30 | components: 31 | - componentName: tracker-postgres-db 32 | - componentName: data-api 33 | parameterValues: 34 | - name: dbsecret 35 | value: "dbuser" 36 | - name: dbname 37 | value: "hackfest" 38 | - name: dbport 39 | value: "5432" 40 | - name: dbdriver 41 | value: "postgres" 42 | - name: dboptions 43 | value: "" 44 | - componentName: flights-api 45 | parameterValues: 46 | - name: dataUri 47 | value: "http://data-api.default.svc.cluster.local:3009/" 48 | traits: 49 | - trait: 50 | apiVersion: core.oam.dev/v1alpha2 51 | kind: ManualScalerTrait 52 | metadata: 53 | name: flights-api 54 | spec: 55 | replicaCount: 2 56 | - componentName: quakes-api 57 | parameterValues: 58 | - name: dataUri 59 | value: "http://data-api.default.svc.cluster.local:3009/" 60 | traits: 61 | - trait: 62 | apiVersion: core.oam.dev/v1alpha2 63 | kind: ManualScalerTrait 64 | metadata: 65 | name: quakes-api 66 | spec: 67 | replicaCount: 2 68 | - componentName: weather-api 69 | parameterValues: 70 | - name: dataUri 71 | value: "http://data-api.default.svc.cluster.local:3009/" 72 | traits: 73 | - trait: 74 | apiVersion: core.oam.dev/v1alpha2 75 | kind: ManualScalerTrait 76 | metadata: 77 | name: weather-api 78 | spec: 79 | replicaCount: 2 80 | - componentName: service-tracker-ui 81 | parameterValues: 82 | - name: flightsUri 83 | value: "http://flights-api.default.svc.cluster.local:3003/" 84 | - name: weatherUri 85 | value: "http://weather-api.default.svc.cluster.local:3015/" 86 | - name: quakesUri 87 | value: "http://quakes-api.default.svc.cluster.local:3012/" 88 | traits: 89 | - trait: 90 | apiVersion: extensions/v1beta1 91 | kind: Ingress 92 | metadata: 93 | name: tracker-ingress 94 | spec: 95 | backend: 96 | serviceName: web-ui 97 | servicePort: 8080 98 | -------------------------------------------------------------------------------- /examples/flight-tracker/components/tracker-data-component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.oam.dev/v1alpha2 2 | kind: Component 3 | metadata: 4 | name: data-api 5 | spec: 6 | workload: 7 | apiVersion: core.oam.dev/v1alpha2 8 | kind: ContainerizedWorkload 9 | metadata: 10 | name: data-api 11 | spec: 12 | osType: linux 13 | arch: amd64 14 | containers: 15 | - name: data-api 16 | image: artursouza/rudr-data-api:0.50 17 | env: 18 | - name: DATABASE_USER 19 | fromSecret: 20 | name: postgresqlconn 21 | key: username 22 | - name: DATABASE_PASSWORD 23 | fromSecret: 24 | name: postgresqlconn 25 | key: password 26 | - name: DATABASE_HOSTNAME 27 | fromSecret: 28 | name: postgresqlconn 29 | key: endpoint 30 | - name: DATABASE_NAME 31 | value: postgres 32 | - name: DATABASE_PORT 33 | value: 5432 34 | - name: DATABASE_DRIVER 35 | value: postgres 36 | - name: DATABASE_OPTIONS 37 | value: "" 38 | ports: 39 | - name: http 40 | containerPort: 3009 41 | protocol: TCP 42 | readinessProbe: 43 | exec: 44 | command: 45 | - wget 46 | - -q 47 | - 'http://127.0.0.1:3009/status' 48 | - -O 49 | - /dev/null 50 | - -S 51 | failureThreshold: 6 52 | initialDelaySeconds: 5 53 | periodSeconds: 10 54 | successThreshold: 1 55 | timeoutSeconds: 5 56 | livenessProbe: 57 | exec: 58 | command: 59 | - wget 60 | - -q 61 | - 'http://127.0.0.1:3009/status' 62 | - -O 63 | - /dev/null 64 | - -S 65 | failureThreshold: 6 66 | initialDelaySeconds: 30 67 | periodSeconds: 10 68 | successThreshold: 1 69 | timeoutSeconds: 5 70 | parameters: 71 | - name: dbsecret 72 | description: secret with database connection information 73 | required: false 74 | fieldPaths: 75 | - spec.containers[0].env[0].fromSecret.name 76 | - spec.containers[0].env[1].fromSecret.name 77 | - spec.containers[0].env[2].fromSecret.name 78 | - name: dbname 79 | description: database name 80 | required: false 81 | fieldPaths: 82 | - spec.containers[0].env[3].value 83 | - name: dbport 84 | description: database port number 85 | required: false 86 | fieldPaths: 87 | - spec.containers[0].env[4].value 88 | - name: dbdriver 89 | description: database driver - one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' 90 | required: false 91 | fieldPaths: 92 | - spec.containers[0].env[5].value 93 | - name: dboptions 94 | description: config as JSON 95 | required: false 96 | fieldPaths: 97 | - spec.containers[0].env[6].value 98 | -------------------------------------------------------------------------------- /examples/data-passing/demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: dependency-clusterrole 5 | labels: 6 | rbac.oam.dev/aggregate-to-controller: "true" 7 | rules: 8 | - apiGroups: 9 | - example.com 10 | resources: 11 | - 'foo' 12 | verbs: 13 | - '*' 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - 'configmaps' 18 | verbs: 19 | - '*' 20 | --- 21 | apiVersion: core.oam.dev/v1alpha2 22 | kind: Component 23 | metadata: 24 | name: source 25 | spec: 26 | workload: 27 | apiVersion: example.com/v1 28 | kind: Foo 29 | metadata: 30 | name: source 31 | spec: 32 | key1: test-value1 33 | key2: test-value2 34 | status: # comment this part to mimic dependency wait time. 35 | state: pending 36 | --- 37 | apiVersion: core.oam.dev/v1alpha2 38 | kind: Component 39 | metadata: 40 | name: sink 41 | spec: 42 | workload: 43 | apiVersion: example.com/v1 44 | kind: Foo 45 | metadata: 46 | name: sink 47 | --- 48 | apiVersion: core.oam.dev/v1alpha2 49 | kind: ApplicationConfiguration 50 | metadata: 51 | name: example-appconfig 52 | spec: 53 | components: 54 | - componentName: source 55 | dataOutputs: 56 | - OutputStore: 57 | apiVersion: v1 58 | kind: ConfigMap 59 | metadata: 60 | name: store 61 | operations: 62 | - type: "jsonPatch" 63 | operator: "add" 64 | toFieldPath: "data" 65 | value: "{}" 66 | - type: "jsonPatch" 67 | operator: "add" 68 | toFieldPath: "data.key1" 69 | valueFrom: 70 | fieldPath: "spec.key1" 71 | conditions: 72 | - operator: notEq 73 | fieldPath: "spec.key1" 74 | value: "" 75 | - type: "jsonPatch" 76 | operator: "add" 77 | toFieldPath: "data.key2" 78 | valueFrom: 79 | fieldPath: "spec.key2" 80 | - componentName: sink 81 | dataInputs: 82 | - InputStore: 83 | apiVersion: v1 84 | kind: ConfigMap 85 | metadata: 86 | name: store 87 | operations: 88 | - type: "jsonPatch" 89 | operator: "add" 90 | toFieldPath: "spec" 91 | value: "{}" 92 | - type: "jsonPatch" 93 | operator: "add" 94 | toFieldPath: "spec.key" 95 | value: '"{}"' 96 | - type: "jsonPatch" 97 | operator: "add" 98 | toFieldPath: "spec.key" 99 | toDataPath: "key1" 100 | valueFrom: 101 | fieldPath: "data.key1" 102 | - type: "jsonPatch" 103 | operator: "add" 104 | toFieldPath: "spec.key" 105 | toDataPath: "key2" 106 | valueFrom: 107 | fieldPath: "data.key2" 108 | 109 | -------------------------------------------------------------------------------- /pkg/oam/discoverymapper/mapper.go: -------------------------------------------------------------------------------- 1 | package discoverymapper 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/api/meta" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | "k8s.io/client-go/discovery" 7 | "k8s.io/client-go/rest" 8 | "k8s.io/client-go/restmapper" 9 | ) 10 | 11 | // DiscoveryMapper is a interface for refresh and discovery resources from GVK. 12 | type DiscoveryMapper interface { 13 | GetMapper() (meta.RESTMapper, error) 14 | Refresh() (meta.RESTMapper, error) 15 | RESTMapping(gk schema.GroupKind, version ...string) (*meta.RESTMapping, error) 16 | KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) 17 | } 18 | 19 | var _ DiscoveryMapper = &DefaultDiscoveryMapper{} 20 | 21 | // DefaultDiscoveryMapper is a K8s resource mapper for discovery, it will cache the result 22 | type DefaultDiscoveryMapper struct { 23 | dc *discovery.DiscoveryClient 24 | mapper meta.RESTMapper 25 | } 26 | 27 | // New will create a new DefaultDiscoveryMapper by giving a K8s rest config 28 | func New(c *rest.Config) (DiscoveryMapper, error) { 29 | dc, err := discovery.NewDiscoveryClientForConfig(c) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return &DefaultDiscoveryMapper{ 34 | dc: dc, 35 | }, nil 36 | } 37 | 38 | // GetMapper will get the cached restmapper, if nil, it will create one by refresh 39 | // Prefer lazy discovery, because resources created after refresh can not be found 40 | func (d *DefaultDiscoveryMapper) GetMapper() (meta.RESTMapper, error) { 41 | if d.mapper == nil { 42 | return d.Refresh() 43 | } 44 | return d.mapper, nil 45 | } 46 | 47 | // Refresh will re-create the mapper by getting the new resource from K8s API by using discovery client 48 | func (d *DefaultDiscoveryMapper) Refresh() (meta.RESTMapper, error) { 49 | gr, err := restmapper.GetAPIGroupResources(d.dc) 50 | if err != nil { 51 | return nil, err 52 | } 53 | d.mapper = restmapper.NewDiscoveryRESTMapper(gr) 54 | return d.mapper, nil 55 | } 56 | 57 | // RESTMapping will mapping resources from GVK, if not found, it will refresh from APIServer and try once again 58 | func (d *DefaultDiscoveryMapper) RESTMapping(gk schema.GroupKind, version ...string) (*meta.RESTMapping, error) { 59 | mapper, err := d.GetMapper() 60 | if err != nil { 61 | return nil, err 62 | } 63 | mapping, err := mapper.RESTMapping(gk, version...) 64 | if meta.IsNoMatchError(err) { 65 | // if no kind match err, refresh and try once more. 66 | mapper, err = d.Refresh() 67 | if err != nil { 68 | return nil, err 69 | } 70 | mapping, err = mapper.RESTMapping(gk, version...) 71 | } 72 | return mapping, err 73 | } 74 | 75 | // KindsFor will get kinds from GroupVersionResource, if version not set, all resources matched will be returned. 76 | func (d *DefaultDiscoveryMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { 77 | mapper, err := d.GetMapper() 78 | if err != nil { 79 | return nil, err 80 | } 81 | mapping, err := mapper.KindsFor(input) 82 | if meta.IsNoMatchError(err) { 83 | // if no kind match err, refresh and try once more. 84 | mapper, err = d.Refresh() 85 | if err != nil { 86 | return nil, err 87 | } 88 | mapping, err = mapper.KindsFor(input) 89 | } 90 | return mapping, err 91 | } 92 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_scopedefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: scopedefinitions.core.oam.dev 10 | spec: 11 | additionalPrinterColumns: 12 | - JSONPath: .spec.definitionRef.name 13 | name: DEFINITION-NAME 14 | type: string 15 | group: core.oam.dev 16 | names: 17 | categories: 18 | - crossplane 19 | - oam 20 | kind: ScopeDefinition 21 | listKind: ScopeDefinitionList 22 | plural: scopedefinitions 23 | singular: scopedefinition 24 | scope: Cluster 25 | subresources: {} 26 | validation: 27 | openAPIV3Schema: 28 | description: A ScopeDefinition registers a kind of Kubernetes custom resource as a valid OAM scope kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the scope when it is embedded in an OAM ApplicationConfiguration. 29 | properties: 30 | apiVersion: 31 | 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' 32 | type: string 33 | kind: 34 | 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' 35 | type: string 36 | metadata: 37 | type: object 38 | spec: 39 | description: A ScopeDefinitionSpec defines the desired state of a ScopeDefinition. 40 | properties: 41 | allowComponentOverlap: 42 | description: AllowComponentOverlap specifies whether an OAM component may exist in multiple instances of this kind of scope. 43 | type: boolean 44 | definitionRef: 45 | description: Reference to the CustomResourceDefinition that defines this scope kind. 46 | properties: 47 | name: 48 | description: Name of the referenced CustomResourceDefinition. 49 | type: string 50 | version: 51 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 52 | type: string 53 | required: 54 | - name 55 | type: object 56 | extension: 57 | description: Extension is used for extension needs by OAM platform builders 58 | type: object 59 | 60 | workloadRefsPath: 61 | description: WorkloadRefsPath indicates if/where a scope accepts workloadRef objects 62 | type: string 63 | required: 64 | - allowComponentOverlap 65 | - definitionRef 66 | type: object 67 | type: object 68 | version: v1alpha2 69 | versions: 70 | - name: v1alpha2 71 | served: true 72 | storage: true 73 | status: 74 | acceptedNames: 75 | kind: "" 76 | plural: "" 77 | conditions: [] 78 | storedVersions: [] 79 | -------------------------------------------------------------------------------- /apis/core/v1alpha2/methods.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This code is manually implemented, but should be generated in the future. 18 | 19 | package v1alpha2 20 | 21 | import ( 22 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 23 | ) 24 | 25 | // GetCondition of this ManualScalerTrait. 26 | func (tr *ManualScalerTrait) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition { 27 | return tr.Status.GetCondition(ct) 28 | } 29 | 30 | // SetConditions of this ManualScalerTrait. 31 | func (tr *ManualScalerTrait) SetConditions(c ...runtimev1alpha1.Condition) { 32 | tr.Status.SetConditions(c...) 33 | } 34 | 35 | // GetWorkloadReference of this ManualScalerTrait. 36 | func (tr *ManualScalerTrait) GetWorkloadReference() runtimev1alpha1.TypedReference { 37 | return tr.Spec.WorkloadReference 38 | } 39 | 40 | // SetWorkloadReference of this ManualScalerTrait. 41 | func (tr *ManualScalerTrait) SetWorkloadReference(r runtimev1alpha1.TypedReference) { 42 | tr.Spec.WorkloadReference = r 43 | } 44 | 45 | // GetCondition of this ApplicationConfiguration. 46 | func (ac *ApplicationConfiguration) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition { 47 | return ac.Status.GetCondition(ct) 48 | } 49 | 50 | // SetConditions of this ApplicationConfiguration. 51 | func (ac *ApplicationConfiguration) SetConditions(c ...runtimev1alpha1.Condition) { 52 | ac.Status.SetConditions(c...) 53 | } 54 | 55 | // GetCondition of this Component. 56 | func (cm *Component) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition { 57 | return cm.Status.GetCondition(ct) 58 | } 59 | 60 | // SetConditions of this Component. 61 | func (cm *Component) SetConditions(c ...runtimev1alpha1.Condition) { 62 | cm.Status.SetConditions(c...) 63 | } 64 | 65 | // GetCondition of this ContainerizedWorkload. 66 | func (wl *ContainerizedWorkload) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition { 67 | return wl.Status.GetCondition(ct) 68 | } 69 | 70 | // SetConditions of this ContainerizedWorkload. 71 | func (wl *ContainerizedWorkload) SetConditions(c ...runtimev1alpha1.Condition) { 72 | wl.Status.SetConditions(c...) 73 | } 74 | 75 | // GetCondition of this HealthScope. 76 | func (hs *HealthScope) GetCondition(ct runtimev1alpha1.ConditionType) runtimev1alpha1.Condition { 77 | return hs.Status.GetCondition(ct) 78 | } 79 | 80 | // SetConditions of this HealthScope. 81 | func (hs *HealthScope) SetConditions(c ...runtimev1alpha1.Condition) { 82 | hs.Status.SetConditions(c...) 83 | } 84 | 85 | // GetWorkloadReferences to get all workload references for scope. 86 | func (hs *HealthScope) GetWorkloadReferences() []runtimev1alpha1.TypedReference { 87 | return hs.Spec.WorkloadReferences 88 | } 89 | 90 | // AddWorkloadReference to add a workload reference to this scope. 91 | func (hs *HealthScope) AddWorkloadReference(r runtimev1alpha1.TypedReference) { 92 | hs.Spec.WorkloadReferences = append(hs.Spec.WorkloadReferences, r) 93 | } 94 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/crds/core.oam.dev_scopedefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: scopedefinitions.core.oam.dev 10 | spec: 11 | group: core.oam.dev 12 | names: 13 | categories: 14 | - crossplane 15 | - oam 16 | kind: ScopeDefinition 17 | listKind: ScopeDefinitionList 18 | plural: scopedefinitions 19 | singular: scopedefinition 20 | scope: Cluster 21 | versions: 22 | - additionalPrinterColumns: 23 | - jsonPath: .spec.definitionRef.name 24 | name: DEFINITION-NAME 25 | type: string 26 | name: v1alpha2 27 | schema: 28 | openAPIV3Schema: 29 | description: A ScopeDefinition registers a kind of Kubernetes custom resource as a valid OAM scope kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the scope when it is embedded in an OAM ApplicationConfiguration. 30 | properties: 31 | apiVersion: 32 | 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' 33 | type: string 34 | kind: 35 | 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' 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: A ScopeDefinitionSpec defines the desired state of a ScopeDefinition. 41 | properties: 42 | allowComponentOverlap: 43 | description: AllowComponentOverlap specifies whether an OAM component may exist in multiple instances of this kind of scope. 44 | type: boolean 45 | definitionRef: 46 | description: Reference to the CustomResourceDefinition that defines this scope kind. 47 | properties: 48 | name: 49 | description: Name of the referenced CustomResourceDefinition. 50 | type: string 51 | version: 52 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 53 | type: string 54 | required: 55 | - name 56 | type: object 57 | extension: 58 | description: Extension is used for extension needs by OAM platform builders 59 | type: object 60 | x-kubernetes-preserve-unknown-fields: true 61 | workloadRefsPath: 62 | description: WorkloadRefsPath indicates if/where a scope accepts workloadRef objects 63 | type: string 64 | required: 65 | - allowComponentOverlap 66 | - definitionRef 67 | type: object 68 | type: object 69 | served: true 70 | storage: true 71 | subresources: {} 72 | status: 73 | acceptedNames: 74 | kind: "" 75 | plural: "" 76 | conditions: [] 77 | storedVersions: [] 78 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_traitdefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: traitdefinitions.core.oam.dev 10 | spec: 11 | additionalPrinterColumns: 12 | - JSONPath: .spec.definitionRef.name 13 | name: DEFINITION-NAME 14 | type: string 15 | group: core.oam.dev 16 | names: 17 | categories: 18 | - crossplane 19 | - oam 20 | kind: TraitDefinition 21 | listKind: TraitDefinitionList 22 | plural: traitdefinitions 23 | singular: traitdefinition 24 | scope: Cluster 25 | subresources: {} 26 | validation: 27 | openAPIV3Schema: 28 | description: A TraitDefinition registers a kind of Kubernetes custom resource as a valid OAM trait kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the trait when it is embedded in an OAM ApplicationConfiguration. 29 | properties: 30 | apiVersion: 31 | 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' 32 | type: string 33 | kind: 34 | 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' 35 | type: string 36 | metadata: 37 | type: object 38 | spec: 39 | description: A TraitDefinitionSpec defines the desired state of a TraitDefinition. 40 | properties: 41 | appliesToWorkloads: 42 | description: AppliesToWorkloads specifies the list of workload kinds this trait applies to. Workload kinds are specified in kind.group/version format, e.g. server.core.oam.dev/v1alpha2. Traits that omit this field apply to all workload kinds. 43 | items: 44 | type: string 45 | type: array 46 | definitionRef: 47 | description: Reference to the CustomResourceDefinition that defines this trait kind. 48 | properties: 49 | name: 50 | description: Name of the referenced CustomResourceDefinition. 51 | type: string 52 | version: 53 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 54 | type: string 55 | required: 56 | - name 57 | type: object 58 | extension: 59 | description: Extension is used for extension needs by OAM platform builders 60 | type: object 61 | 62 | revisionEnabled: 63 | description: Revision indicates whether a trait is aware of component revision 64 | type: boolean 65 | workloadRefPath: 66 | description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object 67 | type: string 68 | required: 69 | - definitionRef 70 | type: object 71 | type: object 72 | version: v1alpha2 73 | versions: 74 | - name: v1alpha2 75 | served: true 76 | storage: true 77 | status: 78 | acceptedNames: 79 | kind: "" 80 | plural: "" 81 | conditions: [] 82 | storedVersions: [] 83 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/templates/webhook.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.useWebhook -}} 2 | --- 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | name: {{ include "oam-kubernetes-runtime.fullname" . }} 7 | labels: 8 | {{- include "oam-kubernetes-runtime.selectorLabels" . | nindent 4 }} 9 | webhooks: 10 | - name: "validate.applicationconfigurations.core.oam.dev" 11 | rules: 12 | - apiGroups: ["core.oam.dev"] 13 | apiVersions: ["v1alpha2"] 14 | operations: ["CREATE", "UPDATE"] 15 | resources: ["applicationconfigurations"] 16 | scope: "Namespaced" 17 | clientConfig: 18 | service: 19 | namespace: {{.Release.Namespace}} 20 | name: {{ template "oam-kubernetes-runtime.name" . }}-webhook 21 | path: /validating-core-oam-dev-v1alpha2-applicationconfigurations 22 | caBundle: "{{.Values.certificate.caBundle}}" 23 | admissionReviewVersions: ["v1beta1"] 24 | failurePolicy: Fail 25 | timeoutSeconds: 5 26 | - name: "validate.component.core.oam.dev" 27 | clientConfig: 28 | service: 29 | name: {{ template "oam-kubernetes-runtime.name" . }}-webhook 30 | namespace: {{.Release.Namespace}} 31 | path: /validating-core-oam-dev-v1alpha2-components 32 | caBundle: "{{.Values.certificate.caBundle}}" 33 | rules: 34 | - apiGroups: ["core.oam.dev"] 35 | apiVersions: ["v1alpha2"] 36 | operations: ["CREATE", "UPDATE"] 37 | resources: ["components"] 38 | scope: "Namespaced" 39 | admissionReviewVersions: ["v1beta1"] 40 | failurePolicy: Fail 41 | timeoutSeconds: 5 42 | --- 43 | apiVersion: admissionregistration.k8s.io/v1beta1 44 | kind: MutatingWebhookConfiguration 45 | metadata: 46 | name: {{ include "oam-kubernetes-runtime.fullname" . }} 47 | labels: 48 | {{- include "oam-kubernetes-runtime.selectorLabels" . | nindent 4 }} 49 | webhooks: 50 | - name: "mutate.applicationconfigurations.core.oam.dev" 51 | clientConfig: 52 | service: 53 | name: {{ template "oam-kubernetes-runtime.name" . }}-webhook 54 | namespace: {{.Release.Namespace}} 55 | path: /mutating-core-oam-dev-v1alpha2-applicationconfigurations 56 | caBundle: "{{.Values.certificate.caBundle}}" 57 | rules: 58 | - apiGroups: ["core.oam.dev"] 59 | apiVersions: ["v1alpha2"] 60 | operations: ["CREATE", "UPDATE"] 61 | resources: ["applicationconfigurations"] 62 | scope: "Namespaced" 63 | admissionReviewVersions: ["v1beta1"] 64 | failurePolicy: Fail 65 | timeoutSeconds: 5 66 | - name: "mutate.component.core.oam.dev" 67 | clientConfig: 68 | service: 69 | name: {{ template "oam-kubernetes-runtime.name" . }}-webhook 70 | namespace: {{.Release.Namespace}} 71 | path: /mutating-core-oam-dev-v1alpha2-components 72 | caBundle: "{{.Values.certificate.caBundle}}" 73 | rules: 74 | - apiGroups: ["core.oam.dev"] 75 | apiVersions: ["v1alpha2"] 76 | operations: ["CREATE", "UPDATE"] 77 | resources: ["components"] 78 | scope: "Namespaced" 79 | admissionReviewVersions: ["v1beta1"] 80 | failurePolicy: Fail 81 | timeoutSeconds: 5 82 | --- 83 | apiVersion: v1 84 | kind: Service 85 | metadata: 86 | name: {{ template "oam-kubernetes-runtime.name" . }}-webhook 87 | labels: 88 | {{- include "oam-kubernetes-runtime.selectorLabels" . | nindent 4 }} 89 | spec: 90 | type: {{ .Values.webhookService.type }} 91 | ports: 92 | - port: 443 93 | targetPort: {{ .Values.webhookService.port }} 94 | protocol: TCP 95 | name: https 96 | selector: 97 | {{ include "oam-kubernetes-runtime.selectorLabels" . | nindent 6 }} 98 | 99 | {{- end -}} -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/crds/core.oam.dev_traitdefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: traitdefinitions.core.oam.dev 10 | spec: 11 | group: core.oam.dev 12 | names: 13 | categories: 14 | - crossplane 15 | - oam 16 | kind: TraitDefinition 17 | listKind: TraitDefinitionList 18 | plural: traitdefinitions 19 | singular: traitdefinition 20 | scope: Cluster 21 | versions: 22 | - additionalPrinterColumns: 23 | - jsonPath: .spec.definitionRef.name 24 | name: DEFINITION-NAME 25 | type: string 26 | name: v1alpha2 27 | schema: 28 | openAPIV3Schema: 29 | description: A TraitDefinition registers a kind of Kubernetes custom resource as a valid OAM trait kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the trait when it is embedded in an OAM ApplicationConfiguration. 30 | properties: 31 | apiVersion: 32 | 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' 33 | type: string 34 | kind: 35 | 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' 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: A TraitDefinitionSpec defines the desired state of a TraitDefinition. 41 | properties: 42 | appliesToWorkloads: 43 | description: AppliesToWorkloads specifies the list of workload kinds this trait applies to. Workload kinds are specified in kind.group/version format, e.g. server.core.oam.dev/v1alpha2. Traits that omit this field apply to all workload kinds. 44 | items: 45 | type: string 46 | type: array 47 | definitionRef: 48 | description: Reference to the CustomResourceDefinition that defines this trait kind. 49 | properties: 50 | name: 51 | description: Name of the referenced CustomResourceDefinition. 52 | type: string 53 | version: 54 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 55 | type: string 56 | required: 57 | - name 58 | type: object 59 | extension: 60 | description: Extension is used for extension needs by OAM platform builders 61 | type: object 62 | x-kubernetes-preserve-unknown-fields: true 63 | revisionEnabled: 64 | description: Revision indicates whether a trait is aware of component revision 65 | type: boolean 66 | workloadRefPath: 67 | description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object 68 | type: string 69 | required: 70 | - definitionRef 71 | type: object 72 | type: object 73 | served: true 74 | storage: true 75 | subresources: {} 76 | status: 77 | acceptedNames: 78 | kind: "" 79 | plural: "" 80 | conditions: [] 81 | storedVersions: [] 82 | -------------------------------------------------------------------------------- /pkg/oam/util/test_utils.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/onsi/gomega/format" 7 | "github.com/onsi/gomega/types" 8 | apierrors "k8s.io/apimachinery/pkg/api/errors" 9 | ) 10 | 11 | // JSONMarshal returns the JSON encoding 12 | func JSONMarshal(o interface{}) []byte { 13 | j, _ := json.Marshal(o) 14 | return j 15 | } 16 | 17 | // AlreadyExistMatcher matches the error to be already exist 18 | type AlreadyExistMatcher struct { 19 | } 20 | 21 | // Match matches error. 22 | func (matcher AlreadyExistMatcher) Match(actual interface{}) (success bool, err error) { 23 | if actual == nil { 24 | return false, nil 25 | } 26 | actualError := actual.(error) 27 | return apierrors.IsAlreadyExists(actualError), nil 28 | } 29 | 30 | // FailureMessage builds an error message. 31 | func (matcher AlreadyExistMatcher) FailureMessage(actual interface{}) (message string) { 32 | return format.Message(actual, "to be already exist") 33 | } 34 | 35 | // NegatedFailureMessage builds an error message. 36 | func (matcher AlreadyExistMatcher) NegatedFailureMessage(actual interface{}) (message string) { 37 | return format.Message(actual, "not to be already exist") 38 | } 39 | 40 | // NotFoundMatcher matches the error to be not found. 41 | type NotFoundMatcher struct { 42 | } 43 | 44 | // Match matches the api error. 45 | func (matcher NotFoundMatcher) Match(actual interface{}) (success bool, err error) { 46 | if actual == nil { 47 | return false, nil 48 | } 49 | actualError := actual.(error) 50 | return apierrors.IsNotFound(actualError), nil 51 | } 52 | 53 | // FailureMessage builds an error message. 54 | func (matcher NotFoundMatcher) FailureMessage(actual interface{}) (message string) { 55 | return format.Message(actual, "to be not found") 56 | } 57 | 58 | // NegatedFailureMessage builds an error message. 59 | func (matcher NotFoundMatcher) NegatedFailureMessage(actual interface{}) (message string) { 60 | return format.Message(actual, "not to be not found") 61 | } 62 | 63 | // BeEquivalentToError matches the error to take care of nil. 64 | func BeEquivalentToError(expected error) types.GomegaMatcher { 65 | return &ErrorMatcher{ 66 | ExpectedError: expected, 67 | } 68 | } 69 | 70 | // ErrorMatcher matches errors. 71 | type ErrorMatcher struct { 72 | ExpectedError error 73 | } 74 | 75 | // Match matches an error. 76 | func (matcher ErrorMatcher) Match(actual interface{}) (success bool, err error) { 77 | if actual == nil { 78 | return matcher.ExpectedError == nil, nil 79 | } 80 | actualError := actual.(error) 81 | return actualError.Error() == matcher.ExpectedError.Error(), nil 82 | } 83 | 84 | // FailureMessage builds an error message. 85 | func (matcher ErrorMatcher) FailureMessage(actual interface{}) (message string) { 86 | actualError, actualOK := actual.(error) 87 | expectedError, expectedOK := matcher.ExpectedError.(error) 88 | 89 | if actualOK && expectedOK { 90 | return format.MessageWithDiff(actualError.Error(), "to equal", expectedError.Error()) 91 | } 92 | 93 | if actualOK && !expectedOK { 94 | return format.Message(actualError.Error(), "to equal", expectedError.Error()) 95 | } 96 | 97 | if !actualOK && expectedOK { 98 | return format.Message(actual, "to equal", expectedError.Error()) 99 | } 100 | 101 | return format.Message(actual, "to equal", expectedError) 102 | } 103 | 104 | // NegatedFailureMessage builds an error message. 105 | func (matcher ErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) { 106 | actualError, actualOK := actual.(error) 107 | expectedError, expectedOK := matcher.ExpectedError.(error) 108 | 109 | if actualOK && expectedOK { 110 | return format.MessageWithDiff(actualError.Error(), "not to equal", expectedError.Error()) 111 | } 112 | 113 | if actualOK && !expectedOK { 114 | return format.Message(actualError.Error(), "not to equal", expectedError.Error()) 115 | } 116 | 117 | if !actualOK && expectedOK { 118 | return format.Message(actual, "not to equal", expectedError.Error()) 119 | } 120 | 121 | return format.Message(actual, "not to equal", expectedError) 122 | } 123 | -------------------------------------------------------------------------------- /test/integration/builder.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | /* 4 | Copyright 2020 The Crossplane Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package integration 20 | 21 | import ( 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | 25 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 26 | ) 27 | 28 | type wdModifier func(*v1alpha2.WorkloadDefinition) 29 | 30 | func wdNameAndDef(n string) wdModifier { 31 | return func(wd *v1alpha2.WorkloadDefinition) { 32 | wd.ObjectMeta.Name = n 33 | wd.Spec.Reference = v1alpha2.DefinitionReference{ 34 | Name: n, 35 | } 36 | } 37 | } 38 | 39 | func wd(m ...wdModifier) *v1alpha2.WorkloadDefinition { 40 | w := &v1alpha2.WorkloadDefinition{ 41 | TypeMeta: v1.TypeMeta{ 42 | Kind: v1alpha2.WorkloadDefinitionKind, 43 | APIVersion: v1alpha2.SchemeGroupVersion.String(), 44 | }, 45 | } 46 | 47 | for _, fn := range m { 48 | fn(w) 49 | } 50 | return w 51 | } 52 | 53 | type compModifier func(*v1alpha2.Component) 54 | 55 | func compWithName(n string) compModifier { 56 | return func(c *v1alpha2.Component) { 57 | c.Name = n 58 | } 59 | } 60 | 61 | func compWithNamespace(n string) compModifier { 62 | return func(c *v1alpha2.Component) { 63 | c.Namespace = n 64 | } 65 | } 66 | 67 | func compWithWorkload(w runtime.RawExtension) compModifier { 68 | return func(c *v1alpha2.Component) { 69 | c.Spec.Workload = w 70 | } 71 | } 72 | 73 | func compWithParams(p []v1alpha2.ComponentParameter) compModifier { 74 | return func(c *v1alpha2.Component) { 75 | c.Spec.Parameters = p 76 | } 77 | } 78 | 79 | func comp(m ...compModifier) *v1alpha2.Component { 80 | c := &v1alpha2.Component{ 81 | TypeMeta: v1.TypeMeta{ 82 | Kind: v1alpha2.ComponentKind, 83 | APIVersion: v1alpha2.SchemeGroupVersion.String(), 84 | }, 85 | } 86 | 87 | for _, fn := range m { 88 | fn(c) 89 | } 90 | return c 91 | } 92 | 93 | type acModifier func(*v1alpha2.ApplicationConfiguration) 94 | 95 | func acWithName(n string) acModifier { 96 | return func(a *v1alpha2.ApplicationConfiguration) { 97 | a.Name = n 98 | } 99 | } 100 | 101 | func acWithNamspace(n string) acModifier { 102 | return func(a *v1alpha2.ApplicationConfiguration) { 103 | a.Namespace = n 104 | } 105 | } 106 | 107 | func acWithComps(c []v1alpha2.ApplicationConfigurationComponent) acModifier { 108 | return func(a *v1alpha2.ApplicationConfiguration) { 109 | a.Spec.Components = c 110 | } 111 | } 112 | 113 | func ac(m ...acModifier) *v1alpha2.ApplicationConfiguration { 114 | a := &v1alpha2.ApplicationConfiguration{ 115 | TypeMeta: v1.TypeMeta{ 116 | Kind: v1alpha2.ApplicationConfigurationKind, 117 | APIVersion: v1alpha2.SchemeGroupVersion.String(), 118 | }, 119 | } 120 | 121 | for _, fn := range m { 122 | fn(a) 123 | } 124 | return a 125 | } 126 | 127 | type cwModifier func(*v1alpha2.ContainerizedWorkload) 128 | 129 | func cwWithName(n string) cwModifier { 130 | return func(cw *v1alpha2.ContainerizedWorkload) { 131 | cw.Name = n 132 | } 133 | } 134 | 135 | func cwWithNamspace(n string) cwModifier { 136 | return func(cw *v1alpha2.ContainerizedWorkload) { 137 | cw.Namespace = n 138 | } 139 | } 140 | 141 | func cwWithContainers(c []v1alpha2.Container) cwModifier { 142 | return func(cw *v1alpha2.ContainerizedWorkload) { 143 | cw.Spec.Containers = c 144 | } 145 | } 146 | 147 | func cw(m ...cwModifier) *v1alpha2.ContainerizedWorkload { 148 | cw := &v1alpha2.ContainerizedWorkload{ 149 | TypeMeta: v1.TypeMeta{ 150 | Kind: v1alpha2.ContainerizedWorkloadKind, 151 | APIVersion: v1alpha2.SchemeGroupVersion.String(), 152 | }, 153 | } 154 | 155 | for _, fn := range m { 156 | fn(cw) 157 | } 158 | return cw 159 | } 160 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/scopes/healthscope/standard_test.go: -------------------------------------------------------------------------------- 1 | package healthscope 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 8 | "github.com/crossplane/crossplane-runtime/pkg/test" 9 | "github.com/stretchr/testify/assert" 10 | apps "k8s.io/api/apps/v1" 11 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/types" 14 | 15 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util" 16 | ) 17 | 18 | func TestCheckPodSpecWorkloadHealth(t *testing.T) { 19 | mockClient := test.NewMockClient() 20 | scRef := runtimev1alpha1.TypedReference{} 21 | scRef.SetGroupVersionKind(podSpecWorkloadGVK) 22 | 23 | deployRef := runtimev1alpha1.TypedReference{} 24 | deployRef.SetGroupVersionKind(apps.SchemeGroupVersion.WithKind(kindDeployment)) 25 | svcRef := runtimev1alpha1.TypedReference{} 26 | svcRef.SetGroupVersionKind(apps.SchemeGroupVersion.WithKind(kindService)) 27 | 28 | deployRefData, _ := util.Object2Map(deployRef) 29 | svcRefData, _ := util.Object2Map(svcRef) 30 | 31 | scUnstructured := unstructured.Unstructured{} 32 | scUnstructured.SetGroupVersionKind(podSpecWorkloadGVK) 33 | unstructured.SetNestedSlice(scUnstructured.Object, []interface{}{deployRefData, svcRefData}, "status", "resources") 34 | 35 | tests := []struct { 36 | caseName string 37 | mockGetFn test.MockGetFn 38 | wlRef runtimev1alpha1.TypedReference 39 | expect *WorkloadHealthCondition 40 | }{ 41 | { 42 | caseName: "not matched checker", 43 | wlRef: runtimev1alpha1.TypedReference{}, 44 | expect: nil, 45 | }, 46 | { 47 | caseName: "healthy workload", 48 | wlRef: scRef, 49 | mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error { 50 | if o, ok := obj.(*unstructured.Unstructured); ok { 51 | *o = scUnstructured 52 | return nil 53 | } 54 | if o, ok := obj.(*apps.Deployment); ok { 55 | *o = apps.Deployment{ 56 | Spec: apps.DeploymentSpec{ 57 | Replicas: &varInt1, 58 | }, 59 | Status: apps.DeploymentStatus{ 60 | ReadyReplicas: 1, // healthy 61 | }, 62 | } 63 | } 64 | return nil 65 | }, 66 | expect: &WorkloadHealthCondition{ 67 | HealthStatus: StatusHealthy, 68 | }, 69 | }, 70 | { 71 | caseName: "unhealthy for deployment not ready", 72 | wlRef: scRef, 73 | mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error { 74 | if o, ok := obj.(*unstructured.Unstructured); ok { 75 | *o = scUnstructured 76 | return nil 77 | } 78 | if o, ok := obj.(*apps.Deployment); ok { 79 | *o = apps.Deployment{ 80 | Spec: apps.DeploymentSpec{ 81 | Replicas: &varInt1, 82 | }, 83 | Status: apps.DeploymentStatus{ 84 | ReadyReplicas: 0, // unhealthy 85 | }, 86 | } 87 | } 88 | return nil 89 | }, 90 | expect: &WorkloadHealthCondition{ 91 | HealthStatus: StatusUnhealthy, 92 | }, 93 | }, 94 | { 95 | caseName: "unhealthy for PodSpecWorkload not found", 96 | wlRef: scRef, 97 | mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error { 98 | return errMockErr 99 | }, 100 | expect: &WorkloadHealthCondition{ 101 | HealthStatus: StatusUnhealthy, 102 | }, 103 | }, 104 | { 105 | caseName: "unhealthy for deployment not found", 106 | wlRef: scRef, 107 | mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error { 108 | if o, ok := obj.(*unstructured.Unstructured); ok { 109 | *o = scUnstructured 110 | return nil 111 | } 112 | if _, ok := obj.(*apps.Deployment); ok { 113 | return errMockErr 114 | } 115 | return nil 116 | }, 117 | expect: &WorkloadHealthCondition{ 118 | HealthStatus: StatusUnhealthy, 119 | }, 120 | }, 121 | } 122 | 123 | for _, tc := range tests { 124 | func(t *testing.T) { 125 | mockClient.MockGet = tc.mockGetFn 126 | result := CheckPodSpecWorkloadHealth(ctx, mockClient, tc.wlRef, namespace) 127 | if tc.expect == nil { 128 | assert.Nil(t, result, tc.caseName) 129 | } else { 130 | assert.Equal(t, tc.expect.HealthStatus, result.HealthStatus, tc.caseName) 131 | } 132 | 133 | }(t) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /apis/core/v1alpha2/core_scope_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 24 | ) 25 | 26 | // HealthStatus represents health status strings. 27 | type HealthStatus string 28 | 29 | const ( 30 | // StatusHealthy represents healthy status. 31 | StatusHealthy HealthStatus = "HEALTHY" 32 | // StatusUnhealthy represents unhealthy status. 33 | StatusUnhealthy = "UNHEALTHY" 34 | // StatusUnknown represents unknown status. 35 | StatusUnknown = "UNKNOWN" 36 | ) 37 | 38 | var _ oam.Scope = &HealthScope{} 39 | 40 | // A HealthScopeSpec defines the desired state of a HealthScope. 41 | type HealthScopeSpec struct { 42 | // ProbeTimeout is the amount of time in seconds to wait when receiving a response before marked failure. 43 | ProbeTimeout *int32 `json:"probe-timeout,omitempty"` 44 | 45 | // ProbeInterval is the amount of time in seconds between probing tries. 46 | ProbeInterval *int32 `json:"probe-interval,omitempty"` 47 | 48 | // WorkloadReferences to the workloads that are in this scope. 49 | WorkloadReferences []runtimev1alpha1.TypedReference `json:"workloadRefs"` 50 | } 51 | 52 | // A HealthScopeStatus represents the observed state of a HealthScope. 53 | type HealthScopeStatus struct { 54 | runtimev1alpha1.ConditionedStatus `json:",inline"` 55 | 56 | // ScopeHealthCondition represents health condition summary of the scope 57 | ScopeHealthCondition ScopeHealthCondition `json:"scopeHealthCondition"` 58 | 59 | // WorkloadHealthConditions represents health condition of workloads in the scope 60 | WorkloadHealthConditions []*WorkloadHealthCondition `json:"healthConditions,omitempty"` 61 | } 62 | 63 | // ScopeHealthCondition represents health condition summary of a scope. 64 | type ScopeHealthCondition struct { 65 | HealthStatus HealthStatus `json:"healthStatus"` 66 | Total int64 `json:"total,omitempty"` 67 | HealthyWorkloads int64 `json:"healthyWorkloads,omitempty"` 68 | UnhealthyWorkloads int64 `json:"unhealthyWorkloads,omitempty"` 69 | UnknownWorkloads int64 `json:"unknownWorkloads,omitempty"` 70 | } 71 | 72 | // WorkloadHealthCondition represents informative health condition. 73 | type WorkloadHealthCondition struct { 74 | // ComponentName represents the component name if target is a workload 75 | ComponentName string `json:"componentName,omitempty"` 76 | TargetWorkload runtimev1alpha1.TypedReference `json:"targetWorkload,omitempty"` 77 | HealthStatus HealthStatus `json:"healthStatus"` 78 | Diagnosis string `json:"diagnosis,omitempty"` 79 | // WorkloadStatus represents status of workloads whose HealthStatus is UNKNOWN. 80 | WorkloadStatus string `json:"workloadStatus,omitempty"` 81 | } 82 | 83 | // +kubebuilder:object:root=true 84 | 85 | // A HealthScope determines an aggregate health status based of the health of components. 86 | // +kubebuilder:resource:categories={crossplane,oam} 87 | // +kubebuilder:subresource:status 88 | // +kubebuilder:printcolumn:JSONPath=".status.health",name=HEALTH,type=string 89 | type HealthScope struct { 90 | metav1.TypeMeta `json:",inline"` 91 | metav1.ObjectMeta `json:"metadata,omitempty"` 92 | 93 | Spec HealthScopeSpec `json:"spec,omitempty"` 94 | Status HealthScopeStatus `json:"status,omitempty"` 95 | } 96 | 97 | // +kubebuilder:object:root=true 98 | 99 | // HealthScopeList contains a list of HealthScope. 100 | type HealthScopeList struct { 101 | metav1.TypeMeta `json:",inline"` 102 | metav1.ListMeta `json:"metadata,omitempty"` 103 | Items []HealthScope `json:"items"` 104 | } 105 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_workloaddefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: workloaddefinitions.core.oam.dev 10 | spec: 11 | additionalPrinterColumns: 12 | - JSONPath: .spec.definitionRef.name 13 | name: DEFINITION-NAME 14 | type: string 15 | group: core.oam.dev 16 | names: 17 | categories: 18 | - crossplane 19 | - oam 20 | kind: WorkloadDefinition 21 | listKind: WorkloadDefinitionList 22 | plural: workloaddefinitions 23 | singular: workloaddefinition 24 | scope: Cluster 25 | subresources: {} 26 | validation: 27 | openAPIV3Schema: 28 | description: A WorkloadDefinition registers a kind of Kubernetes custom resource as a valid OAM workload kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the workload when it is embedded in an OAM Component. 29 | properties: 30 | apiVersion: 31 | 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' 32 | type: string 33 | kind: 34 | 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' 35 | type: string 36 | metadata: 37 | type: object 38 | spec: 39 | description: A WorkloadDefinitionSpec defines the desired state of a WorkloadDefinition. 40 | properties: 41 | childResourceKinds: 42 | description: ChildResourceKinds are the list of GVK of the child resources this workload generates 43 | items: 44 | description: A ChildResourceKind defines a child Kubernetes resource kind with a selector 45 | properties: 46 | apiVersion: 47 | description: APIVersion of the child resource 48 | type: string 49 | kind: 50 | description: Kind of the child resource 51 | type: string 52 | selector: 53 | additionalProperties: 54 | type: string 55 | description: Selector to select the child resources that the workload wants to expose to traits 56 | type: object 57 | required: 58 | - apiVersion 59 | - kind 60 | type: object 61 | type: array 62 | definitionRef: 63 | description: Reference to the CustomResourceDefinition that defines this workload kind. 64 | properties: 65 | name: 66 | description: Name of the referenced CustomResourceDefinition. 67 | type: string 68 | version: 69 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 70 | type: string 71 | required: 72 | - name 73 | type: object 74 | extension: 75 | description: Extension is used for extension needs by OAM platform builders 76 | type: object 77 | 78 | podSpecPath: 79 | description: PodSpecPath indicates where/if this workload has K8s podSpec field if one workload has podSpec, trait can do lot's of assumption such as port, env, volume fields. 80 | type: string 81 | revisionLabel: 82 | description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods). 83 | type: string 84 | required: 85 | - definitionRef 86 | type: object 87 | type: object 88 | version: v1alpha2 89 | versions: 90 | - name: v1alpha2 91 | served: true 92 | storage: true 93 | status: 94 | acceptedNames: 95 | kind: "" 96 | plural: "" 97 | conditions: [] 98 | storedVersions: [] 99 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/crds/core.oam.dev_workloaddefinitions.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: workloaddefinitions.core.oam.dev 10 | spec: 11 | group: core.oam.dev 12 | names: 13 | categories: 14 | - crossplane 15 | - oam 16 | kind: WorkloadDefinition 17 | listKind: WorkloadDefinitionList 18 | plural: workloaddefinitions 19 | singular: workloaddefinition 20 | scope: Cluster 21 | versions: 22 | - additionalPrinterColumns: 23 | - jsonPath: .spec.definitionRef.name 24 | name: DEFINITION-NAME 25 | type: string 26 | name: v1alpha2 27 | schema: 28 | openAPIV3Schema: 29 | description: A WorkloadDefinition registers a kind of Kubernetes custom resource as a valid OAM workload kind by referencing its CustomResourceDefinition. The CRD is used to validate the schema of the workload when it is embedded in an OAM Component. 30 | properties: 31 | apiVersion: 32 | 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' 33 | type: string 34 | kind: 35 | 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' 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: A WorkloadDefinitionSpec defines the desired state of a WorkloadDefinition. 41 | properties: 42 | childResourceKinds: 43 | description: ChildResourceKinds are the list of GVK of the child resources this workload generates 44 | items: 45 | description: A ChildResourceKind defines a child Kubernetes resource kind with a selector 46 | properties: 47 | apiVersion: 48 | description: APIVersion of the child resource 49 | type: string 50 | kind: 51 | description: Kind of the child resource 52 | type: string 53 | selector: 54 | additionalProperties: 55 | type: string 56 | description: Selector to select the child resources that the workload wants to expose to traits 57 | type: object 58 | required: 59 | - apiVersion 60 | - kind 61 | type: object 62 | type: array 63 | definitionRef: 64 | description: Reference to the CustomResourceDefinition that defines this workload kind. 65 | properties: 66 | name: 67 | description: Name of the referenced CustomResourceDefinition. 68 | type: string 69 | version: 70 | description: Version indicate which version should be used if CRD has multiple versions by default it will use the first one if not specified 71 | type: string 72 | required: 73 | - name 74 | type: object 75 | extension: 76 | description: Extension is used for extension needs by OAM platform builders 77 | type: object 78 | x-kubernetes-preserve-unknown-fields: true 79 | podSpecPath: 80 | description: PodSpecPath indicates where/if this workload has K8s podSpec field if one workload has podSpec, trait can do lot's of assumption such as port, env, volume fields. 81 | type: string 82 | revisionLabel: 83 | description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods). 84 | type: string 85 | required: 86 | - definitionRef 87 | type: object 88 | type: object 89 | served: true 90 | storage: true 91 | subresources: {} 92 | status: 93 | acceptedNames: 94 | kind: "" 95 | plural: "" 96 | conditions: [] 97 | storedVersions: [] 98 | -------------------------------------------------------------------------------- /legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_manualscalertraits.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: manualscalertraits.core.oam.dev 10 | spec: 11 | group: core.oam.dev 12 | names: 13 | categories: 14 | - crossplane 15 | - oam 16 | kind: ManualScalerTrait 17 | listKind: ManualScalerTraitList 18 | plural: manualscalertraits 19 | singular: manualscalertrait 20 | scope: Namespaced 21 | subresources: 22 | status: {} 23 | validation: 24 | openAPIV3Schema: 25 | description: A ManualScalerTrait determines how many replicas a workload should have. 26 | properties: 27 | apiVersion: 28 | 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' 29 | type: string 30 | kind: 31 | 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' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | description: A ManualScalerTraitSpec defines the desired state of a ManualScalerTrait. 37 | properties: 38 | replicaCount: 39 | description: ReplicaCount of the workload this trait applies to. 40 | format: int32 41 | type: integer 42 | workloadRef: 43 | description: WorkloadReference to the workload this trait applies to. 44 | properties: 45 | apiVersion: 46 | description: APIVersion of the referenced object. 47 | type: string 48 | kind: 49 | description: Kind of the referenced object. 50 | type: string 51 | name: 52 | description: Name of the referenced object. 53 | type: string 54 | uid: 55 | description: UID of the referenced object. 56 | type: string 57 | required: 58 | - apiVersion 59 | - kind 60 | - name 61 | type: object 62 | required: 63 | - replicaCount 64 | - workloadRef 65 | type: object 66 | status: 67 | description: A ManualScalerTraitStatus represents the observed state of a ManualScalerTrait. 68 | properties: 69 | conditions: 70 | description: Conditions of the resource. 71 | items: 72 | description: A Condition that may apply to a resource. 73 | properties: 74 | lastTransitionTime: 75 | description: LastTransitionTime is the last time this condition transitioned from one status to another. 76 | format: date-time 77 | type: string 78 | message: 79 | description: A Message containing details about this condition's last transition from one status to another, if any. 80 | type: string 81 | reason: 82 | description: A Reason for this condition's last transition from one status to another. 83 | type: string 84 | status: 85 | description: Status of this condition; is it currently True, False, or Unknown? 86 | type: string 87 | type: 88 | description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. 89 | type: string 90 | required: 91 | - lastTransitionTime 92 | - reason 93 | - status 94 | - type 95 | type: object 96 | type: array 97 | type: object 98 | type: object 99 | version: v1alpha2 100 | versions: 101 | - name: v1alpha2 102 | served: true 103 | storage: true 104 | status: 105 | acceptedNames: 106 | kind: "" 107 | plural: "" 108 | conditions: [] 109 | storedVersions: [] 110 | -------------------------------------------------------------------------------- /charts/oam-kubernetes-runtime/crds/core.oam.dev_manualscalertraits.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.4 8 | creationTimestamp: null 9 | name: manualscalertraits.core.oam.dev 10 | spec: 11 | group: core.oam.dev 12 | names: 13 | categories: 14 | - crossplane 15 | - oam 16 | kind: ManualScalerTrait 17 | listKind: ManualScalerTraitList 18 | plural: manualscalertraits 19 | singular: manualscalertrait 20 | scope: Namespaced 21 | versions: 22 | - name: v1alpha2 23 | schema: 24 | openAPIV3Schema: 25 | description: A ManualScalerTrait determines how many replicas a workload should have. 26 | properties: 27 | apiVersion: 28 | 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' 29 | type: string 30 | kind: 31 | 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' 32 | type: string 33 | metadata: 34 | type: object 35 | spec: 36 | description: A ManualScalerTraitSpec defines the desired state of a ManualScalerTrait. 37 | properties: 38 | replicaCount: 39 | description: ReplicaCount of the workload this trait applies to. 40 | format: int32 41 | type: integer 42 | workloadRef: 43 | description: WorkloadReference to the workload this trait applies to. 44 | properties: 45 | apiVersion: 46 | description: APIVersion of the referenced object. 47 | type: string 48 | kind: 49 | description: Kind of the referenced object. 50 | type: string 51 | name: 52 | description: Name of the referenced object. 53 | type: string 54 | uid: 55 | description: UID of the referenced object. 56 | type: string 57 | required: 58 | - apiVersion 59 | - kind 60 | - name 61 | type: object 62 | required: 63 | - replicaCount 64 | - workloadRef 65 | type: object 66 | status: 67 | description: A ManualScalerTraitStatus represents the observed state of a ManualScalerTrait. 68 | properties: 69 | conditions: 70 | description: Conditions of the resource. 71 | items: 72 | description: A Condition that may apply to a resource. 73 | properties: 74 | lastTransitionTime: 75 | description: LastTransitionTime is the last time this condition transitioned from one status to another. 76 | format: date-time 77 | type: string 78 | message: 79 | description: A Message containing details about this condition's last transition from one status to another, if any. 80 | type: string 81 | reason: 82 | description: A Reason for this condition's last transition from one status to another. 83 | type: string 84 | status: 85 | description: Status of this condition; is it currently True, False, or Unknown? 86 | type: string 87 | type: 88 | description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. 89 | type: string 90 | required: 91 | - lastTransitionTime 92 | - reason 93 | - status 94 | - type 95 | type: object 96 | type: array 97 | type: object 98 | type: object 99 | served: true 100 | storage: true 101 | subresources: 102 | status: {} 103 | status: 104 | acceptedNames: 105 | kind: "" 106 | plural: "" 107 | conditions: [] 108 | storedVersions: [] 109 | -------------------------------------------------------------------------------- /cmd/oam-kubernetes-runtime/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/crossplane/crossplane-runtime/pkg/logging" 11 | "go.uber.org/zap/zapcore" 12 | "gopkg.in/natefinch/lumberjack.v2" 13 | crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 16 | ctrl "sigs.k8s.io/controller-runtime" 17 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 18 | 19 | "github.com/crossplane/oam-kubernetes-runtime/apis/core" 20 | "github.com/crossplane/oam-kubernetes-runtime/pkg/controller" 21 | appController "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2" 22 | webhook "github.com/crossplane/oam-kubernetes-runtime/pkg/webhook/v1alpha2" 23 | ) 24 | 25 | var scheme = runtime.NewScheme() 26 | 27 | func init() { 28 | _ = clientgoscheme.AddToScheme(scheme) 29 | _ = core.AddToScheme(scheme) 30 | _ = crdv1.AddToScheme(scheme) 31 | // +kubebuilder:scaffold:scheme 32 | } 33 | 34 | func main() { 35 | var metricsAddr, logFilePath, leaderElectionNamespace string 36 | var enableLeaderElection, logCompress bool 37 | var logRetainDate int 38 | var certDir string 39 | var webhookPort int 40 | var useWebhook bool 41 | var debugLogs bool 42 | var controllerArgs controller.Args 43 | 44 | flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook") 45 | flag.StringVar(&certDir, "webhook-cert-dir", "/k8s-webhook-server/serving-certs", "Admission webhook cert/key dir.") 46 | flag.IntVar(&webhookPort, "webhook-port", 9443, "admission webhook listen address") 47 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 48 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 49 | "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") 50 | flag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "", 51 | "Determines the namespace in which the leader election configmap will be created.") 52 | flag.StringVar(&logFilePath, "log-file-path", "", "The file to write logs to") 53 | flag.IntVar(&logRetainDate, "log-retain-date", 7, "The number of days of logs history to retain.") 54 | flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.") 55 | flag.BoolVar(&debugLogs, "debug-logs", false, "Enable the debug logs useful for development") 56 | flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 50, 57 | "RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.") 58 | flag.BoolVar(&controllerArgs.ApplyOnceOnly, "apply-once-only", false, 59 | "For the purpose of some production environment that workload or trait should not be affected if no spec change") 60 | flag.DurationVar(&controllerArgs.LongWait, "long-wait", 1*time.Minute, "long-wait is controller next reconcile interval time like 30s, 2m etc. The default value is 1m, "+ 61 | "you can set it to 0 for no reconcile routine after success ") 62 | flag.Parse() 63 | 64 | // setup logging 65 | var w io.Writer 66 | if len(logFilePath) > 0 { 67 | w = zapcore.AddSync(&lumberjack.Logger{ 68 | Filename: logFilePath, 69 | MaxAge: logRetainDate, // days 70 | Compress: logCompress, 71 | }) 72 | } else { 73 | w = os.Stdout 74 | } 75 | 76 | ctrl.SetLogger(zap.New(func(o *zap.Options) { 77 | o.Development = debugLogs 78 | o.DestWritter = w 79 | })) 80 | 81 | oamLog := ctrl.Log.WithName("oam-kubernetes-runtime") 82 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 83 | Scheme: scheme, 84 | MetricsBindAddress: metricsAddr, 85 | LeaderElection: enableLeaderElection, 86 | LeaderElectionID: "oam-kubernetes-runtime", 87 | LeaderElectionNamespace: leaderElectionNamespace, 88 | Port: webhookPort, 89 | CertDir: certDir, 90 | }) 91 | if err != nil { 92 | oamLog.Error(err, "unable to create a controller manager") 93 | os.Exit(1) 94 | } 95 | 96 | if useWebhook { 97 | oamLog.Info("OAM webhook enabled, will serving at :" + strconv.Itoa(webhookPort)) 98 | if err = webhook.Add(mgr); err != nil { 99 | oamLog.Error(err, "unable to setup the webhook for core controller") 100 | os.Exit(1) 101 | } 102 | 103 | } 104 | 105 | if err = appController.Setup(mgr, controllerArgs, logging.NewLogrLogger(oamLog)); err != nil { 106 | oamLog.Error(err, "unable to setup the oam core controller") 107 | os.Exit(1) 108 | } 109 | oamLog.Info("starting the controller manager") 110 | if controllerArgs.ApplyOnceOnly { 111 | oamLog.Info("applyOnceOnly is enabled that means workload or trait only apply once if no spec change even they are changed by others") 112 | } 113 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 114 | oamLog.Error(err, "problem running manager") 115 | os.Exit(1) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/applicationconfiguration/helper.go: -------------------------------------------------------------------------------- 1 | package applicationconfiguration 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | 12 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 13 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/discoverymapper" 14 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util" 15 | ) 16 | 17 | const ( 18 | errFmtGetComponent = "cannot get component %q" 19 | errFmtGetTraitDefinition = "cannot get trait definition in component %q" 20 | errFmtUnmarshalWorkload = "cannot unmarshal workload of component %q" 21 | errFmtUnmarshalTrait = "cannot unmarshal trait of component %q" 22 | errFmtGetWorkloadDefinition = "cannot get workload definition of component %q" 23 | ) 24 | 25 | // ValidatingAppConfig is used for validating ApplicationConfiguration 26 | type ValidatingAppConfig struct { 27 | appConfig v1alpha2.ApplicationConfiguration 28 | validatingComps []ValidatingComponent 29 | } 30 | 31 | // ValidatingComponent is used for validatiing ApplicationConfigurationComponent 32 | type ValidatingComponent struct { 33 | appConfigComponent v1alpha2.ApplicationConfigurationComponent 34 | 35 | // below data is convenient for validation 36 | compName string 37 | component v1alpha2.Component 38 | workloadDefinition v1alpha2.WorkloadDefinition 39 | workloadContent unstructured.Unstructured 40 | validatingTraits []ValidatingTrait 41 | } 42 | 43 | // ValidatingTrait is used for validating Trait 44 | type ValidatingTrait struct { 45 | componentTrait v1alpha2.ComponentTrait 46 | 47 | // below data is convenient for validation 48 | traitDefinition v1alpha2.TraitDefinition 49 | traitContent unstructured.Unstructured 50 | } 51 | 52 | // PrepareForValidation prepares data for validations to avoiding repetitive GET/unmarshal operations 53 | func (v *ValidatingAppConfig) PrepareForValidation(ctx context.Context, c client.Reader, dm discoverymapper.DiscoveryMapper, ac *v1alpha2.ApplicationConfiguration) error { 54 | v.appConfig = *ac 55 | v.validatingComps = make([]ValidatingComponent, 0, len(ac.Spec.Components)) 56 | for _, acc := range ac.Spec.Components { 57 | tmp := ValidatingComponent{} 58 | tmp.appConfigComponent = acc 59 | 60 | if acc.ComponentName != "" { 61 | tmp.compName = acc.ComponentName 62 | } else { 63 | tmp.compName = acc.RevisionName 64 | } 65 | comp, _, err := util.GetComponent(ctx, c, acc, ac.Namespace) 66 | if err != nil { 67 | return errors.Wrapf(err, errFmtGetComponent, tmp.compName) 68 | } 69 | tmp.component = *comp 70 | 71 | // get worload content from raw 72 | var wlContentObject map[string]interface{} 73 | if err := json.Unmarshal(comp.Spec.Workload.Raw, &wlContentObject); err != nil { 74 | return errors.Wrapf(err, errFmtUnmarshalWorkload, tmp.compName) 75 | } 76 | wl := unstructured.Unstructured{ 77 | Object: wlContentObject, 78 | } 79 | tmp.workloadContent = wl 80 | 81 | // get workload definition 82 | wlDef, err := util.FetchWorkloadDefinition(ctx, c, dm, &wl) 83 | if err != nil { 84 | return errors.Wrapf(err, errFmtGetWorkloadDefinition, tmp.compName) 85 | } 86 | tmp.workloadDefinition = *wlDef 87 | 88 | tmp.validatingTraits = make([]ValidatingTrait, 0, len(acc.Traits)) 89 | for _, t := range acc.Traits { 90 | tmpT := ValidatingTrait{} 91 | tmpT.componentTrait = t 92 | // get trait content from raw 93 | var tContentObject map[string]interface{} 94 | if err := json.Unmarshal(t.Trait.Raw, &tContentObject); err != nil { 95 | return errors.Wrapf(err, errFmtUnmarshalTrait, tmp.compName) 96 | } 97 | tContent := unstructured.Unstructured{ 98 | Object: tContentObject, 99 | } 100 | // get trait definition 101 | tDef, err := util.FetchTraitDefinition(ctx, c, dm, &tContent) 102 | if err != nil { 103 | return errors.Wrapf(err, errFmtGetTraitDefinition, tmp.compName) 104 | } 105 | tmpT.traitContent = tContent 106 | tmpT.traitDefinition = *tDef 107 | tmp.validatingTraits = append(tmp.validatingTraits, tmpT) 108 | } 109 | v.validatingComps = append(v.validatingComps, tmp) 110 | } 111 | return nil 112 | } 113 | 114 | // checkParams will check whether exist parameter assigning value to workload name 115 | func checkParams(cp []v1alpha2.ComponentParameter, cpv []v1alpha2.ComponentParameterValue) (bool, string) { 116 | targetParams := make(map[string]bool) 117 | for _, v := range cp { 118 | for _, fp := range v.FieldPaths { 119 | // only check metadata.name field parameter 120 | if strings.Contains(fp, WorkloadNamePath) { 121 | targetParams[v.Name] = true 122 | } 123 | } 124 | } 125 | for _, v := range cpv { 126 | if targetParams[v.Name] { 127 | // check fails if get parameter to overwrite workload name 128 | return false, v.Value.StrVal 129 | } 130 | } 131 | return true, "" 132 | } 133 | -------------------------------------------------------------------------------- /pkg/webhook/v1alpha2/component/validating_handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package component 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | 24 | admissionv1beta1 "k8s.io/api/admission/v1beta1" 25 | apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 26 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 | "k8s.io/apimachinery/pkg/util/validation/field" 28 | logf "sigs.k8s.io/controller-runtime/pkg/log" 29 | "sigs.k8s.io/controller-runtime/pkg/manager" 30 | "sigs.k8s.io/controller-runtime/pkg/webhook" 31 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 32 | 33 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 34 | ) 35 | 36 | // ValidatingHandler handles Component 37 | type ValidatingHandler struct { 38 | // To use the client, you need to do the following: 39 | // - uncomment it 40 | // - import sigs.k8s.io/controller-runtime/pkg/client 41 | // - uncomment the InjectClient method at the bottom of this file. 42 | // Client client.Client 43 | 44 | // Decoder decodes objects 45 | Decoder *admission.Decoder 46 | } 47 | 48 | // log is for logging in this package. 49 | var validatelog = logf.Log.WithName("component validate webhook") 50 | 51 | var _ admission.Handler = &ValidatingHandler{} 52 | 53 | // Handle handles admission requests. 54 | func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { 55 | obj := &v1alpha2.Component{} 56 | 57 | err := h.Decoder.Decode(req, obj) 58 | if err != nil { 59 | validatelog.Error(err, "decoder failed", "req operation", req.AdmissionRequest.Operation, "req", 60 | req.AdmissionRequest) 61 | return admission.Denied(err.Error()) 62 | } 63 | 64 | switch req.AdmissionRequest.Operation { //nolint:exhaustive 65 | case admissionv1beta1.Create: 66 | if allErrs := ValidateComponentObject(obj); len(allErrs) > 0 { 67 | validatelog.Info("create failed", "name", obj.Name, "errMsg", allErrs.ToAggregate().Error()) 68 | return admission.Denied(allErrs.ToAggregate().Error()) 69 | } 70 | case admissionv1beta1.Update: 71 | if allErrs := ValidateComponentObject(obj); len(allErrs) > 0 { 72 | validatelog.Info("update failed", "name", obj.Name, "errMsg", allErrs.ToAggregate().Error()) 73 | return admission.Denied(allErrs.ToAggregate().Error()) 74 | } 75 | } 76 | 77 | return admission.Allowed("") 78 | } 79 | 80 | // ValidateComponentObject validates the Component on creation 81 | func ValidateComponentObject(obj *v1alpha2.Component) field.ErrorList { 82 | validatelog.Info("validate component", "name", obj.Name) 83 | allErrs := apimachineryvalidation.ValidateObjectMeta(&obj.ObjectMeta, true, 84 | apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) 85 | fldPath := field.NewPath("spec") 86 | var content map[string]interface{} 87 | if err := json.Unmarshal(obj.Spec.Workload.Raw, &content); err != nil { 88 | allErrs = append(allErrs, field.Invalid(fldPath.Child("workload"), string(obj.Spec.Workload.Raw), 89 | "the workload is malformat")) 90 | return allErrs 91 | } 92 | if content[TypeField] != nil { 93 | allErrs = append(allErrs, field.Invalid(fldPath.Child("workload"), string(obj.Spec.Workload.Raw), 94 | "the workload contains type info")) 95 | } 96 | workload := unstructured.Unstructured{ 97 | Object: content, 98 | } 99 | if len(workload.GetAPIVersion()) == 0 || len(workload.GetKind()) == 0 { 100 | allErrs = append(allErrs, field.Invalid(fldPath.Child("workload"), content, 101 | fmt.Sprintf("the workload data missing GVK, api = %s, kind = %s,", workload.GetAPIVersion(), workload.GetKind()))) 102 | } 103 | return allErrs 104 | } 105 | 106 | /* 107 | var _ inject.Client = &ValidatingHandler{} 108 | 109 | // InjectClient injects the client into the ComponentValidatingHandler 110 | func (h *ValidatingHandler) InjectClient(c client.Client) error { 111 | h.Client = c 112 | return nil 113 | } 114 | */ 115 | var _ admission.DecoderInjector = &ValidatingHandler{} 116 | 117 | // InjectDecoder injects the decoder into the ComponentValidatingHandler 118 | func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error { 119 | h.Decoder = d 120 | return nil 121 | } 122 | 123 | // RegisterValidatingHandler will regsiter component mutation handler to the webhook 124 | func RegisterValidatingHandler(mgr manager.Manager) { 125 | server := mgr.GetWebhookServer() 126 | server.Register("/validating-core-oam-dev-v1alpha2-components", &webhook.Admission{Handler: &ValidatingHandler{}}) 127 | } 128 | -------------------------------------------------------------------------------- /examples/dependency/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisite 2 | 3 | 4 | Prepare CRD and Definitions: 5 | 6 | ```shell 7 | kubectl apply -f examples/dependency/definition.yaml 8 | ``` 9 | 10 | Make sure [`OAM runtime`](../../README.md#install-oam-runtime) was installed and started. 11 | 12 | 13 | # Case 1: Use status as output and pass through to another workload 14 | 15 | Let's see the application yaml: 16 | 17 | ```yaml 18 | apiVersion: core.oam.dev/v1alpha2 19 | kind: ApplicationConfiguration 20 | metadata: 21 | name: example-appconfig 22 | spec: 23 | components: 24 | - componentName: source 25 | dataOutputs: 26 | - name: example-key 27 | fieldPath: "status.key" 28 | - componentName: sink 29 | dataInputs: 30 | - valueFrom: 31 | dataOutputName: example-key 32 | toFieldPaths: 33 | - "spec.key" 34 | ``` 35 | 36 | In this example, we want `status.key` from `source` component and pass it as `spec.key` to `sink` component. 37 | 38 | Run this command to make this demo work: 39 | 40 | ```shell script 41 | kubectl apply -f examples/dependency/demo.yaml 42 | ``` 43 | 44 | At initial time, the workload of `source` component don't have `status.key`: 45 | 46 | ```shell script 47 | $ kubectl get foo.example.com source -o yaml 48 | apiVersion: example.com/v1 49 | kind: Foo 50 | metadata: 51 | name: source 52 | namespace: default 53 | ``` 54 | 55 | And the workload of `sink` component not exist yet. 56 | 57 | After a while, assuming the controller of this CRD will reconcile and give the status. 58 | 59 | Let manually add it by `kubectl edit foo.example.com source` 60 | 61 | ```yaml 62 | apiVersion: example.com/v1 63 | kind: Foo 64 | metadata: 65 | name: source 66 | namespace: default 67 | + status: 68 | + key: test 69 | ``` 70 | 71 | Then the dependency will meet the requirement. You should see that the `sink` workload appears and 72 | the field `spec.key` of `sink` workload has been filled: 73 | 74 | ```shell script 75 | $ kubectl get foo sink -o yaml 76 | apiVersion: example.com/v1 77 | kind: Foo 78 | metadata: 79 | name: sink 80 | namespace: default 81 | spec: 82 | key: test 83 | ``` 84 | 85 | clean resource for next case: 86 | 87 | ```shell script 88 | kubectl delete -f examples/dependency/demo.yaml 89 | kubectl delete foo --all 90 | ``` 91 | 92 | # Case 2: Use matcher to evaluate whether a resource is ready 93 | 94 | In this example, we will add matcher base on dependency. 95 | 96 | ```shell script 97 | apiVersion: core.oam.dev/v1alpha2 98 | kind: ApplicationConfiguration 99 | metadata: 100 | name: example-appconfig 101 | spec: 102 | components: 103 | - componentName: source 104 | dataOutputs: 105 | - name: example-key 106 | fieldPath: "spec.key" 107 | conditions: 108 | - op: eq 109 | value: running 110 | fieldPath: "status.state" 111 | - componentName: sink 112 | dataInputs: 113 | - valueFrom: 114 | dataOutputName: example-key 115 | toFieldPaths: 116 | - "spec.key" 117 | ``` 118 | 119 | Run this command to make this demo work: 120 | 121 | ```shell script 122 | kubectl apply -f examples/dependency/demo-with-conditions.yaml 123 | ``` 124 | 125 | 126 | You can see that we want field `spec.key` from source component which already exist. 127 | 128 | ```shell script 129 | $ kubectl get foo.example.com source -o yaml 130 | apiVersion: example.com/v1 131 | kind: Foo 132 | metadata: 133 | name: source 134 | namespace: default 135 | spec: 136 | key: test-value 137 | status: 138 | state: pending 139 | ``` 140 | 141 | But the state of status is `pending`. We hope this state to be running and only after that the dependency system 142 | can pass `spec.key` to `sink` component. So we specify a matcher for it. 143 | 144 | ```yaml 145 | matchers: 146 | - op: eq 147 | value: running 148 | fieldPath: "status.state" 149 | ``` 150 | 151 | And now `sink` workload is not created because its dataInputs is not ready. 152 | 153 | ```shell script 154 | $ kubectl get foo.example.com sink -o yaml 155 | Error from server (NotFound): foo.example.com "sink" not found 156 | ``` 157 | 158 | After a while, assuming the controller of this CRD will reconcile and make the `status.state` to be running. 159 | Let's update it manually. 160 | 161 | ```shell script 162 | $ kubectl edit foo.example.com source -o yaml 163 | apiVersion: example.com/v1 164 | kind: Foo 165 | metadata: 166 | name: source 167 | namespace: default 168 | spec: 169 | key: test-value 170 | status: 171 | - state: pending 172 | + state: running 173 | ``` 174 | 175 | Then the dependency will meet the requirement. You should see that the field "spec.key" of `sink` workload has been filled. 176 | 177 | ```shell 178 | $ kubectl get foo.example.com sink -o yaml 179 | apiVersion: example.com/v1 180 | kind: Foo 181 | metadata: 182 | name: sink 183 | namespace: default 184 | spec: 185 | key: test-value 186 | ``` 187 | 188 | clean resource for next case: 189 | 190 | ```shell script 191 | kubectl delete -f examples/dependency/demo-with-conditions.yaml 192 | kubectl delete foo --all 193 | ``` -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | This doc explains how to set up a development environment, so you can get started 4 | contributing to `oam-kubernetes-runtime` or build a PoC (Proof of Concept). 5 | 6 | ## Prerequisites 7 | 8 | 1. Golang version 1.12+ 9 | 2. Kubernetes version v1.16+ with kubectl configured 10 | 11 | ## Build 12 | 13 | The functionality of this library can be demonstrated with the following steps: 14 | 15 | * Clone this project 16 | ```console 17 | git clone git@github.com:crossplane/oam-kubernetes-runtime.git 18 | ``` 19 | 20 | * Build the library 21 | 22 | ```console 23 | $ make submodules 24 | Submodule 'build' (https://github.com/upbound/build) registered for path 'build' 25 | Cloning into '/Users/xxx/Programming/golang/src/github.com/crossplane/oam-kubernetes-runtime/build'... 26 | Submodule path 'build': checked out 'e8fb77d69aefc49dd2e9ead59da21bd719cacb78' 27 | ``` 28 | 29 | 30 | ``` 31 | $ make 32 | 12:42:21 [ .. ] verify dependencies have expected content 33 | all modules verified 34 | 12:42:21 [ OK ] go modules dependencies verified 35 | 12:42:21 [ .. ] installing golangci-lint-v1.23.8 darwin-amd64 36 | 12:42:28 [ OK ] installing golangci-lint-v1.23.8 darwin-amd64 37 | 12:42:28 [ .. ] golangci-lint 38 | 12:42:43 [ OK ] golangci-lint 39 | 12:42:44 [ .. ] go build linux_amd64 40 | 12:42:44 [ OK ] go build linux_amd64 41 | ``` 42 | 43 | ## Make your change 44 | * Generate and install CRDs to your Kubernetes cluster 45 | 46 | ```shell 47 | $ make generate 48 | 12:43:01 [ .. ] go generate darwin_amd64 49 | go: downloading sigs.k8s.io/controller-tools v0.2.4 50 | go: extracting sigs.k8s.io/controller-tools v0.2.4 51 | go: downloading github.com/spf13/cobra v0.0.5 52 | go: downloading gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 53 | go: downloading github.com/fatih/color v1.7.0 54 | go: downloading golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 55 | go: downloading github.com/gobuffalo/flect v0.1.5 56 | go: extracting github.com/gobuffalo/flect v0.1.5 57 | go: extracting github.com/fatih/color v1.7.0 58 | go: extracting gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 59 | go: downloading github.com/mattn/go-colorable v0.1.2 60 | go: downloading github.com/mattn/go-isatty v0.0.8 61 | go: extracting github.com/spf13/cobra v0.0.5 62 | go: extracting github.com/mattn/go-colorable v0.1.2 63 | go: extracting github.com/mattn/go-isatty v0.0.8 64 | go: extracting golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 65 | go: finding sigs.k8s.io/controller-tools v0.2.4 66 | go: finding github.com/spf13/cobra v0.0.5 67 | go: finding golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 68 | go: finding github.com/fatih/color v1.7.0 69 | go: finding github.com/gobuffalo/flect v0.1.5 70 | go: finding gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 71 | go: finding github.com/mattn/go-colorable v0.1.2 72 | go: finding github.com/mattn/go-isatty v0.0.8 73 | 12:43:08 [ OK ] go generate darwin_amd64 74 | ``` 75 | 76 | 77 | ``` 78 | $ kubectl apply -f charts/oam-kubernetes-runtime/crds 79 | customresourcedefinition.apiextensions.k8s.io/applicationconfigurations.core.oam.dev configured 80 | customresourcedefinition.apiextensions.k8s.io/components.core.oam.dev configured 81 | customresourcedefinition.apiextensions.k8s.io/containerizedworkloads.core.oam.dev configured 82 | customresourcedefinition.apiextensions.k8s.io/manualscalertraits.core.oam.dev configured 83 | customresourcedefinition.apiextensions.k8s.io/scopedefinitions.core.oam.dev configured 84 | customresourcedefinition.apiextensions.k8s.io/traitdefinitions.core.oam.dev configured 85 | customresourcedefinition.apiextensions.k8s.io/workloaddefinitions.core.oam.dev configured 86 | ``` 87 | 88 | * Make your code changes 89 | 90 | Make changes and run the following to test it. 91 | 92 | If you only change code under [core apis](./apis/core), remember to 93 | regenerate crd manifests as the step above. 94 | 95 | ## Run a simple and basic workflow locally 96 | You can start running OAM Kubernetes runtime to verify your changes. 97 | * Run OAM sample controller 98 | ``` 99 | go run cmd/oam-kubernetes-runtime/main.go 100 | ``` 101 | 102 | * Apply the sample application configurations or other manifests 103 | which you created for your code change verification 104 | 105 | ``` 106 | kubectl apply -f examples/containerized-workload/ 107 | ``` 108 | 109 | * Verify that corresponding CRs are emitted. 110 | 111 | You should see a `ContainerizedWorkload` looking like below 112 | ``` 113 | kubectl get containerizedworkloads.core.oam.dev 114 | NAME AGE 115 | example-appconfig-workload 12s 116 | ``` 117 | 118 | And a `Manualscalertrait` looking like below 119 | ``` 120 | kubectl get manualscalertraits.core.oam.dev 121 | NAME AGE 122 | example-appconfig-trait 54s 123 | 124 | ``` 125 | 126 | And a `HealthScope` looking like below 127 | ``` 128 | kubectl get healthscopes.core.oam.dev 129 | NAME AGE 130 | example-health-scope 23s 131 | ``` 132 | 133 | ## Prepare for Pull Request 134 | 135 | Before make a PR, please: 136 | 137 | 1) write respective unit-test 138 | 2) run `make reviewable` to generate and lint the code. 139 | 3) run `make test` to run unit-tests 140 | 141 | ## Clean up 142 | 143 | Delete all crds by `kubectl delete -f crds/` and other resources you created. 144 | -------------------------------------------------------------------------------- /apis/core/v1alpha2/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha2 18 | 19 | import ( 20 | "reflect" 21 | 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | // Package type metadata. 27 | const ( 28 | Group = "core.oam.dev" 29 | Version = "v1alpha2" 30 | ) 31 | 32 | var ( 33 | // SchemeGroupVersion is group version used to register these objects 34 | SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version} 35 | 36 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 37 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 38 | ) 39 | 40 | // WorkloadDefinition type metadata. 41 | var ( 42 | WorkloadDefinitionKind = reflect.TypeOf(WorkloadDefinition{}).Name() 43 | WorkloadDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: WorkloadDefinitionKind}.String() 44 | WorkloadDefinitionKindAPIVersion = WorkloadDefinitionKind + "." + SchemeGroupVersion.String() 45 | WorkloadDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(WorkloadDefinitionKind) 46 | ) 47 | 48 | // TraitDefinition type metadata. 49 | var ( 50 | TraitDefinitionKind = reflect.TypeOf(TraitDefinition{}).Name() 51 | TraitDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: TraitDefinitionKind}.String() 52 | TraitDefinitionKindAPIVersion = TraitDefinitionKind + "." + SchemeGroupVersion.String() 53 | TraitDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(TraitDefinitionKind) 54 | ) 55 | 56 | // ScopeDefinition type metadata. 57 | var ( 58 | ScopeDefinitionKind = reflect.TypeOf(ScopeDefinition{}).Name() 59 | ScopeDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: ScopeDefinitionKind}.String() 60 | ScopeDefinitionKindAPIVersion = ScopeDefinitionKind + "." + SchemeGroupVersion.String() 61 | ScopeDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(ScopeDefinitionKind) 62 | ) 63 | 64 | // Component type metadata. 65 | var ( 66 | ComponentKind = reflect.TypeOf(Component{}).Name() 67 | ComponentGroupKind = schema.GroupKind{Group: Group, Kind: ComponentKind}.String() 68 | ComponentKindAPIVersion = ComponentKind + "." + SchemeGroupVersion.String() 69 | ComponentGroupVersionKind = SchemeGroupVersion.WithKind(ComponentKind) 70 | ) 71 | 72 | // ApplicationConfiguration type metadata. 73 | var ( 74 | ApplicationConfigurationKind = reflect.TypeOf(ApplicationConfiguration{}).Name() 75 | ApplicationConfigurationGroupKind = schema.GroupKind{Group: Group, Kind: ApplicationConfigurationKind}.String() 76 | ApplicationConfigurationKindAPIVersion = ApplicationConfigurationKind + "." + SchemeGroupVersion.String() 77 | ApplicationConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(ApplicationConfigurationKind) 78 | ) 79 | 80 | // ContainerizedWorkload type metadata. 81 | var ( 82 | ContainerizedWorkloadKind = reflect.TypeOf(ContainerizedWorkload{}).Name() 83 | ContainerizedWorkloadGroupKind = schema.GroupKind{Group: Group, Kind: ContainerizedWorkloadKind}.String() 84 | ContainerizedWorkloadKindAPIVersion = ContainerizedWorkloadKind + "." + SchemeGroupVersion.String() 85 | ContainerizedWorkloadGroupVersionKind = SchemeGroupVersion.WithKind(ContainerizedWorkloadKind) 86 | ) 87 | 88 | // ManualScalerTrait type metadata. 89 | var ( 90 | ManualScalerTraitKind = reflect.TypeOf(ManualScalerTrait{}).Name() 91 | ManualScalerTraitGroupKind = schema.GroupKind{Group: Group, Kind: ManualScalerTraitKind}.String() 92 | ManualScalerTraitKindAPIVersion = ManualScalerTraitKind + "." + SchemeGroupVersion.String() 93 | ManualScalerTraitGroupVersionKind = SchemeGroupVersion.WithKind(ManualScalerTraitKind) 94 | ) 95 | 96 | // HealthScope type metadata. 97 | var ( 98 | HealthScopeKind = reflect.TypeOf(HealthScope{}).Name() 99 | HealthScopeGroupKind = schema.GroupKind{Group: Group, Kind: HealthScopeKind}.String() 100 | HealthScopeKindAPIVersion = HealthScopeKind + "." + SchemeGroupVersion.String() 101 | HealthScopeGroupVersionKind = SchemeGroupVersion.WithKind(HealthScopeKind) 102 | ) 103 | 104 | func init() { 105 | SchemeBuilder.Register(&WorkloadDefinition{}, &WorkloadDefinitionList{}) 106 | SchemeBuilder.Register(&TraitDefinition{}, &TraitDefinitionList{}) 107 | SchemeBuilder.Register(&ScopeDefinition{}, &ScopeDefinitionList{}) 108 | SchemeBuilder.Register(&Component{}, &ComponentList{}) 109 | SchemeBuilder.Register(&ApplicationConfiguration{}, &ApplicationConfigurationList{}) 110 | SchemeBuilder.Register(&ContainerizedWorkload{}, &ContainerizedWorkloadList{}) 111 | SchemeBuilder.Register(&ManualScalerTrait{}, &ManualScalerTraitList{}) 112 | SchemeBuilder.Register(&HealthScope{}, &HealthScopeList{}) 113 | } 114 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller_helper.go: -------------------------------------------------------------------------------- 1 | package containerizedworkload 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | apierrors "k8s.io/apimachinery/pkg/api/errors" 10 | "k8s.io/apimachinery/pkg/types" 11 | ctrl "sigs.k8s.io/controller-runtime" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 15 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 16 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util" 17 | ) 18 | 19 | // create a corresponding deployment 20 | func (r *Reconciler) renderDeployment(ctx context.Context, 21 | workload *v1alpha2.ContainerizedWorkload) (*appsv1.Deployment, error) { 22 | 23 | resources, err := TranslateContainerWorkload(ctx, workload) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | deploy, ok := resources[0].(*appsv1.Deployment) 29 | if !ok { 30 | return nil, fmt.Errorf("internal error, deployment is not rendered correctly") 31 | } 32 | // make sure we don't have opinion on the replica count 33 | deploy.Spec.Replicas = nil 34 | // k8s server-side patch complains if the protocol is not set 35 | for i := 0; i < len(deploy.Spec.Template.Spec.Containers); i++ { 36 | for j := 0; j < len(deploy.Spec.Template.Spec.Containers[i].Ports); j++ { 37 | if len(deploy.Spec.Template.Spec.Containers[i].Ports[j].Protocol) == 0 { 38 | deploy.Spec.Template.Spec.Containers[i].Ports[j].Protocol = corev1.ProtocolTCP 39 | } 40 | } 41 | } 42 | r.log.Info("rendered a deployment", "deploy", deploy.Spec.Template.Spec) 43 | 44 | // set the controller reference so that we can watch this deployment and it will be deleted automatically 45 | if err := ctrl.SetControllerReference(workload, deploy, r.Scheme); err != nil { 46 | return nil, err 47 | } 48 | 49 | return deploy, nil 50 | } 51 | 52 | // create a service for the deployment 53 | func (r *Reconciler) renderService(ctx context.Context, 54 | workload *v1alpha2.ContainerizedWorkload, deploy *appsv1.Deployment) (*corev1.Service, error) { 55 | // create a service for the workload 56 | resources, err := ServiceInjector(ctx, workload, []oam.Object{deploy}) 57 | if err != nil { 58 | return nil, err 59 | } 60 | service, ok := resources[1].(*corev1.Service) 61 | if !ok { 62 | return nil, fmt.Errorf("internal error, service is not rendered correctly") 63 | } 64 | // the service injector lib doesn't set the namespace and serviceType 65 | service.Namespace = workload.Namespace 66 | service.Spec.Type = corev1.ServiceTypeClusterIP 67 | // k8s server-side patch complains if the protocol is not set 68 | for i := 0; i < len(service.Spec.Ports); i++ { 69 | service.Spec.Ports[i].Protocol = corev1.ProtocolTCP 70 | } 71 | // always set the controller reference so that we can watch this service and 72 | if err := ctrl.SetControllerReference(workload, service, r.Scheme); err != nil { 73 | return nil, err 74 | } 75 | return service, nil 76 | } 77 | 78 | // create ConfigMaps for ContainerConfigFiles 79 | func (r *Reconciler) renderConfigMaps(ctx context.Context, 80 | workload *v1alpha2.ContainerizedWorkload, deploy *appsv1.Deployment) ([]*corev1.ConfigMap, error) { 81 | configMaps, err := TranslateConfigMaps(ctx, workload) 82 | if err != nil { 83 | return nil, err 84 | } 85 | for _, cm := range configMaps { 86 | // always set the controller reference so that we can watch this configmap and it will be deleted automatically 87 | if err := ctrl.SetControllerReference(deploy, cm, r.Scheme); err != nil { 88 | return nil, err 89 | } 90 | } 91 | return configMaps, nil 92 | } 93 | 94 | // delete deployments/services that are not the same as the existing 95 | // nolint:gocyclo 96 | func (r *Reconciler) cleanupResources(ctx context.Context, 97 | workload *v1alpha2.ContainerizedWorkload, deployUID, serviceUID *types.UID) error { 98 | log := r.log.WithValues("gc deployment", workload.Name) 99 | var deploy appsv1.Deployment 100 | var service corev1.Service 101 | for _, res := range workload.Status.Resources { 102 | uid := res.UID 103 | if res.Kind == util.KindDeployment && res.APIVersion == appsv1.SchemeGroupVersion.String() { 104 | if uid != *deployUID { 105 | log.Info("Found an orphaned deployment", "deployment UID", *deployUID, "orphaned UID", uid) 106 | dn := client.ObjectKey{Name: res.Name, Namespace: workload.Namespace} 107 | if err := r.Get(ctx, dn, &deploy); err != nil { 108 | if apierrors.IsNotFound(err) { 109 | continue 110 | } 111 | return err 112 | } 113 | if err := r.Delete(ctx, &deploy); err != nil { 114 | return err 115 | } 116 | log.Info("Removed an orphaned deployment", "deployment UID", *deployUID, "orphaned UID", uid) 117 | } 118 | } else if res.Kind == util.KindService && res.APIVersion == corev1.SchemeGroupVersion.String() { 119 | if uid != *serviceUID { 120 | log.Info("Found an orphaned service", "orphaned UID", uid) 121 | sn := client.ObjectKey{Name: res.Name, Namespace: workload.Namespace} 122 | if err := r.Get(ctx, sn, &service); err != nil { 123 | if apierrors.IsNotFound(err) { 124 | continue 125 | } 126 | return err 127 | } 128 | if err := r.Delete(ctx, &service); err != nil { 129 | return err 130 | } 131 | log.Info("Removed an orphaned service", "orphaned UID", uid) 132 | } 133 | } 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /pkg/oam/discoverymapper/suit_test.go: -------------------------------------------------------------------------------- 1 | package discoverymapper 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | 11 | crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 12 | "k8s.io/apimachinery/pkg/api/meta" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | "k8s.io/apimachinery/pkg/runtime/schema" 16 | "k8s.io/client-go/rest" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | "sigs.k8s.io/controller-runtime/pkg/envtest" 19 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 20 | // +kubebuilder:scaffold:imports 21 | ) 22 | 23 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 24 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 25 | 26 | var cfg *rest.Config 27 | var k8sClient client.Client 28 | var testEnv *envtest.Environment 29 | var scheme = runtime.NewScheme() 30 | 31 | func TestMapper(t *testing.T) { 32 | RegisterFailHandler(Fail) 33 | 34 | RunSpecsWithDefaultAndCustomReporters(t, 35 | "Test Mapper Suite", 36 | []Reporter{printer.NewlineReporter{}}) 37 | } 38 | 39 | var _ = BeforeSuite(func(done Done) { 40 | By("Bootstrapping test environment") 41 | testEnv = &envtest.Environment{} 42 | var err error 43 | cfg, err = testEnv.Start() 44 | Expect(err).ToNot(HaveOccurred()) 45 | Expect(cfg).ToNot(BeNil()) 46 | 47 | Expect(crdv1.AddToScheme(scheme)).Should(BeNil()) 48 | // +kubebuilder:scaffold:scheme 49 | By("Create the k8s client") 50 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 51 | Expect(err).ToNot(HaveOccurred()) 52 | Expect(k8sClient).ToNot(BeNil()) 53 | 54 | close(done) 55 | }, 60) 56 | 57 | var _ = AfterSuite(func() { 58 | By("Tearing down the test environment") 59 | err := testEnv.Stop() 60 | Expect(err).ToNot(HaveOccurred()) 61 | }) 62 | 63 | var _ = Describe("Mapper discovery resources", func() { 64 | 65 | It("discovery built-in k8s resource", func() { 66 | dism, err := New(cfg) 67 | Expect(err).Should(BeNil()) 68 | mapper, err := dism.GetMapper() 69 | Expect(err).Should(BeNil()) 70 | mapping, err := mapper.RESTMapping(schema.GroupKind{Group: "apps", Kind: "Deployment"}, "v1") 71 | Expect(err).Should(BeNil()) 72 | Expect(mapping.Resource).Should(Equal(schema.GroupVersionResource{ 73 | Group: "apps", 74 | Version: "v1", 75 | Resource: "deployments", 76 | })) 77 | }) 78 | 79 | It("discovery CRD", func() { 80 | 81 | By("Check built-in resource") 82 | dism, err := New(cfg) 83 | Expect(err).Should(BeNil()) 84 | mapper, err := dism.GetMapper() 85 | Expect(err).Should(BeNil()) 86 | var mapping *meta.RESTMapping 87 | mapping, err = mapper.RESTMapping(schema.GroupKind{Group: "", Kind: "Pod"}, "v1") 88 | Expect(err).Should(BeNil()) 89 | Expect(mapping.Resource).Should(Equal(schema.GroupVersionResource{ 90 | Group: "", 91 | Version: "v1", 92 | Resource: "pods", 93 | })) 94 | 95 | By("CRD should be discovered after refresh") 96 | crd := crdv1.CustomResourceDefinition{ 97 | ObjectMeta: metav1.ObjectMeta{ 98 | Name: "foos.example.com", 99 | Labels: map[string]string{"crd": "dependency"}, 100 | }, 101 | Spec: crdv1.CustomResourceDefinitionSpec{ 102 | Group: "example.com", 103 | Names: crdv1.CustomResourceDefinitionNames{ 104 | Kind: "Foo", 105 | Plural: "foos", 106 | }, 107 | Versions: []crdv1.CustomResourceDefinitionVersion{{ 108 | Name: "v1", 109 | Served: true, 110 | Storage: true, 111 | Schema: &crdv1.CustomResourceValidation{ 112 | OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 113 | Type: "object", 114 | }}, 115 | }, { 116 | Name: "v1beta1", 117 | Served: true, 118 | Schema: &crdv1.CustomResourceValidation{ 119 | OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 120 | Type: "object", 121 | }}, 122 | }}, 123 | Scope: crdv1.NamespaceScoped, 124 | }, 125 | } 126 | Expect(k8sClient.Create(context.Background(), &crd)).Should(BeNil()) 127 | updatedCrdObj := crdv1.CustomResourceDefinition{} 128 | Eventually(func() bool { 129 | if err := k8sClient.Get(context.Background(), 130 | client.ObjectKey{Name: "foos.example.com"}, &updatedCrdObj); err != nil { 131 | return false 132 | } 133 | return len(updatedCrdObj.Spec.Versions) == 2 134 | }, 3*time.Second, time.Second).Should(BeTrue()) 135 | 136 | Eventually(func() error { 137 | mapping, err = dism.RESTMapping(schema.GroupKind{Group: "example.com", Kind: "Foo"}, "v1") 138 | return err 139 | }, time.Second*2, time.Millisecond*300).Should(BeNil()) 140 | Expect(mapping.Resource).Should(Equal(schema.GroupVersionResource{ 141 | Group: "example.com", 142 | Version: "v1", 143 | Resource: "foos", 144 | })) 145 | 146 | var kinds []schema.GroupVersionKind 147 | Eventually(func() error { 148 | kinds, err = dism.KindsFor(schema.GroupVersionResource{Group: "example.com", Version: "", Resource: "foos"}) 149 | return err 150 | }, time.Second*10, time.Millisecond*300).Should(BeNil()) 151 | Expect(kinds).Should(Equal([]schema.GroupVersionKind{ 152 | {Group: "example.com", Version: "v1", Kind: "Foo"}, 153 | {Group: "example.com", Version: "v1beta1", Kind: "Foo"}, 154 | })) 155 | kinds, err = dism.KindsFor(schema.GroupVersionResource{Group: "example.com", Version: "v1", Resource: "foos"}) 156 | Expect(err).Should(BeNil()) 157 | Expect(kinds).Should(Equal([]schema.GroupVersionKind{{Group: "example.com", Version: "v1", Kind: "Foo"}})) 158 | }) 159 | }) 160 | -------------------------------------------------------------------------------- /pkg/oam/mock/mocks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Crossplane Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package mock provides fake OAM resources for use in tests. 18 | package mock 19 | 20 | import ( 21 | "encoding/json" 22 | "reflect" 23 | 24 | "k8s.io/client-go/rest" 25 | 26 | corev1 "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/runtime/schema" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | "sigs.k8s.io/controller-runtime/pkg/manager" 32 | 33 | "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" 34 | ) 35 | 36 | // Conditioned is a mock that implements Conditioned interface. 37 | type Conditioned struct{ Conditions []v1alpha1.Condition } 38 | 39 | // SetConditions sets the Conditions. 40 | func (m *Conditioned) SetConditions(c ...v1alpha1.Condition) { m.Conditions = c } 41 | 42 | // GetCondition get the Condition with the given ConditionType. 43 | func (m *Conditioned) GetCondition(ct v1alpha1.ConditionType) v1alpha1.Condition { 44 | return v1alpha1.Condition{Type: ct, Status: corev1.ConditionUnknown} 45 | } 46 | 47 | // ManagedResourceReferencer is a mock that implements ManagedResourceReferencer interface. 48 | type ManagedResourceReferencer struct{ Ref *corev1.ObjectReference } 49 | 50 | // SetResourceReference sets the ResourceReference. 51 | func (m *ManagedResourceReferencer) SetResourceReference(r *corev1.ObjectReference) { m.Ref = r } 52 | 53 | // GetResourceReference gets the ResourceReference. 54 | func (m *ManagedResourceReferencer) GetResourceReference() *corev1.ObjectReference { return m.Ref } 55 | 56 | // A WorkloadReferencer references an OAM Workload type. 57 | type WorkloadReferencer struct{ Ref v1alpha1.TypedReference } 58 | 59 | // GetWorkloadReference gets the WorkloadReference. 60 | func (w *WorkloadReferencer) GetWorkloadReference() v1alpha1.TypedReference { 61 | return w.Ref 62 | } 63 | 64 | // SetWorkloadReference sets the WorkloadReference. 65 | func (w *WorkloadReferencer) SetWorkloadReference(r v1alpha1.TypedReference) { 66 | w.Ref = r 67 | } 68 | 69 | // Object is a mock that implements Object interface. 70 | type Object struct { 71 | metav1.ObjectMeta 72 | runtime.Object 73 | } 74 | 75 | // GetObjectKind returns schema.ObjectKind. 76 | func (o *Object) GetObjectKind() schema.ObjectKind { 77 | return schema.EmptyObjectKind 78 | } 79 | 80 | // DeepCopyObject returns a copy of the object as runtime.Object 81 | func (o *Object) DeepCopyObject() runtime.Object { 82 | out := &Object{} 83 | j, err := json.Marshal(o) 84 | if err != nil { 85 | panic(err) 86 | } 87 | _ = json.Unmarshal(j, out) 88 | return out 89 | } 90 | 91 | // Trait is a mock that implements Trait interface. 92 | type Trait struct { 93 | metav1.ObjectMeta 94 | runtime.Object 95 | v1alpha1.ConditionedStatus 96 | WorkloadReferencer 97 | } 98 | 99 | // GetObjectKind returns schema.ObjectKind. 100 | func (t *Trait) GetObjectKind() schema.ObjectKind { 101 | return schema.EmptyObjectKind 102 | } 103 | 104 | // DeepCopyObject returns a copy of the object as runtime.Object 105 | func (t *Trait) DeepCopyObject() runtime.Object { 106 | out := &Trait{} 107 | j, err := json.Marshal(t) 108 | if err != nil { 109 | panic(err) 110 | } 111 | _ = json.Unmarshal(j, out) 112 | return out 113 | } 114 | 115 | // Workload is a mock that implements Workload interface. 116 | type Workload struct { 117 | metav1.ObjectMeta 118 | runtime.Object 119 | v1alpha1.ConditionedStatus 120 | } 121 | 122 | // GetObjectKind returns schema.ObjectKind. 123 | func (w *Workload) GetObjectKind() schema.ObjectKind { 124 | return schema.EmptyObjectKind 125 | } 126 | 127 | // DeepCopyObject returns a copy of the object as runtime.Object 128 | func (w *Workload) DeepCopyObject() runtime.Object { 129 | out := &Workload{} 130 | j, err := json.Marshal(w) 131 | if err != nil { 132 | panic(err) 133 | } 134 | _ = json.Unmarshal(j, out) 135 | return out 136 | } 137 | 138 | // Manager is a mock object that satisfies manager.Manager interface. 139 | type Manager struct { 140 | manager.Manager 141 | 142 | Client client.Client 143 | Scheme *runtime.Scheme 144 | } 145 | 146 | // GetClient returns the client. 147 | func (m *Manager) GetClient() client.Client { return m.Client } 148 | 149 | // GetScheme returns the scheme. 150 | func (m *Manager) GetScheme() *runtime.Scheme { return m.Scheme } 151 | 152 | // GetConfig returns the config for test. 153 | func (m *Manager) GetConfig() *rest.Config { 154 | return &rest.Config{} 155 | } 156 | 157 | // GV returns a mock schema.GroupVersion. 158 | var GV = schema.GroupVersion{Group: "g", Version: "v"} 159 | 160 | // GVK returns the mock GVK of the given object. 161 | func GVK(o runtime.Object) schema.GroupVersionKind { 162 | return GV.WithKind(reflect.TypeOf(o).Elem().Name()) 163 | } 164 | 165 | // SchemeWith returns a scheme with list of `runtime.Object`s registered. 166 | func SchemeWith(o ...runtime.Object) *runtime.Scheme { 167 | s := runtime.NewScheme() 168 | s.AddKnownTypes(GV, o...) 169 | return s 170 | } 171 | -------------------------------------------------------------------------------- /pkg/controller/v1alpha2/applicationconfiguration/appconfig_suit_test.go: -------------------------------------------------------------------------------- 1 | package applicationconfiguration 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 17 | 18 | "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 19 | "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util" 20 | ) 21 | 22 | var _ = Describe("CRD without definition can run in an ApplicationConfiguration", func() { 23 | ctx := context.Background() 24 | It("run workload and trait without CRD", func() { 25 | 26 | By("Creating CRD foo.crdtest1.com") 27 | // Create a crd for appconfig dependency test 28 | crd = crdv1.CustomResourceDefinition{ 29 | ObjectMeta: metav1.ObjectMeta{ 30 | Name: "foo.crdtest1.com", 31 | Labels: map[string]string{"crd": "dependency"}, 32 | }, 33 | Spec: crdv1.CustomResourceDefinitionSpec{ 34 | Group: "crdtest1.com", 35 | Names: crdv1.CustomResourceDefinitionNames{ 36 | Kind: "Foo", 37 | ListKind: "FooList", 38 | Plural: "foo", 39 | Singular: "foo", 40 | }, 41 | Versions: []crdv1.CustomResourceDefinitionVersion{{ 42 | Name: "v1", 43 | Served: true, 44 | Storage: true, 45 | Schema: &crdv1.CustomResourceValidation{ 46 | OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 47 | Type: "object", 48 | Properties: map[string]crdv1.JSONSchemaProps{ 49 | "spec": { 50 | Type: "object", 51 | Properties: map[string]crdv1.JSONSchemaProps{ 52 | "key": {Type: "string"}, 53 | }}}}}}, 54 | }, 55 | Scope: crdv1.NamespaceScoped, 56 | }, 57 | } 58 | Expect(k8sClient.Create(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 59 | 60 | By("Creating namespace trait-no-def-test") 61 | namespace := "trait-no-def-test" 62 | var ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} 63 | Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 64 | 65 | By("creating component using workload by foo.crdtest1.com without definition") 66 | tempFoo := &unstructured.Unstructured{} 67 | tempFoo.SetAPIVersion("crdtest1.com/v1") 68 | tempFoo.SetKind("Foo") 69 | tempFoo.SetNamespace(namespace) 70 | // Define a workload 71 | wl := tempFoo.DeepCopy() 72 | // Set Name so we can get easily 73 | wlname := "test-workload" 74 | wl.SetName(wlname) 75 | // Create a component 76 | componentName := "component" 77 | comp := v1alpha2.Component{ 78 | ObjectMeta: metav1.ObjectMeta{ 79 | Name: componentName, 80 | Namespace: namespace, 81 | }, 82 | Spec: v1alpha2.ComponentSpec{ 83 | Workload: runtime.RawExtension{ 84 | Object: wl, 85 | }, 86 | }, 87 | } 88 | Expect(k8sClient.Create(ctx, &comp)).Should(BeNil()) 89 | 90 | By("Create application configuration with trait using foo.crdtest1.com without definition") 91 | tr := tempFoo.DeepCopy() 92 | // Set Name so we can get easily 93 | trname := "test-trait" 94 | tr.SetName(trname) 95 | appConfigName := "appconfig-trait-no-def" 96 | appConfig := v1alpha2.ApplicationConfiguration{ 97 | ObjectMeta: metav1.ObjectMeta{ 98 | Name: appConfigName, 99 | Namespace: namespace, 100 | }, 101 | Spec: v1alpha2.ApplicationConfigurationSpec{ 102 | Components: []v1alpha2.ApplicationConfigurationComponent{{ 103 | ComponentName: componentName, 104 | Traits: []v1alpha2.ComponentTrait{{ 105 | Trait: runtime.RawExtension{ 106 | Object: tr, 107 | }}}, 108 | }}}, 109 | } 110 | By("Creating application config") 111 | Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) 112 | 113 | By("Reconcile") 114 | appconfigKey := client.ObjectKey{ 115 | Name: appConfigName, 116 | Namespace: namespace, 117 | } 118 | req := reconcile.Request{NamespacedName: appconfigKey} 119 | Expect(func() error { _, err := reconciler.Reconcile(req); return err }()).Should(BeNil()) 120 | 121 | By("Checking that workload should be created") 122 | workloadKey := client.ObjectKey{ 123 | Name: wlname, 124 | Namespace: namespace, 125 | } 126 | workloadFoo := tempFoo.DeepCopy() 127 | Eventually(func() error { 128 | err := k8sClient.Get(ctx, workloadKey, workloadFoo) 129 | if err != nil { 130 | // Try 3 (= 1s/300ms) times 131 | reconciler.Reconcile(req) 132 | } 133 | return err 134 | }, time.Second, 300*time.Millisecond).Should(BeNil()) 135 | 136 | By("Checking that trait should be created") 137 | traitKey := client.ObjectKey{ 138 | Name: trname, 139 | Namespace: namespace, 140 | } 141 | traitFoo := tempFoo.DeepCopy() 142 | Eventually(func() error { 143 | err := k8sClient.Get(ctx, traitKey, traitFoo) 144 | if err != nil { 145 | // Try 3 (= 1s/300ms) times 146 | reconciler.Reconcile(req) 147 | } 148 | return err 149 | }, time.Second, 300*time.Millisecond).Should(BeNil()) 150 | 151 | By("Checking the application status has right warning message") 152 | Expect(func() string { 153 | err := k8sClient.Get(ctx, appconfigKey, &appConfig) 154 | if err != nil { 155 | return "" 156 | } 157 | return appConfig.Status.Workloads[0].Traits[0].Message 158 | }()).Should(Equal(util.DummyTraitMessage)) 159 | }) 160 | 161 | }) 162 | --------------------------------------------------------------------------------