├── .dockerignore
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.dev
├── Gemfile
├── Gemfile.lock
├── Gopkg.lock
├── Gopkg.toml
├── LICENSE
├── Makefile
├── README.md
├── apis
└── metacontroller
│ └── v1alpha1
│ ├── doc.go
│ ├── register.go
│ ├── roundtrip_test.go
│ ├── types.go
│ └── zz_generated.deepcopy.go
├── client
└── generated
│ ├── clientset
│ └── internalclientset
│ │ ├── clientset.go
│ │ ├── doc.go
│ │ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ │ └── typed
│ │ └── metacontroller
│ │ └── v1alpha1
│ │ ├── compositecontroller.go
│ │ ├── controllerrevision.go
│ │ ├── controllerrevision_expansion.go
│ │ ├── decoratorcontroller.go
│ │ ├── doc.go
│ │ ├── generated_expansion.go
│ │ └── metacontroller_client.go
│ ├── informer
│ └── externalversions
│ │ ├── factory.go
│ │ ├── generic.go
│ │ ├── internalinterfaces
│ │ └── factory_interfaces.go
│ │ └── metacontroller
│ │ ├── interface.go
│ │ └── v1alpha1
│ │ ├── compositecontroller.go
│ │ ├── controllerrevision.go
│ │ ├── decoratorcontroller.go
│ │ └── interface.go
│ └── lister
│ └── metacontroller
│ └── v1alpha1
│ ├── compositecontroller.go
│ ├── controllerrevision.go
│ ├── decoratorcontroller.go
│ └── expansion_generated.go
├── code-of-conduct.md
├── controller
├── common
│ ├── common.go
│ ├── finalizer
│ │ └── finalizer.go
│ ├── manage_children.go
│ └── manage_children_test.go
├── composite
│ ├── controller.go
│ ├── controller_revision.go
│ ├── hooks.go
│ ├── metacontroller.go
│ └── rolling_update.go
└── decorator
│ ├── controller.go
│ ├── hooks.go
│ ├── metacontroller.go
│ └── selector.go
├── docs
├── .gitignore
├── 404.html
├── _api
│ ├── apply.md
│ ├── compositecontroller.md
│ ├── controllerrevision.md
│ ├── decoratorcontroller.md
│ └── hook.md
├── _config.yml
├── _contrib
│ └── build.md
├── _data
│ └── navigation.yml
├── _design
│ ├── customize-hook.md
│ └── map-controller.md
├── _guide
│ ├── best-practices.md
│ ├── create.md
│ ├── install.md
│ └── troubleshooting.md
├── _includes
│ └── footer.html
├── _redirects
├── api.md
├── assets
│ └── css
│ │ └── main.scss
├── concepts.md
├── contrib.md
├── design.md
├── examples.md
├── faq.md
├── features.md
├── go-import.html
├── guide.md
├── intro.md
└── pronunciation.md
├── dynamic
├── apply
│ ├── apply.go
│ └── apply_test.go
├── clientset
│ └── clientset.go
├── controllerref
│ ├── controller_ref.go
│ ├── controller_revision.go
│ └── unstructured.go
├── discovery
│ └── discovery.go
├── informer
│ ├── factory.go
│ └── informer.go
├── lister
│ └── lister.go
└── object
│ ├── metadata.go
│ ├── metadata_test.go
│ └── status.go
├── examples
├── bluegreen
│ ├── README.md
│ ├── bluegreen-controller.yaml
│ ├── my-bluegreen.yaml
│ ├── sync.js
│ └── test.sh
├── catset
│ ├── README.md
│ ├── catset-controller.yaml
│ ├── my-catset.yaml
│ ├── sync.js
│ └── test.sh
├── clusteredparent
│ ├── README.md
│ ├── cluster-parent.yaml
│ ├── my-clusterrole.yaml
│ ├── sync.py
│ └── test.sh
├── crd-roles
│ ├── README.md
│ ├── crd-role-controller.yaml
│ ├── my-crd.yaml
│ ├── sync.py
│ └── test.sh
├── daemonjob
│ ├── README.md
│ ├── daemonjob-controller.yaml
│ ├── my-daemonjob.yaml
│ ├── sync.py
│ └── test.sh
├── go
│ ├── .gitignore
│ ├── Dockerfile
│ ├── Gopkg.lock
│ ├── Gopkg.toml
│ ├── README.md
│ ├── main.go
│ ├── my-thing.yaml
│ └── thing-controller.yaml
├── indexedjob
│ ├── README.md
│ ├── indexedjob-controller.yaml
│ ├── my-indexedjob.yaml
│ ├── sync.py
│ └── test.sh
├── jsonnetd
│ ├── .gitignore
│ ├── Dockerfile
│ ├── Gopkg.lock
│ ├── Gopkg.toml
│ ├── Makefile
│ ├── README.md
│ ├── extensions.go
│ └── main.go
├── nodejs
│ ├── Dockerfile
│ ├── Makefile
│ ├── README.md
│ └── server.js
├── service-per-pod
│ ├── README.md
│ ├── hooks
│ │ ├── finalize-service-per-pod.jsonnet
│ │ ├── sync-pod-name-label.jsonnet
│ │ └── sync-service-per-pod.jsonnet
│ ├── my-statefulset.yaml
│ ├── service-per-pod.yaml
│ └── test.sh
├── status
│ ├── README.md
│ ├── my-noop.yaml
│ ├── noop-controller.yaml
│ ├── sync.js
│ └── test.sh
├── test.sh
└── vitess
│ └── README.md
├── hack
└── get-kube-binaries.sh
├── hooks
├── hooks.go
└── webhook.go
├── kustomization.yaml
├── main.go
├── manifests
├── dev
│ ├── args.yaml
│ ├── image.yaml
│ └── kustomization.yaml
├── metacontroller-namespace.yaml
├── metacontroller-rbac.yaml
└── metacontroller.yaml
├── netlify.toml
├── server
└── server.go
├── skaffold.yaml
├── test
└── integration
│ ├── composite
│ └── composite_test.go
│ ├── decorator
│ └── decorator_test.go
│ └── framework
│ ├── apiserver.go
│ ├── crd.go
│ ├── etcd.go
│ ├── fixture.go
│ ├── main.go
│ ├── metacontroller.go
│ └── webhook.go
└── third_party
└── kubernetes
├── LICENSE
├── controller.go
├── controller_ref_manager.go
├── pointer.go
└── unstructured.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Ignoring things that aren't needed to build the Docker image
2 | # helps Skaffold know when not to rebuild the image.
3 | docs
4 | examples
5 | manifests
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /vendor/
3 | /metacontroller
4 | /hack/bin/
5 | .*.swp
6 | .history
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - "1.11.x"
4 | go_import_path: metacontroller.app
5 | cache:
6 | directories:
7 | - vendor
8 | addons:
9 | apt:
10 | packages:
11 | - wget
12 | install:
13 | - hack/get-kube-binaries.sh
14 | - go get -u github.com/golang/dep/cmd/dep
15 | - dep ensure
16 | script:
17 | # We intentionally don't generate files in CI, since we check them in.
18 | - go install
19 | - make unit-test
20 | - make integration-test
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code Reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Contributor Guide
26 |
27 | Aside from the above administrative requirements, you can find more
28 | technical details about project internals in the
29 | [contributor guide](https://metacontroller.app/contrib/).
30 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.10 AS build
2 |
3 | RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
4 |
5 | COPY . /go/src/metacontroller.app/
6 | WORKDIR /go/src/metacontroller.app/
7 | RUN dep ensure && go install
8 |
9 | FROM debian:stretch-slim
10 | RUN apt-get update && apt-get install --no-install-recommends -y ca-certificates && rm -rf /var/lib/apt/lists/*
11 | COPY --from=build /go/bin/metacontroller.app /usr/bin/metacontroller
12 | CMD ["/usr/bin/metacontroller"]
13 |
--------------------------------------------------------------------------------
/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # This is the same as Dockerfile, but skips `dep ensure`.
2 | # It assumes you already ran that locally.
3 | FROM golang:1.10 AS build
4 |
5 | COPY . /go/src/metacontroller.app/
6 | WORKDIR /go/src/metacontroller.app/
7 | RUN go install
8 |
9 | FROM debian:stretch-slim
10 | RUN apt-get update && apt-get install --no-install-recommends -y ca-certificates && rm -rf /var/lib/apt/lists/*
11 | COPY --from=build /go/bin/metacontroller.app /usr/bin/metacontroller
12 | CMD ["/usr/bin/metacontroller"]
13 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Hello! This is where you manage which Jekyll version is used to run.
4 | # When you want to use a different version, change it below, save the
5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
6 | #
7 | # bundle exec jekyll serve
8 | #
9 | # This will help ensure the proper Jekyll version is running.
10 | # Happy Jekylling!
11 | gem "jekyll", "~> 3.7.3"
12 |
13 | gem "minimal-mistakes-jekyll"
14 |
15 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
16 | # uncomment the line below. To upgrade, run `bundle update github-pages`.
17 | #gem "github-pages", group: :jekyll_plugins
18 |
19 | # If you have any plugins, put them here!
20 | group :jekyll_plugins do
21 | gem "jekyll-feed", "~> 0.6"
22 | gem "jekyll-data"
23 | end
24 |
25 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
26 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
27 |
28 | # Performance-booster for watching directories on Windows
29 | gem "wdm", "~> 0.1.0" if Gem.win_platform?
30 |
31 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | activesupport (4.2.10)
5 | i18n (~> 0.7)
6 | minitest (~> 5.1)
7 | thread_safe (~> 0.3, >= 0.3.4)
8 | tzinfo (~> 1.1)
9 | addressable (2.5.2)
10 | public_suffix (>= 2.0.2, < 4.0)
11 | colorator (1.1.0)
12 | concurrent-ruby (1.0.5)
13 | em-websocket (0.5.1)
14 | eventmachine (>= 0.12.9)
15 | http_parser.rb (~> 0.6.0)
16 | eventmachine (1.2.5)
17 | faraday (0.14.0)
18 | multipart-post (>= 1.2, < 3)
19 | ffi (1.9.23)
20 | forwardable-extended (2.6.0)
21 | gemoji (3.0.0)
22 | html-pipeline (2.7.1)
23 | activesupport (>= 2)
24 | nokogiri (>= 1.4)
25 | http_parser.rb (0.6.0)
26 | i18n (0.9.5)
27 | concurrent-ruby (~> 1.0)
28 | jekyll (3.7.3)
29 | addressable (~> 2.4)
30 | colorator (~> 1.0)
31 | em-websocket (~> 0.5)
32 | i18n (~> 0.7)
33 | jekyll-sass-converter (~> 1.0)
34 | jekyll-watch (~> 2.0)
35 | kramdown (~> 1.14)
36 | liquid (~> 4.0)
37 | mercenary (~> 0.3.3)
38 | pathutil (~> 0.9)
39 | rouge (>= 1.7, < 4)
40 | safe_yaml (~> 1.0)
41 | jekyll-data (1.0.0)
42 | jekyll (~> 3.3)
43 | jekyll-feed (0.9.3)
44 | jekyll (~> 3.3)
45 | jekyll-gist (1.5.0)
46 | octokit (~> 4.2)
47 | jekyll-paginate (1.1.0)
48 | jekyll-sass-converter (1.5.2)
49 | sass (~> 3.4)
50 | jekyll-sitemap (1.2.0)
51 | jekyll (~> 3.3)
52 | jekyll-watch (2.0.0)
53 | listen (~> 3.0)
54 | jemoji (0.9.0)
55 | activesupport (~> 4.0, >= 4.2.9)
56 | gemoji (~> 3.0)
57 | html-pipeline (~> 2.2)
58 | jekyll (~> 3.0)
59 | kramdown (1.16.2)
60 | liquid (4.0.0)
61 | listen (3.1.5)
62 | rb-fsevent (~> 0.9, >= 0.9.4)
63 | rb-inotify (~> 0.9, >= 0.9.7)
64 | ruby_dep (~> 1.2)
65 | mercenary (0.3.6)
66 | mini_portile2 (2.3.0)
67 | minimal-mistakes-jekyll (4.11.1)
68 | jekyll (~> 3.6)
69 | jekyll-data (~> 1.0)
70 | jekyll-feed (~> 0.9.2)
71 | jekyll-gist (~> 1.4)
72 | jekyll-paginate (~> 1.1)
73 | jekyll-sitemap (~> 1.1)
74 | jemoji (~> 0.8)
75 | minitest (5.11.3)
76 | multipart-post (2.0.0)
77 | nokogiri (1.8.2)
78 | mini_portile2 (~> 2.3.0)
79 | octokit (4.8.0)
80 | sawyer (~> 0.8.0, >= 0.5.3)
81 | pathutil (0.16.1)
82 | forwardable-extended (~> 2.6)
83 | public_suffix (3.0.2)
84 | rb-fsevent (0.10.3)
85 | rb-inotify (0.9.10)
86 | ffi (>= 0.5.0, < 2)
87 | rouge (3.1.1)
88 | ruby_dep (1.5.0)
89 | safe_yaml (1.0.4)
90 | sass (3.5.6)
91 | sass-listen (~> 4.0.0)
92 | sass-listen (4.0.0)
93 | rb-fsevent (~> 0.9, >= 0.9.4)
94 | rb-inotify (~> 0.9, >= 0.9.7)
95 | sawyer (0.8.1)
96 | addressable (>= 2.3.5, < 2.6)
97 | faraday (~> 0.8, < 1.0)
98 | thread_safe (0.3.6)
99 | tzinfo (1.2.5)
100 | thread_safe (~> 0.1)
101 |
102 | PLATFORMS
103 | ruby
104 |
105 | DEPENDENCIES
106 | jekyll (~> 3.7.3)
107 | jekyll-data
108 | jekyll-feed (~> 0.6)
109 | minimal-mistakes-jekyll
110 | tzinfo-data
111 |
112 | BUNDLED WITH
113 | 1.16.1
114 |
--------------------------------------------------------------------------------
/Gopkg.toml:
--------------------------------------------------------------------------------
1 |
2 | # Gopkg.toml example
3 | #
4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
5 | # for detailed Gopkg.toml documentation.
6 | #
7 | # required = ["github.com/user/thing/cmd/thing"]
8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
9 | #
10 | # [[constraint]]
11 | # name = "github.com/user/project"
12 | # version = "1.0.0"
13 | #
14 | # [[constraint]]
15 | # name = "github.com/user/project2"
16 | # branch = "dev"
17 | # source = "github.com/myfork/project2"
18 | #
19 | # [[override]]
20 | # name = "github.com/x/y"
21 | # version = "2.4.0"
22 |
23 | # Adding the generators here allows us to pin them to a particular version.
24 | required = [
25 | "k8s.io/code-generator/cmd/deepcopy-gen",
26 | "k8s.io/code-generator/cmd/client-gen",
27 | "k8s.io/code-generator/cmd/lister-gen",
28 | "k8s.io/code-generator/cmd/informer-gen"
29 | ]
30 |
31 | # The code in examples/ is not part of the actual build.
32 | ignored = [
33 | "metacontroller.app/examples/*"
34 | ]
35 |
36 | [[constraint]]
37 | name = "k8s.io/client-go"
38 | version = "8.0.0"
39 |
40 | [[constraint]]
41 | name = "k8s.io/apimachinery"
42 | version = "kubernetes-1.11.0"
43 |
44 | [[constraint]]
45 | name = "k8s.io/apiextensions-apiserver"
46 | version = "kubernetes-1.11.0"
47 |
48 | [[constraint]]
49 | name = "k8s.io/code-generator"
50 | version = "kubernetes-1.11.0"
51 |
52 | [[override]]
53 | name = "github.com/json-iterator/go"
54 | version = "1.1.5" # same minor version track as used by apimachinery@kubernetes-1.11.0
55 |
56 | [[override]]
57 | name = "github.com/prometheus/client_model"
58 | revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
59 | # last version compatable with golang/protobuf@v1.0.0 (the version used by apimachinery@kubernetes-1.9.9)
60 |
61 | [prune]
62 | go-tests = true
63 | unused-packages = true
64 |
65 | [[prune.project]]
66 | name = "k8s.io/code-generator"
67 | unused-packages = false
68 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | TAG = dev
2 |
3 | PKG := metacontroller.app
4 | API_GROUPS := metacontroller/v1alpha1
5 |
6 | all: install
7 |
8 | install: generated_files
9 | go install
10 |
11 | unit-test:
12 | pkgs="$$(go list ./... | grep -v '/test/integration/\|/examples/')" ; \
13 | go test -i $${pkgs} && \
14 | go test $${pkgs}
15 |
16 | integration-test:
17 | go test -i ./test/integration/...
18 | PATH="$(PWD)/hack/bin:$(PATH)" go test ./test/integration/... -v -timeout 5m -args -v=6
19 |
20 | image: generated_files
21 | docker build -t metacontroller/metacontroller:$(TAG) .
22 |
23 | push: image
24 | docker push metacontroller/metacontroller:$(TAG)
25 |
26 | # Code generators
27 | # https://github.com/kubernetes/community/blob/master/contributors/devel/api_changes.md#generate-code
28 |
29 | generated_files: deepcopy clientset lister informer
30 |
31 | # also builds vendored version of deepcopy-gen tool
32 | deepcopy:
33 | @go install ./vendor/k8s.io/code-generator/cmd/deepcopy-gen
34 | @echo "+ Generating deepcopy funcs for $(API_GROUPS)"
35 | @deepcopy-gen \
36 | --input-dirs $(PKG)/apis/$(API_GROUPS) \
37 | --output-file-base zz_generated.deepcopy
38 |
39 | # also builds vendored version of client-gen tool
40 | clientset:
41 | @go install ./vendor/k8s.io/code-generator/cmd/client-gen
42 | @echo "+ Generating clientsets for $(API_GROUPS)"
43 | @client-gen \
44 | --fake-clientset=false \
45 | --input $(API_GROUPS) \
46 | --input-base $(PKG)/apis \
47 | --clientset-path $(PKG)/client/generated/clientset
48 |
49 | # also builds vendored version of lister-gen tool
50 | lister:
51 | @go install ./vendor/k8s.io/code-generator/cmd/lister-gen
52 | @echo "+ Generating lister for $(API_GROUPS)"
53 | @lister-gen \
54 | --input-dirs $(PKG)/apis/$(API_GROUPS) \
55 | --output-package $(PKG)/client/generated/lister
56 |
57 | # also builds vendored version of informer-gen tool
58 | informer:
59 | @go install ./vendor/k8s.io/code-generator/cmd/informer-gen
60 | @echo "+ Generating informer for $(API_GROUPS)"
61 | @informer-gen \
62 | --input-dirs $(PKG)/apis/$(API_GROUPS) \
63 | --output-package $(PKG)/client/generated/informer \
64 | --versioned-clientset-package $(PKG)/client/generated/clientset/internalclientset \
65 | --listers-package $(PKG)/client/generated/lister
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## :zap: Metacontroller Has Moved
2 | Active development of metacontroller continues at [metacontroller/metacontroller](https://github.com/metacontroller/metacontroller). This repository is no longer maintained.
3 |
--------------------------------------------------------------------------------
/apis/metacontroller/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 | // +k8s:deepcopy-gen=package,register
18 |
19 | package v1alpha1 // import "metacontroller.app/apis/metacontroller/v1alpha1"
20 |
--------------------------------------------------------------------------------
/apis/metacontroller/v1alpha1/register.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 v1alpha1
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | "k8s.io/apimachinery/pkg/runtime"
22 | "k8s.io/apimachinery/pkg/runtime/schema"
23 | )
24 |
25 | var (
26 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
27 | AddToScheme = SchemeBuilder.AddToScheme
28 | )
29 |
30 | // GroupName is the group name used in this package.
31 | const GroupName = "metacontroller.k8s.io"
32 |
33 | // SchemeGroupVersion is the group version used to register these objects.
34 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
35 |
36 | // Resource takes an unqualified resource and returns a Group-qualified GroupResource.
37 | func Resource(resource string) schema.GroupResource {
38 | return SchemeGroupVersion.WithResource(resource).GroupResource()
39 | }
40 |
41 | // addKnownTypes adds the set of types defined in this package to the supplied scheme.
42 | func addKnownTypes(scheme *runtime.Scheme) error {
43 | scheme.AddKnownTypes(SchemeGroupVersion,
44 | &CompositeController{},
45 | &CompositeControllerList{},
46 | &DecoratorController{},
47 | &DecoratorControllerList{},
48 | &ControllerRevision{},
49 | &ControllerRevisionList{},
50 | )
51 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/apis/metacontroller/v1alpha1/roundtrip_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 v1alpha1
18 |
19 | import (
20 | "math/rand"
21 | "testing"
22 |
23 | "k8s.io/apimachinery/pkg/api/testing/fuzzer"
24 | roundtrip "k8s.io/apimachinery/pkg/api/testing/roundtrip"
25 | metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
26 | "k8s.io/apimachinery/pkg/runtime"
27 | "k8s.io/apimachinery/pkg/runtime/serializer"
28 | )
29 |
30 | // TestRoundTrip tests that the third-party kinds can be marshaled and unmarshaled correctly to/from JSON
31 | // without the loss of information. Moreover, deep copy is tested.
32 | func TestRoundTrip(t *testing.T) {
33 | scheme := runtime.NewScheme()
34 | codecs := serializer.NewCodecFactory(scheme)
35 |
36 | AddToScheme(scheme)
37 |
38 | fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(1), codecs)
39 |
40 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("CompositeController"), scheme, codecs, fuzzer, nil)
41 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("CompositeControllerList"), scheme, codecs, fuzzer, nil)
42 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("DecoratorController"), scheme, codecs, fuzzer, nil)
43 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("DecoratorControllerList"), scheme, codecs, fuzzer, nil)
44 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("ControllerRevision"), scheme, codecs, fuzzer, nil)
45 | roundtrip.RoundTripSpecificKindWithoutProtobuf(t, SchemeGroupVersion.WithKind("ControllerRevisionList"), scheme, codecs, fuzzer, nil)
46 | }
47 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/clientset.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package internalclientset
20 |
21 | import (
22 | discovery "k8s.io/client-go/discovery"
23 | rest "k8s.io/client-go/rest"
24 | flowcontrol "k8s.io/client-go/util/flowcontrol"
25 | metacontrollerv1alpha1 "metacontroller.app/client/generated/clientset/internalclientset/typed/metacontroller/v1alpha1"
26 | )
27 |
28 | type Interface interface {
29 | Discovery() discovery.DiscoveryInterface
30 | MetacontrollerV1alpha1() metacontrollerv1alpha1.MetacontrollerV1alpha1Interface
31 | // Deprecated: please explicitly pick a version if possible.
32 | Metacontroller() metacontrollerv1alpha1.MetacontrollerV1alpha1Interface
33 | }
34 |
35 | // Clientset contains the clients for groups. Each group has exactly one
36 | // version included in a Clientset.
37 | type Clientset struct {
38 | *discovery.DiscoveryClient
39 | metacontrollerV1alpha1 *metacontrollerv1alpha1.MetacontrollerV1alpha1Client
40 | }
41 |
42 | // MetacontrollerV1alpha1 retrieves the MetacontrollerV1alpha1Client
43 | func (c *Clientset) MetacontrollerV1alpha1() metacontrollerv1alpha1.MetacontrollerV1alpha1Interface {
44 | return c.metacontrollerV1alpha1
45 | }
46 |
47 | // Deprecated: Metacontroller retrieves the default version of MetacontrollerClient.
48 | // Please explicitly pick a version.
49 | func (c *Clientset) Metacontroller() metacontrollerv1alpha1.MetacontrollerV1alpha1Interface {
50 | return c.metacontrollerV1alpha1
51 | }
52 |
53 | // Discovery retrieves the DiscoveryClient
54 | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
55 | if c == nil {
56 | return nil
57 | }
58 | return c.DiscoveryClient
59 | }
60 |
61 | // NewForConfig creates a new Clientset for the given config.
62 | func NewForConfig(c *rest.Config) (*Clientset, error) {
63 | configShallowCopy := *c
64 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
65 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
66 | }
67 | var cs Clientset
68 | var err error
69 | cs.metacontrollerV1alpha1, err = metacontrollerv1alpha1.NewForConfig(&configShallowCopy)
70 | if err != nil {
71 | return nil, err
72 | }
73 |
74 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
75 | if err != nil {
76 | return nil, err
77 | }
78 | return &cs, nil
79 | }
80 |
81 | // NewForConfigOrDie creates a new Clientset for the given config and
82 | // panics if there is an error in the config.
83 | func NewForConfigOrDie(c *rest.Config) *Clientset {
84 | var cs Clientset
85 | cs.metacontrollerV1alpha1 = metacontrollerv1alpha1.NewForConfigOrDie(c)
86 |
87 | cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
88 | return &cs
89 | }
90 |
91 | // New creates a new Clientset for the given RESTClient.
92 | func New(c rest.Interface) *Clientset {
93 | var cs Clientset
94 | cs.metacontrollerV1alpha1 = metacontrollerv1alpha1.New(c)
95 |
96 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
97 | return &cs
98 | }
99 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package has the automatically generated clientset.
20 | package internalclientset
21 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/scheme/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package contains the scheme of the automatically generated clientset.
20 | package scheme
21 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/scheme/register.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package scheme
20 |
21 | import (
22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | runtime "k8s.io/apimachinery/pkg/runtime"
24 | schema "k8s.io/apimachinery/pkg/runtime/schema"
25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
26 | metacontrollerv1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
27 | )
28 |
29 | var Scheme = runtime.NewScheme()
30 | var Codecs = serializer.NewCodecFactory(Scheme)
31 | var ParameterCodec = runtime.NewParameterCodec(Scheme)
32 |
33 | func init() {
34 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
35 | AddToScheme(Scheme)
36 | }
37 |
38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition
39 | // of clientsets, like in:
40 | //
41 | // import (
42 | // "k8s.io/client-go/kubernetes"
43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme"
44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
45 | // )
46 | //
47 | // kclientset, _ := kubernetes.NewForConfig(c)
48 | // aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
49 | //
50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
51 | // correctly.
52 | func AddToScheme(scheme *runtime.Scheme) {
53 | metacontrollerv1alpha1.AddToScheme(scheme)
54 | }
55 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/typed/metacontroller/v1alpha1/controllerrevision_expansion.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 v1alpha1
18 |
19 | import (
20 | "fmt"
21 |
22 | apierrors "k8s.io/apimachinery/pkg/api/errors"
23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24 | "k8s.io/client-go/util/retry"
25 |
26 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
27 | )
28 |
29 | type ControllerRevisionExpansion interface {
30 | UpdateWithRetries(orig *v1alpha1.ControllerRevision, updateFn func(*v1alpha1.ControllerRevision) bool) (result *v1alpha1.ControllerRevision, err error)
31 | }
32 |
33 | func (c *controllerRevisions) UpdateWithRetries(orig *v1alpha1.ControllerRevision, updateFn func(*v1alpha1.ControllerRevision) bool) (result *v1alpha1.ControllerRevision, err error) {
34 | name := orig.GetName()
35 | err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
36 | current, err := c.Get(name, metav1.GetOptions{})
37 | if err != nil {
38 | return err
39 | }
40 | if current.GetUID() != orig.GetUID() {
41 | return apierrors.NewGone(fmt.Sprintf("can't update ControllerRevision %v/%v: original object is gone: got uid %v, want %v", orig.GetNamespace(), orig.GetName(), current.GetUID(), orig.GetUID()))
42 | }
43 | if changed := updateFn(current); !changed {
44 | // There's nothing to do.
45 | return nil
46 | }
47 | result, err = c.Update(current)
48 | return err
49 | })
50 | return result, err
51 | }
52 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/typed/metacontroller/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package has the automatically generated typed clients.
20 | package v1alpha1
21 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/typed/metacontroller/v1alpha1/generated_expansion.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | type CompositeControllerExpansion interface{}
22 |
23 | type DecoratorControllerExpansion interface{}
24 |
--------------------------------------------------------------------------------
/client/generated/clientset/internalclientset/typed/metacontroller/v1alpha1/metacontroller_client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
23 | rest "k8s.io/client-go/rest"
24 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
25 | "metacontroller.app/client/generated/clientset/internalclientset/scheme"
26 | )
27 |
28 | type MetacontrollerV1alpha1Interface interface {
29 | RESTClient() rest.Interface
30 | CompositeControllersGetter
31 | ControllerRevisionsGetter
32 | DecoratorControllersGetter
33 | }
34 |
35 | // MetacontrollerV1alpha1Client is used to interact with features provided by the metacontroller group.
36 | type MetacontrollerV1alpha1Client struct {
37 | restClient rest.Interface
38 | }
39 |
40 | func (c *MetacontrollerV1alpha1Client) CompositeControllers() CompositeControllerInterface {
41 | return newCompositeControllers(c)
42 | }
43 |
44 | func (c *MetacontrollerV1alpha1Client) ControllerRevisions(namespace string) ControllerRevisionInterface {
45 | return newControllerRevisions(c, namespace)
46 | }
47 |
48 | func (c *MetacontrollerV1alpha1Client) DecoratorControllers() DecoratorControllerInterface {
49 | return newDecoratorControllers(c)
50 | }
51 |
52 | // NewForConfig creates a new MetacontrollerV1alpha1Client for the given config.
53 | func NewForConfig(c *rest.Config) (*MetacontrollerV1alpha1Client, error) {
54 | config := *c
55 | if err := setConfigDefaults(&config); err != nil {
56 | return nil, err
57 | }
58 | client, err := rest.RESTClientFor(&config)
59 | if err != nil {
60 | return nil, err
61 | }
62 | return &MetacontrollerV1alpha1Client{client}, nil
63 | }
64 |
65 | // NewForConfigOrDie creates a new MetacontrollerV1alpha1Client for the given config and
66 | // panics if there is an error in the config.
67 | func NewForConfigOrDie(c *rest.Config) *MetacontrollerV1alpha1Client {
68 | client, err := NewForConfig(c)
69 | if err != nil {
70 | panic(err)
71 | }
72 | return client
73 | }
74 |
75 | // New creates a new MetacontrollerV1alpha1Client for the given RESTClient.
76 | func New(c rest.Interface) *MetacontrollerV1alpha1Client {
77 | return &MetacontrollerV1alpha1Client{c}
78 | }
79 |
80 | func setConfigDefaults(config *rest.Config) error {
81 | gv := v1alpha1.SchemeGroupVersion
82 | config.GroupVersion = &gv
83 | config.APIPath = "/apis"
84 | config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
85 |
86 | if config.UserAgent == "" {
87 | config.UserAgent = rest.DefaultKubernetesUserAgent()
88 | }
89 |
90 | return nil
91 | }
92 |
93 | // RESTClient returns a RESTClient that is used to communicate
94 | // with API server by this client implementation.
95 | func (c *MetacontrollerV1alpha1Client) RESTClient() rest.Interface {
96 | if c == nil {
97 | return nil
98 | }
99 | return c.restClient
100 | }
101 |
--------------------------------------------------------------------------------
/client/generated/informer/externalversions/generic.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package externalversions
20 |
21 | import (
22 | "fmt"
23 |
24 | schema "k8s.io/apimachinery/pkg/runtime/schema"
25 | cache "k8s.io/client-go/tools/cache"
26 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
27 | )
28 |
29 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other
30 | // sharedInformers based on type
31 | type GenericInformer interface {
32 | Informer() cache.SharedIndexInformer
33 | Lister() cache.GenericLister
34 | }
35 |
36 | type genericInformer struct {
37 | informer cache.SharedIndexInformer
38 | resource schema.GroupResource
39 | }
40 |
41 | // Informer returns the SharedIndexInformer.
42 | func (f *genericInformer) Informer() cache.SharedIndexInformer {
43 | return f.informer
44 | }
45 |
46 | // Lister returns the GenericLister.
47 | func (f *genericInformer) Lister() cache.GenericLister {
48 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
49 | }
50 |
51 | // ForResource gives generic access to a shared informer of the matching type
52 | // TODO extend this to unknown resources with a client pool
53 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
54 | switch resource {
55 | // Group=metacontroller, Version=v1alpha1
56 | case v1alpha1.SchemeGroupVersion.WithResource("compositecontrollers"):
57 | return &genericInformer{resource: resource.GroupResource(), informer: f.Metacontroller().V1alpha1().CompositeControllers().Informer()}, nil
58 | case v1alpha1.SchemeGroupVersion.WithResource("controllerrevisions"):
59 | return &genericInformer{resource: resource.GroupResource(), informer: f.Metacontroller().V1alpha1().ControllerRevisions().Informer()}, nil
60 | case v1alpha1.SchemeGroupVersion.WithResource("decoratorcontrollers"):
61 | return &genericInformer{resource: resource.GroupResource(), informer: f.Metacontroller().V1alpha1().DecoratorControllers().Informer()}, nil
62 |
63 | }
64 |
65 | return nil, fmt.Errorf("no informer found for %v", resource)
66 | }
67 |
--------------------------------------------------------------------------------
/client/generated/informer/externalversions/internalinterfaces/factory_interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package internalinterfaces
20 |
21 | import (
22 | time "time"
23 |
24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | runtime "k8s.io/apimachinery/pkg/runtime"
26 | cache "k8s.io/client-go/tools/cache"
27 | internalclientset "metacontroller.app/client/generated/clientset/internalclientset"
28 | )
29 |
30 | type NewInformerFunc func(internalclientset.Interface, time.Duration) cache.SharedIndexInformer
31 |
32 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle
33 | type SharedInformerFactory interface {
34 | Start(stopCh <-chan struct{})
35 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
36 | }
37 |
38 | type TweakListOptionsFunc func(*v1.ListOptions)
39 |
--------------------------------------------------------------------------------
/client/generated/informer/externalversions/metacontroller/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package metacontroller
20 |
21 | import (
22 | internalinterfaces "metacontroller.app/client/generated/informer/externalversions/internalinterfaces"
23 | v1alpha1 "metacontroller.app/client/generated/informer/externalversions/metacontroller/v1alpha1"
24 | )
25 |
26 | // Interface provides access to each of this group's versions.
27 | type Interface interface {
28 | // V1alpha1 provides access to shared informers for resources in V1alpha1.
29 | V1alpha1() v1alpha1.Interface
30 | }
31 |
32 | type group struct {
33 | factory internalinterfaces.SharedInformerFactory
34 | namespace string
35 | tweakListOptions internalinterfaces.TweakListOptionsFunc
36 | }
37 |
38 | // New returns a new Interface.
39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
41 | }
42 |
43 | // V1alpha1 returns a new v1alpha1.Interface.
44 | func (g *group) V1alpha1() v1alpha1.Interface {
45 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
46 | }
47 |
--------------------------------------------------------------------------------
/client/generated/informer/externalversions/metacontroller/v1alpha1/compositecontroller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | time "time"
23 |
24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | runtime "k8s.io/apimachinery/pkg/runtime"
26 | watch "k8s.io/apimachinery/pkg/watch"
27 | cache "k8s.io/client-go/tools/cache"
28 | metacontrollerv1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
29 | internalclientset "metacontroller.app/client/generated/clientset/internalclientset"
30 | internalinterfaces "metacontroller.app/client/generated/informer/externalversions/internalinterfaces"
31 | v1alpha1 "metacontroller.app/client/generated/lister/metacontroller/v1alpha1"
32 | )
33 |
34 | // CompositeControllerInformer provides access to a shared informer and lister for
35 | // CompositeControllers.
36 | type CompositeControllerInformer interface {
37 | Informer() cache.SharedIndexInformer
38 | Lister() v1alpha1.CompositeControllerLister
39 | }
40 |
41 | type compositeControllerInformer struct {
42 | factory internalinterfaces.SharedInformerFactory
43 | tweakListOptions internalinterfaces.TweakListOptionsFunc
44 | }
45 |
46 | // NewCompositeControllerInformer constructs a new informer for CompositeController type.
47 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
48 | // one. This reduces memory footprint and number of connections to the server.
49 | func NewCompositeControllerInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
50 | return NewFilteredCompositeControllerInformer(client, resyncPeriod, indexers, nil)
51 | }
52 |
53 | // NewFilteredCompositeControllerInformer constructs a new informer for CompositeController type.
54 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
55 | // one. This reduces memory footprint and number of connections to the server.
56 | func NewFilteredCompositeControllerInformer(client internalclientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
57 | return cache.NewSharedIndexInformer(
58 | &cache.ListWatch{
59 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
60 | if tweakListOptions != nil {
61 | tweakListOptions(&options)
62 | }
63 | return client.MetacontrollerV1alpha1().CompositeControllers().List(options)
64 | },
65 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
66 | if tweakListOptions != nil {
67 | tweakListOptions(&options)
68 | }
69 | return client.MetacontrollerV1alpha1().CompositeControllers().Watch(options)
70 | },
71 | },
72 | &metacontrollerv1alpha1.CompositeController{},
73 | resyncPeriod,
74 | indexers,
75 | )
76 | }
77 |
78 | func (f *compositeControllerInformer) defaultInformer(client internalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
79 | return NewFilteredCompositeControllerInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
80 | }
81 |
82 | func (f *compositeControllerInformer) Informer() cache.SharedIndexInformer {
83 | return f.factory.InformerFor(&metacontrollerv1alpha1.CompositeController{}, f.defaultInformer)
84 | }
85 |
86 | func (f *compositeControllerInformer) Lister() v1alpha1.CompositeControllerLister {
87 | return v1alpha1.NewCompositeControllerLister(f.Informer().GetIndexer())
88 | }
89 |
--------------------------------------------------------------------------------
/client/generated/informer/externalversions/metacontroller/v1alpha1/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | internalinterfaces "metacontroller.app/client/generated/informer/externalversions/internalinterfaces"
23 | )
24 |
25 | // Interface provides access to all the informers in this group version.
26 | type Interface interface {
27 | // CompositeControllers returns a CompositeControllerInformer.
28 | CompositeControllers() CompositeControllerInformer
29 | // ControllerRevisions returns a ControllerRevisionInformer.
30 | ControllerRevisions() ControllerRevisionInformer
31 | // DecoratorControllers returns a DecoratorControllerInformer.
32 | DecoratorControllers() DecoratorControllerInformer
33 | }
34 |
35 | type version struct {
36 | factory internalinterfaces.SharedInformerFactory
37 | namespace string
38 | tweakListOptions internalinterfaces.TweakListOptionsFunc
39 | }
40 |
41 | // New returns a new Interface.
42 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
43 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
44 | }
45 |
46 | // CompositeControllers returns a CompositeControllerInformer.
47 | func (v *version) CompositeControllers() CompositeControllerInformer {
48 | return &compositeControllerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
49 | }
50 |
51 | // ControllerRevisions returns a ControllerRevisionInformer.
52 | func (v *version) ControllerRevisions() ControllerRevisionInformer {
53 | return &controllerRevisionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
54 | }
55 |
56 | // DecoratorControllers returns a DecoratorControllerInformer.
57 | func (v *version) DecoratorControllers() DecoratorControllerInformer {
58 | return &decoratorControllerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
59 | }
60 |
--------------------------------------------------------------------------------
/client/generated/lister/metacontroller/v1alpha1/compositecontroller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | "k8s.io/apimachinery/pkg/api/errors"
23 | "k8s.io/apimachinery/pkg/labels"
24 | "k8s.io/client-go/tools/cache"
25 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
26 | )
27 |
28 | // CompositeControllerLister helps list CompositeControllers.
29 | type CompositeControllerLister interface {
30 | // List lists all CompositeControllers in the indexer.
31 | List(selector labels.Selector) (ret []*v1alpha1.CompositeController, err error)
32 | // Get retrieves the CompositeController from the index for a given name.
33 | Get(name string) (*v1alpha1.CompositeController, error)
34 | CompositeControllerListerExpansion
35 | }
36 |
37 | // compositeControllerLister implements the CompositeControllerLister interface.
38 | type compositeControllerLister struct {
39 | indexer cache.Indexer
40 | }
41 |
42 | // NewCompositeControllerLister returns a new CompositeControllerLister.
43 | func NewCompositeControllerLister(indexer cache.Indexer) CompositeControllerLister {
44 | return &compositeControllerLister{indexer: indexer}
45 | }
46 |
47 | // List lists all CompositeControllers in the indexer.
48 | func (s *compositeControllerLister) List(selector labels.Selector) (ret []*v1alpha1.CompositeController, err error) {
49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) {
50 | ret = append(ret, m.(*v1alpha1.CompositeController))
51 | })
52 | return ret, err
53 | }
54 |
55 | // Get retrieves the CompositeController from the index for a given name.
56 | func (s *compositeControllerLister) Get(name string) (*v1alpha1.CompositeController, error) {
57 | obj, exists, err := s.indexer.GetByKey(name)
58 | if err != nil {
59 | return nil, err
60 | }
61 | if !exists {
62 | return nil, errors.NewNotFound(v1alpha1.Resource("compositecontroller"), name)
63 | }
64 | return obj.(*v1alpha1.CompositeController), nil
65 | }
66 |
--------------------------------------------------------------------------------
/client/generated/lister/metacontroller/v1alpha1/controllerrevision.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | "k8s.io/apimachinery/pkg/api/errors"
23 | "k8s.io/apimachinery/pkg/labels"
24 | "k8s.io/client-go/tools/cache"
25 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
26 | )
27 |
28 | // ControllerRevisionLister helps list ControllerRevisions.
29 | type ControllerRevisionLister interface {
30 | // List lists all ControllerRevisions in the indexer.
31 | List(selector labels.Selector) (ret []*v1alpha1.ControllerRevision, err error)
32 | // ControllerRevisions returns an object that can list and get ControllerRevisions.
33 | ControllerRevisions(namespace string) ControllerRevisionNamespaceLister
34 | ControllerRevisionListerExpansion
35 | }
36 |
37 | // controllerRevisionLister implements the ControllerRevisionLister interface.
38 | type controllerRevisionLister struct {
39 | indexer cache.Indexer
40 | }
41 |
42 | // NewControllerRevisionLister returns a new ControllerRevisionLister.
43 | func NewControllerRevisionLister(indexer cache.Indexer) ControllerRevisionLister {
44 | return &controllerRevisionLister{indexer: indexer}
45 | }
46 |
47 | // List lists all ControllerRevisions in the indexer.
48 | func (s *controllerRevisionLister) List(selector labels.Selector) (ret []*v1alpha1.ControllerRevision, err error) {
49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) {
50 | ret = append(ret, m.(*v1alpha1.ControllerRevision))
51 | })
52 | return ret, err
53 | }
54 |
55 | // ControllerRevisions returns an object that can list and get ControllerRevisions.
56 | func (s *controllerRevisionLister) ControllerRevisions(namespace string) ControllerRevisionNamespaceLister {
57 | return controllerRevisionNamespaceLister{indexer: s.indexer, namespace: namespace}
58 | }
59 |
60 | // ControllerRevisionNamespaceLister helps list and get ControllerRevisions.
61 | type ControllerRevisionNamespaceLister interface {
62 | // List lists all ControllerRevisions in the indexer for a given namespace.
63 | List(selector labels.Selector) (ret []*v1alpha1.ControllerRevision, err error)
64 | // Get retrieves the ControllerRevision from the indexer for a given namespace and name.
65 | Get(name string) (*v1alpha1.ControllerRevision, error)
66 | ControllerRevisionNamespaceListerExpansion
67 | }
68 |
69 | // controllerRevisionNamespaceLister implements the ControllerRevisionNamespaceLister
70 | // interface.
71 | type controllerRevisionNamespaceLister struct {
72 | indexer cache.Indexer
73 | namespace string
74 | }
75 |
76 | // List lists all ControllerRevisions in the indexer for a given namespace.
77 | func (s controllerRevisionNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ControllerRevision, err error) {
78 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
79 | ret = append(ret, m.(*v1alpha1.ControllerRevision))
80 | })
81 | return ret, err
82 | }
83 |
84 | // Get retrieves the ControllerRevision from the indexer for a given namespace and name.
85 | func (s controllerRevisionNamespaceLister) Get(name string) (*v1alpha1.ControllerRevision, error) {
86 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
87 | if err != nil {
88 | return nil, err
89 | }
90 | if !exists {
91 | return nil, errors.NewNotFound(v1alpha1.Resource("controllerrevision"), name)
92 | }
93 | return obj.(*v1alpha1.ControllerRevision), nil
94 | }
95 |
--------------------------------------------------------------------------------
/client/generated/lister/metacontroller/v1alpha1/decoratorcontroller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | "k8s.io/apimachinery/pkg/api/errors"
23 | "k8s.io/apimachinery/pkg/labels"
24 | "k8s.io/client-go/tools/cache"
25 | v1alpha1 "metacontroller.app/apis/metacontroller/v1alpha1"
26 | )
27 |
28 | // DecoratorControllerLister helps list DecoratorControllers.
29 | type DecoratorControllerLister interface {
30 | // List lists all DecoratorControllers in the indexer.
31 | List(selector labels.Selector) (ret []*v1alpha1.DecoratorController, err error)
32 | // Get retrieves the DecoratorController from the index for a given name.
33 | Get(name string) (*v1alpha1.DecoratorController, error)
34 | DecoratorControllerListerExpansion
35 | }
36 |
37 | // decoratorControllerLister implements the DecoratorControllerLister interface.
38 | type decoratorControllerLister struct {
39 | indexer cache.Indexer
40 | }
41 |
42 | // NewDecoratorControllerLister returns a new DecoratorControllerLister.
43 | func NewDecoratorControllerLister(indexer cache.Indexer) DecoratorControllerLister {
44 | return &decoratorControllerLister{indexer: indexer}
45 | }
46 |
47 | // List lists all DecoratorControllers in the indexer.
48 | func (s *decoratorControllerLister) List(selector labels.Selector) (ret []*v1alpha1.DecoratorController, err error) {
49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) {
50 | ret = append(ret, m.(*v1alpha1.DecoratorController))
51 | })
52 | return ret, err
53 | }
54 |
55 | // Get retrieves the DecoratorController from the index for a given name.
56 | func (s *decoratorControllerLister) Get(name string) (*v1alpha1.DecoratorController, error) {
57 | obj, exists, err := s.indexer.GetByKey(name)
58 | if err != nil {
59 | return nil, err
60 | }
61 | if !exists {
62 | return nil, errors.NewNotFound(v1alpha1.Resource("decoratorcontroller"), name)
63 | }
64 | return obj.(*v1alpha1.DecoratorController), nil
65 | }
66 |
--------------------------------------------------------------------------------
/client/generated/lister/metacontroller/v1alpha1/expansion_generated.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright The Kubernetes 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 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | // CompositeControllerListerExpansion allows custom methods to be added to
22 | // CompositeControllerLister.
23 | type CompositeControllerListerExpansion interface{}
24 |
25 | // ControllerRevisionListerExpansion allows custom methods to be added to
26 | // ControllerRevisionLister.
27 | type ControllerRevisionListerExpansion interface{}
28 |
29 | // ControllerRevisionNamespaceListerExpansion allows custom methods to be added to
30 | // ControllerRevisionNamespaceLister.
31 | type ControllerRevisionNamespaceListerExpansion interface{}
32 |
33 | // DecoratorControllerListerExpansion allows custom methods to be added to
34 | // DecoratorControllerLister.
35 | type DecoratorControllerListerExpansion interface{}
36 |
--------------------------------------------------------------------------------
/code-of-conduct.md:
--------------------------------------------------------------------------------
1 | # Kubernetes Community Code of Conduct
2 |
3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
4 |
--------------------------------------------------------------------------------
/controller/common/finalizer/finalizer.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 finalizer
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22 |
23 | dynamicclientset "metacontroller.app/dynamic/clientset"
24 | dynamicobject "metacontroller.app/dynamic/object"
25 | )
26 |
27 | // Manager encapsulates controller logic for dealing with finalizers.
28 | type Manager struct {
29 | Name string
30 | Enabled bool
31 | }
32 |
33 | // SyncObject adds or removes the finalizer on the given object as necessary.
34 | func (m *Manager) SyncObject(client *dynamicclientset.ResourceClient, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
35 | // If the cached object passed in is already in the right state,
36 | // we'll assume we don't need to check the live object.
37 | if dynamicobject.HasFinalizer(obj, m.Name) == m.Enabled {
38 | return obj, nil
39 | }
40 | // Otherwise, we may need to update the object.
41 | if m.Enabled {
42 | // If the object is already pending deletion, we don't add the finalizer.
43 | // We might have already removed it.
44 | if obj.GetDeletionTimestamp() != nil {
45 | return obj, nil
46 | }
47 | return client.Namespace(obj.GetNamespace()).AddFinalizer(obj, m.Name)
48 | } else {
49 | return client.Namespace(obj.GetNamespace()).RemoveFinalizer(obj, m.Name)
50 | }
51 | }
52 |
53 | // ShouldFinalize returns true if the controller should take action to manage
54 | // children even though the parent is pending deletion (i.e. finalize).
55 | func (m *Manager) ShouldFinalize(parent metav1.Object) bool {
56 | // There's no point managing children if the parent has a GC finalizer,
57 | // because we'd be fighting the GC.
58 | if hasGCFinalizer(parent) {
59 | return false
60 | }
61 | // If we already removed the finalizer, don't try to manage children anymore.
62 | if !dynamicobject.HasFinalizer(parent, m.Name) {
63 | return false
64 | }
65 | return m.Enabled
66 | }
67 |
68 | // hasGCFinalizer returns true if obj has any GC finalizer.
69 | // In other words, true means the GC will start messing with its children,
70 | // either deleting or orphaning them.
71 | func hasGCFinalizer(obj metav1.Object) bool {
72 | for _, item := range obj.GetFinalizers() {
73 | switch item {
74 | case metav1.FinalizerDeleteDependents, metav1.FinalizerOrphanDependents:
75 | return true
76 | }
77 | }
78 | return false
79 | }
80 |
--------------------------------------------------------------------------------
/controller/common/manage_children_test.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
8 | "k8s.io/apimachinery/pkg/util/diff"
9 | "k8s.io/apimachinery/pkg/util/json"
10 | )
11 |
12 | func TestRevertObjectMetaSystemFields(t *testing.T) {
13 | origJSON := `{
14 | "metadata": {
15 | "origMeta": "should stay gone",
16 | "otherMeta": "should change value",
17 | "creationTimestamp": "should restore orig value",
18 | "deletionTimestamp": "should restore orig value",
19 | "uid": "should bring back removed value"
20 | },
21 | "other": "should change value"
22 | }`
23 | newObjJSON := `{
24 | "metadata": {
25 | "creationTimestamp": null,
26 | "deletionTimestamp": "new value",
27 | "newMeta": "new value",
28 | "otherMeta": "new value",
29 | "selfLink": "should be removed"
30 | },
31 | "other": "new value"
32 | }`
33 | wantJSON := `{
34 | "metadata": {
35 | "otherMeta": "new value",
36 | "newMeta": "new value",
37 | "creationTimestamp": "should restore orig value",
38 | "deletionTimestamp": "should restore orig value",
39 | "uid": "should bring back removed value"
40 | },
41 | "other": "new value"
42 | }`
43 |
44 | orig := make(map[string]interface{})
45 | if err := json.Unmarshal([]byte(origJSON), &orig); err != nil {
46 | t.Fatalf("can't unmarshal orig: %v", err)
47 | }
48 | newObj := make(map[string]interface{})
49 | if err := json.Unmarshal([]byte(newObjJSON), &newObj); err != nil {
50 | t.Fatalf("can't unmarshal newObj: %v", err)
51 | }
52 | want := make(map[string]interface{})
53 | if err := json.Unmarshal([]byte(wantJSON), &want); err != nil {
54 | t.Fatalf("can't unmarshal want: %v", err)
55 | }
56 |
57 | err := revertObjectMetaSystemFields(&unstructured.Unstructured{Object: newObj}, &unstructured.Unstructured{Object: orig})
58 | if err != nil {
59 | t.Fatalf("revertObjectMetaSystemFields error: %v", err)
60 | }
61 |
62 | if got := newObj; !reflect.DeepEqual(got, want) {
63 | t.Logf("reflect diff: a=got, b=want:\n%s", diff.ObjectReflectDiff(got, want))
64 | t.Fatalf("revertObjectMetaSystemFields() = %#v, want %#v", got, want)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/controller/composite/hooks.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 composite
18 |
19 | import (
20 | "fmt"
21 |
22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23 |
24 | "metacontroller.app/apis/metacontroller/v1alpha1"
25 | "metacontroller.app/controller/common"
26 | "metacontroller.app/hooks"
27 | )
28 |
29 | // SyncHookRequest is the object sent as JSON to the sync hook.
30 | type SyncHookRequest struct {
31 | Controller *v1alpha1.CompositeController `json:"controller"`
32 | Parent *unstructured.Unstructured `json:"parent"`
33 | Children common.ChildMap `json:"children"`
34 | Finalizing bool `json:"finalizing"`
35 | }
36 |
37 | // SyncHookResponse is the expected format of the JSON response from the sync hook.
38 | type SyncHookResponse struct {
39 | Status map[string]interface{} `json:"status"`
40 | Children []*unstructured.Unstructured `json:"children"`
41 |
42 | ResyncAfterSeconds float64 `json:"resyncAfterSeconds"`
43 |
44 | // Finalized is only used by the finalize hook.
45 | Finalized bool `json:"finalized"`
46 | }
47 |
48 | func callSyncHook(cc *v1alpha1.CompositeController, request *SyncHookRequest) (*SyncHookResponse, error) {
49 | if cc.Spec.Hooks == nil {
50 | return nil, fmt.Errorf("no hooks defined")
51 | }
52 |
53 | var response SyncHookResponse
54 |
55 | // First check if we should instead call the finalize hook,
56 | // which has the same API as the sync hook except that it's
57 | // called while the object is pending deletion.
58 | if request.Parent.GetDeletionTimestamp() != nil && cc.Spec.Hooks.Finalize != nil {
59 | // Finalize
60 | request.Finalizing = true
61 | if err := hooks.Call(cc.Spec.Hooks.Finalize, request, &response); err != nil {
62 | return nil, fmt.Errorf("finalize hook failed: %v", err)
63 | }
64 | } else {
65 | // Sync
66 | request.Finalizing = false
67 | if cc.Spec.Hooks.Sync == nil {
68 | return nil, fmt.Errorf("sync hook not defined")
69 | }
70 |
71 | if err := hooks.Call(cc.Spec.Hooks.Sync, request, &response); err != nil {
72 | return nil, fmt.Errorf("sync hook failed: %v", err)
73 | }
74 | }
75 |
76 | return &response, nil
77 | }
78 |
--------------------------------------------------------------------------------
/controller/decorator/hooks.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 decorator
18 |
19 | import (
20 | "fmt"
21 |
22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23 |
24 | "metacontroller.app/apis/metacontroller/v1alpha1"
25 | "metacontroller.app/controller/common"
26 | "metacontroller.app/hooks"
27 | )
28 |
29 | // SyncHookRequest is the object sent as JSON to the sync hook.
30 | type SyncHookRequest struct {
31 | Controller *v1alpha1.DecoratorController `json:"controller"`
32 | Object *unstructured.Unstructured `json:"object"`
33 | Attachments common.ChildMap `json:"attachments"`
34 | Finalizing bool `json:"finalizing"`
35 | }
36 |
37 | // SyncHookResponse is the expected format of the JSON response from the sync hook.
38 | type SyncHookResponse struct {
39 | Labels map[string]*string `json:"labels"`
40 | Annotations map[string]*string `json:"annotations"`
41 | Status map[string]interface{} `json:"status"`
42 | Attachments []*unstructured.Unstructured `json:"attachments"`
43 |
44 | ResyncAfterSeconds float64 `json:"resyncAfterSeconds"`
45 |
46 | // Finalized is only used by the finalize hook.
47 | Finalized bool `json:"finalized"`
48 | }
49 |
50 | func (c *decoratorController) callSyncHook(request *SyncHookRequest) (*SyncHookResponse, error) {
51 | if c.dc.Spec.Hooks == nil {
52 | return nil, fmt.Errorf("no hooks defined")
53 | }
54 |
55 | var response SyncHookResponse
56 |
57 | // First check if we should instead call the finalize hook,
58 | // which has the same API as the sync hook except that it's
59 | // called while the object is pending deletion.
60 | //
61 | // In addition to finalizing when the object is deleted, we also finalize
62 | // when the object no longer matches our decorator selector.
63 | // This allows the decorator to clean up after itself if the object has been
64 | // updated to disable the functionality added by the decorator.
65 | if c.dc.Spec.Hooks.Finalize != nil &&
66 | (request.Object.GetDeletionTimestamp() != nil || !c.parentSelector.Matches(request.Object)) {
67 | // Finalize
68 | request.Finalizing = true
69 | if err := hooks.Call(c.dc.Spec.Hooks.Finalize, request, &response); err != nil {
70 | return nil, fmt.Errorf("finalize hook failed: %v", err)
71 | }
72 | } else {
73 | // Sync
74 | request.Finalizing = false
75 | if c.dc.Spec.Hooks.Sync == nil {
76 | return nil, fmt.Errorf("sync hook not defined")
77 | }
78 |
79 | if err := hooks.Call(c.dc.Spec.Hooks.Sync, request, &response); err != nil {
80 | return nil, fmt.Errorf("sync hook failed: %v", err)
81 | }
82 | }
83 |
84 | return &response, nil
85 | }
86 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | .sass-cache
3 | .jekyll-metadata
4 |
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 |
18 |
19 |
20 |
404
21 |
22 |
Page not found :(
23 |
The requested page could not be found.
24 |
25 |
--------------------------------------------------------------------------------
/docs/_api/controllerrevision.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ControllerRevision
3 | classes: wide
4 | ---
5 | ControllerRevision is an internal API used by Metacontroller to implement
6 | declarative rolling updates.
7 |
8 | Users of Metacontroller normally shouldn't need to know about this API,
9 | but it is documented here for Metacontroller [contributors](/contrib/),
10 | as well as for [troubleshooting](/guide/troubleshooting/).
11 |
12 | Note that this is different from the ControllerRevision in `apps/v1`,
13 | although it serves a similar purpose.
14 | You will likely need to use a fully-qualified resource name to inspect
15 | Metacontroller's ControllerRevisions:
16 |
17 | ```sh
18 | kubectl get controllerrevisions.metacontroller.k8s.io
19 | ```
20 |
21 | Each ControllerRevision's name is a combination of the name and API group
22 | (excluding the version suffix) of the resource that it's a revision of,
23 | as well as a hash that is deterministic yet unique (used only for idempotent
24 | creation, not for lookup).
25 |
26 | By default, ControllerRevisions belonging to a particular parent instance
27 | will get garbage-collected if the parent is deleted.
28 | However, it is possible to orphan ControllerRevisions during parent
29 | deletion, and then create a replacement parent to adopt them.
30 | ControllerRevisions are adopted based on the parent's label selector,
31 | the same way controllers like ReplicaSet adopt Pods.
32 |
33 | ## Example
34 |
35 | ```yaml
36 | apiVersion: metacontroller.k8s.io/v1alpha1
37 | kind: ControllerRevision
38 | metadata:
39 | name: catsets.ctl.enisoc.com-5463ba99b804a121d35d14a5ab74546d1e8ba953
40 | labels:
41 | app: nginx
42 | component: backend
43 | metacontroller.k8s.io/apiGroup: ctl.enisoc.com
44 | metacontroller.k8s.io/resource: catsets
45 | parentPatch:
46 | spec:
47 | template:
48 | [...]
49 | children:
50 | - apiGroup: ""
51 | kind: Pod
52 | names:
53 | - nginx-backend-0
54 | - nginx-backend-1
55 | - nginx-backend-2
56 | ```
57 |
58 | ## Parent Patch
59 |
60 | The `parentPatch` field stores a partial representation of the parent object
61 | at a given revision, containing only those fields listed by the lambda controller
62 | author as participating in rolling updates.
63 |
64 | For example, if a CompositeController's [revision history][] specifies
65 | a `fieldPaths` list of `["spec.template"]`, the parent patch will contain
66 | only `spec.template` and any subfields nested within it.
67 |
68 | This mirrors the selective behavior of rolling updates in built-in APIs
69 | like Deployment and StatefulSet.
70 | Any fields that aren't part of the parent patch take effect immediately,
71 | rather than rolling out gradually.
72 |
73 | [revision history]: /api/compositecontroller/#revision-history
74 |
75 | ## Children
76 |
77 | The `children` field stores a list of child objects that "belong" to this
78 | particular revision of the parent.
79 |
80 | This is how Metacontroller keeps track of the current desired revision of
81 | a given child.
82 | For example, if a Pod that hasn't been updated yet gets deleted by a Node
83 | drain, it should be replaced at the revision it was on before it got deleted,
84 | not at the latest revision.
85 |
86 | When Metacontroller decides it's time to update a given child to another
87 | revision, it first records this intention by updating the relevant
88 | ControllerRevision objects.
89 | After committing these records, it then begins updating that child according
90 | to the configured [child update strategy](/api/compositecontroller/#child-update-strategy).
91 | This ensures that the intermediate progress of the rollout is persisted
92 | in the API server so it survives process restarts.
93 |
94 | Children are grouped by API Group (excluding the version suffix) and Kind.
95 | For each Group-Kind, we store a list of object names.
96 | Note that parent and children must be in the same namespace,
97 | and ControllerRevisions for a given parent also live in that
98 | parent's namespace.
--------------------------------------------------------------------------------
/docs/_api/hook.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Hook
3 | classes: wide
4 | ---
5 | This page describes how hook targets are defined in various APIs.
6 |
7 | Each hook that you define as part of using one of the hook-based APIs
8 | has the following fields:
9 |
10 | | Field | Description |
11 | | ----- | ----------- |
12 | | [webhook](#webhook) | Specify how to invoke this hook over HTTP(S). |
13 |
14 | ## Example
15 |
16 | ```yaml
17 | webhook:
18 | url: http://my-controller-svc/sync
19 | ```
20 |
21 | ## Webhook
22 |
23 | Each Webhook has the following fields:
24 |
25 | | Field | Description |
26 | | ----- | ----------- |
27 | | url | A full URL for the webhook (e.g. `http://my-controller-svc/hook`). If present, this overrides any values provided for `path` and `service`. |
28 | | timeout | A duration (in the format of Go's time.Duration) indicating the time that Metacontroller should wait for a response. If the webhook takes longer than this time, the webhook call is aborted and retried later. Defaults to 10s. |
29 | | path | A path to be appended to the accompanying `service` to reach this hook (e.g. `/hook`). Ignored if full `url` is specified. |
30 | | [service](#service-reference) | A reference to a Kubernetes Service through which this hook can be reached. |
31 |
32 | ### Service Reference
33 |
34 | Within a `webhook`, the `service` field has the following subfields:
35 |
36 | | Field | Description |
37 | | ----- | ----------- |
38 | | name | The `metadata.name` of the target Service. |
39 | | namespace | The `metadata.namespace` of the target Service. |
40 | | port | The port number to connect to on the target Service. Defaults to `80`. |
41 | | protocol | The protocol to use for the target Service. Defaults to `http`. |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | # Welcome to Jekyll!
2 | #
3 | # This config file is meant for settings that affect your whole blog, values
4 | # which you are expected to set up once and rarely edit after that. If you find
5 | # yourself editing this file very often, consider using Jekyll's data files
6 | # feature for the data you need to update frequently.
7 | #
8 | # For technical reasons, this file is *NOT* reloaded automatically when you use
9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10 |
11 | # Site settings
12 | # These are used to personalize your new site. If you look in the HTML files,
13 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
14 | # You can create any custom variable you would like, and they will be accessible
15 | # in the templates via {{ site.myvariable }}.
16 | title: Metacontroller
17 | title_separator: "-"
18 | locale: en-US
19 | encoding: utf-8
20 | description: Lightweight Kubernetes controllers as a service
21 | baseurl: "" # the subpath of your site, e.g. /blog
22 | url: "" # the base hostname & protocol for your site, e.g. http://example.com
23 | repository: GoogleCloudPlatform/metacontroller
24 | repo_url: https://github.com/GoogleCloudPlatform/metacontroller
25 | repo_dir: https://github.com/GoogleCloudPlatform/metacontroller/tree/master
26 | repo_file: https://github.com/GoogleCloudPlatform/metacontroller/blob/master
27 | repo_raw: https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/master
28 |
29 | breadcrumbs: true
30 | analytics:
31 | provider: google
32 | google:
33 | tracking_id: UA-136881871-1
34 |
35 | # Build settings
36 | markdown: kramdown
37 | highlighter: rouge
38 | theme: minimal-mistakes-jekyll
39 | plugins:
40 | - jekyll-paginate
41 | - jekyll-sitemap
42 | - jekyll-gist
43 | - jekyll-feed
44 | sass:
45 | sass_dir: _sass
46 | style: compressed
47 | timezone: America/Los_Angeles
48 |
49 | collections:
50 | api:
51 | output: true
52 | permalink: /:collection/:path/
53 | guide:
54 | output: true
55 | permalink: /:collection/:path/
56 | contrib:
57 | output: true
58 | permalink: /:collection/:path/
59 | design:
60 | output: true
61 | permalink: /:collection/:path/
62 |
63 | defaults:
64 | - scope:
65 | path: ""
66 | values:
67 | layout: single
68 | toc: true
69 | sidebar:
70 | nav: docs
71 |
72 | include:
73 | - "_redirects"
74 |
--------------------------------------------------------------------------------
/docs/_data/navigation.yml:
--------------------------------------------------------------------------------
1 | main:
2 | - title: GitHub
3 | url: https://github.com/GoogleCloudPlatform/metacontroller
4 | - title: Slack
5 | url: https://kubernetes.slack.com/messages/metacontroller/
6 | - title: Forum
7 | url: https://groups.google.com/forum/#!forum/metacontroller
8 | - title: Announcements
9 | url: https://groups.google.com/forum/#!forum/metacontroller-announce
10 |
11 | docs:
12 | - title: Getting Started
13 | url: /
14 | children:
15 | - title: Introduction
16 | url: /
17 | - title: Examples
18 | url: /examples/
19 | - title: Concepts
20 | url: /concepts/
21 | - title: Features
22 | url: /features/
23 | - title: FAQ
24 | url: /faq/
25 | - title: User Guide
26 | url: /guide/
27 | children:
28 | - title: Install Metacontroller
29 | url: /guide/install/
30 | - title: Create a Controller
31 | url: /guide/create/
32 | - title: Best Practices
33 | url: /guide/best-practices/
34 | - title: Troubleshooting
35 | url: /guide/troubleshooting/
36 | - title: API Reference
37 | url: /api/
38 | children:
39 | - title: CompositeController
40 | url: /api/compositecontroller/
41 | - title: DecoratorController
42 | url: /api/decoratorcontroller/
43 | - title: Contributing
44 | url: /contrib/
45 |
--------------------------------------------------------------------------------
/docs/_design/customize-hook.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Customize Hook
3 | ---
4 | ### Controller changes
5 |
6 | The composite and decorator controller `sync` and `finalize` hooks will contain a new field, `related`.
7 |
8 | This field is in the same format as `children` / `ChildMap`.
9 |
10 | ### Customize Hook
11 |
12 | If the `customize` hook is defined, Metacontroller will ask for which related
13 | objects, or classes of objects that your `sync` and `finalize` hooks need to
14 | know about.
15 |
16 | This is useful for mapping across many objects. One example would be a
17 | controller that lets you specify ConfigMaps to be placed in every Namespace.
18 |
19 | Another use-case is being able to reference other objects, e.g. the `env`
20 | section from a core `Pod` object.
21 |
22 | If you don't define a `customize` hook, then the related section of the hooks will
23 | be empty.
24 |
25 | The `customize` hook will not provide any information about the current state of
26 | the cluster. Thus, the set of related objects may only depend on the state of
27 | the parent object.
28 |
29 | This hook may also accept other fields in future, for other customizations.
30 |
31 | #### Customize Hook Request
32 |
33 | A separate request will be sent for each parent object,
34 | so your hook only needs to think about one parent at a time.
35 |
36 | The body of the request (a POST in the case of a [webhook][])
37 | will be a JSON object with the following fields:
38 |
39 | | Field | Description |
40 | | ----- | ----------- |
41 | | `controller` | The whole CompositeController object, like what you might get from `kubectl get compositecontroller -o json`. |
42 | | `parent` | The parent object, like what you might get from `kubectl get -o json`. |
43 |
44 | #### Customize Hook Response
45 |
46 | The body of your response should be a JSON object with the following fields:
47 |
48 | | Field | Description |
49 | | ----- | ----------- |
50 | | `relatedResources` | A list of JSON objects representing all the desired related resource label selectors. |
51 |
52 | The `relatedResources` field should contain a flat list of objects,
53 | not an associative array.
54 |
55 | Each resource rule object should be a JSON object with the following fields:
56 | | Field | Description |
57 | | ----- | ----------- |
58 | | `apiVersion` | The API `/` of the parent resource, or just `` for core APIs. (e.g. `v1`, `apps/v1`, `batch/v1`) |
59 | | `resource` | The canonical, lowercase, plural name of the parent resource. (e.g. `deployments`, `replicasets`, `statefulsets`) |
60 | | `labelSelector` | A `v1.LabelSelector` object. |
61 | | `namespace` | Optional. The Namespace to select in |
62 | | `names` | Optional. A list of strings, representing individual objects to return |
63 |
64 | If the parent resource is cluster scoped and the related resource is namespaced,
65 | the namespace may be used to restrict which objects to look at. If the parent
66 | resource is namespaced, the related resources must come from the same namespace.
67 | Specifying the namespace is optional, but if specified must match.
68 |
69 | Note that your webhook handler must return a response with a status code of `200`
70 | to be considered successful. Metacontroller will wait for a response for up to the
71 | amount defined in the [Webhook spec](/api/hook/#webhook).
72 |
--------------------------------------------------------------------------------
/docs/_guide/best-practices.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Best Practices
3 | ---
4 | This is a collection of recommendations for writing controllers with Metacontroller.
5 |
6 | If you have something to add to the collection, please send a pull request against
7 | [this document]({{ site.repo_file }}/docs/_guide/best-practices.md).
8 |
9 | ## Lambda Hooks
10 |
11 | ### Apply Semantics
12 |
13 | Because Metacontroller uses [apply semantics](/api/apply/), you don't have to
14 | think about whether a given object needs to be created (because it doesn't exist)
15 | or patched (because it exists and some fields don't match your desired state).
16 | In either case, you should generate a fresh object from scratch with only the
17 | fields you care about filled in.
18 |
19 | For example, suppose you create an object like this:
20 |
21 | ```yaml
22 | apiVersion: example.com/v1
23 | kind: Foo
24 | metadata:
25 | name: my-foo
26 | spec:
27 | importantField: 1
28 | ```
29 |
30 | Then later you decide to change the value of `importantField` to 2.
31 |
32 | Since Kubernetes API objects can be edited by the API server, users, and other
33 | controllers to collaboratively produce emergent behavior, the object you observe
34 | might now look like this:
35 |
36 | ```yaml
37 | apiVersion: example.com/v1
38 | kind: Foo
39 | metadata:
40 | name: my-foo
41 | stuffFilledByAPIServer: blah
42 | spec:
43 | importantField: 1
44 | otherField: 5
45 | ```
46 |
47 | To avoid overwriting the parts of the object you don't care about, you would
48 | ordinarily need to either build a patch or use a retry loop to send
49 | concurrency-safe updates.
50 | With apply semantics, you instead just call your "generate object" function
51 | again with the new values you want, and return this (as JSON):
52 |
53 | ```yaml
54 | apiVersion: example.com/v1
55 | kind: Foo
56 | metadata:
57 | name: my-foo
58 | spec:
59 | importantField: 2
60 | ```
61 |
62 | Metacontroller will take care of merging your change to `importantField` while
63 | preserving the fields you don't care about that were set by others.
64 |
65 | ### Side Effects
66 |
67 | Your hook code should generally be free of side effects whenever possible.
68 | Ideally, you should interpret a call to your hook as asking,
69 | "Hypothetically, if the observed state of the world were like this, what would
70 | your desired state be?"
71 |
72 | In particular, Metacontroller may ask you about such hypothetical scenarios
73 | during rolling updates, when your object is undergoing a slow transition between
74 | two desired states.
75 | If your hook has to produce side effects to work, you should avoid enabling
76 | rolling updates on that controller.
77 |
78 | ### Status
79 |
80 | If your object uses the Spec/Status convention, keep in mind that the Status
81 | returned from your hook should ideally reflect a judgement on only the observed
82 | objects that were sent to you.
83 | The Status you compute should not yet account for your desired state, because
84 | the actual state of the world may not match what you want yet.
85 |
86 | For example, if you observe 2 Pods, but you return a desired list of 3 Pods,
87 | you should return a Status that reflects only the observed Pods
88 | (e.g. `replicas: 2`).
89 | This is important so that Status reflects present reality, not future desires.
90 |
--------------------------------------------------------------------------------
/docs/_guide/install.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | classes: wide
4 | toc: false
5 | ---
6 | This page describes how to install Metacontroller, either to develop your own
7 | controllers or just to run third-party controllers that depend on it.
8 |
9 | ## Prerequisites
10 |
11 | * Kubernetes v1.9+
12 | * You should have `kubectl` available and configured to talk to the desired cluster.
13 |
14 | ### Grant yourself cluster-admin (GKE only)
15 |
16 | Due to a [known issue](https://cloud.google.com/container-engine/docs/role-based-access-control#defining_permissions_in_a_role)
17 | in GKE, you'll need to first grant yourself `cluster-admin` privileges before
18 | you can install the necessary RBAC manifests.
19 |
20 | ```sh
21 | kubectl create clusterrolebinding -cluster-admin-binding --clusterrole=cluster-admin --user=@
22 | ```
23 |
24 | Replace `` and `` above based on the account you use to authenticate to GKE.
25 |
26 | ## Install Metacontroller
27 |
28 | ```sh
29 | # Create metacontroller namespace.
30 | kubectl create namespace metacontroller
31 | # Create metacontroller service account and role/binding.
32 | kubectl apply -f {{ site.repo_raw }}/manifests/metacontroller-rbac.yaml
33 | # Create CRDs for Metacontroller APIs, and the Metacontroller StatefulSet.
34 | kubectl apply -f {{ site.repo_raw }}/manifests/metacontroller.yaml
35 | ```
36 |
37 | If you prefer to build and host your own images, please see the
38 | [build instructions](/contrib/build/) in the contributor guide.
39 |
40 | ## Configuration
41 |
42 | The Metacontroller server has a few settings that can be configured
43 | with command-line flags (by editing the Metacontroller StatefulSet
44 | in `manifests/metacontroller.yaml`):
45 |
46 | | Flag | Description |
47 | | ---- | ----------- |
48 | | `-v` | Set the logging verbosity level (e.g. `-v=4`). Level 4 logs Metacontroller's interaction with the API server. Levels 5 and up additionally log details of Metacontroller's invocation of lambda hooks. See the [troubleshooting guide](/guide/troubleshooting/) for more. |
49 | | `--discovery-interval` | How often to refresh discovery cache to pick up newly-installed resources (e.g. `--discovery-interval=10s`). |
50 | | `--cache-flush-interval` | How often to flush local caches and relist objects from the API server (e.g. `--cache-flush-interval=30m`). |
51 |
--------------------------------------------------------------------------------
/docs/_guide/troubleshooting.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Troubleshooting
3 | ---
4 | This is a collection of tips for debugging controllers written with Metacontroller.
5 |
6 | If you have something to add to the collection, please send a pull request against
7 | [this document]({{ site.repo_file }}/docs/guide/troubleshooting.md).
8 |
9 | ## Metacontroller Logs
10 |
11 | Until Metacontroller [emits events]({{ site.repo_url }}//issues/7),
12 | the first place to look when troubleshooting controller behavior is the logs for
13 | the Metacontroller server itself.
14 |
15 | For example, you can fetch the last 25 lines with a command like this:
16 |
17 | ```sh
18 | kubectl -n metacontroller logs --tail=25 -l app=metacontroller
19 | ```
20 |
21 | ### Log Levels
22 |
23 | You can customize the verbosity of the Metacontroller server's logs with the
24 | `-v=N` flag, where `N` is the log level.
25 |
26 | At all log levels, Metacontroller will log the progress of server startup and
27 | shutdown, as well as major changes like starting and stopping hosted controllers.
28 |
29 | At level 4 and above, Metacontroller will log actions (like create/update/delete)
30 | on individual objects (like Pods) that it takes on behalf of hosted controllers.
31 | It will also log when it decides to sync a given controller as well as events
32 | that may trigger a sync.
33 |
34 | At level 5 and above, Metacontroller will log the diffs between existing objects
35 | and the desired state of those objects returned by controller hooks.
36 |
37 | At level 6 and above, Metacontroller will log every hook invocation as well as
38 | the JSON request and response bodies.
39 |
40 | ### Common Log Messages
41 |
42 | Since API discovery info is refreshed periodically, you may see log messages
43 | like this when you start a controller that depends on a recently-installed CRD:
44 |
45 | ```
46 | failed to sync CompositeController "my-controller": discovery: can't find resource in apiVersion /
47 | ```
48 |
49 | Usually, this should fix itself within about 30s when the new CRD is discovered.
50 | If this message continues indefinitely, check that the resource name and API
51 | group/version are correct.
52 |
53 | You may also notice periodic log messages like this:
54 |
55 | ```
56 | Watch close - *unstructured.Unstructured total items received
57 | ```
58 |
59 | This comes from the underlying client-go library, and just indicates when the
60 | shared caches are periodically flushed to place an upper bound on cache
61 | inconsistency due to potential silent failures in long-running watches.
62 |
63 | ## Webhook Logs
64 |
65 | If you return an HTTP error code (e.g. 500) from your webhook,
66 | the Metacontroller server will log the text of the response body.
67 |
68 | If you need more detail on what's happening inside your hook code, as opposed to
69 | what Metacontroller does for you, you'll need to add log statements to your own
70 | code and inspect the logs on your webhook server.
71 |
--------------------------------------------------------------------------------
/docs/_includes/footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/_redirects:
--------------------------------------------------------------------------------
1 | https://metacontroller.netlify.com/* https://metacontroller.app/:splat 301!
2 | /* go-get=1 /go-import.html 200!
3 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: API Reference
3 | layout: collection
4 | collection: api
5 | sort_by: title
6 | permalink: /api/
7 | classes: wide
8 | ---
9 | This section contains detailed reference information for the APIs offered by Metacontroller.
10 |
11 | See the [user guide](/guide/) for introductions and step-by-step walkthroughs.
--------------------------------------------------------------------------------
/docs/assets/css/main.scss:
--------------------------------------------------------------------------------
1 | ---
2 | # Only the main Sass file needs front matter (the dashes are enough)
3 | ---
4 |
5 | @charset "utf-8";
6 |
7 | @import "minimal-mistakes/skins/{{ site.minimal_mistakes_skin | default: 'default' }}"; // skin
8 | @import "minimal-mistakes"; // main partials
9 |
10 | .page__content p,
11 | .page__content li,
12 | .archive p {
13 | font-size: .8em;
14 | }
15 |
16 | .masthead__menu-item {
17 | font-size: 20px;
18 | }
19 |
20 | .nav-tag {
21 | font-size: 80%;
22 | }
23 |
24 | a {
25 | text-decoration: none;
26 | }
27 |
28 | .page__content code {
29 | white-space: pre;
30 | }
--------------------------------------------------------------------------------
/docs/contrib.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Contributor Guide
3 | layout: collection
4 | collection: contrib
5 | sort_by: title
6 | permalink: /contrib/
7 | classes: wide
8 | ---
9 | This section contains information for people who want to hack on or
10 | contribute to Metacontroller.
11 |
12 | See the [User Guide](/guide/) if you just want to use Metacontroller.
13 |
14 | ## GitHub
15 |
16 | * [Issues]({{ site.repo_url }}/issues)
17 | * [Project Boards]({{ site.repo_url }}/projects)
18 | * [Roadmap]({{ site.repo_url }}/issues/9)
19 |
--------------------------------------------------------------------------------
/docs/design.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Design Docs
3 | layout: collection
4 | collection: design
5 | sort_by: title
6 | permalink: /design/
7 | ---
8 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Examples
3 | permalink: /examples/
4 | ---
5 | This page lists some examples of what you can make with Metacontroller.
6 |
7 | If you'd like to add a link to another example that demonstrates a new
8 | language or technique, please send a pull request against
9 | [this document]({{ site.repo_file }}/docs/examples.md).
10 |
11 | ## CompositeController
12 |
13 | [CompositeController](/api/compositecontroller/)
14 | is an API provided by Metacontroller, designed to facilitate
15 | custom controllers whose primary purpose is to manage a set of child objects
16 | based on the desired state specified in a parent object.
17 | Workload controllers like Deployment and StatefulSet are examples of existing
18 | controllers that fit this pattern.
19 |
20 | ### CatSet (JavaScript)
21 |
22 | [CatSet]({{ site.repo_dir }}/examples/catset) is a rewrite of
23 | [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/),
24 | including rolling updates, as a CompositeController.
25 | It shows that existing workload controllers already use a pattern that could
26 | fit within a CompositeController, namely managing child objects based on a
27 | parent spec.
28 |
29 | ### BlueGreenDeployment (JavaScript)
30 |
31 | [BlueGreenDeployment]({{ site.repo_dir }}/examples/bluegreen)
32 | is an alternative to [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
33 | that implements a [Blue-Green](https://martinfowler.com/bliki/BlueGreenDeployment.html)
34 | rollout strategy.
35 | It shows how CompositeController can be used to add various automation on top
36 | of built-in APIs like ReplicaSet.
37 |
38 | ### IndexedJob (Python)
39 |
40 | [IndexedJob]({{ site.repo_dir }}/examples/indexedjob)
41 | is an alternative to [Job](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)
42 | that gives each Pod a unique index, like StatefulSet.
43 | It shows how to write a CompositeController in Python, and also demonstrates
44 | [selector generation](/api/compositecontroller/#selector-generation).
45 |
46 | ### Vitess Operator (Jsonnet)
47 |
48 | The [Vitess Operator]({{ site.repo_dir }}/examples/vitess)
49 | is an example of using Metacontroller to write an Operator for a complex
50 | stateful application, in this case [Vitess](https://vitess.io).
51 | It shows how CompositeController can be layered to handle complex systems
52 | by breaking them down.
53 |
54 | ## DecoratorController
55 |
56 | [DecoratorController](/api/decoratorcontroller/)
57 | is an API provided by Metacontroller, designed to facilitate
58 | adding new behavior to existing resources. You can define rules for which
59 | resources to watch, as well as filters on labels and annotations.
60 |
61 | For each object you watch, you can add, edit, or remove labels and annotations,
62 | as well as create new objects and attach them. Unlike CompositeController,
63 | these new objects don't have to match the main object's label selector.
64 | Since they're attached to the main object, they'll be cleaned up automatically
65 | when the main object is deleted.
66 |
67 | ### Service Per Pod (Jsonnet)
68 |
69 | [Service Per Pod]({{ site.repo_dir }}/examples/service-per-pod)
70 | is an example DecoratorController that creates an individual Service for
71 | every Pod in a StatefulSet (e.g. to give them static IPs), effectively adding
72 | new behavior to StatefulSet without having to reimplement it.
--------------------------------------------------------------------------------
/docs/go-import.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: User Guide
3 | layout: collection
4 | collection: guide
5 | sort_by: title
6 | permalink: /guide/
7 | classes: wide
8 | ---
9 | This section contains general tips and step-by-step tutorials for using Metacontroller.
10 |
11 | See the [API Reference](/api/) for details about all the available options.
12 |
--------------------------------------------------------------------------------
/docs/pronunciation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: How to pronounce Metacontroller
3 | permalink: /pronunciation/
4 | ---
5 | *Metacontroller* is pronounced as *me-ta-con-trol-ler*.
--------------------------------------------------------------------------------
/dynamic/controllerref/controller_ref.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 controllerref
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | "k8s.io/apimachinery/pkg/types"
22 | )
23 |
24 | func addOwnerReference(in []metav1.OwnerReference, add metav1.OwnerReference) []metav1.OwnerReference {
25 | out := make([]metav1.OwnerReference, 0, len(in)+1)
26 | found := false
27 | for _, ref := range in {
28 | if ref.UID == add.UID {
29 | // We already own this. Update other fields as needed.
30 | out = append(out, add)
31 | found = true
32 | continue
33 | }
34 | out = append(out, ref)
35 | }
36 | if !found {
37 | // Add ourselves to the list.
38 | // Note that server-side validation is responsible for ensuring only one ControllerRef.
39 | out = append(out, add)
40 | }
41 | return out
42 | }
43 |
44 | func removeOwnerReference(in []metav1.OwnerReference, uid types.UID) []metav1.OwnerReference {
45 | out := make([]metav1.OwnerReference, 0, len(in))
46 | for _, ref := range in {
47 | if ref.UID != uid {
48 | out = append(out, ref)
49 | }
50 | }
51 | return out
52 | }
53 |
--------------------------------------------------------------------------------
/dynamic/lister/lister.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 lister
18 |
19 | import (
20 | "fmt"
21 |
22 | "k8s.io/apimachinery/pkg/api/errors"
23 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24 | "k8s.io/apimachinery/pkg/labels"
25 | "k8s.io/apimachinery/pkg/runtime/schema"
26 | "k8s.io/client-go/tools/cache"
27 | )
28 |
29 | type Lister struct {
30 | indexer cache.Indexer
31 | groupResource schema.GroupResource
32 | }
33 |
34 | func New(groupResource schema.GroupResource, indexer cache.Indexer) *Lister {
35 | return &Lister{
36 | groupResource: groupResource,
37 | indexer: indexer,
38 | }
39 | }
40 |
41 | func (l *Lister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
42 | err = cache.ListAll(l.indexer, selector, func(obj interface{}) {
43 | ret = append(ret, obj.(*unstructured.Unstructured))
44 | })
45 | return ret, err
46 | }
47 |
48 | func (l *Lister) ListNamespace(namespace string, selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
49 | err = cache.ListAllByNamespace(l.indexer, namespace, selector, func(obj interface{}) {
50 | ret = append(ret, obj.(*unstructured.Unstructured))
51 | })
52 | return ret, err
53 | }
54 |
55 | func (l *Lister) Get(namespace, name string) (*unstructured.Unstructured, error) {
56 | key := name
57 | if namespace != "" {
58 | key = fmt.Sprintf("%s/%s", namespace, name)
59 | }
60 | obj, exists, err := l.indexer.GetByKey(key)
61 | if err != nil {
62 | return nil, err
63 | }
64 | if !exists {
65 | return nil, errors.NewNotFound(l.groupResource, name)
66 | }
67 | return obj.(*unstructured.Unstructured), nil
68 | }
69 |
--------------------------------------------------------------------------------
/dynamic/object/metadata.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 object
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | )
22 |
23 | // HasFinalizer returns true if obj has the named finalizer.
24 | func HasFinalizer(obj metav1.Object, name string) bool {
25 | for _, item := range obj.GetFinalizers() {
26 | if item == name {
27 | return true
28 | }
29 | }
30 | return false
31 | }
32 |
33 | // AddFinalizer adds the named finalizer to obj, if it isn't already present.
34 | func AddFinalizer(obj metav1.Object, name string) {
35 | if HasFinalizer(obj, name) {
36 | // It's already present, so there's nothing to do.
37 | return
38 | }
39 | obj.SetFinalizers(append(obj.GetFinalizers(), name))
40 | }
41 |
42 | // RemoveFinalizer removes the named finalizer from obj, if it's present.
43 | func RemoveFinalizer(obj metav1.Object, name string) {
44 | finalizers := obj.GetFinalizers()
45 | for i, item := range finalizers {
46 | if item == name {
47 | obj.SetFinalizers(append(finalizers[:i], finalizers[i+1:]...))
48 | return
49 | }
50 | }
51 | // We never found it, so it's already gone and there's nothing to do.
52 | }
53 |
--------------------------------------------------------------------------------
/dynamic/object/status.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 object
18 |
19 | import (
20 | k8s "metacontroller.app/third_party/kubernetes"
21 | )
22 |
23 | type StatusCondition struct {
24 | Type string `json:"type"`
25 | Status string `json:"status"`
26 | Reason string `json:"reason,omitempty"`
27 | Message string `json:"message,omitempty"`
28 | }
29 |
30 | func (c *StatusCondition) Object() map[string]interface{} {
31 | obj := map[string]interface{}{
32 | "type": c.Type,
33 | "status": c.Status,
34 | }
35 | if c.Reason != "" {
36 | obj["reason"] = c.Reason
37 | }
38 | if c.Message != "" {
39 | obj["message"] = c.Message
40 | }
41 | return obj
42 | }
43 |
44 | func NewStatusCondition(obj map[string]interface{}) *StatusCondition {
45 | cond := &StatusCondition{}
46 | if ctype, ok := obj["type"].(string); ok {
47 | cond.Type = ctype
48 | }
49 | if cstatus, ok := obj["status"].(string); ok {
50 | cond.Status = cstatus
51 | }
52 | if creason, ok := obj["reason"].(string); ok {
53 | cond.Reason = creason
54 | }
55 | if cmessage, ok := obj["message"].(string); ok {
56 | cond.Message = cmessage
57 | }
58 | return cond
59 | }
60 |
61 | func GetStatusCondition(obj map[string]interface{}, conditionType string) *StatusCondition {
62 | conditions := k8s.GetNestedArray(obj, "status", "conditions")
63 | for _, item := range conditions {
64 | if obj, ok := item.(map[string]interface{}); ok {
65 | if ctype, ok := obj["type"].(string); ok && ctype == conditionType {
66 | return NewStatusCondition(obj)
67 | }
68 | }
69 | }
70 | return nil
71 | }
72 |
73 | func SetCondition(status map[string]interface{}, condition *StatusCondition) {
74 | conditions := k8s.GetNestedArray(status, "conditions")
75 | // If the condition is already there, update it.
76 | for i, item := range conditions {
77 | if cobj, ok := item.(map[string]interface{}); ok {
78 | if ctype, ok := cobj["type"].(string); ok && ctype == condition.Type {
79 | conditions[i] = condition.Object()
80 | return
81 | }
82 | }
83 | }
84 | // The condition wasn't found. Append it.
85 | conditions = append(conditions, condition.Object())
86 | k8s.SetNestedField(status, conditions, "conditions")
87 | }
88 |
89 | func SetStatusCondition(obj map[string]interface{}, condition *StatusCondition) {
90 | status := k8s.GetNestedObject(obj, "status")
91 | if status == nil {
92 | status = make(map[string]interface{})
93 | }
94 | SetCondition(status, condition)
95 | k8s.SetNestedField(obj, status, "status")
96 | }
97 |
98 | func GetObservedGeneration(obj map[string]interface{}) int64 {
99 | return k8s.GetNestedInt64(obj, "status", "observedGeneration")
100 | }
101 |
--------------------------------------------------------------------------------
/examples/bluegreen/README.md:
--------------------------------------------------------------------------------
1 | ## BlueGreenDeployment
2 |
3 | This is an example CompositeController that implements a custom rollout strategy
4 | based on a technique called Blue-Green Deployment.
5 |
6 | The controller ramps up a completely separate ReplicaSet in the background for any change to the
7 | Pod template. It then waits for the new ReplicaSet to be fully Ready and Available
8 | (all Pods satisfy minReadySeconds), and then switches a Service to point to the new ReplicaSet.
9 | Finally, it scales down the old ReplicaSet.
10 |
11 | ### Prerequisites
12 |
13 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
14 |
15 | ### Deploy the controller
16 |
17 | ```sh
18 | kubectl create configmap bluegreen-controller -n metacontroller --from-file=sync.js
19 | kubectl apply -f bluegreen-controller.yaml
20 | ```
21 |
22 | ### Create a BlueGreenDeployment
23 |
24 | ```sh
25 | kubectl apply -f my-bluegreen.yaml
26 | ```
27 |
--------------------------------------------------------------------------------
/examples/bluegreen/bluegreen-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: bluegreendeployments.ctl.enisoc.com
5 | spec:
6 | group: ctl.enisoc.com
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: bluegreendeployments
11 | singular: bluegreendeployment
12 | kind: BlueGreenDeployment
13 | shortNames:
14 | - bgd
15 | subresources:
16 | status: {}
17 | ---
18 | apiVersion: metacontroller.k8s.io/v1alpha1
19 | kind: CompositeController
20 | metadata:
21 | name: bluegreen-controller
22 | spec:
23 | parentResource:
24 | apiVersion: ctl.enisoc.com/v1
25 | resource: bluegreendeployments
26 | childResources:
27 | - apiVersion: v1
28 | resource: services
29 | updateStrategy:
30 | method: InPlace
31 | - apiVersion: extensions/v1beta1
32 | resource: replicasets
33 | updateStrategy:
34 | method: InPlace
35 | hooks:
36 | sync:
37 | webhook:
38 | url: http://bluegreen-controller.metacontroller/sync
39 | ---
40 | apiVersion: apps/v1beta1
41 | kind: Deployment
42 | metadata:
43 | name: bluegreen-controller
44 | namespace: metacontroller
45 | spec:
46 | replicas: 1
47 | selector:
48 | matchLabels:
49 | app: bluegreen-controller
50 | template:
51 | metadata:
52 | labels:
53 | app: bluegreen-controller
54 | spec:
55 | containers:
56 | - name: controller
57 | image: metacontroller/nodejs-server:0.1
58 | imagePullPolicy: Always
59 | volumeMounts:
60 | - name: hooks
61 | mountPath: /node/hooks
62 | volumes:
63 | - name: hooks
64 | configMap:
65 | name: bluegreen-controller
66 | ---
67 | apiVersion: v1
68 | kind: Service
69 | metadata:
70 | name: bluegreen-controller
71 | namespace: metacontroller
72 | spec:
73 | selector:
74 | app: bluegreen-controller
75 | ports:
76 | - port: 80
77 |
--------------------------------------------------------------------------------
/examples/bluegreen/my-bluegreen.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: ctl.enisoc.com/v1
2 | kind: BlueGreenDeployment
3 | metadata:
4 | name: nginx
5 | labels:
6 | app: nginx
7 | spec:
8 | replicas: 3
9 | minReadySeconds: 5
10 | selector:
11 | matchLabels:
12 | app: nginx
13 | component: frontend
14 | template:
15 | metadata:
16 | labels:
17 | app: nginx
18 | component: frontend
19 | spec:
20 | containers:
21 | - name: nginx
22 | image: nginx:1.7.9
23 | ports:
24 | - containerPort: 80
25 | service:
26 | metadata:
27 | name: nginx-frontend
28 | labels:
29 | app: nginx
30 | component: frontend
31 | spec:
32 | selector:
33 | app: nginx
34 | component: frontend
35 | ports:
36 | - port: 80
37 |
--------------------------------------------------------------------------------
/examples/bluegreen/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-bluegreen.yaml
7 | kubectl delete rs,svc -l app=nginx,component=frontend
8 | kubectl delete -f bluegreen-controller.yaml
9 | kubectl delete configmap bluegreen-controller -n metacontroller
10 | }
11 | trap cleanup EXIT
12 |
13 | set -ex
14 |
15 | bgd="bluegreendeployments"
16 |
17 | echo "Install controller..."
18 | kubectl create configmap bluegreen-controller -n metacontroller --from-file=sync.js
19 | kubectl apply -f bluegreen-controller.yaml
20 |
21 | echo "Wait until CRD is available..."
22 | until kubectl get $bgd; do sleep 1; done
23 |
24 | echo "Create an object..."
25 | kubectl apply -f my-bluegreen.yaml
26 |
27 | # TODO(juntee): change observedGeneration steps to compare against generation number when k8s 1.10 and earlier retire.
28 |
29 | echo "Wait for nginx-blue RS to be active..."
30 | until [[ "$(kubectl get rs nginx-blue -o 'jsonpath={.status.readyReplicas}')" -eq 3 ]]; do sleep 1; done
31 | until [[ "$(kubectl get rs nginx-green -o 'jsonpath={.status.replicas}')" -eq 0 ]]; do sleep 1; done
32 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.activeColor}')" -eq "blue" ]]; do sleep 1; done
33 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.active.availableReplicas}')" -eq 3 ]]; do sleep 1; done
34 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.observedGeneration}')" -eq "$(kubectl get $bgd nginx -o 'jsonpath={.metadata.generation}')" ]]; do sleep 1; done
35 |
36 |
37 | echo "Trigger a rollout..."
38 | kubectl patch $bgd nginx --type=merge -p '{"spec":{"template":{"metadata":{"labels":{"new":"label"}}}}}'
39 |
40 | echo "Wait for nginx-green RS to be active..."
41 | until [[ "$(kubectl get rs nginx-green -o 'jsonpath={.status.readyReplicas}')" -eq 3 ]]; do sleep 1; done
42 | until [[ "$(kubectl get rs nginx-blue -o 'jsonpath={.status.replicas}')" -eq 0 ]]; do sleep 1; done
43 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.activeColor}')" -eq "green" ]]; do sleep 1; done
44 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.active.availableReplicas}')" -eq 3 ]]; do sleep 1; done
45 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.observedGeneration}')" -eq "$(kubectl get $bgd nginx -o 'jsonpath={.metadata.generation}')" ]]; do sleep 1; done
46 |
47 | echo "Trigger another rollout..."
48 | kubectl patch $bgd nginx --type=merge -p '{"spec":{"template":{"metadata":{"labels":{"new2":"label2"}}}}}'
49 |
50 | echo "Wait for nginx-blue RS to be active..."
51 | until [[ "$(kubectl get rs nginx-blue -o 'jsonpath={.status.readyReplicas}')" -eq 3 ]]; do sleep 1; done
52 | until [[ "$(kubectl get rs nginx-green -o 'jsonpath={.status.replicas}')" -eq 0 ]]; do sleep 1; done
53 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.activeColor}')" -eq "blue" ]]; do sleep 1; done
54 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.active.availableReplicas}')" -eq 3 ]]; do sleep 1; done
55 | until [[ "$(kubectl get $bgd nginx -o 'jsonpath={.status.observedGeneration}')" -eq "$(kubectl get $bgd nginx -o 'jsonpath={.metadata.generation}')" ]]; do sleep 1; done
--------------------------------------------------------------------------------
/examples/catset/README.md:
--------------------------------------------------------------------------------
1 | ## CatSet
2 |
3 | This is a reimplementation of StatefulSet (now including rolling updates) as a CompositeController.
4 |
5 | CatSet also demonstrates using a finalizer with a lambda hook to support
6 | graceful, ordered teardown when the parent object is deleted.
7 | Unlike StatefulSet, which previously exhibited this behavior only because of a
8 | client-side kubectl feature, CatSet ordered teardown happens on the server side,
9 | so it works when the CatSet is deleted through any means (not just kubectl).
10 |
11 | For this example, you need a cluster with a default storage class and a dynamic provisioner.
12 |
13 | ### Prerequisites
14 |
15 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
16 |
17 | ### Deploy the controller
18 |
19 | ```sh
20 | kubectl create configmap catset-controller -n metacontroller --from-file=sync.js
21 | kubectl apply -f catset-controller.yaml
22 | ```
23 |
24 | ### Create a CatSet
25 |
26 | ```sh
27 | kubectl apply -f my-catset.yaml
28 | ```
29 |
--------------------------------------------------------------------------------
/examples/catset/catset-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: catsets.ctl.enisoc.com
5 | spec:
6 | group: ctl.enisoc.com
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: catsets
11 | singular: catset
12 | kind: CatSet
13 | shortNames:
14 | - cs
15 | subresources:
16 | status: {}
17 | ---
18 | apiVersion: metacontroller.k8s.io/v1alpha1
19 | kind: CompositeController
20 | metadata:
21 | name: catset-controller
22 | spec:
23 | parentResource:
24 | apiVersion: ctl.enisoc.com/v1
25 | resource: catsets
26 | revisionHistory:
27 | fieldPaths:
28 | - spec.template
29 | childResources:
30 | - apiVersion: v1
31 | resource: pods
32 | updateStrategy:
33 | method: RollingRecreate
34 | statusChecks:
35 | conditions:
36 | - type: Ready
37 | status: "True"
38 | - apiVersion: v1
39 | resource: persistentvolumeclaims
40 | hooks:
41 | sync:
42 | webhook:
43 | url: http://catset-controller.metacontroller/sync
44 | finalize:
45 | webhook:
46 | url: http://catset-controller.metacontroller/sync
47 | ---
48 | apiVersion: apps/v1beta1
49 | kind: Deployment
50 | metadata:
51 | name: catset-controller
52 | namespace: metacontroller
53 | spec:
54 | replicas: 1
55 | selector:
56 | matchLabels:
57 | app: catset-controller
58 | template:
59 | metadata:
60 | labels:
61 | app: catset-controller
62 | spec:
63 | containers:
64 | - name: controller
65 | image: metacontroller/nodejs-server:0.1
66 | imagePullPolicy: Always
67 | volumeMounts:
68 | - name: hooks
69 | mountPath: /node/hooks
70 | volumes:
71 | - name: hooks
72 | configMap:
73 | name: catset-controller
74 | ---
75 | apiVersion: v1
76 | kind: Service
77 | metadata:
78 | name: catset-controller
79 | namespace: metacontroller
80 | spec:
81 | selector:
82 | app: catset-controller
83 | ports:
84 | - port: 80
85 |
--------------------------------------------------------------------------------
/examples/catset/my-catset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: nginx-backend
5 | spec:
6 | ports:
7 | - port: 80
8 | name: web
9 | clusterIP: None
10 | selector:
11 | app: nginx
12 | ---
13 | apiVersion: ctl.enisoc.com/v1
14 | kind: CatSet
15 | metadata:
16 | name: nginx-backend
17 | spec:
18 | serviceName: nginx-backend
19 | replicas: 3
20 | selector:
21 | matchLabels:
22 | app: nginx
23 | template:
24 | metadata:
25 | labels:
26 | app: nginx
27 | component: backend
28 | spec:
29 | terminationGracePeriodSeconds: 1
30 | containers:
31 | - name: nginx
32 | image: gcr.io/google_containers/nginx-slim:0.8
33 | ports:
34 | - containerPort: 80
35 | name: web
36 | volumeMounts:
37 | - name: www
38 | mountPath: /usr/share/nginx/html
39 | volumeClaimTemplates:
40 | - metadata:
41 | name: www
42 | labels:
43 | app: nginx
44 | component: backend
45 | spec:
46 | accessModes: [ "ReadWriteOnce" ]
47 | resources:
48 | requests:
49 | storage: 1Gi
50 |
--------------------------------------------------------------------------------
/examples/catset/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl patch $cs nginx-backend --type=merge -p '{"metadata":{"finalizers":[]}}'
7 | kubectl delete -f my-catset.yaml
8 | kubectl delete po,pvc -l app=nginx,component=backend
9 | kubectl delete -f catset-controller.yaml
10 | kubectl delete configmap catset-controller -n metacontroller
11 | }
12 | trap cleanup EXIT
13 |
14 | set -ex
15 |
16 | cs="catsets"
17 | finalizer="metacontroller.app/catset-test"
18 |
19 | echo "Install controller..."
20 | kubectl create configmap catset-controller -n metacontroller --from-file=sync.js
21 | kubectl apply -f catset-controller.yaml
22 |
23 | echo "Wait until CRD is available..."
24 | until kubectl get $cs; do sleep 1; done
25 |
26 | echo "Create an object..."
27 | kubectl apply -f my-catset.yaml
28 |
29 | echo "Wait for 3 Pods to be Ready..."
30 | until [[ "$(kubectl get $cs nginx-backend -o 'jsonpath={.status.readyReplicas}')" -eq 3 ]]; do sleep 1; done
31 |
32 | echo "Scale up to 4 replicas..."
33 | kubectl patch $cs nginx-backend --type=merge -p '{"spec":{"replicas":4}}'
34 |
35 | echo "Wait for 4 Pods to be Ready..."
36 | until [[ "$(kubectl get $cs nginx-backend -o 'jsonpath={.status.readyReplicas}')" -eq 4 ]]; do sleep 1; done
37 |
38 | echo "Scale down to 2 replicas..."
39 | kubectl patch $cs nginx-backend --type=merge -p '{"spec":{"replicas":2}}'
40 |
41 | echo "Wait for 2 Pods to be Ready..."
42 | until [[ "$(kubectl get $cs nginx-backend -o 'jsonpath={.status.readyReplicas}')" -eq 2 ]]; do sleep 1; done
43 |
44 | echo "Append our own finalizer so we can read the final state..."
45 | kubectl patch $cs nginx-backend --type=json -p '[{"op":"add","path":"/metadata/finalizers/-","value":"'${finalizer}'"}]'
46 |
47 | echo "Delete CatSet..."
48 | kubectl delete $cs nginx-backend --wait=false
49 |
50 | echo "Expect CatSet's finalizer to scale the CatSet to 0 replicas..."
51 | until [[ "$(kubectl get $cs nginx-backend -o 'jsonpath={.status.replicas}')" -eq 0 ]]; do sleep 1; done
52 |
53 | echo "Wait for our finalizer to be the only one left, then remove it..."
54 | until [[ "$(kubectl get $cs nginx-backend -o 'jsonpath={.metadata.finalizers}')" == "[${finalizer}]" ]]; do sleep 1; done
55 | kubectl patch $cs nginx-backend --type=merge -p '{"metadata":{"finalizers":[]}}'
56 |
--------------------------------------------------------------------------------
/examples/clusteredparent/README.md:
--------------------------------------------------------------------------------
1 | ## ClusterRole service account binding
2 |
3 | This is an example DecoratorController that creates a namespaced resources from a
4 | cluster scoped parent resource.
5 |
6 | This controller will bind any ClusterRole with the "default-service-account-binding"
7 | annotation to the default service account in the default namespace.
8 |
9 | ### Prerequisites
10 |
11 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
12 |
13 | ### Deploy the controller
14 |
15 | ```sh
16 | kubectl create configmap cluster-parent-controller -n metacontroller --from-file=sync.py
17 | kubectl apply -f cluster-parent.yaml
18 | ```
19 |
20 | ### Create a ClusterRole
21 |
22 | ```sh
23 | kubectl apply -f my-clusterole.yaml
24 | ```
25 |
26 | A RoleBinding should be created for the ClusterRole:
27 |
28 | ```console
29 | $ kubectl get rolebinding -n default my-clusterrole -o wide
30 | NAME AGE ROLE USERS GROUPS SERVICEACCOUNTS
31 | my-clusterrole 40s ClusterRole/my-clusterrole default/default
32 | ```
33 |
--------------------------------------------------------------------------------
/examples/clusteredparent/cluster-parent.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: metacontroller.k8s.io/v1alpha1
2 | kind: DecoratorController
3 | metadata:
4 | name: cluster-parent
5 | spec:
6 | resources:
7 | - apiVersion: rbac.authorization.k8s.io/v1
8 | resource: clusterroles
9 | annotationSelector:
10 | matchExpressions:
11 | - {key: default-service-account-binding, operator: Exists}
12 | attachments:
13 | - apiVersion: rbac.authorization.k8s.io/v1
14 | resource: rolebindings
15 | hooks:
16 | sync:
17 | webhook:
18 | url: http://cluster-parent-controller.metacontroller/sync
19 | ---
20 | apiVersion: apps/v1beta1
21 | kind: Deployment
22 | metadata:
23 | name: clusterparent-controller
24 | namespace: metacontroller
25 | spec:
26 | replicas: 1
27 | selector:
28 | matchLabels:
29 | app: cluster-parent-controller
30 | template:
31 | metadata:
32 | labels:
33 | app: cluster-parent-controller
34 | spec:
35 | containers:
36 | - name: controller
37 | image: python:2.7
38 | command: ["python", "/hooks/sync.py"]
39 | volumeMounts:
40 | - name: hooks
41 | mountPath: /hooks
42 | volumes:
43 | - name: hooks
44 | configMap:
45 | name: cluster-parent-controller
46 | ---
47 | apiVersion: v1
48 | kind: Service
49 | metadata:
50 | name: cluster-parent-controller
51 | namespace: metacontroller
52 | spec:
53 | selector:
54 | app: cluster-parent-controller
55 | ports:
56 | - port: 80
57 |
--------------------------------------------------------------------------------
/examples/clusteredparent/my-clusterrole.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRole
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: my-clusterrole
5 | annotations:
6 | default-service-account-binding: "default"
7 | rules:
8 | - apiGroups: [""]
9 | resources: ["nodes"]
10 | verbs: ["get", "watch", "list"]
11 | ---
12 |
--------------------------------------------------------------------------------
/examples/clusteredparent/sync.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
18 | import json
19 |
20 | def new_rolebinding(name):
21 | rolebinding = {}
22 | rolebinding['apiVersion'] = 'rbac.authorization.k8s.io/v1'
23 | rolebinding['kind'] = 'RoleBinding'
24 | rolebinding['metadata'] = {}
25 | rolebinding['metadata']['name'] = name
26 | rolebinding['metadata']['namespace'] = "default"
27 | rolebinding['subjects'] = [{'kind': 'ServiceAccount', 'name': 'default', 'namespace': 'default'}]
28 | rolebinding['roleRef'] = {'kind': 'ClusterRole', 'name': name, 'apiGroup': 'rbac.authorization.k8s.io'}
29 | return rolebinding
30 |
31 | class Controller(BaseHTTPRequestHandler):
32 | def sync(self, clusterrole, children):
33 | return {'attachments': [new_rolebinding(clusterrole['metadata']['name'])] }
34 |
35 |
36 | def do_POST(self):
37 | observed = json.loads(self.rfile.read(int(self.headers.getheader('content-length'))))
38 | desired = self.sync(observed['object'], observed['attachments'])
39 |
40 | self.send_response(200)
41 | self.send_header('Content-type', 'application/json')
42 | self.end_headers()
43 | self.wfile.write(json.dumps(desired))
44 |
45 | HTTPServer(('', 80), Controller).serve_forever()
46 |
--------------------------------------------------------------------------------
/examples/clusteredparent/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-clusterrole.yaml
7 | kubectl delete -f cluster-parent.yaml
8 | kubectl delete configmap cluster-parent-controller -n metacontroller
9 | }
10 | trap cleanup EXIT
11 |
12 | set -ex
13 |
14 | echo "Install controller..."
15 | kubectl create configmap cluster-parent-controller -n metacontroller --from-file=sync.py
16 | kubectl apply -f cluster-parent.yaml
17 |
18 | echo "Create a ClusterRole..."
19 | kubectl apply -f my-clusterrole.yaml
20 |
21 | echo "Wait for Namespaced child..."
22 | until [[ "$(kubectl get rolebinding -n default my-clusterrole -o 'jsonpath={.metadata.name}')" == "my-clusterrole" ]]; do sleep 1; done
23 |
24 | echo "Delete Namespaced child..."
25 | kubectl delete rolebinding -n default my-clusterrole --wait=true
26 |
27 | # Test that the controller with cluster-scoped parent notices the namespaced child got deleted.
28 | echo "Wait for Namespaced child to be recreated..."
29 | until [[ "$(kubectl get rolebinding -n default my-clusterrole -o 'jsonpath={.metadata.name}')" == "my-clusterrole" ]]; do sleep 1; done
30 |
31 | # Test to make sure cascading deletion of cross namespaces resources works.
32 | echo "Deleting ClusterRole..."
33 | kubectl delete -f my-clusterrole.yaml
34 |
35 | echo "Wait for Namespaced child cleanup..."
36 | until [[ "$(kubectl get clusterrole.rbac.authorization.k8s.io -n default my-clusterrole 2>&1 )" == *NotFound* ]]; do sleep 1; done
37 |
--------------------------------------------------------------------------------
/examples/crd-roles/README.md:
--------------------------------------------------------------------------------
1 | ## CRD Roles Controller
2 |
3 | This is an example DecoratorController that manages Cluster scoped resources.
4 | Both the parent and child resouces are Cluster scoped.
5 |
6 | ### Prerequisites
7 |
8 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
9 |
10 | ### Deploy the controller
11 |
12 | ```sh
13 | kubectl create configmap crd-role-contoller -n metacontroller --from-file=sync.py
14 | kubectl apply -f crd-role-controller.yaml
15 | ```
16 |
17 | ### Create a CRD
18 |
19 | ```sh
20 | kubectl apply -f my-crd.yaml
21 | ```
22 |
23 | A ClusterRole should be created configured with read access to the CRD.
24 |
25 | ```console
26 | $ kubectl get clusterrole my-crd-reader
27 | NAME AGE
28 | my-crd-reader 3m
29 | ```
30 |
--------------------------------------------------------------------------------
/examples/crd-roles/crd-role-controller.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: metacontroller.k8s.io/v1alpha1
3 | kind: DecoratorController
4 | metadata:
5 | name: crd-role-controller
6 | spec:
7 | resources:
8 | - apiVersion: apiextensions.k8s.io/v1beta1
9 | resource: customresourcedefinitions
10 | annotationSelector:
11 | matchExpressions:
12 | - {key: enable-default-roles, operator: Exists}
13 | attachments:
14 | - apiVersion: rbac.authorization.k8s.io/v1
15 | resource: clusterroles
16 | hooks:
17 | sync:
18 | webhook:
19 | url: http://crd-role-controller.metacontroller/sync-crd-role
20 | timeout: 10s
21 | ---
22 | apiVersion: apps/v1beta1
23 | kind: Deployment
24 | metadata:
25 | name: crd-role-controller
26 | namespace: metacontroller
27 | spec:
28 | replicas: 1
29 | selector:
30 | matchLabels:
31 | app: crd-role-controller
32 | template:
33 | metadata:
34 | labels:
35 | app: crd-role-controller
36 | spec:
37 | containers:
38 | - name: controller
39 | image: python:2.7
40 | command: ["python", "/hooks/sync.py"]
41 | volumeMounts:
42 | - name: hooks
43 | mountPath: /hooks
44 | volumes:
45 | - name: hooks
46 | configMap:
47 | name: crd-role-controller
48 | ---
49 | apiVersion: v1
50 | kind: Service
51 | metadata:
52 | name: crd-role-controller
53 | namespace: metacontroller
54 | spec:
55 | selector:
56 | app: crd-role-controller
57 | ports:
58 | - port: 80
59 |
--------------------------------------------------------------------------------
/examples/crd-roles/my-crd.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: my-tests.ctl.rlg.io
5 | annotations:
6 | enable-default-roles: "yes"
7 | spec:
8 | group: ctl.rlg.io
9 | version: v1
10 | scope: Cluster
11 | names:
12 | plural: my-tests
13 | singular: my-test
14 | kind: MyTest
15 |
--------------------------------------------------------------------------------
/examples/crd-roles/sync.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2017 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
18 | import json
19 |
20 | def new_cluster_role(crd):
21 | cr = {}
22 | cr['apiVersion'] = 'rbac.authorization.k8s.io/v1'
23 | cr['kind'] = "ClusterRole"
24 | cr['metadata'] = {}
25 | cr['metadata']['name'] = crd['metadata']['name'] + "-reader"
26 | apiGroup = crd['spec']['group']
27 | resource = crd['spec']['names']['plural']
28 | cr['rules'] = []
29 | # cr['rules'] = [{'apiGroups': [apiGroup], 'resouces':[resource], 'verbs': ["*"]}]
30 | return cr
31 |
32 | class Controller(BaseHTTPRequestHandler):
33 |
34 | def do_POST(self):
35 | observed = json.loads(self.rfile.read(int(self.headers.getheader('content-length'))))
36 | desired = {'attachments': [new_cluster_role(observed['object'])]}
37 |
38 | self.send_response(200)
39 | self.send_header('Content-type', 'application/json')
40 | self.end_headers()
41 | self.wfile.write(json.dumps(desired))
42 |
43 | HTTPServer(('', 80), Controller).serve_forever()
44 |
--------------------------------------------------------------------------------
/examples/crd-roles/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-crd.yaml
7 | kubectl delete -f crd-role-controller.yaml
8 | kubectl delete configmap crd-role-controller -n metacontroller
9 | }
10 | trap cleanup EXIT
11 |
12 | set -ex
13 |
14 | echo "Install controller..."
15 | kubectl create configmap crd-role-controller -n metacontroller --from-file=sync.py
16 | kubectl apply -f crd-role-controller.yaml
17 |
18 | echo "Create a CRD..."
19 | kubectl apply -f my-crd.yaml
20 |
21 | echo "Wait for ClusterRole..."
22 | until [[ "$(kubectl get clusterrole my-tests.ctl.rlg.io-reader -o 'jsonpath={.metadata.name}')" == "my-tests.ctl.rlg.io-reader" ]]; do sleep 1; done
23 |
--------------------------------------------------------------------------------
/examples/daemonjob/README.md:
--------------------------------------------------------------------------------
1 | ## DaemonJob
2 |
3 | This is an example CompositeController that's similar to Job,
4 | except that a pod will be scheduled to each node, similar to DaemonSet.
5 |
6 | The implementation was inspired by this [blog post](http://blog.itaysk.com/2017/12/26/the-single-use-daemonset-pattern-and-prepulling-images-in-kubernetes).
7 | This particular pattern has some caveats, like how multiple containers run in
8 | series instead of concurrently, but it suffices as an example of using
9 | CompositeController to wrap up any such pattern you could think of.
10 |
11 | ### Prerequisites
12 |
13 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
14 |
15 | ### Deploy the controller
16 |
17 | ```sh
18 | kubectl create configmap daemonjob-controller -n metacontroller --from-file=sync.py
19 | kubectl apply -f daemonjob-controller.yaml
20 | ```
21 |
22 | ### Create a DaemonJob
23 |
24 | In a separate terminal watch for DaemonJobs, DaemonSets and Pods
25 |
26 | ```sh
27 | watch -n1 kubectl get ds,dj,po
28 | ```
29 |
30 | Create the DaemonJob
31 |
32 | ```sh
33 | kubectl apply -f my-daemonjob.yaml
34 | ```
35 |
36 | In the terminal where you have the `watch` command running you will see a
37 | DaemonSet being created as soon as the DaemonJob is deployed and then a pod will
38 | start on each node of your cluster.
39 | These pods will stay in the init stage for about 30s (sleep command in the
40 | container) and will be terminated as soon as all of them have reached the
41 | `Running` state.
42 | The DeamonSet generated by the DaemonJob will also be cleaned up after all the
43 | Pods are done.
44 |
45 | ### Clean up
46 |
47 | ```sh
48 | kubectl delete -f daemonjob-controller.yaml
49 | kubectl delete configmap -n metacontroller daemonjob-controller
50 | ```
51 |
--------------------------------------------------------------------------------
/examples/daemonjob/daemonjob-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: daemonjobs.ctl.example.com
5 | spec:
6 | group: ctl.example.com
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: daemonjobs
11 | singular: daemonjob
12 | kind: DaemonJob
13 | shortNames: ["dj"]
14 | subresources:
15 | status: {}
16 | ---
17 | apiVersion: metacontroller.k8s.io/v1alpha1
18 | kind: CompositeController
19 | metadata:
20 | name: daemonjob-controller
21 | spec:
22 | generateSelector: true
23 | parentResource:
24 | apiVersion: ctl.example.com/v1
25 | resource: daemonjobs
26 | childResources:
27 | - apiVersion: apps/v1
28 | resource: daemonsets
29 | hooks:
30 | sync:
31 | webhook:
32 | url: http://daemonjob-controller.metacontroller/sync
33 | ---
34 | apiVersion: apps/v1
35 | kind: Deployment
36 | metadata:
37 | name: daemonjob-controller
38 | namespace: metacontroller
39 | spec:
40 | replicas: 1
41 | selector:
42 | matchLabels:
43 | app: daemonjob-controller
44 | template:
45 | metadata:
46 | labels:
47 | app: daemonjob-controller
48 | spec:
49 | containers:
50 | - name: controller
51 | image: python:2.7
52 | command: ["python", "/hooks/sync.py"]
53 | volumeMounts:
54 | - name: hooks
55 | mountPath: /hooks
56 | volumes:
57 | - name: hooks
58 | configMap:
59 | name: daemonjob-controller
60 | ---
61 | apiVersion: v1
62 | kind: Service
63 | metadata:
64 | name: daemonjob-controller
65 | namespace: metacontroller
66 | spec:
67 | selector:
68 | app: daemonjob-controller
69 | ports:
70 | - port: 80
71 |
--------------------------------------------------------------------------------
/examples/daemonjob/my-daemonjob.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: ctl.example.com/v1
2 | kind: DaemonJob
3 | metadata:
4 | name: hello-world
5 | spec:
6 | template:
7 | metadata:
8 | labels:
9 | app: hello-world
10 | spec:
11 | containers:
12 | - name: hello-world
13 | image: busybox
14 | command: ["sh", "-c", "echo 'Hello world' && sleep 30"]
15 | resources:
16 | requests:
17 | cpu: 10m
18 | terminationGracePeriodSeconds: 10
19 |
--------------------------------------------------------------------------------
/examples/daemonjob/sync.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2019 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
18 | import json
19 | import copy
20 | import re
21 |
22 | def is_job_finished(job):
23 | if 'status' in job:
24 | desiredNumberScheduled = job['status'].get('desiredNumberScheduled',1)
25 | numberReady = job['status'].get('numberReady',0)
26 | if desiredNumberScheduled == numberReady and desiredNumberScheduled > 0:
27 | return True
28 | return False
29 |
30 | def new_daemon(job):
31 | daemon = copy.deepcopy(job)
32 | daemon['apiVersion'] = 'apps/v1'
33 | daemon['kind'] = 'DaemonSet'
34 | daemon['metadata'] = {}
35 | daemon['metadata']['name'] = '%s-dj' % (job['metadata']['name'])
36 | daemon['metadata']['labels'] = copy.deepcopy(job['spec']['template']['metadata']['labels'])
37 | daemon['spec'] = {}
38 | daemon['spec']['template'] = copy.deepcopy(job['spec']['template'])
39 | daemon['spec']['template']['spec']['initContainers'] = copy.deepcopy(job['spec']['template']['spec']['containers'])
40 | daemon['spec']['template']['spec']['containers'] = [{
41 | 'name': "pause",
42 | 'image': job['spec'].get('pauseImage', 'gcr.io/google_containers/pause'),
43 | 'resources': {'requests': {'cpu': '10m'}}
44 | }]
45 | daemon['spec']['selector'] = {'matchLabels': copy.deepcopy(job['spec']['template']['metadata']['labels'])}
46 |
47 | return daemon
48 |
49 | class Controller(BaseHTTPRequestHandler):
50 | def sync(self, job, children):
51 | desired_status = {}
52 | child = '%s-dj' % (job['metadata']['name'])
53 |
54 | self.log_message(" Children: %s", children)
55 |
56 | # If the job already finished at some point, freeze the status,
57 | # delete children, and take no further action.
58 | if is_job_finished(job):
59 | desired_status = copy.deepcopy(job['status'])
60 | desired_status['conditions'] = [{'type': 'Complete', 'status': 'True'}]
61 | return {'status': desired_status, 'children': []}
62 |
63 | # Compute status based on what we observed, before building desired state.
64 | # Our .status is just a copy of the DaemonSet .status with extra fields.
65 | desired_status = copy.deepcopy(children['DaemonSet.apps/v1'].get(child, {}).get('status',{}))
66 | if is_job_finished(children['DaemonSet.apps/v1'].get(child, {})):
67 | desired_status['conditions'] = [{'type': 'Complete', 'status': 'True'}]
68 | else:
69 | desired_status['conditions'] = [{'type': 'Complete', 'status': 'False'}]
70 |
71 | # Always generate desired state for child if we reach this point.
72 | # We should not delete children until after we know we've recorded
73 | # completion in our status, which was the first check we did above.
74 | desired_child = new_daemon(job)
75 | return {'status': desired_status, 'children': [desired_child]}
76 |
77 |
78 | def do_POST(self):
79 | observed = json.loads(self.rfile.read(int(self.headers.getheader('content-length'))))
80 | desired = self.sync(observed['parent'], observed['children'])
81 |
82 | self.send_response(200)
83 | self.send_header('Content-type', 'application/json')
84 | self.end_headers()
85 | self.wfile.write(json.dumps(desired))
86 |
87 | HTTPServer(('', 80), Controller).serve_forever()
88 |
--------------------------------------------------------------------------------
/examples/daemonjob/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-daemonjob.yaml
7 | kubectl delete daemonset hello-world-dj
8 | kubectl delete po -l app=hello-world
9 | kubectl delete -f daemonjob-controller.yaml
10 | kubectl delete configmap daemonjob-controller -n metacontroller
11 | }
12 | trap cleanup EXIT
13 |
14 | set -ex
15 |
16 | dj="daemonjobs"
17 |
18 | echo "Install controller..."
19 | kubectl create configmap daemonjob-controller -n metacontroller --from-file=sync.py
20 | kubectl apply -f daemonjob-controller.yaml
21 |
22 | echo "Wait until CRD is available..."
23 | until kubectl get $dj; do sleep 1; done
24 |
25 | echo "Create an object..."
26 | kubectl apply -f my-daemonjob.yaml
27 |
28 | echo "Wait for successful completion..."
29 | until [[ "$(kubectl get $dj hello-world -o 'jsonpath={.status.conditions[0].status}')" == "True" ]]; do sleep 1; done
30 |
31 | echo "Check that DaemonSet gets cleaned up after finishing..."
32 | until [[ "$(kubectl get daemonset hello-world-dj 2>&1)" =~ NotFound ]]; do sleep 1; done
33 |
--------------------------------------------------------------------------------
/examples/go/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 |
--------------------------------------------------------------------------------
/examples/go/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.11 AS build
2 |
3 | COPY . /go/src/thing-controller
4 | WORKDIR /go/src/thing-controller
5 | RUN go get -u github.com/golang/dep/cmd/dep && dep ensure && go build -o /go/bin/thing-controller
6 |
7 | FROM debian:stretch-slim
8 |
9 | COPY --from=build /go/bin/thing-controller /usr/bin/thing-controller
10 |
--------------------------------------------------------------------------------
/examples/go/Gopkg.lock:
--------------------------------------------------------------------------------
1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
2 |
3 |
4 | [[projects]]
5 | digest = "1:0a3f6a0c68ab8f3d455f8892295503b179e571b7fefe47cc6c556405d1f83411"
6 | name = "github.com/gogo/protobuf"
7 | packages = [
8 | "proto",
9 | "sortkeys",
10 | ]
11 | pruneopts = ""
12 | revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
13 | version = "v1.0.0"
14 |
15 | [[projects]]
16 | branch = "master"
17 | digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a"
18 | name = "github.com/golang/glog"
19 | packages = ["."]
20 | pruneopts = ""
21 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
22 |
23 | [[projects]]
24 | branch = "master"
25 | digest = "1:754f77e9c839b24778a4b64422236d38515301d2baeb63113aa3edc42e6af692"
26 | name = "github.com/google/gofuzz"
27 | packages = ["."]
28 | pruneopts = ""
29 | revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
30 |
31 | [[projects]]
32 | digest = "1:261bc565833ef4f02121450d74eb88d5ae4bd74bfe5d0e862cddb8550ec35000"
33 | name = "github.com/spf13/pflag"
34 | packages = ["."]
35 | pruneopts = ""
36 | revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
37 | version = "v1.0.0"
38 |
39 | [[projects]]
40 | branch = "master"
41 | digest = "1:d6ff496a166a2371bfc9b653632c1a94de1b8cf3bed6ee0f2dd38ba75c8efe2c"
42 | name = "golang.org/x/net"
43 | packages = [
44 | "http2",
45 | "http2/hpack",
46 | "idna",
47 | "lex/httplex",
48 | ]
49 | pruneopts = ""
50 | revision = "d0aafc73d5cdc42264b0af071c261abac580695e"
51 |
52 | [[projects]]
53 | digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
54 | name = "golang.org/x/text"
55 | packages = [
56 | "collate",
57 | "collate/build",
58 | "internal/colltab",
59 | "internal/gen",
60 | "internal/tag",
61 | "internal/triegen",
62 | "internal/ucd",
63 | "language",
64 | "secure/bidirule",
65 | "transform",
66 | "unicode/bidi",
67 | "unicode/cldr",
68 | "unicode/norm",
69 | "unicode/rangetable",
70 | ]
71 | pruneopts = ""
72 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
73 | version = "v0.3.0"
74 |
75 | [[projects]]
76 | digest = "1:e5d1fb981765b6f7513f793a3fcaac7158408cca77f75f7311ac82cc88e9c445"
77 | name = "gopkg.in/inf.v0"
78 | packages = ["."]
79 | pruneopts = ""
80 | revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
81 | version = "v0.9.0"
82 |
83 | [[projects]]
84 | branch = "master"
85 | digest = "1:25a4794663763eacfae30ae399b17a0e203ef514de0d4d8a87613b33f6bb4995"
86 | name = "k8s.io/api"
87 | packages = ["core/v1"]
88 | pruneopts = ""
89 | revision = "fd252c3a3e1debf912ff5b80221a31a6a3c24493"
90 |
91 | [[projects]]
92 | branch = "master"
93 | digest = "1:6e643d2bbfd60bd5a03965fcc0ea5a40947656c5a77727fd62f5bc757fd24b80"
94 | name = "k8s.io/apimachinery"
95 | packages = [
96 | "pkg/api/resource",
97 | "pkg/apis/meta/v1",
98 | "pkg/conversion",
99 | "pkg/conversion/queryparams",
100 | "pkg/fields",
101 | "pkg/labels",
102 | "pkg/runtime",
103 | "pkg/runtime/schema",
104 | "pkg/selection",
105 | "pkg/types",
106 | "pkg/util/errors",
107 | "pkg/util/intstr",
108 | "pkg/util/json",
109 | "pkg/util/net",
110 | "pkg/util/runtime",
111 | "pkg/util/sets",
112 | "pkg/util/validation",
113 | "pkg/util/validation/field",
114 | "pkg/util/wait",
115 | "pkg/watch",
116 | "third_party/forked/golang/reflect",
117 | ]
118 | pruneopts = ""
119 | revision = "e9ff529c66f83aeac6dff90f11ea0c5b7c4d626a"
120 |
121 | [solve-meta]
122 | analyzer-name = "dep"
123 | analyzer-version = 1
124 | input-imports = [
125 | "k8s.io/api/core/v1",
126 | "k8s.io/apimachinery/pkg/apis/meta/v1",
127 | "k8s.io/apimachinery/pkg/runtime",
128 | "k8s.io/apimachinery/pkg/util/json",
129 | ]
130 | solver-name = "gps-cdcl"
131 | solver-version = 1
132 |
--------------------------------------------------------------------------------
/examples/go/Gopkg.toml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleCloudPlatform/metacontroller/ddfc021ec8a1685f17349096b84a432817932884/examples/go/Gopkg.toml
--------------------------------------------------------------------------------
/examples/go/README.md:
--------------------------------------------------------------------------------
1 | ## Example Go Controller
2 |
3 | This controller doesn't do anything useful.
4 | It's just an example skeleton for writing Metacontroller hooks with Go.
5 |
6 | **WARNING**
7 |
8 | There's a [known issue](https://github.com/GoogleCloudPlatform/metacontroller/issues/76)
9 | that makes it difficult to produce JSON according to the rules that Metacontroller
10 | requires if you import the official Go structs for Kubernetes APIs.
11 | In particular, some fields will always be emitted, even if you never set them,
12 | which goes against Metacontroller's [apply semantics](https://metacontroller.app/api/apply/).
13 |
14 | ### Prerequisites
15 |
16 | * [Install Metacontroller](https://metacontroller.app/guide/install/)
17 |
18 | ### Install Thing Controller
19 |
20 | ```sh
21 | kubectl apply -f thing-controller.yaml
22 | ```
23 |
24 | ### Create a Thing
25 |
26 | ```sh
27 | kubectl apply -f my-thing.yaml
28 | ```
29 |
30 | Look at the thing:
31 |
32 | ```sh
33 | kubectl get thing -o yaml
34 | ```
35 |
36 | Look at the thing the thing created:
37 |
38 | ```sh
39 | kubectl get pod thing-1 -a
40 | ```
41 |
42 | Look at what the thing the thing created said:
43 |
44 | ```sh
45 | kubectl logs thing-1
46 | ```
47 |
48 | ### Clean up
49 |
50 | ```sh
51 | kubectl delete -f thing-controller.yaml
52 | ```
53 |
54 | ### Building
55 |
56 | You don't need to build to run the example above,
57 | but if you make changes:
58 |
59 | ```sh
60 | go get -u github.com/golang/dep/cmd/dep
61 | dep ensure
62 | go build -o thing-controller
63 | ```
64 |
65 | Or just make a new container image:
66 |
67 | ```sh
68 | docker build . -t /thing-controller
69 | ```
70 |
--------------------------------------------------------------------------------
/examples/go/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "net/http"
7 |
8 | "k8s.io/api/core/v1"
9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10 | "k8s.io/apimachinery/pkg/runtime"
11 | "k8s.io/apimachinery/pkg/util/json"
12 | )
13 |
14 | type Controller struct {
15 | metav1.TypeMeta `json:",inline"`
16 | metav1.ObjectMeta `json:"metadata"`
17 | Spec ControllerSpec `json:"spec"`
18 | Status ControllerStatus `json:"status"`
19 | }
20 |
21 | type ControllerSpec struct {
22 | Message string `json:"message"`
23 | }
24 |
25 | type ControllerStatus struct {
26 | Replicas int `json:"replicas"`
27 | Succeeded int `json:"succeeded"`
28 | }
29 |
30 | type SyncRequest struct {
31 | Parent Controller `json:"parent"`
32 | Children SyncRequestChildren `json:"children"`
33 | }
34 |
35 | type SyncRequestChildren struct {
36 | Pods map[string]*v1.Pod `json:"Pod.v1"`
37 | }
38 |
39 | type SyncResponse struct {
40 | Status ControllerStatus `json:"status"`
41 | Children []runtime.Object `json:"children"`
42 | }
43 |
44 | func sync(request *SyncRequest) (*SyncResponse, error) {
45 | response := &SyncResponse{}
46 |
47 | // Compute status based on latest observed state.
48 | for _, pod := range request.Children.Pods {
49 | response.Status.Replicas += 1
50 | if pod.Status.Phase == v1.PodSucceeded {
51 | response.Status.Succeeded += 1
52 | }
53 | }
54 |
55 | // Generate desired children.
56 | pod := &v1.Pod{
57 | TypeMeta: metav1.TypeMeta{
58 | APIVersion: "v1",
59 | Kind: "Pod",
60 | },
61 | ObjectMeta: metav1.ObjectMeta{
62 | Name: request.Parent.Name,
63 | },
64 | Spec: v1.PodSpec{
65 | RestartPolicy: v1.RestartPolicyOnFailure,
66 | Containers: []v1.Container{
67 | {
68 | Name: "hello",
69 | Image: "busybox",
70 | Command: []string{"echo", request.Parent.Spec.Message},
71 | },
72 | },
73 | },
74 | }
75 | response.Children = append(response.Children, pod)
76 |
77 | return response, nil
78 | }
79 |
80 | func syncHandler(w http.ResponseWriter, r *http.Request) {
81 | body, err := ioutil.ReadAll(r.Body)
82 | if err != nil {
83 | http.Error(w, err.Error(), http.StatusInternalServerError)
84 | return
85 | }
86 | request := &SyncRequest{}
87 | if err := json.Unmarshal(body, request); err != nil {
88 | http.Error(w, err.Error(), http.StatusBadRequest)
89 | return
90 | }
91 | response, err := sync(request)
92 | if err != nil {
93 | http.Error(w, err.Error(), http.StatusInternalServerError)
94 | return
95 | }
96 | body, err = json.Marshal(&response)
97 | if err != nil {
98 | http.Error(w, err.Error(), http.StatusInternalServerError)
99 | return
100 | }
101 | w.Header().Set("Content-Type", "application/json")
102 | w.Write(body)
103 | }
104 |
105 | func main() {
106 | http.HandleFunc("/sync", syncHandler)
107 |
108 | log.Fatal(http.ListenAndServe(":8080", nil))
109 | }
110 |
--------------------------------------------------------------------------------
/examples/go/my-thing.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: ctl.enisoc.com/v1
2 | kind: Thing
3 | metadata:
4 | name: thing-1
5 | spec:
6 | message: Hello, World!
7 |
--------------------------------------------------------------------------------
/examples/go/thing-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: things.ctl.enisoc.com
5 | spec:
6 | group: ctl.enisoc.com
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: things
11 | singular: thing
12 | kind: Thing
13 | ---
14 | apiVersion: metacontroller.k8s.io/v1alpha1
15 | kind: CompositeController
16 | metadata:
17 | name: thing-controller
18 | spec:
19 | generateSelector: true
20 | parentResource:
21 | apiVersion: ctl.enisoc.com/v1
22 | resource: things
23 | childResources:
24 | - apiVersion: v1
25 | resource: pods
26 | hooks:
27 | sync:
28 | webhook:
29 | url: http://thing-controller.metacontroller/sync
30 | ---
31 | apiVersion: apps/v1
32 | kind: Deployment
33 | metadata:
34 | name: thing-controller
35 | namespace: metacontroller
36 | spec:
37 | replicas: 1
38 | selector:
39 | matchLabels:
40 | app: thing-controller
41 | template:
42 | metadata:
43 | labels:
44 | app: thing-controller
45 | spec:
46 | containers:
47 | - name: controller
48 | image: enisoc/thing-controller:latest
49 | command: ["thing-controller"]
50 | ---
51 | apiVersion: v1
52 | kind: Service
53 | metadata:
54 | name: thing-controller
55 | namespace: metacontroller
56 | spec:
57 | selector:
58 | app: thing-controller
59 | ports:
60 | - port: 80
61 | targetPort: 8080
62 |
--------------------------------------------------------------------------------
/examples/indexedjob/README.md:
--------------------------------------------------------------------------------
1 | ## IndexedJob
2 |
3 | This is an example CompositeController that's similar to Job,
4 | except that each Pod gets assigned a unique index, similar to StatefulSet.
5 |
6 | ### Prerequisites
7 |
8 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
9 |
10 | ### Deploy the controller
11 |
12 | ```sh
13 | kubectl create configmap indexedjob-controller -n metacontroller --from-file=sync.py
14 | kubectl apply -f indexedjob-controller.yaml
15 | ```
16 |
17 | ### Create an IndexedJob
18 |
19 | ```sh
20 | kubectl apply -f my-indexedjob.yaml
21 | ```
22 |
23 | Each Pod created should print its index:
24 |
25 | ```console
26 | $ kubectl logs print-index-2
27 | 2
28 | ```
29 |
30 | ### Failure Policy
31 |
32 | Implementing `activeDeadlineSeconds` and `backoffLimit` is left as an exercise for the reader.
33 |
--------------------------------------------------------------------------------
/examples/indexedjob/indexedjob-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: indexedjobs.ctl.enisoc.com
5 | spec:
6 | group: ctl.enisoc.com
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: indexedjobs
11 | singular: indexedjob
12 | kind: IndexedJob
13 | shortNames: ["ij", "idxj"]
14 | subresources:
15 | status: {}
16 | ---
17 | apiVersion: metacontroller.k8s.io/v1alpha1
18 | kind: CompositeController
19 | metadata:
20 | name: indexedjob-controller
21 | spec:
22 | generateSelector: true
23 | parentResource:
24 | apiVersion: ctl.enisoc.com/v1
25 | resource: indexedjobs
26 | childResources:
27 | - apiVersion: v1
28 | resource: pods
29 | hooks:
30 | sync:
31 | webhook:
32 | url: http://indexedjob-controller.metacontroller/sync
33 | ---
34 | apiVersion: apps/v1beta1
35 | kind: Deployment
36 | metadata:
37 | name: indexedjob-controller
38 | namespace: metacontroller
39 | spec:
40 | replicas: 1
41 | selector:
42 | matchLabels:
43 | app: indexedjob-controller
44 | template:
45 | metadata:
46 | labels:
47 | app: indexedjob-controller
48 | spec:
49 | containers:
50 | - name: controller
51 | image: python:2.7
52 | command: ["python", "/hooks/sync.py"]
53 | volumeMounts:
54 | - name: hooks
55 | mountPath: /hooks
56 | volumes:
57 | - name: hooks
58 | configMap:
59 | name: indexedjob-controller
60 | ---
61 | apiVersion: v1
62 | kind: Service
63 | metadata:
64 | name: indexedjob-controller
65 | namespace: metacontroller
66 | spec:
67 | selector:
68 | app: indexedjob-controller
69 | ports:
70 | - port: 80
71 |
--------------------------------------------------------------------------------
/examples/indexedjob/my-indexedjob.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: ctl.enisoc.com/v1
2 | kind: IndexedJob
3 | metadata:
4 | name: print-index
5 | spec:
6 | completions: 10
7 | parallelism: 2
8 | template:
9 | metadata:
10 | labels:
11 | app: print-index
12 | spec:
13 | restartPolicy: OnFailure
14 | containers:
15 | - name: print-index
16 | image: busybox
17 | command: ["sh", "-c", "echo ${JOB_INDEX}"]
18 |
--------------------------------------------------------------------------------
/examples/indexedjob/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-indexedjob.yaml
7 | kubectl delete po -l app=print-index
8 | kubectl delete -f indexedjob-controller.yaml
9 | kubectl delete configmap indexedjob-controller -n metacontroller
10 | }
11 | trap cleanup EXIT
12 |
13 | set -ex
14 |
15 | ij="indexedjobs"
16 |
17 | echo "Install controller..."
18 | kubectl create configmap indexedjob-controller -n metacontroller --from-file=sync.py
19 | kubectl apply -f indexedjob-controller.yaml
20 |
21 | echo "Wait until CRD is available..."
22 | until kubectl get $ij; do sleep 1; done
23 |
24 | echo "Create an object..."
25 | kubectl apply -f my-indexedjob.yaml
26 |
27 | echo "Wait for 10 successful completions..."
28 | until [[ "$(kubectl get $ij print-index -o 'jsonpath={.status.succeeded}')" -eq 10 ]]; do sleep 1; done
29 |
30 | echo "Check that correct index is printed..."
31 | if [[ "$(kubectl logs print-index-9)" != "9" ]]; then
32 | exit 1
33 | fi
34 |
--------------------------------------------------------------------------------
/examples/jsonnetd/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /jsonnetd
3 |
--------------------------------------------------------------------------------
/examples/jsonnetd/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.10 AS build
2 |
3 | RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
4 |
5 | COPY . /go/src/jsonnetd/
6 | WORKDIR /go/src/jsonnetd/
7 | RUN dep ensure && go install
8 |
9 | FROM debian:stretch-slim
10 | COPY --from=build /go/bin/jsonnetd /jsonnetd/
11 | WORKDIR /jsonnetd
12 | ENTRYPOINT ["/jsonnetd/jsonnetd"]
13 | EXPOSE 8080
--------------------------------------------------------------------------------
/examples/jsonnetd/Gopkg.lock:
--------------------------------------------------------------------------------
1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
2 |
3 |
4 | [[projects]]
5 | branch = "master"
6 | name = "github.com/google/go-jsonnet"
7 | packages = [".","ast","parser"]
8 | revision = "c7a5b68f1c7a2f5e69736e489790f58edd27f7f3"
9 |
10 | [solve-meta]
11 | analyzer-name = "dep"
12 | analyzer-version = 1
13 | inputs-digest = "dff655412715ccf4f74481ef79f3d20d587d66eb1e1b47aa715890f610302c6e"
14 | solver-name = "gps-cdcl"
15 | solver-version = 1
16 |
--------------------------------------------------------------------------------
/examples/jsonnetd/Gopkg.toml:
--------------------------------------------------------------------------------
1 |
2 | # Gopkg.toml example
3 | #
4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
5 | # for detailed Gopkg.toml documentation.
6 | #
7 | # required = ["github.com/user/thing/cmd/thing"]
8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
9 | #
10 | # [[constraint]]
11 | # name = "github.com/user/project"
12 | # version = "1.0.0"
13 | #
14 | # [[constraint]]
15 | # name = "github.com/user/project2"
16 | # branch = "dev"
17 | # source = "github.com/myfork/project2"
18 | #
19 | # [[override]]
20 | # name = "github.com/x/y"
21 | # version = "2.4.0"
22 |
23 |
--------------------------------------------------------------------------------
/examples/jsonnetd/Makefile:
--------------------------------------------------------------------------------
1 | tag = 0.1
2 |
3 | image:
4 | docker build -t metacontroller/jsonnetd:$(tag) .
5 |
6 | push: image
7 | docker push metacontroller/jsonnetd:$(tag)
8 |
--------------------------------------------------------------------------------
/examples/jsonnetd/README.md:
--------------------------------------------------------------------------------
1 | ## jsonnetd
2 |
3 | This is an HTTP server that loads a directory of Jsonnet files and
4 | serves each one as a webhook.
5 |
6 | Each hook should evaluate to a Jsonnet function:
7 |
8 | ```js
9 | function(request) {
10 | // response body
11 | }
12 | ```
13 |
14 | The body of the POST request is itself interpreted as Jsonnet
15 | and given to the hook as a top-level `request` argument.
16 |
17 | The entire result of evaluating the Jsonnet function is returned as
18 | the webhook response body, unless the function returns an error.
19 |
--------------------------------------------------------------------------------
/examples/jsonnetd/extensions.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 main
18 |
19 | import (
20 | "encoding/json"
21 | "fmt"
22 | "strconv"
23 |
24 | jsonnet "github.com/google/go-jsonnet"
25 | "github.com/google/go-jsonnet/ast"
26 | )
27 |
28 | var extensions = []*jsonnet.NativeFunction{
29 | // jsonUnmarshal adds a native function for unmarshaling JSON,
30 | // since there doesn't seem to be one in the standard library.
31 | {
32 | Name: "jsonUnmarshal",
33 | Params: ast.Identifiers{"jsonStr"},
34 | Func: func(args []interface{}) (interface{}, error) {
35 | jsonStr, ok := args[0].(string)
36 | if !ok {
37 | return nil, fmt.Errorf("unexpected type %T for 'jsonStr' arg", args[0])
38 | }
39 | val := make(map[string]interface{})
40 | if err := json.Unmarshal([]byte(jsonStr), &val); err != nil {
41 | return nil, fmt.Errorf("can't unmarshal JSON: %v", err)
42 | }
43 | return val, nil
44 | },
45 | },
46 |
47 | // parseInt adds a native function for parsing non-decimal integers,
48 | // since there doesn't seem to be one in the standard library.
49 | {
50 | Name: "parseInt",
51 | Params: ast.Identifiers{"intStr", "base"},
52 | Func: func(args []interface{}) (interface{}, error) {
53 | str, ok := args[0].(string)
54 | if !ok {
55 | return nil, fmt.Errorf("unexpected type %T for 'intStr' arg", args[0])
56 | }
57 | base, ok := args[1].(float64)
58 | if !ok {
59 | return nil, fmt.Errorf("unexpected type %T for 'base' arg", args[1])
60 | }
61 | intVal, err := strconv.ParseInt(str, int(base), 64)
62 | if err != nil {
63 | return nil, fmt.Errorf("can't parse 'intStr': %v", err)
64 | }
65 | return float64(intVal), nil
66 | },
67 | },
68 | }
69 |
--------------------------------------------------------------------------------
/examples/jsonnetd/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 main
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io/ioutil"
23 | "log"
24 | "net/http"
25 | "os"
26 | "os/signal"
27 | "strings"
28 | "syscall"
29 |
30 | jsonnet "github.com/google/go-jsonnet"
31 | )
32 |
33 | func main() {
34 | // Read all Jsonnet files in the working dir.
35 | files, err := ioutil.ReadDir(".")
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 | for _, file := range files {
40 | filename := file.Name()
41 | if !strings.HasSuffix(filename, ".jsonnet") {
42 | continue
43 | }
44 |
45 | hookname := strings.TrimSuffix(filename, ".jsonnet")
46 | filedata, err := ioutil.ReadFile(filename)
47 | if err != nil {
48 | log.Fatalf("can't read %q: %v", filename, err)
49 | }
50 | hookcode := string(filedata)
51 |
52 | // Serve the Jsonnet file as a webhook.
53 | http.HandleFunc("/"+hookname, func(w http.ResponseWriter, r *http.Request) {
54 | // Read POST body as jsonnet input.
55 | if r.Method != http.MethodPost {
56 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
57 | return
58 | }
59 | body, err := ioutil.ReadAll(r.Body)
60 | if err != nil {
61 | http.Error(w, "Can't read body", http.StatusInternalServerError)
62 | return
63 | }
64 |
65 | // Evaluate Jsonnet hook, passing request body as a top-level argument.
66 | vm := jsonnet.MakeVM()
67 | for _, ext := range extensions {
68 | vm.NativeFunction(ext)
69 | }
70 | vm.TLACode("request", string(body))
71 | result, err := vm.EvaluateSnippet(filename, hookcode)
72 | if err != nil {
73 | log.Printf("/%s request: %s", hookname, body)
74 | log.Printf("/%s error: %s", hookname, err)
75 | http.Error(w, err.Error(), http.StatusInternalServerError)
76 | return
77 | }
78 | w.Header().Set("Content-Type", "application/json")
79 | fmt.Fprint(w, result)
80 | })
81 | }
82 |
83 | server := &http.Server{Addr: ":8080"}
84 | go func() {
85 | log.Fatal(server.ListenAndServe())
86 | }()
87 |
88 | // Shutdown on SIGTERM.
89 | sigchan := make(chan os.Signal, 2)
90 | signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM)
91 | sig := <-sigchan
92 | log.Printf("Received %v signal. Shutting down...", sig)
93 | server.Shutdown(context.Background())
94 | }
95 |
--------------------------------------------------------------------------------
/examples/nodejs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8
2 | COPY server.js /node/
3 | WORKDIR /node
4 | ENTRYPOINT ["node", "/node/server.js"]
5 |
--------------------------------------------------------------------------------
/examples/nodejs/Makefile:
--------------------------------------------------------------------------------
1 | tag = 0.1
2 |
3 | image:
4 | docker build -t metacontroller/nodejs-server:$(tag) .
5 |
6 | push: image
7 | docker push metacontroller/nodejs-server:$(tag)
8 |
--------------------------------------------------------------------------------
/examples/nodejs/README.md:
--------------------------------------------------------------------------------
1 | ## nodejs-server
2 |
3 | This is a generic nodejs server that wraps a function written for
4 | fission's nodejs environment, making it deployable without fission.
5 |
6 | This shows that it's possible to deploy a new CompositeController instantly,
7 | without baking it into a Docker image, either with or without a system like fission.
8 |
--------------------------------------------------------------------------------
/examples/nodejs/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 is a generic nodejs server that wraps a function written for
18 | // fission's nodejs environment, making it deployable without fission.
19 |
20 | const fs = require('fs');
21 | const path = require('path');
22 | const http = require('http');
23 |
24 | let hooks = {};
25 | for (let file of fs.readdirSync('./hooks')) {
26 | if (file.endsWith('.js')) {
27 | let name = path.basename(file, '.js');
28 | console.log('loading hook: /' + name);
29 | hooks[name] = require('./hooks/' + name);
30 | }
31 | }
32 |
33 | http.createServer((request, response) => {
34 | let hook = hooks[request.url.split('/')[1]];
35 | if (!hook) {
36 | response.writeHead(404, {'Content-Type': 'text/plain'});
37 | response.end('Not found');
38 | return;
39 | }
40 |
41 | // Read the whole request body.
42 | let body = [];
43 | request.on('error', (err) => {
44 | console.error(err);
45 | }).on('data', (chunk) => {
46 | body.push(chunk);
47 | }).on('end', () => {
48 | body = Buffer.concat(body).toString();
49 |
50 | if (request.headers['content-type'] === 'application/json') {
51 | body = JSON.parse(body);
52 | }
53 |
54 | // Emulate part of the fission.io nodejs environment,
55 | // so we can use the same sync.js file.
56 | hook({request: {body: body}}).then((result) => {
57 | response.writeHead(result.status, result.headers);
58 | let body = result.body;
59 | if (typeof body !== 'string') {
60 | body = JSON.stringify(body);
61 | }
62 | response.end(body);
63 | }, (err) => {
64 | response.writeHead(500, {'Content-Type': 'text/plain'});
65 | response.end(err.toString());
66 | });
67 | });
68 | }).listen(80);
69 |
--------------------------------------------------------------------------------
/examples/service-per-pod/README.md:
--------------------------------------------------------------------------------
1 | ## Service-Per-Pod Decorator
2 |
3 | This is an example DecoratorController that adds a Service for each Pod in a
4 | StatefulSet, for any StatefulSet that requests this by adding an annotation
5 | that specifies the name of the label containing the Pod name.
6 |
7 | In Kubernetes 1.9+, StatefulSet automatically adds the Pod name as a label on
8 | each of its Pods, so you can enable Service-Per-Pod like this:
9 |
10 | ```yaml
11 | apiVersion: apps/v1beta2
12 | kind: StatefulSet
13 | metadata:
14 | annotations:
15 | service-per-pod-label: "statefulset.kubernetes.io/pod-name"
16 | service-per-pod-ports: "80:8080"
17 | ...
18 | ```
19 |
20 | For earlier versions, this example also contains a second DecoratorController
21 | that adds the Pod name label since StatefulSet previously didn't do it.
22 |
23 | The Pod name label is only added to Pods that request it with an annotation,
24 | which you can add in the StatefulSet's Pod template:
25 |
26 | ```yaml
27 | apiVersion: apps/v1beta2
28 | kind: StatefulSet
29 | metadata:
30 | annotations:
31 | service-per-pod-label: "pod-name"
32 | service-per-pod-ports: "80:8080"
33 | ...
34 | spec:
35 | template:
36 | metadata:
37 | annotations:
38 | pod-name-label: "pod-name"
39 | ...
40 | ```
41 |
42 | If the StatefulSet is then deleted, or if the `service-per-pod-label` annotation
43 | is removed to opt out of the decorator, any Services created will be cleaned up.
44 |
45 | ### Prerequisites
46 |
47 | * Kubernetes 1.8+ is recommended for its improved CRD support,
48 | especially garbage collection.
49 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller).
50 |
51 | ### Deploy the DecoratorControllers
52 |
53 | ```sh
54 | kubectl create configmap service-per-pod-hooks -n metacontroller --from-file=hooks
55 | kubectl apply -f service-per-pod.yaml
56 | ```
57 |
58 | ### Create an Example StatefulSet
59 |
60 | ```sh
61 | kubectl apply -f my-statefulset.yaml
62 | ```
63 |
64 | Watch for the Services to get created:
65 |
66 | ```sh
67 | kubectl get services --watch
68 | ```
69 |
70 | Check that the StatefulSet's Pods can be selected by `pod-name` label:
71 |
72 | ```sh
73 | kubectl get pod -l pod-name=nginx-0
74 | kubectl get pod -l pod-name=nginx-1
75 | kubectl get pod -l pod-name=nginx-2
76 | ```
77 |
78 | Check that the per-Pod Services get cleaned up when the StatefulSet is deleted:
79 |
80 | ```sh
81 | kubectl delete -f my-statefulset.yaml
82 | kubectl get services
83 | ```
84 |
--------------------------------------------------------------------------------
/examples/service-per-pod/hooks/finalize-service-per-pod.jsonnet:
--------------------------------------------------------------------------------
1 | function(request) {
2 | // If the StatefulSet is updated to no longer match our decorator selector,
3 | // or if the StatefulSet is deleted, clean up any attachments we made.
4 | attachments: [],
5 | // Mark as finalized once we observe all Services are gone.
6 | finalized: std.length(request.attachments['Service.v1']) == 0
7 | }
8 |
--------------------------------------------------------------------------------
/examples/service-per-pod/hooks/sync-pod-name-label.jsonnet:
--------------------------------------------------------------------------------
1 | function(request) {
2 | local pod = request.object,
3 | local labelKey = pod.metadata.annotations["pod-name-label"],
4 |
5 | // Inject the Pod name as a label with the key requested in the annotation.
6 | labels: {
7 | [labelKey]: pod.metadata.name
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/service-per-pod/hooks/sync-service-per-pod.jsonnet:
--------------------------------------------------------------------------------
1 | function(request) {
2 | local statefulset = request.object,
3 | local labelKey = statefulset.metadata.annotations["service-per-pod-label"],
4 | local ports = statefulset.metadata.annotations["service-per-pod-ports"],
5 |
6 | // Create a service for each Pod, with a selector on the given label key.
7 | attachments: [
8 | {
9 | apiVersion: "v1",
10 | kind: "Service",
11 | metadata: {
12 | name: statefulset.metadata.name + "-" + index,
13 | labels: {app: "service-per-pod"}
14 | },
15 | spec: {
16 | selector: {
17 | [labelKey]: statefulset.metadata.name + "-" + index
18 | },
19 | ports: [
20 | {
21 | local parts = std.split(portnums, ":"),
22 | port: std.parseInt(parts[0]),
23 | targetPort: std.parseInt(parts[1]),
24 | }
25 | for portnums in std.split(ports, ",")
26 | ]
27 | }
28 | }
29 | for index in std.range(0, statefulset.spec.replicas - 1)
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/examples/service-per-pod/my-statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: nginx
5 | labels:
6 | app: nginx
7 | spec:
8 | ports:
9 | - port: 80
10 | name: web
11 | clusterIP: None
12 | selector:
13 | app: nginx
14 | ---
15 | apiVersion: apps/v1beta2
16 | kind: StatefulSet
17 | metadata:
18 | name: nginx
19 | annotations:
20 | service-per-pod-label: pod-name
21 | service-per-pod-ports: "80:80"
22 | spec:
23 | selector:
24 | matchLabels:
25 | app: nginx
26 | serviceName: nginx
27 | replicas: 3
28 | template:
29 | metadata:
30 | labels:
31 | app: nginx
32 | annotations:
33 | pod-name-label: pod-name
34 | spec:
35 | terminationGracePeriodSeconds: 1
36 | containers:
37 | - name: nginx
38 | image: gcr.io/google_containers/nginx-slim:0.8
39 | ports:
40 | - containerPort: 80
41 | name: web
42 |
--------------------------------------------------------------------------------
/examples/service-per-pod/service-per-pod.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: metacontroller.k8s.io/v1alpha1
2 | kind: DecoratorController
3 | metadata:
4 | name: service-per-pod
5 | spec:
6 | resources:
7 | - apiVersion: apps/v1beta1
8 | resource: statefulsets
9 | annotationSelector:
10 | matchExpressions:
11 | - {key: service-per-pod-label, operator: Exists}
12 | - {key: service-per-pod-ports, operator: Exists}
13 | attachments:
14 | - apiVersion: v1
15 | resource: services
16 | hooks:
17 | sync:
18 | webhook:
19 | url: http://service-per-pod.metacontroller/sync-service-per-pod
20 | finalize:
21 | webhook:
22 | url: http://service-per-pod.metacontroller/finalize-service-per-pod
23 | ---
24 | apiVersion: metacontroller.k8s.io/v1alpha1
25 | kind: DecoratorController
26 | metadata:
27 | name: pod-name-label
28 | spec:
29 | resources:
30 | - apiVersion: v1
31 | resource: pods
32 | labelSelector:
33 | matchExpressions:
34 | - {key: pod-name, operator: DoesNotExist}
35 | annotationSelector:
36 | matchExpressions:
37 | - {key: pod-name-label, operator: Exists}
38 | hooks:
39 | sync:
40 | webhook:
41 | url: http://service-per-pod.metacontroller/sync-pod-name-label
42 | ---
43 | apiVersion: apps/v1beta1
44 | kind: Deployment
45 | metadata:
46 | name: service-per-pod
47 | namespace: metacontroller
48 | spec:
49 | replicas: 1
50 | selector:
51 | matchLabels:
52 | app: service-per-pod
53 | template:
54 | metadata:
55 | labels:
56 | app: service-per-pod
57 | spec:
58 | containers:
59 | - name: hooks
60 | image: metacontroller/jsonnetd:0.1
61 | imagePullPolicy: Always
62 | workingDir: /hooks
63 | volumeMounts:
64 | - name: hooks
65 | mountPath: /hooks
66 | volumes:
67 | - name: hooks
68 | configMap:
69 | name: service-per-pod-hooks
70 | ---
71 | apiVersion: v1
72 | kind: Service
73 | metadata:
74 | name: service-per-pod
75 | namespace: metacontroller
76 | spec:
77 | selector:
78 | app: service-per-pod
79 | ports:
80 | - port: 80
81 | targetPort: 8080
82 |
--------------------------------------------------------------------------------
/examples/service-per-pod/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl patch statefulset nginx --type=merge -p '{"metadata":{"finalizers":[]}}'
7 | kubectl delete -f my-statefulset.yaml
8 | kubectl delete -f service-per-pod.yaml
9 | kubectl delete svc -l app=service-per-pod
10 | kubectl delete configmap service-per-pod-hooks -n metacontroller
11 | }
12 | trap cleanup EXIT
13 |
14 | set -ex
15 |
16 | finalizer="metacontroller.app/service-per-pod-test"
17 |
18 | echo "Install controller..."
19 | kubectl create configmap service-per-pod-hooks -n metacontroller --from-file=hooks
20 | kubectl apply -f service-per-pod.yaml
21 |
22 | echo "Create a StatefulSet..."
23 | kubectl apply -f my-statefulset.yaml
24 |
25 | echo "Wait for per-pod Service..."
26 | until [[ "$(kubectl get svc nginx-2 -o 'jsonpath={.spec.selector.pod-name}')" == "nginx-2" ]]; do sleep 1; done
27 |
28 | echo "Wait for pod-name label..."
29 | until [[ "$(kubectl get pod nginx-2 -o 'jsonpath={.metadata.labels.pod-name}')" == "nginx-2" ]]; do sleep 1; done
30 |
31 | echo "Remove annotation to opt out of service-per-pod without deleting the StatefulSet..."
32 | kubectl annotate statefulset nginx service-per-pod-label-
33 |
34 | echo "Wait for per-pod Service to get cleaned up by the decorator's finalizer..."
35 | until [[ "$(kubectl get svc nginx-2 2>&1)" == *NotFound* ]]; do sleep 1; done
36 |
37 | echo "Wait for the decorator's finalizer to be removed..."
38 | while [[ "$(kubectl get statefulset nginx -o 'jsonpath={.metadata.finalizers}')" == *decoratorcontroller-service-per-pod* ]]; do sleep 1; done
39 |
40 | echo "Add the annotation back to opt in again..."
41 | kubectl annotate statefulset nginx service-per-pod-label=pod-name
42 |
43 | echo "Wait for per-pod Service to come back..."
44 | until [[ "$(kubectl get svc nginx-2 -o 'jsonpath={.spec.selector.pod-name}')" == "nginx-2" ]]; do sleep 1; done
45 |
46 | echo "Append our own finalizer so we can check deletion ordering..."
47 | kubectl patch statefulset nginx --type=json -p '[{"op":"add","path":"/metadata/finalizers/-","value":"'${finalizer}'"}]'
48 |
49 | echo "Delete the StatefulSet..."
50 | kubectl delete statefulset nginx --wait=false
51 |
52 | echo "Wait for per-pod Service to get cleaned up by the decorator's finalizer..."
53 | until [[ "$(kubectl get svc nginx-2 2>&1)" == *NotFound* ]]; do sleep 1; done
54 |
55 | echo "Wait for the decorator's finalizer to be removed..."
56 | while [[ "$(kubectl get statefulset nginx -o 'jsonpath={.metadata.finalizers}')" == *decoratorcontroller-service-per-pod* ]]; do sleep 1; done
57 |
--------------------------------------------------------------------------------
/examples/status/README.md:
--------------------------------------------------------------------------------
1 | ## Noop
2 |
3 | This is an example DecoratorController returning a status
4 |
5 | ### Prerequisites
6 |
7 | * Install [Metacontroller](https://github.com/GoogleCloudPlatform/metacontroller)
8 |
9 | ### Deploy the controller
10 |
11 | ```sh
12 | kubectl create configmap noop-controller -n metacontroller --from-file=sync.js
13 | kubectl apply -f noop-controller.yaml
14 | ```
15 |
16 | ### Create a Noop
17 |
18 | ```sh
19 | kubectl apply -f my-noop.yaml
20 | ```
21 |
--------------------------------------------------------------------------------
/examples/status/my-noop.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: metacontroller.k8s.io/v1
2 | kind: Noop
3 | metadata:
4 | name: noop
5 | spec:
6 |
--------------------------------------------------------------------------------
/examples/status/noop-controller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: noops.metacontroller.k8s.io
5 | spec:
6 | group: metacontroller.k8s.io
7 | version: v1
8 | scope: Namespaced
9 | names:
10 | plural: noops
11 | singular: noop
12 | kind: Noop
13 | subresources:
14 | status: {}
15 | ---
16 | apiVersion: metacontroller.k8s.io/v1alpha1
17 | kind: DecoratorController
18 | metadata:
19 | name: noop-controller
20 | spec:
21 | resources:
22 | - apiVersion: metacontroller.k8s.io/v1
23 | resource: noops
24 | hooks:
25 | sync:
26 | webhook:
27 | url: http://noop-controller.metacontroller/sync
28 | ---
29 | apiVersion: apps/v1beta1
30 | kind: Deployment
31 | metadata:
32 | name: noop-controller
33 | namespace: metacontroller
34 | spec:
35 | replicas: 1
36 | selector:
37 | matchLabels:
38 | app: noop-controller
39 | template:
40 | metadata:
41 | labels:
42 | app: noop-controller
43 | spec:
44 | containers:
45 | - name: controller
46 | image: metacontroller/nodejs-server:0.1
47 | imagePullPolicy: Always
48 | volumeMounts:
49 | - name: hooks
50 | mountPath: /node/hooks
51 | volumes:
52 | - name: hooks
53 | configMap:
54 | name: noop-controller
55 |
56 | ---
57 | apiVersion: v1
58 | kind: Service
59 | metadata:
60 | name: noop-controller
61 | namespace: metacontroller
62 | spec:
63 | selector:
64 | app: noop-controller
65 | ports:
66 | - port: 80
67 |
--------------------------------------------------------------------------------
/examples/status/sync.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 |
18 | module.exports = async function (context) {
19 | return {status: 200, body: { status: { message: "success"} }, headers: {'Content-Type': 'application/json'}};
20 | };
21 |
--------------------------------------------------------------------------------
/examples/status/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cleanup() {
4 | set +e
5 | echo "Clean up..."
6 | kubectl delete -f my-noop.yaml
7 | kubectl delete rs,svc -l app=noop-controller
8 | kubectl delete -f noop-controller.yaml
9 | kubectl delete configmap noop-controller -n metacontroller
10 | }
11 | trap cleanup EXIT
12 |
13 | set -ex
14 |
15 | np="noops.metacontroller.k8s.io"
16 |
17 | echo "Install controller..."
18 | kubectl create configmap noop-controller -n metacontroller --from-file=sync.js
19 | kubectl apply -f noop-controller.yaml
20 |
21 | echo "Wait until CRD is available..."
22 | until kubectl get $np; do sleep 1; done
23 |
24 | echo "Create an object..."
25 | kubectl apply -f my-noop.yaml
26 |
27 | echo "Wait for status to be updated..."
28 | until [[ "$(kubectl get $np noop -o 'jsonpath={.status.message}')" == "success" ]]; do sleep 1; done
29 |
--------------------------------------------------------------------------------
/examples/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script runs the smoke tests that check basic Metacontroller functionality
4 | # by running through each example controller.
5 | #
6 | # * You should only run this in a test cluster.
7 | # * You should already have Metacontroller installed in your test cluster.
8 | # * You should have kubectl in your PATH and configured for the right cluster.
9 |
10 | set -e
11 |
12 | logfile=$(mktemp)
13 | echo "Logging test output to ${logfile}"
14 |
15 | cleanup() {
16 | rm ${logfile}
17 | }
18 | trap cleanup EXIT
19 |
20 | for test in */test.sh; do
21 | echo -n "Running ${test}..."
22 | if ! (cd "$(dirname "${test}")" && ./test.sh > ${logfile} 2>&1); then
23 | echo "FAILED"
24 | cat ${logfile}
25 | echo "Test ${test} failed!"
26 | exit 1
27 | fi
28 | echo "PASSED"
29 | done
30 |
--------------------------------------------------------------------------------
/examples/vitess/README.md:
--------------------------------------------------------------------------------
1 | ## Vitess Operator
2 |
3 | **NOTE: The [Vitess Operator][] has moved to its own repository,
4 | and is now maintained by the Vitess project.**
5 |
6 | [Vitess Operator]: https://github.com/vitessio/vitess-operator
7 |
8 | This is an example of an app-specific [Operator](https://coreos.com/operators/),
9 | in this case for [Vitess](http://vitess.io), built with Metacontroller.
10 |
11 | It's meant to demonstrate the following patterns:
12 |
13 | * Building an Operator for a complex, stateful application out of a set of small
14 | Lambda Controllers that each do one thing well.
15 | * In addition to presenting a k8s-style API to users, this Operator uses
16 | custom k8s API objects to coordinate within itself.
17 | * Each controller manages one layer of the hierarchical Vitess cluster topology.
18 | The user only needs to create and manage a single, top-level VitessCluster
19 | object.
20 | * Replacing static, client-side template rendering with Lambda Controllers,
21 | which can adjust based on dynamic cluster state.
22 | * Each controller aggregates status and orchestrates app-specific rolling
23 | updates for its immediate children.
24 | * The top-level object contains a continuously-updated, aggregate "Ready"
25 | condition for the whole app, and can be directly edited to trigger rolling
26 | updates throughout the app.
27 | * Using a functional-style language ([Jsonnet](http://jsonnet.org)) to
28 | define Lambda Controllers in terms of template-like transformations on JSON
29 | objects.
30 | * You can use any language to write a Lambda Controller webhook, but the
31 | functional style is a good fit for a process that conceptually consists of
32 | declarative input, declarative output, and no side effects.
33 | * As a JSON templating language, Jsonnet is a particularly good fit for
34 | generating k8s manifests, providing functionality missing from pure
35 | JavaScript, such as first-class *merge* and *deep equal* operations.
36 | * Using the "Apply" update strategy feature of CompositeController, which
37 | emulates the behavior of `kubectl apply`, except that it attempts to do
38 | pseudo-strategic merges for CRDs.
39 |
40 | See the [Vitess Operator][] repository for details.
41 |
--------------------------------------------------------------------------------
/hack/get-kube-binaries.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 | set -u
5 |
6 | # This script downloads etcd and Kubernetes binaries that are
7 | # used as part of the integration test environment,
8 | # and places them in hack/bin/.
9 | #
10 | # The integration test framework expects these binaries to be found in the PATH.
11 |
12 | # This is the kube-apiserver version to test against.
13 | KUBE_VERSION="${KUBE_VERSION:-v1.11.3}"
14 | KUBERNETES_RELEASE_URL="${KUBERNETES_RELEASE_URL:-https://dl.k8s.io}"
15 |
16 | # This should be the etcd version downloaded by kubernetes/hack/lib/etcd.sh
17 | # as of the above Kubernetes version.
18 | ETCD_VERSION="${ETCD_VERSION:-v3.2.18}"
19 |
20 | mkdir -p hack/bin
21 | cd hack/bin
22 |
23 | # Download kubectl.
24 | rm -f kubectl
25 | wget "${KUBERNETES_RELEASE_URL}/${KUBE_VERSION}/bin/linux/amd64/kubectl"
26 | chmod +x kubectl
27 |
28 | # Download kube-apiserver.
29 | rm -f kube-apiserver
30 | wget "${KUBERNETES_RELEASE_URL}/${KUBE_VERSION}/bin/linux/amd64/kube-apiserver"
31 | chmod +x kube-apiserver
32 |
33 | # Download etcd.
34 | rm -f etcd
35 | basename="etcd-${ETCD_VERSION}-linux-amd64"
36 | filename="${basename}.tar.gz"
37 | url="https://github.com/coreos/etcd/releases/download/${ETCD_VERSION}/${filename}"
38 | wget "${url}"
39 | tar -zxf "${filename}"
40 | mv "${basename}/etcd" etcd
41 | rm -rf "${basename}" "${filename}"
42 |
--------------------------------------------------------------------------------
/hooks/hooks.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2018 Google Inc.
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 | https://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 hooks
18 |
19 | import (
20 | "fmt"
21 |
22 | "metacontroller.app/apis/metacontroller/v1alpha1"
23 | )
24 |
25 | func Call(hook *v1alpha1.Hook, request interface{}, response interface{}) error {
26 | if hook.Webhook != nil {
27 | return callWebhook(hook.Webhook, request, response)
28 | }
29 | return fmt.Errorf("hook spec not defined")
30 | }
31 |
--------------------------------------------------------------------------------
/hooks/webhook.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 hooks
18 |
19 | import (
20 | "bytes"
21 | gojson "encoding/json"
22 | "fmt"
23 | "io/ioutil"
24 | "net/http"
25 | "time"
26 |
27 | "github.com/golang/glog"
28 | "k8s.io/apimachinery/pkg/util/json"
29 |
30 | "metacontroller.app/apis/metacontroller/v1alpha1"
31 | )
32 |
33 | func callWebhook(webhook *v1alpha1.Webhook, request interface{}, response interface{}) error {
34 | url, err := webhookURL(webhook)
35 | hookTimeout, err := webhookTimeout(webhook)
36 | if err != nil {
37 | return err
38 | }
39 |
40 | // Encode request.
41 | reqBody, err := json.Marshal(request)
42 | if err != nil {
43 | return fmt.Errorf("can't marshal request: %v", err)
44 | }
45 | if glog.V(6) {
46 | reqBodyIndent, _ := gojson.MarshalIndent(request, "", " ")
47 | glog.Infof("DEBUG: webhook url: %s request body: %s", url, reqBodyIndent)
48 | }
49 |
50 | // Send request.
51 | client := &http.Client{Timeout: hookTimeout}
52 | glog.V(6).Infof("DEBUG: webhook timeout: %v", hookTimeout)
53 | resp, err := client.Post(url, "application/json", bytes.NewReader(reqBody))
54 | if err != nil {
55 | return fmt.Errorf("http error: %v", err)
56 | }
57 | defer resp.Body.Close()
58 |
59 | // Read response.
60 | respBody, err := ioutil.ReadAll(resp.Body)
61 | if err != nil {
62 | return fmt.Errorf("can't read response body: %v", err)
63 | }
64 | glog.V(6).Infof("DEBUG: webhook url: %s response body: %s", url, respBody)
65 |
66 | // Check status code.
67 | if resp.StatusCode != http.StatusOK {
68 | return fmt.Errorf("remote error: %s", respBody)
69 | }
70 |
71 | // Decode response.
72 | if err := json.Unmarshal(respBody, response); err != nil {
73 | return fmt.Errorf("can't unmarshal response: %v", err)
74 | }
75 | return nil
76 | }
77 |
78 | func webhookURL(webhook *v1alpha1.Webhook) (string, error) {
79 | if webhook.URL != nil {
80 | // Full URL overrides everything else.
81 | return *webhook.URL, nil
82 | }
83 | if webhook.Service == nil || webhook.Path == nil {
84 | return "", fmt.Errorf("invalid webhook config: must specify either full 'url', or both 'service' and 'path'")
85 | }
86 |
87 | // For now, just use cluster DNS to resolve Services.
88 | // If necessary, we can use a Lister to get more info about Services.
89 | if webhook.Service.Name == "" || webhook.Service.Namespace == "" {
90 | return "", fmt.Errorf("invalid client config: must specify service 'name' and 'namespace'")
91 | }
92 | port := int32(80)
93 | if webhook.Service.Port != nil {
94 | port = *webhook.Service.Port
95 | }
96 | protocol := "http"
97 | if webhook.Service.Protocol != nil {
98 | protocol = *webhook.Service.Protocol
99 | }
100 | return fmt.Sprintf("%s://%s.%s:%v%s", protocol, webhook.Service.Name, webhook.Service.Namespace, port, *webhook.Path), nil
101 | }
102 |
103 | func webhookTimeout(webhook *v1alpha1.Webhook) (time.Duration, error) {
104 | if webhook.Timeout == nil {
105 | // Defaults to 10 Seconds to preserve current behavior.
106 | return 10 * time.Second, nil
107 | }
108 |
109 | if webhook.Timeout.Duration <= 0 {
110 | // Defaults to 10 Seconds if invalid.
111 | return 10 * time.Second, fmt.Errorf("invalid client config: timeout must be a non-zero positive duration")
112 | }
113 |
114 | return webhook.Timeout.Duration, nil
115 | }
116 |
--------------------------------------------------------------------------------
/kustomization.yaml:
--------------------------------------------------------------------------------
1 | commonLabels:
2 | app.kubernetes.io/name: metacontroller
3 |
4 | resources:
5 | - manifests/metacontroller-rbac.yaml
6 | - manifests/metacontroller.yaml
7 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 Google Inc.
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 | https://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 main
18 |
19 | import (
20 | "context"
21 | "flag"
22 | "net/http"
23 | "os"
24 | "os/signal"
25 | "syscall"
26 | "time"
27 |
28 | "github.com/golang/glog"
29 | "go.opencensus.io/exporter/prometheus"
30 | "go.opencensus.io/stats/view"
31 |
32 | "k8s.io/client-go/rest"
33 | "k8s.io/client-go/tools/clientcmd"
34 |
35 | "metacontroller.app/server"
36 |
37 | _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
38 | )
39 |
40 | var (
41 | discoveryInterval = flag.Duration("discovery-interval", 30*time.Second, "How often to refresh discovery cache to pick up newly-installed resources")
42 | informerRelist = flag.Duration("cache-flush-interval", 30*time.Minute, "How often to flush local caches and relist objects from the API server")
43 | debugAddr = flag.String("debug-addr", ":9999", "The address to bind the debug http endpoints")
44 | clientConfigPath = flag.String("client-config-path", "", "Path to kubeconfig file (same format as used by kubectl); if not specified, use in-cluster config")
45 | )
46 |
47 | func main() {
48 | flag.Parse()
49 |
50 | glog.Infof("Discovery cache flush interval: %v", *discoveryInterval)
51 | glog.Infof("API server object cache flush interval: %v", *informerRelist)
52 | glog.Infof("Debug http server address: %v", *debugAddr)
53 |
54 | var config *rest.Config
55 | var err error
56 | if *clientConfigPath != "" {
57 | glog.Infof("Using current context from kubeconfig file: %v", *clientConfigPath)
58 | config, err = clientcmd.BuildConfigFromFlags("", *clientConfigPath)
59 | } else {
60 | glog.Info("No kubeconfig file specified; trying in-cluster auto-config...")
61 | config, err = rest.InClusterConfig()
62 | }
63 | if err != nil {
64 | glog.Fatal(err)
65 | }
66 |
67 | stopServer, err := server.Start(config, *discoveryInterval, *informerRelist)
68 | if err != nil {
69 | glog.Fatal(err)
70 | }
71 |
72 | exporter, err := prometheus.NewExporter(prometheus.Options{})
73 | if err != nil {
74 | glog.Fatalf("can't create prometheus exporter: %v", err)
75 | }
76 | view.RegisterExporter(exporter)
77 |
78 | mux := http.NewServeMux()
79 | mux.Handle("/metrics", exporter)
80 | srv := &http.Server{
81 | Addr: *debugAddr,
82 | Handler: mux,
83 | }
84 | go func() {
85 | glog.Errorf("Error serving debug endpoint: %v", srv.ListenAndServe())
86 | }()
87 |
88 | // On SIGTERM, stop all controllers gracefully.
89 | sigchan := make(chan os.Signal, 2)
90 | signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM)
91 | sig := <-sigchan
92 | glog.Infof("Received %q signal. Shutting down...", sig)
93 |
94 | stopServer()
95 | srv.Shutdown(context.Background())
96 | }
97 |
--------------------------------------------------------------------------------
/manifests/dev/args.yaml:
--------------------------------------------------------------------------------
1 | # Override args for development mode.
2 | apiVersion: apps/v1
3 | kind: StatefulSet
4 | metadata:
5 | name: metacontroller
6 | namespace: metacontroller
7 | spec:
8 | template:
9 | spec:
10 | containers:
11 | - name: metacontroller
12 | args:
13 | - --logtostderr
14 | - -v=5
15 | - --discovery-interval=5s
16 |
--------------------------------------------------------------------------------
/manifests/dev/image.yaml:
--------------------------------------------------------------------------------
1 | # Override image for development mode (skaffold fills in the tag).
2 | apiVersion: apps/v1
3 | kind: StatefulSet
4 | metadata:
5 | name: metacontroller
6 | namespace: metacontroller
7 | spec:
8 | template:
9 | spec:
10 | containers:
11 | - name: metacontroller
12 | image: enisoc/metacontroller
13 |
--------------------------------------------------------------------------------
/manifests/dev/kustomization.yaml:
--------------------------------------------------------------------------------
1 | bases:
2 | - ../..
3 | resources:
4 | - ../../manifests/metacontroller-namespace.yaml
5 | patches:
6 | - image.yaml
7 | - args.yaml
8 |
--------------------------------------------------------------------------------
/manifests/metacontroller-namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: metacontroller
5 |
--------------------------------------------------------------------------------
/manifests/metacontroller-rbac.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: metacontroller
5 | namespace: metacontroller
6 | ---
7 | apiVersion: rbac.authorization.k8s.io/v1
8 | kind: ClusterRole
9 | metadata:
10 | name: metacontroller
11 | rules:
12 | - apiGroups:
13 | - "*"
14 | resources:
15 | - "*"
16 | verbs:
17 | - "*"
18 | ---
19 | apiVersion: rbac.authorization.k8s.io/v1
20 | kind: ClusterRoleBinding
21 | metadata:
22 | name: metacontroller
23 | subjects:
24 | - kind: ServiceAccount
25 | name: metacontroller
26 | namespace: metacontroller
27 | roleRef:
28 | kind: ClusterRole
29 | name: metacontroller
30 | apiGroup: rbac.authorization.k8s.io
31 | ---
32 | kind: ClusterRole
33 | apiVersion: rbac.authorization.k8s.io/v1
34 | metadata:
35 | name: aggregate-metacontroller-view
36 | labels:
37 | rbac.authorization.k8s.io/aggregate-to-admin: "true"
38 | rbac.authorization.k8s.io/aggregate-to-edit: "true"
39 | rbac.authorization.k8s.io/aggregate-to-view: "true"
40 | rules:
41 | - apiGroups:
42 | - metacontroller.k8s.io
43 | resources:
44 | - compositecontrollers
45 | - controllerrevisions
46 | - decoratorcontrollers
47 | verbs:
48 | - get
49 | - list
50 | - watch
51 | ---
52 | kind: ClusterRole
53 | apiVersion: rbac.authorization.k8s.io/v1
54 | metadata:
55 | name: aggregate-metacontroller-edit
56 | labels:
57 | rbac.authorization.k8s.io/aggregate-to-admin: "true"
58 | rbac.authorization.k8s.io/aggregate-to-edit: "true"
59 | rules:
60 | - apiGroups:
61 | - metacontroller.k8s.io
62 | resources:
63 | - controllerrevisions
64 | verbs:
65 | - create
66 | - delete
67 | - deletecollection
68 | - get
69 | - list
70 | - patch
71 | - update
72 | - watch
73 |
--------------------------------------------------------------------------------
/manifests/metacontroller.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1beta1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: compositecontrollers.metacontroller.k8s.io
5 | spec:
6 | group: metacontroller.k8s.io
7 | version: v1alpha1
8 | scope: Cluster
9 | names:
10 | plural: compositecontrollers
11 | singular: compositecontroller
12 | kind: CompositeController
13 | shortNames:
14 | - cc
15 | - cctl
16 | ---
17 | apiVersion: apiextensions.k8s.io/v1beta1
18 | kind: CustomResourceDefinition
19 | metadata:
20 | name: decoratorcontrollers.metacontroller.k8s.io
21 | spec:
22 | group: metacontroller.k8s.io
23 | version: v1alpha1
24 | scope: Cluster
25 | names:
26 | plural: decoratorcontrollers
27 | singular: decoratorcontroller
28 | kind: DecoratorController
29 | shortNames:
30 | - dec
31 | - decorators
32 | ---
33 | apiVersion: apiextensions.k8s.io/v1beta1
34 | kind: CustomResourceDefinition
35 | metadata:
36 | name: controllerrevisions.metacontroller.k8s.io
37 | spec:
38 | group: metacontroller.k8s.io
39 | version: v1alpha1
40 | scope: Namespaced
41 | names:
42 | plural: controllerrevisions
43 | singular: controllerrevision
44 | kind: ControllerRevision
45 | ---
46 | apiVersion: apps/v1
47 | kind: StatefulSet
48 | metadata:
49 | labels:
50 | app.kubernetes.io/name: metacontroller
51 | name: metacontroller
52 | namespace: metacontroller
53 | spec:
54 | replicas: 1
55 | selector:
56 | matchLabels:
57 | app.kubernetes.io/name: metacontroller
58 | serviceName: ""
59 | template:
60 | metadata:
61 | labels:
62 | app.kubernetes.io/name: metacontroller
63 | spec:
64 | serviceAccountName: metacontroller
65 | containers:
66 | - name: metacontroller
67 | image: metacontroller/metacontroller:v0.4.0
68 | command: ["/usr/bin/metacontroller"]
69 | args:
70 | - --logtostderr
71 | - -v=4
72 | - --discovery-interval=20s
73 | volumeClaimTemplates: []
74 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [context.production.environment]
2 | JEKYLL_ENV = "production"
3 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Google Inc.
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 | https://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 server
18 |
19 | import (
20 | "fmt"
21 | "sync"
22 | "time"
23 |
24 | "github.com/0xRLG/ocworkqueue"
25 | "go.opencensus.io/stats/view"
26 |
27 | "k8s.io/client-go/discovery"
28 | "k8s.io/client-go/rest"
29 | "k8s.io/client-go/util/workqueue"
30 |
31 | "metacontroller.app/apis/metacontroller/v1alpha1"
32 | mcclientset "metacontroller.app/client/generated/clientset/internalclientset"
33 | mcinformers "metacontroller.app/client/generated/informer/externalversions"
34 | "metacontroller.app/controller/composite"
35 | "metacontroller.app/controller/decorator"
36 | dynamicclientset "metacontroller.app/dynamic/clientset"
37 | dynamicdiscovery "metacontroller.app/dynamic/discovery"
38 | dynamicinformer "metacontroller.app/dynamic/informer"
39 |
40 | _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
41 | )
42 |
43 | type controller interface {
44 | Start()
45 | Stop()
46 | }
47 |
48 | func Start(config *rest.Config, discoveryInterval, informerRelist time.Duration) (stop func(), err error) {
49 | // Periodically refresh discovery to pick up newly-installed resources.
50 | dc := discovery.NewDiscoveryClientForConfigOrDie(config)
51 | resources := dynamicdiscovery.NewResourceMap(dc)
52 | // We don't care about stopping this cleanly since it has no external effects.
53 | resources.Start(discoveryInterval)
54 |
55 | // Create informer factory for metacontroller API objects.
56 | mcClient, err := mcclientset.NewForConfig(config)
57 | if err != nil {
58 | return nil, fmt.Errorf("Can't create client for api %s: %v", v1alpha1.SchemeGroupVersion, err)
59 | }
60 | mcInformerFactory := mcinformers.NewSharedInformerFactory(mcClient, informerRelist)
61 |
62 | // Create dynamic clientset (factory for dynamic clients).
63 | dynClient, err := dynamicclientset.New(config, resources)
64 | if err != nil {
65 | return nil, err
66 | }
67 | // Create dynamic informer factory (for sharing dynamic informers).
68 | dynInformers := dynamicinformer.NewSharedInformerFactory(dynClient, informerRelist)
69 |
70 | workqueue.SetProvider(ocworkqueue.MetricsProvider())
71 | view.Register(ocworkqueue.DefaultViews...)
72 |
73 | // Start metacontrollers (controllers that spawn controllers).
74 | // Each one requests the informers it needs from the factory.
75 | controllers := []controller{
76 | composite.NewMetacontroller(resources, dynClient, dynInformers, mcInformerFactory, mcClient),
77 | decorator.NewMetacontroller(resources, dynClient, dynInformers, mcInformerFactory),
78 | }
79 |
80 | // Start all requested informers.
81 | // We don't care about stopping this cleanly since it has no external effects.
82 | mcInformerFactory.Start(nil)
83 |
84 | // Start all controllers.
85 | for _, c := range controllers {
86 | c.Start()
87 | }
88 |
89 | // Return a function that will stop all controllers.
90 | return func() {
91 | var wg sync.WaitGroup
92 | for _, c := range controllers {
93 | wg.Add(1)
94 | go func(c controller) {
95 | defer wg.Done()
96 | c.Stop()
97 | }(c)
98 | }
99 | wg.Wait()
100 | }, nil
101 | }
102 |
--------------------------------------------------------------------------------
/skaffold.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v1beta2
2 | kind: Config
3 | build:
4 | artifacts:
5 | - image: enisoc/metacontroller
6 | docker:
7 | dockerfile: Dockerfile.dev
8 | deploy:
9 | kustomize:
10 | path: manifests/dev
11 |
--------------------------------------------------------------------------------
/test/integration/framework/apiserver.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Google Inc.
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 | https://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 framework
18 |
19 | /*
20 | This file replaces the mechanism for starting kube-apiserver used in
21 | k8s.io/kubernetes integration tests. In k8s.io/kubernetes, the apiserver is
22 | one of the components being tested, so it makes sense that there we build it
23 | from scratch and link it into the test binary. However, here we treat the
24 | apiserver as an external component just like etcd. This avoids having to vendor
25 | and build all of Kubernetes into our test binary.
26 | */
27 |
28 | import (
29 | "context"
30 | "fmt"
31 | "io/ioutil"
32 | "os"
33 | "os/exec"
34 | "strconv"
35 |
36 | "k8s.io/client-go/rest"
37 | "k8s.io/klog"
38 | )
39 |
40 | var apiserverURL = ""
41 |
42 | const installApiserver = `
43 | Cannot find kube-apiserver, cannot run integration tests
44 |
45 | Please download kube-apiserver and ensure it is somewhere in the PATH.
46 | See hack/get-kube-binaries.sh
47 |
48 | `
49 |
50 | // getApiserverPath returns a path to a kube-apiserver executable.
51 | func getApiserverPath() (string, error) {
52 | return exec.LookPath("kube-apiserver")
53 | }
54 |
55 | // startApiserver executes a kube-apiserver instance.
56 | // The returned function will signal the process and wait for it to exit.
57 | func startApiserver() (func(), error) {
58 | apiserverPath, err := getApiserverPath()
59 | if err != nil {
60 | fmt.Fprintf(os.Stderr, installApiserver)
61 | return nil, fmt.Errorf("could not find kube-apiserver in PATH: %v", err)
62 | }
63 | apiserverPort, err := getAvailablePort()
64 | if err != nil {
65 | return nil, fmt.Errorf("could not get a port: %v", err)
66 | }
67 | apiserverURL = fmt.Sprintf("http://127.0.0.1:%d", apiserverPort)
68 | klog.Infof("starting kube-apiserver on %s", apiserverURL)
69 |
70 | apiserverDataDir, err := ioutil.TempDir(os.TempDir(), "integration_test_apiserver_data")
71 | if err != nil {
72 | return nil, fmt.Errorf("unable to make temp kube-apiserver data dir: %v", err)
73 | }
74 | klog.Infof("storing kube-apiserver data in: %v", apiserverDataDir)
75 |
76 | ctx, cancel := context.WithCancel(context.Background())
77 | cmd := exec.CommandContext(
78 | ctx,
79 | apiserverPath,
80 | "--cert-dir", apiserverDataDir,
81 | // Disable secure port since we don't use it, so we don't conflict with other apiservers.
82 | "--secure-port", "0",
83 | "--insecure-port", strconv.Itoa(apiserverPort),
84 | "--etcd-servers", etcdURL,
85 | )
86 |
87 | // Uncomment these to see kube-apiserver output in test logs.
88 | // For Metacontroller tests, we generally don't expect problems at this level.
89 | //cmd.Stdout = os.Stdout
90 | //cmd.Stderr = os.Stderr
91 |
92 | stop := func() {
93 | cancel()
94 | err := cmd.Wait()
95 | klog.Infof("kube-apiserver exit status: %v", err)
96 | err = os.RemoveAll(apiserverDataDir)
97 | if err != nil {
98 | klog.Warningf("error during kube-apiserver cleanup: %v", err)
99 | }
100 | }
101 |
102 | if err := cmd.Start(); err != nil {
103 | return nil, fmt.Errorf("failed to run kube-apiserver: %v", err)
104 | }
105 | return stop, nil
106 | }
107 |
108 | // ApiserverURL returns the URL of the kube-apiserver instance started by TestMain.
109 | func ApiserverURL() string {
110 | return apiserverURL
111 | }
112 |
113 | // ApiserverConfig returns a rest.Config to connect to the test instance.
114 | func ApiserverConfig() *rest.Config {
115 | return &rest.Config{
116 | Host: ApiserverURL(),
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/test/integration/framework/crd.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Google Inc.
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 | https://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 framework
18 |
19 | import (
20 | "fmt"
21 | "strings"
22 |
23 | "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26 | "k8s.io/apimachinery/pkg/util/json"
27 |
28 | dynamicclientset "metacontroller.app/dynamic/clientset"
29 | )
30 |
31 | const (
32 | // APIGroup is the group used for CRDs created as part of the test.
33 | APIGroup = "test.metacontroller.app"
34 | // APIVersion is the group-version used for CRDs created as part of the test.
35 | APIVersion = APIGroup + "/v1"
36 | )
37 |
38 | // CreateCRD generates a quick-and-dirty CRD for use in tests,
39 | // and installs it in the test environment's API server.
40 | func (f *Fixture) CreateCRD(kind string, scope v1beta1.ResourceScope) (*v1beta1.CustomResourceDefinition, *dynamicclientset.ResourceClient) {
41 | singular := strings.ToLower(kind)
42 | plural := singular + "s"
43 | crd := &v1beta1.CustomResourceDefinition{
44 | ObjectMeta: metav1.ObjectMeta{
45 | Name: fmt.Sprintf("%s.%s", plural, APIGroup),
46 | },
47 | Spec: v1beta1.CustomResourceDefinitionSpec{
48 | Group: APIGroup,
49 | Scope: scope,
50 | Names: v1beta1.CustomResourceDefinitionNames{
51 | Singular: singular,
52 | Plural: plural,
53 | Kind: kind,
54 | },
55 | Versions: []v1beta1.CustomResourceDefinitionVersion{
56 | {
57 | Name: "v1",
58 | Served: true,
59 | Storage: true,
60 | },
61 | },
62 | },
63 | }
64 | crd, err := f.apiextensions.CustomResourceDefinitions().Create(crd)
65 | if err != nil {
66 | f.t.Fatal(err)
67 | }
68 | f.deferTeardown(func() error {
69 | return f.apiextensions.CustomResourceDefinitions().Delete(crd.Name, nil)
70 | })
71 |
72 | f.t.Logf("Waiting for %v CRD to appear in API server discovery info...", kind)
73 | err = f.Wait(func() (bool, error) {
74 | return resourceMap.Get(APIVersion, plural) != nil, nil
75 | })
76 | if err != nil {
77 | f.t.Fatal(err)
78 | }
79 |
80 | client, err := f.dynamic.Resource(APIVersion, plural)
81 | if err != nil {
82 | f.t.Fatal(err)
83 | }
84 |
85 | f.t.Logf("Waiting for %v CRD client List() to succeed...", kind)
86 | err = f.Wait(func() (bool, error) {
87 | _, err := client.List(metav1.ListOptions{})
88 | return err == nil, err
89 | })
90 | if err != nil {
91 | f.t.Fatal(err)
92 | }
93 |
94 | return crd, client
95 | }
96 |
97 | // UnstructuredCRD creates a new Unstructured object for the given CRD.
98 | func UnstructuredCRD(crd *v1beta1.CustomResourceDefinition, name string) *unstructured.Unstructured {
99 | obj := &unstructured.Unstructured{}
100 | obj.SetAPIVersion(crd.Spec.Group + "/" + crd.Spec.Versions[0].Name)
101 | obj.SetKind(crd.Spec.Names.Kind)
102 | obj.SetName(name)
103 | return obj
104 | }
105 |
106 | // UnstructuredJSON creates a new Unstructured object from the given JSON.
107 | // It panics on a decode error because it's meant for use with hard-coded test
108 | // data.
109 | func UnstructuredJSON(apiVersion, kind, name, jsonStr string) *unstructured.Unstructured {
110 | obj := map[string]interface{}{}
111 | if err := json.Unmarshal([]byte(jsonStr), &obj); err != nil {
112 | panic(err)
113 | }
114 | u := &unstructured.Unstructured{Object: obj}
115 | u.SetAPIVersion(apiVersion)
116 | u.SetKind(kind)
117 | u.SetName(name)
118 | return u
119 | }
120 |
--------------------------------------------------------------------------------
/test/integration/framework/etcd.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017 The Kubernetes 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 file is copied from k8s.io/kubernetes/test/integration/framework/
18 | // to avoid vendoring the rest of the package, which depends on all of k8s.
19 |
20 | package framework
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "io/ioutil"
26 | "net"
27 | "os"
28 | "os/exec"
29 | "path/filepath"
30 |
31 | "k8s.io/klog"
32 | )
33 |
34 | var etcdURL = ""
35 |
36 | const installEtcd = `
37 | Cannot find etcd, cannot run integration tests
38 |
39 | Please download kube-apiserver and ensure it is somewhere in the PATH.
40 | See hack/get-kube-binaries.sh
41 |
42 | `
43 |
44 | // getEtcdPath returns a path to an etcd executable.
45 | func getEtcdPath() (string, error) {
46 | bazelPath := filepath.Join(os.Getenv("RUNFILES_DIR"), "com_coreos_etcd/etcd")
47 | p, err := exec.LookPath(bazelPath)
48 | if err == nil {
49 | return p, nil
50 | }
51 | return exec.LookPath("etcd")
52 | }
53 |
54 | // getAvailablePort returns a TCP port that is available for binding.
55 | func getAvailablePort() (int, error) {
56 | l, err := net.Listen("tcp", ":0")
57 | if err != nil {
58 | return 0, fmt.Errorf("could not bind to a port: %v", err)
59 | }
60 | // It is possible but unlikely that someone else will bind this port before we
61 | // get a chance to use it.
62 | defer l.Close()
63 | return l.Addr().(*net.TCPAddr).Port, nil
64 | }
65 |
66 | // startEtcd executes an etcd instance. The returned function will signal the
67 | // etcd process and wait for it to exit.
68 | func startEtcd() (func(), error) {
69 | etcdPath, err := getEtcdPath()
70 | if err != nil {
71 | fmt.Fprintf(os.Stderr, installEtcd)
72 | return nil, fmt.Errorf("could not find etcd in PATH: %v", err)
73 | }
74 | etcdPort, err := getAvailablePort()
75 | if err != nil {
76 | return nil, fmt.Errorf("could not get a port: %v", err)
77 | }
78 | etcdURL = fmt.Sprintf("http://127.0.0.1:%d", etcdPort)
79 | klog.Infof("starting etcd on %s", etcdURL)
80 |
81 | etcdDataDir, err := ioutil.TempDir(os.TempDir(), "integration_test_etcd_data")
82 | if err != nil {
83 | return nil, fmt.Errorf("unable to make temp etcd data dir: %v", err)
84 | }
85 | klog.Infof("storing etcd data in: %v", etcdDataDir)
86 |
87 | ctx, cancel := context.WithCancel(context.Background())
88 | cmd := exec.CommandContext(
89 | ctx,
90 | etcdPath,
91 | "--data-dir", etcdDataDir,
92 | "--listen-client-urls", etcdURL,
93 | "--advertise-client-urls", etcdURL,
94 | "--listen-peer-urls", "http://127.0.0.1:0",
95 | )
96 |
97 | // Uncomment these to see etcd output in test logs.
98 | // For Metacontroller tests, we generally don't expect problems at this level.
99 | //cmd.Stdout = os.Stdout
100 | //cmd.Stderr = os.Stderr
101 |
102 | stop := func() {
103 | cancel()
104 | err := cmd.Wait()
105 | klog.Infof("etcd exit status: %v", err)
106 | err = os.RemoveAll(etcdDataDir)
107 | if err != nil {
108 | klog.Warningf("error during etcd cleanup: %v", err)
109 | }
110 | }
111 |
112 | if err := cmd.Start(); err != nil {
113 | return nil, fmt.Errorf("failed to run etcd: %v", err)
114 | }
115 | return stop, nil
116 | }
117 |
--------------------------------------------------------------------------------
/test/integration/framework/webhook.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Google Inc.
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 | https://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 framework
18 |
19 | import (
20 | "io/ioutil"
21 | "net/http"
22 | "net/http/httptest"
23 | )
24 |
25 | // ServeWebhook is a helper for quickly creating a webhook server in tests.
26 | func (f *Fixture) ServeWebhook(handler func(request []byte) (response []byte, err error)) *httptest.Server {
27 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28 | if r.Method != http.MethodPost {
29 | http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
30 | return
31 | }
32 |
33 | body, err := ioutil.ReadAll(r.Body)
34 | r.Body.Close()
35 | if err != nil {
36 | http.Error(w, "can't read body", http.StatusBadRequest)
37 | return
38 | }
39 |
40 | resp, err := handler(body)
41 | if err != nil {
42 | http.Error(w, err.Error(), http.StatusInternalServerError)
43 | return
44 | }
45 | w.Header().Set("Content-Type", "application/json")
46 | w.Write(resp)
47 | }))
48 | f.deferTeardown(func() error {
49 | srv.Close()
50 | return nil
51 | })
52 | return srv
53 | }
54 |
--------------------------------------------------------------------------------
/third_party/kubernetes/controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016 The Kubernetes 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 kubernetes
15 |
16 | // This is copied from k8s.io/kubernetes to avoid a dependency on all of Kubernetes.
17 | // TODO(enisoc): Move the upstream code to somewhere better.
18 |
19 | import (
20 | "fmt"
21 |
22 | "github.com/golang/glog"
23 |
24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
25 | "k8s.io/client-go/tools/cache"
26 | )
27 |
28 | // WaitForCacheSync is a wrapper around cache.WaitForCacheSync that generates log messages
29 | // indicating that the controller identified by controllerName is waiting for syncs, followed by
30 | // either a successful or failed sync.
31 | func WaitForCacheSync(controllerName string, stopCh <-chan struct{}, cacheSyncs ...cache.InformerSynced) bool {
32 | glog.Infof("Waiting for caches to sync for %s controller", controllerName)
33 |
34 | if !cache.WaitForCacheSync(stopCh, cacheSyncs...) {
35 | utilruntime.HandleError(fmt.Errorf("Unable to sync caches for %s controller", controllerName))
36 | return false
37 | }
38 |
39 | glog.Infof("Caches are synced for %s controller", controllerName)
40 | return true
41 | }
42 |
--------------------------------------------------------------------------------
/third_party/kubernetes/pointer.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016 The Kubernetes 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 kubernetes
15 |
16 | // This is copied from k8s.io/kubernetes to avoid a dependency on all of Kubernetes.
17 | // TODO(enisoc): Move the upstream code to somewhere better.
18 |
19 | // BoolPtr returns a pointer to a bool
20 | func BoolPtr(b bool) *bool {
21 | o := b
22 | return &o
23 | }
24 |
--------------------------------------------------------------------------------