From cd4491f18f388bee70105900a3f7670b819a6bbf Mon Sep 17 00:00:00 2001 From: Mengqi Yu Date: Tue, 9 Oct 2018 15:07:11 -0700 Subject: [PATCH] Revert "Revert "Webhook scaffolding"" --- Gopkg.lock | 53 ++- cmd/controller-scaffold/cmd/project.go | 1 + cmd/controller-scaffold/cmd/webhook.go | 103 +++++ generated_golden.sh | 5 + pkg/scaffold/manager/cmd.go | 38 +- pkg/scaffold/manager/config.go | 27 ++ pkg/scaffold/manager/webhook.go | 64 ++++ pkg/scaffold/project/kustomize.go | 7 + .../webhook/add_admissionbuilder_handler.go | 86 +++++ pkg/scaffold/webhook/add_server.go | 60 +++ pkg/scaffold/webhook/admissionbuilder.go | 101 +++++ pkg/scaffold/webhook/admissionhandler.go | 168 ++++++++ pkg/scaffold/webhook/admissionwebhooks.go | 69 ++++ pkg/scaffold/webhook/config.go | 27 ++ pkg/scaffold/webhook/server.go | 125 ++++++ pkg/scaffold/webhook/util.go | 67 ++++ test/cmd/manager/main.go | 37 +- test/config/default/kustomization.yaml | 7 + test/config/manager/manager.yaml | 27 ++ test/config/rbac/rbac_role.yaml | 37 ++ test/pkg/webhook/add_default_server.go | 26 ++ .../default_server/add_mutating_firstmate.go | 48 +++ .../default_server/add_mutating_namespace.go | 48 +++ .../default_server/add_validating_frigate.go | 48 +++ .../default_server/add_validating_kraken.go | 48 +++ .../mutating/create_update_webhook.go | 35 ++ .../firstmates/mutating/delete_webhook.go | 35 ++ .../firstmates_create_update_handler.go | 83 ++++ .../mutating/firstmates_delete_handler.go | 83 ++++ .../firstmates/mutating/webhooks.go | 29 ++ .../validating/frigates_update_handler.go | 82 ++++ .../frigates/validating/update_webhook.go | 35 ++ .../frigates/validating/webhooks.go | 29 ++ .../krakens/validating/create_webhook.go | 35 ++ .../validating/krakens_create_handler.go | 82 ++++ .../krakens/validating/webhooks.go | 29 ++ .../mutating/namespaces_update_handler.go | 83 ++++ .../namespaces/mutating/update_webhook.go | 35 ++ .../namespaces/mutating/webhooks.go | 29 ++ test/pkg/webhook/default_server/server.go | 91 +++++ test/pkg/webhook/webhook.go | 37 ++ vendor/github.com/google/uuid/.travis.yml | 9 + vendor/github.com/google/uuid/CONTRIBUTING.md | 10 + vendor/github.com/google/uuid/CONTRIBUTORS | 9 + vendor/github.com/google/uuid/LICENSE | 27 ++ vendor/github.com/google/uuid/README.md | 19 + vendor/github.com/google/uuid/dce.go | 80 ++++ vendor/github.com/google/uuid/doc.go | 12 + vendor/github.com/google/uuid/hash.go | 53 +++ vendor/github.com/google/uuid/marshal.go | 37 ++ vendor/github.com/google/uuid/node.go | 89 +++++ vendor/github.com/google/uuid/node_js.go | 12 + vendor/github.com/google/uuid/node_net.go | 33 ++ vendor/github.com/google/uuid/sql.go | 59 +++ vendor/github.com/google/uuid/time.go | 123 ++++++ vendor/github.com/google/uuid/util.go | 43 +++ vendor/github.com/google/uuid/uuid.go | 198 ++++++++++ vendor/github.com/google/uuid/version1.go | 44 +++ vendor/github.com/google/uuid/version4.go | 38 ++ .../github.com/mattbaird/jsonpatch/.gitignore | 24 ++ vendor/github.com/mattbaird/jsonpatch/LICENSE | 202 ++++++++++ .../github.com/mattbaird/jsonpatch/README.md | 46 +++ .../mattbaird/jsonpatch/jsonpatch.go | 257 +++++++++++++ vendor/github.com/pborman/uuid/.travis.yml | 10 + .../github.com/pborman/uuid/CONTRIBUTING.md | 10 + vendor/github.com/pborman/uuid/CONTRIBUTORS | 1 + vendor/github.com/pborman/uuid/LICENSE | 27 ++ vendor/github.com/pborman/uuid/README.md | 15 + vendor/github.com/pborman/uuid/dce.go | 84 ++++ vendor/github.com/pborman/uuid/doc.go | 13 + vendor/github.com/pborman/uuid/hash.go | 53 +++ vendor/github.com/pborman/uuid/marshal.go | 85 ++++ vendor/github.com/pborman/uuid/node.go | 50 +++ vendor/github.com/pborman/uuid/sql.go | 68 ++++ vendor/github.com/pborman/uuid/time.go | 57 +++ vendor/github.com/pborman/uuid/util.go | 32 ++ vendor/github.com/pborman/uuid/uuid.go | 163 ++++++++ vendor/github.com/pborman/uuid/version1.go | 23 ++ vendor/github.com/pborman/uuid/version4.go | 26 ++ .../controller-runtime/CONTRIBUTING.md | 20 +- .../sigs.k8s.io/controller-runtime/Gopkg.lock | 14 + .../sigs.k8s.io/controller-runtime/README.md | 8 +- .../controller-runtime/example/controller.go | 77 ++++ .../controller-runtime/example/main.go | 105 ++--- .../example/mutatingwebhook.go | 83 ++++ .../example/validatingwebhook.go | 89 +++++ .../controller-runtime/hack/verify.sh | 2 +- .../pkg/admission/cert/writer/certwriter.go | 211 ---------- .../pkg/admission/cert/writer/doc.go | 89 ----- .../pkg/admission/cert/writer/fs.go | 239 ------------ .../pkg/admission/cert/writer/multiple.go | 41 -- .../pkg/admission/cert/writer/secret.go | 226 ----------- .../pkg/admission/controller.go | 89 ----- .../controller-runtime/pkg/admission/doc.go | 102 ----- .../controller-runtime/pkg/cache/cache.go | 6 +- .../pkg/cache/informer_cache.go | 32 +- .../pkg/cache/internal/deleg_map.go | 96 +++++ .../pkg/cache/internal/informers_map.go | 117 ++++-- .../pkg/client/apiutil/apimachinery.go | 10 +- .../controller-runtime/pkg/client/client.go | 123 +++--- .../pkg/client/fake/client.go | 3 +- .../pkg/client/interfaces.go | 94 ++++- .../controller-runtime/pkg/client/split.go | 33 ++ .../pkg/client/typed_client.go | 127 ++++++ .../pkg/client/unstructured_client.go | 162 ++++++++ .../controllerutil/controllerutil.go | 79 ++++ .../sigs.k8s.io/controller-runtime/pkg/doc.go | 13 + .../pkg/internal/controller/controller.go | 3 + .../pkg/leaderelection/doc.go | 20 + .../pkg/leaderelection/fake/doc.go | 21 + .../leaderelection/fake/leader_election.go | 90 +++++ .../pkg/leaderelection/leader_election.go | 109 ++++++ .../pkg/manager/internal.go | 76 +++- .../controller-runtime/pkg/manager/manager.go | 69 +++- .../controller-runtime/pkg/patch/doc.go | 33 ++ .../controller-runtime/pkg/patch/patch.go | 45 +++ .../pkg/reconcile/reconcile.go | 5 + .../pkg/runtime/inject/inject.go | 19 +- .../pkg/webhook/admission/builder/builder.go | 223 +++++++++++ .../pkg/webhook/admission/builder/doc.go | 107 ++++++ .../pkg/webhook/admission/decode.go | 48 +++ .../pkg/webhook/admission/doc.go | 101 +++++ .../pkg/webhook/admission/http.go | 102 +++++ .../pkg/webhook/admission/response.go | 70 ++++ .../pkg/webhook/admission/types/types.go | 44 +++ .../pkg/webhook/admission/webhook.go | 239 ++++++++++++ .../pkg/webhook/bootstrap.go | 362 ++++++++++++++++++ .../controller-runtime/pkg/webhook/doc.go | 94 +++++ .../pkg/webhook/internal/cert/doc.go | 36 ++ .../internal}/cert/generator/certgenerator.go | 0 .../internal}/cert/generator/doc.go | 2 +- .../cert/generator/fake/certgenerator.go | 6 +- .../internal}/cert/generator/selfsigned.go | 2 +- .../pkg/webhook/internal/cert/provisioner.go | 139 +++++++ .../cert/writer}/atomic/atomic_writer.go | 0 .../internal/cert/writer/certwriter.go | 135 +++++++ .../pkg/webhook/internal/cert/writer/doc.go | 64 ++++ .../internal}/cert/writer/error.go | 0 .../pkg/webhook/internal/cert/writer/fs.go | 208 ++++++++++ .../webhook/internal/cert/writer/secret.go | 198 ++++++++++ .../controller-runtime/pkg/webhook/server.go | 278 ++++++++++++++ .../pkg/webhook/types/webhook.go | 28 ++ .../controller-runtime/pkg/webhook/util.go | 110 ++++++ 143 files changed, 8468 insertions(+), 1208 deletions(-) create mode 100644 cmd/controller-scaffold/cmd/webhook.go create mode 100644 pkg/scaffold/manager/webhook.go create mode 100644 pkg/scaffold/webhook/add_admissionbuilder_handler.go create mode 100644 pkg/scaffold/webhook/add_server.go create mode 100644 pkg/scaffold/webhook/admissionbuilder.go create mode 100644 pkg/scaffold/webhook/admissionhandler.go create mode 100644 pkg/scaffold/webhook/admissionwebhooks.go create mode 100644 pkg/scaffold/webhook/config.go create mode 100644 pkg/scaffold/webhook/server.go create mode 100644 pkg/scaffold/webhook/util.go create mode 100644 test/pkg/webhook/add_default_server.go create mode 100644 test/pkg/webhook/default_server/add_mutating_firstmate.go create mode 100644 test/pkg/webhook/default_server/add_mutating_namespace.go create mode 100644 test/pkg/webhook/default_server/add_validating_frigate.go create mode 100644 test/pkg/webhook/default_server/add_validating_kraken.go create mode 100644 test/pkg/webhook/default_server/firstmates/mutating/create_update_webhook.go create mode 100644 test/pkg/webhook/default_server/firstmates/mutating/delete_webhook.go create mode 100644 test/pkg/webhook/default_server/firstmates/mutating/firstmates_create_update_handler.go create mode 100644 test/pkg/webhook/default_server/firstmates/mutating/firstmates_delete_handler.go create mode 100644 test/pkg/webhook/default_server/firstmates/mutating/webhooks.go create mode 100644 test/pkg/webhook/default_server/frigates/validating/frigates_update_handler.go create mode 100644 test/pkg/webhook/default_server/frigates/validating/update_webhook.go create mode 100644 test/pkg/webhook/default_server/frigates/validating/webhooks.go create mode 100644 test/pkg/webhook/default_server/krakens/validating/create_webhook.go create mode 100644 test/pkg/webhook/default_server/krakens/validating/krakens_create_handler.go create mode 100644 test/pkg/webhook/default_server/krakens/validating/webhooks.go create mode 100644 test/pkg/webhook/default_server/namespaces/mutating/namespaces_update_handler.go create mode 100644 test/pkg/webhook/default_server/namespaces/mutating/update_webhook.go create mode 100644 test/pkg/webhook/default_server/namespaces/mutating/webhooks.go create mode 100644 test/pkg/webhook/default_server/server.go create mode 100644 test/pkg/webhook/webhook.go create mode 100644 vendor/github.com/google/uuid/.travis.yml create mode 100644 vendor/github.com/google/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/google/uuid/LICENSE create mode 100644 vendor/github.com/google/uuid/README.md create mode 100644 vendor/github.com/google/uuid/dce.go create mode 100644 vendor/github.com/google/uuid/doc.go create mode 100644 vendor/github.com/google/uuid/hash.go create mode 100644 vendor/github.com/google/uuid/marshal.go create mode 100644 vendor/github.com/google/uuid/node.go create mode 100644 vendor/github.com/google/uuid/node_js.go create mode 100644 vendor/github.com/google/uuid/node_net.go create mode 100644 vendor/github.com/google/uuid/sql.go create mode 100644 vendor/github.com/google/uuid/time.go create mode 100644 vendor/github.com/google/uuid/util.go create mode 100644 vendor/github.com/google/uuid/uuid.go create mode 100644 vendor/github.com/google/uuid/version1.go create mode 100644 vendor/github.com/google/uuid/version4.go create mode 100644 vendor/github.com/mattbaird/jsonpatch/.gitignore create mode 100644 vendor/github.com/mattbaird/jsonpatch/LICENSE create mode 100644 vendor/github.com/mattbaird/jsonpatch/README.md create mode 100644 vendor/github.com/mattbaird/jsonpatch/jsonpatch.go create mode 100644 vendor/github.com/pborman/uuid/.travis.yml create mode 100644 vendor/github.com/pborman/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/pborman/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/pborman/uuid/LICENSE create mode 100644 vendor/github.com/pborman/uuid/README.md create mode 100644 vendor/github.com/pborman/uuid/dce.go create mode 100644 vendor/github.com/pborman/uuid/doc.go create mode 100644 vendor/github.com/pborman/uuid/hash.go create mode 100644 vendor/github.com/pborman/uuid/marshal.go create mode 100644 vendor/github.com/pborman/uuid/node.go create mode 100644 vendor/github.com/pborman/uuid/sql.go create mode 100644 vendor/github.com/pborman/uuid/time.go create mode 100644 vendor/github.com/pborman/uuid/util.go create mode 100644 vendor/github.com/pborman/uuid/uuid.go create mode 100644 vendor/github.com/pborman/uuid/version1.go create mode 100644 vendor/github.com/pborman/uuid/version4.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/example/controller.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/example/mutatingwebhook.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/example/validatingwebhook.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/certwriter.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/doc.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/fs.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/multiple.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/secret.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/controller.go delete mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/admission/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/leader_election.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/leader_election.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/patch/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/patch/patch.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/builder.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/decode.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/http.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/response.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/types/types.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/webhook.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission => webhook/internal}/cert/generator/certgenerator.go (100%) rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission => webhook/internal}/cert/generator/doc.go (95%) rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission => webhook/internal}/cert/generator/fake/certgenerator.go (84%) rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission => webhook/internal}/cert/generator/selfsigned.go (97%) create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission/cert/writer/internal => webhook/internal/cert/writer}/atomic/atomic_writer.go (100%) create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go rename vendor/sigs.k8s.io/controller-runtime/pkg/{admission => webhook/internal}/cert/writer/error.go (100%) create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/types/webhook.go create mode 100644 vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go diff --git a/Gopkg.lock b/Gopkg.lock index 412e63634..767f3d98d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,6 +189,14 @@ pruneopts = "T" revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" +[[projects]] + digest = "1:3a26588bc48b96825977c1b3df964f8fd842cd6860cc26370588d3563433cf11" + name = "github.com/google/uuid" + packages = ["."] + pruneopts = "T" + revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494" + version = "v1.0.0" + [[projects]] digest = "1:35735e2255fa34521c2a1355fb2a3a2300bc9949f487be1c1ce8ee8efcfa2d04" name = "github.com/googleapis/gnostic" @@ -316,6 +324,14 @@ revision = "dd7de90c06bca70f18136e59dec2270c19a401e7" version = "v1.0.0" +[[projects]] + branch = "master" + digest = "1:fc2b04b0069d6b10bdef96d278fe20c345794009685ed3c8c7f1a6dc023eefec" + name = "github.com/mattbaird/jsonpatch" + packages = ["."] + pruneopts = "T" + revision = "81af80346b1a01caae0cbc27fd3c1ba5b11e189f" + [[projects]] digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af" name = "github.com/mitchellh/go-homedir" @@ -398,6 +414,14 @@ revision = "b6ea1ea48f981d0f615a154a45eabb9dd466556d" version = "v1.4.1" +[[projects]] + digest = "1:e5d0bd87abc2781d14e274807a470acd180f0499f8bf5bb18606e9ec22ad9de9" + name = "github.com/pborman/uuid" + packages = ["."] + pruneopts = "T" + revision = "adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1" + version = "v1.2" + [[projects]] digest = "1:ccf9949c9c53e85dcb7e2905fc620571422567040925381e6baa62f0b7b850fe" name = "github.com/pelletier/go-toml" @@ -675,6 +699,7 @@ digest = "1:75905606c2db8d9526ccff0acf5655759afe3d901d1d64868bd87985b4a46e36" name = "k8s.io/api" packages = [ + "admission/v1beta1", "admissionregistration/v1alpha1", "admissionregistration/v1beta1", "apps/v1", @@ -760,6 +785,7 @@ "pkg/util/runtime", "pkg/util/sets", "pkg/util/strategicpatch", + "pkg/util/uuid", "pkg/util/validation", "pkg/util/validation/field", "pkg/util/wait", @@ -778,6 +804,7 @@ name = "k8s.io/client-go" packages = [ "discovery", + "dynamic", "kubernetes", "kubernetes/scheme", "kubernetes/typed/admissionregistration/v1alpha1", @@ -825,6 +852,8 @@ "tools/clientcmd/api", "tools/clientcmd/api/latest", "tools/clientcmd/api/v1", + "tools/leaderelection", + "tools/leaderelection/resourcelock", "tools/metrics", "tools/pager", "tools/record", @@ -881,7 +910,7 @@ revision = "e3762e86a74c878ffed47484592986685639c2cd" [[projects]] - digest = "1:38d4fe1026d0fe6b42835fd684dcb6b320d5f6be21b021a641fc228060300051" + digest = "1:4c3be496e0e0977b54e265fea184b57e9928fed12a1a14687c04b5d42e044abc" name = "sigs.k8s.io/controller-runtime" packages = [ "pkg/cache", @@ -897,7 +926,9 @@ "pkg/handler", "pkg/internal/controller", "pkg/internal/recorder", + "pkg/leaderelection", "pkg/manager", + "pkg/patch", "pkg/predicate", "pkg/reconcile", "pkg/recorder", @@ -907,10 +938,19 @@ "pkg/runtime/signals", "pkg/source", "pkg/source/internal", + "pkg/webhook", + "pkg/webhook/admission", + "pkg/webhook/admission/builder", + "pkg/webhook/admission/types", + "pkg/webhook/internal/cert", + "pkg/webhook/internal/cert/generator", + "pkg/webhook/internal/cert/writer", + "pkg/webhook/internal/cert/writer/atomic", + "pkg/webhook/types", ] pruneopts = "T" - revision = "2ef903b2a5ed4cf8adde14a27994a291388a1789" - version = "v0.1.2" + revision = "53fc44b56078cd095b11bd44cfa0288ee4cf718f" + version = "v0.1.4" [[projects]] branch = "master" @@ -943,11 +983,13 @@ "golang.org/x/net/context", "golang.org/x/tools/imports", "gopkg.in/yaml.v2", + "k8s.io/api/admissionregistration/v1beta1", "k8s.io/api/apps/v1", "k8s.io/api/core/v1", "k8s.io/api/rbac/v1", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apimachinery/pkg/api/errors", + "k8s.io/apimachinery/pkg/api/meta", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", @@ -970,10 +1012,15 @@ "sigs.k8s.io/controller-runtime/pkg/handler", "sigs.k8s.io/controller-runtime/pkg/manager", "sigs.k8s.io/controller-runtime/pkg/reconcile", + "sigs.k8s.io/controller-runtime/pkg/runtime/inject", "sigs.k8s.io/controller-runtime/pkg/runtime/log", "sigs.k8s.io/controller-runtime/pkg/runtime/scheme", "sigs.k8s.io/controller-runtime/pkg/runtime/signals", "sigs.k8s.io/controller-runtime/pkg/source", + "sigs.k8s.io/controller-runtime/pkg/webhook", + "sigs.k8s.io/controller-runtime/pkg/webhook/admission", + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder", + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types", "sigs.k8s.io/testing_frameworks/integration", ] solver-name = "gps-cdcl" diff --git a/cmd/controller-scaffold/cmd/project.go b/cmd/controller-scaffold/cmd/project.go index 010b54f57..3fe1a0931 100644 --- a/cmd/controller-scaffold/cmd/project.go +++ b/cmd/controller-scaffold/cmd/project.go @@ -91,6 +91,7 @@ controller-scaffold project --domain k8s.io --license apache2 --owner "The Kuber dkr, &manager.APIs{}, &manager.Controller{}, + &manager.Webhook{}, &manager.Config{Image: imgName}, &project.GitIgnore{}, &project.Kustomize{}, diff --git a/cmd/controller-scaffold/cmd/webhook.go b/cmd/controller-scaffold/cmd/webhook.go new file mode 100644 index 000000000..08bb4d0c1 --- /dev/null +++ b/cmd/controller-scaffold/cmd/webhook.go @@ -0,0 +1,103 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-tools/pkg/scaffold" + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" + "sigs.k8s.io/controller-tools/pkg/scaffold/webhook" +) + +var res *resource.Resource +var operations []string +var server string +var webhookType string + +// WebhookCmd represents the webhook command +var WebhookCmd = &cobra.Command{ + Use: "webhook", + Short: "Scaffold a webhook server", + Long: `Scaffold a webhook server`, + Example: `webhook example: TBD`, + Run: func(cmd *cobra.Command, args []string) { + DieIfNoProject() + + fmt.Println("Writing scaffold for you to edit...") + + if len(res.Resource) == 0 { + gvr, _ := meta.UnsafeGuessKindToResource(schema.GroupVersionKind{ + Group: res.Group, Version: res.Version, Kind: res.Kind}) + res.Resource = gvr.Resource + } + + err := (&scaffold.Scaffold{}).Execute(input.Options{}, + &webhook.AdmissionHandler{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + &webhook.AdmissionWebhookBuilder{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + &webhook.AdmissionWebhooks{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + &webhook.AddAdmissionWebhookBuilderHandler{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + &webhook.Server{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + &webhook.AddServer{Resource: res, Config: webhook.Config{Server: server, Type: webhookType, Operations: operations}}, + ) + if err != nil { + log.Fatal(err) + } + + if doMake { + fmt.Println("Running make...") + cm := exec.Command("make") // #nosec + cm.Stderr = os.Stderr + cm.Stdout = os.Stdout + if err := cm.Run(); err != nil { + log.Fatal(err) + } + } + }, +} + +func init() { + rootCmd.AddCommand(WebhookCmd) + WebhookCmd.Flags().StringVar(&server, "server", "default", + "name of the server") + WebhookCmd.Flags().StringVar(&webhookType, "type", "", + "webhook type, e.g. mutating or validating") + WebhookCmd.Flags().StringSliceVar(&operations, "operations", []string{"create"}, + "the operations that the webhook will intercept, e.g. create, update, delete and connect") + WebhookCmd.Flags().BoolVar(&doMake, "make", true, + "if true, run make after generating files") + res = gvkForFlags(WebhookCmd.Flags()) +} + +// gvkForFlags registers flags for Resource fields and returns the Resource +func gvkForFlags(f *flag.FlagSet) *resource.Resource { + r := &resource.Resource{} + f.StringVar(&r.Group, "group", "", "resource Group") + f.StringVar(&r.Version, "version", "", "resource Version") + f.StringVar(&r.Kind, "kind", "", "resource Kind") + f.StringVar(&r.Resource, "resource", "", "resource Resource") + return r +} diff --git a/generated_golden.sh b/generated_golden.sh index 48d1060da..f3a8a0a81 100755 --- a/generated_golden.sh +++ b/generated_golden.sh @@ -22,9 +22,14 @@ cd test ln -s ../vendor vendor ../bin/controller-scaffold project --domain testproject.org --controller-tools-path ".." --license apache2 --owner "The Kubernetes authors" --dep=false ../bin/controller-scaffold api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false +../bin/controller-scaffold webhook --group crew --version v1 --kind FirstMate --type=mutating --operations=create,update +../bin/controller-scaffold webhook --group crew --version v1 --kind FirstMate --type=mutating --operations=delete ../bin/controller-scaffold api --group ship --version v1beta1 --kind Frigate --example=false --controller=true --resource=true --make=false +../bin/controller-scaffold webhook --group ship --version v1beta1 --kind Frigate --type=validating --operations=update ../bin/controller-scaffold api --group creatures --version v2alpha1 --kind Kraken --namespaced=false --example=false --controller=true --resource=true --make=false +../bin/controller-scaffold webhook --group creatures --version v2alpha1 --kind Kraken --type=validating --operations=create ../bin/controller-scaffold api --group core --version v1 --kind Namespace --example=false --controller=true --resource=false --namespaced=false +../bin/controller-scaffold webhook --group core --version v1 --kind Namespace --type=mutating --operations=update ../bin/controller-scaffold api --group policy --version v1beta1 --kind HealthCheckPolicy --example=false --controller=true --resource=true --namespaced=false make rm -rf ./bin/ diff --git a/pkg/scaffold/manager/cmd.go b/pkg/scaffold/manager/cmd.go index 9c5998c01..f3181f674 100644 --- a/pkg/scaffold/manager/cmd.go +++ b/pkg/scaffold/manager/cmd.go @@ -43,44 +43,66 @@ var cmdTemplate = `{{ .Boilerplate }} package main import ( - "log" + "flag" + "os" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" "{{ .Repo }}/pkg/apis" "{{ .Repo }}/pkg/controller" + "{{ .Repo }}/pkg/webhook" ) func main() { + logf.SetLogger(logf.ZapLogger(false)) + log := logf.Log.WithName("entrypoint") + // Get a config to talk to the apiserver + log.Info("setting up client for manager") cfg, err := config.GetConfig() if err != nil { - log.Fatal(err) + log.Error(err, "unable to set up client config") + os.Exit(1) } // Create a new Cmd to provide shared dependencies and start components + log.Info("setting up manager") mgr, err := manager.New(cfg, manager.Options{}) if err != nil { - log.Fatal(err) + log.Error(err, "unable to set up overall controller manager") + os.Exit(1) } - log.Printf("Registering Components.") + log.Info("Registering Components.") // Setup Scheme for all resources + log.Info("setting up scheme") if err := apis.AddToScheme(mgr.GetScheme()); err != nil { - log.Fatal(err) + log.Error(err, "unable add APIs to scheme") + os.Exit(1) } // Setup all Controllers + log.Info("Setting up controller") if err := controller.AddToManager(mgr); err != nil { - log.Fatal(err) + log.Error(err, "unable to register controllers to the manager") + os.Exit(1) } - log.Printf("Starting the Cmd.") + log.Info("setting up webhooks") + if err := webhook.AddToManager(mgr); err != nil { + log.Error(err, "unable to register webhooks to the manager") + os.Exit(1) + } // Start the Cmd - log.Fatal(mgr.Start(signals.SetupSignalHandler())) + log.Info("Starting the Cmd.") + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Error(err, "unable to run the manager") + os.Exit(1) + } } ` diff --git a/pkg/scaffold/manager/config.go b/pkg/scaffold/manager/config.go index 1cd412211..9f58b87a0 100644 --- a/pkg/scaffold/manager/config.go +++ b/pkg/scaffold/manager/config.go @@ -86,7 +86,15 @@ spec: - command: - /root/manager image: {{ .Image }} + imagePullPolicy: Always name: manager + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SECRET_NAME + value: $(WEBHOOK_SECRET_NAME) resources: limits: cpu: 100m @@ -94,5 +102,24 @@ spec: requests: cpu: 100m memory: 20Mi + ports: + - containerPort: 9876 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/cert + name: cert + readOnly: true terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: webhook-server-secret + namespace: system ` diff --git a/pkg/scaffold/manager/webhook.go b/pkg/scaffold/manager/webhook.go new file mode 100644 index 000000000..49baaa7d8 --- /dev/null +++ b/pkg/scaffold/manager/webhook.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "path/filepath" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" +) + +var _ input.File = &Webhook{} + +// Webhook scaffolds a webhook.go to add webhook server(s) to a manager.Cmd +type Webhook struct { + input.Input +} + +// GetInput implements input.File +func (c *Webhook) GetInput() (input.Input, error) { + if c.Path == "" { + c.Path = filepath.Join("pkg", "webhook", "webhook.go") + } + c.TemplateBody = webhookTemplate + return c.Input, nil +} + +var webhookTemplate = `{{ .Boilerplate }} + +package webhook + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// AddToManagerFuncs is a list of functions to add all Controllers to the Manager +var AddToManagerFuncs []func(manager.Manager) error + +// AddToManager adds all Controllers to the Manager +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=mutatingwebhookconfigurations;validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete +func AddToManager(m manager.Manager) error { + for _, f := range AddToManagerFuncs { + if err := f(m); err != nil { + return err + } + } + return nil +} +` diff --git a/pkg/scaffold/project/kustomize.go b/pkg/scaffold/project/kustomize.go index fd3906219..c8d731f67 100644 --- a/pkg/scaffold/project/kustomize.go +++ b/pkg/scaffold/project/kustomize.go @@ -77,4 +77,11 @@ resources: patches: - manager_image_patch.yaml + +vars: +- name: WEBHOOK_SECRET_NAME + objref: + kind: Secret + name: webhook-server-secret + apiVersion: v1 ` diff --git a/pkg/scaffold/webhook/add_admissionbuilder_handler.go b/pkg/scaffold/webhook/add_admissionbuilder_handler.go new file mode 100644 index 000000000..564396031 --- /dev/null +++ b/pkg/scaffold/webhook/add_admissionbuilder_handler.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &AddAdmissionWebhookBuilderHandler{} + +// AddAdmissionWebhookBuilderHandler scaffolds adds a new admission webhook builder. +type AddAdmissionWebhookBuilderHandler struct { + input.Input + + // Resource is a resource in the API group + Resource *resource.Resource + + Config +} + +// GetInput implements input.File +func (a *AddAdmissionWebhookBuilderHandler) GetInput() (input.Input, error) { + a.Server = strings.ToLower(a.Server) + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", + fmt.Sprintf("%s_server", a.Server), + fmt.Sprintf("add_%s_%s.go", a.Type, strings.ToLower(a.Resource.Kind))) + } + a.TemplateBody = addAdmissionWebhookBuilderHandlerTemplate + return a.Input, nil +} + +var addAdmissionWebhookBuilderHandlerTemplate = `{{ .Boilerplate }} + +package {{ .Server }}server + +import ( + "fmt" + + "{{ .Repo }}/pkg/webhook/{{ .Server }}_server/{{ .Resource.Resource }}/{{ .Type }}" +) + +func init() { + for k, v := range {{ .Type }}.Builders { + _, found := builderMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in builder map: %v", k)) + } + builderMap[k] = v + } + for k, v := range {{ .Type }}.HandlerMap { + _, found := HandlerMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in handler map: %v", k)) + } + _, found = builderMap[k] + if !found { + log.V(1).Info(fmt.Sprintf( + "can't find webhook builder name %q in builder map", k)) + continue + } + HandlerMap[k] = v + } +} +` diff --git a/pkg/scaffold/webhook/add_server.go b/pkg/scaffold/webhook/add_server.go new file mode 100644 index 000000000..f3921d7a8 --- /dev/null +++ b/pkg/scaffold/webhook/add_server.go @@ -0,0 +1,60 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &AddServer{} + +// AddServer scaffolds adds a new webhook server. +type AddServer struct { + input.Input + + // Resource is a resource in the API group + Resource *resource.Resource + + Config +} + +// GetInput implements input.File +func (a *AddServer) GetInput() (input.Input, error) { + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", fmt.Sprintf("add_%s_server.go", a.Server)) + } + a.TemplateBody = addServerTemplate + return a.Input, nil +} + +var addServerTemplate = `{{ .Boilerplate }} + +package webhook + +import ( + server "{{ .Repo }}/pkg/webhook/{{ .Server }}_server" +) + +func init() { + // AddToManagerFuncs is a list of functions to create webhook servers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, server.Add) +} +` diff --git a/pkg/scaffold/webhook/admissionbuilder.go b/pkg/scaffold/webhook/admissionbuilder.go new file mode 100644 index 000000000..792497676 --- /dev/null +++ b/pkg/scaffold/webhook/admissionbuilder.go @@ -0,0 +1,101 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &AdmissionWebhookBuilder{} + +// AdmissionWebhookBuilder scaffolds adds a new webhook server. +type AdmissionWebhookBuilder struct { + input.Input + + // Resource is a resource in the API group + Resource *resource.Resource + + // ResourcePackage is the package of the Resource + ResourcePackage string + + // GroupDomain is the Group + "." + Domain for the Resource + GroupDomain string + + Config + + BuilderName string + + OperationsParameterString string + + Mutating bool +} + +// GetInput implements input.File +func (a *AdmissionWebhookBuilder) GetInput() (input.Input, error) { + a.ResourcePackage, a.GroupDomain = getResourceInfo(coreGroups, a.Resource, a.Input) + + if a.Type == "mutating" { + a.Mutating = true + } + a.Type = strings.ToLower(a.Type) + a.BuilderName = builderName(a.Config, a.Resource.Resource) + ops := make([]string, len(a.Operations)) + for i, op := range a.Operations { + ops[i] = "admissionregistrationv1beta1." + strings.Title(op) + } + a.OperationsParameterString = strings.Join(ops, ", ") + + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", + fmt.Sprintf("%s_server", a.Server), + a.Resource.Resource, + a.Type, + fmt.Sprintf("%s_webhook.go", strings.Join(a.Operations, "_"))) + } + a.TemplateBody = admissionWebhookBuilderTemplate + return a.Input, nil +} + +var admissionWebhookBuilderTemplate = `{{ .Boilerplate }} + +package {{ .Type }} + +import ( + {{ .Resource.Group}}{{ .Resource.Version }} "{{ .ResourcePackage }}/{{ .Resource.Group}}/{{ .Resource.Version }}" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +func init() { + builderName := "{{ .BuilderName }}" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName + ".{{ .Domain }}"). + Path("/" + builderName). +{{ if .Mutating }} Mutating(). +{{ else }} Validating(). +{{ end }} + Operations({{ .OperationsParameterString }}). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{}) +} +` diff --git a/pkg/scaffold/webhook/admissionhandler.go b/pkg/scaffold/webhook/admissionhandler.go new file mode 100644 index 000000000..be7edb66f --- /dev/null +++ b/pkg/scaffold/webhook/admissionhandler.go @@ -0,0 +1,168 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &AddAdmissionWebhookBuilderHandler{} + +// AdmissionHandler scaffolds an admission handler +type AdmissionHandler struct { + input.Input + + // ResourcePackage is the package of the Resource + ResourcePackage string + + // GroupDomain is the Group + "." + Domain for the Resource + GroupDomain string + + // Resource is a resource in the API group + Resource *resource.Resource + + Config + + BuilderName string + + OperationsString string + + Mutate bool +} + +// GetInput implements input.File +func (a *AdmissionHandler) GetInput() (input.Input, error) { + a.ResourcePackage, a.GroupDomain = getResourceInfo(coreGroups, a.Resource, a.Input) + a.Type = strings.ToLower(a.Type) + if a.Type == "mutating" { + a.Mutate = true + } + a.BuilderName = builderName(a.Config, a.Resource.Resource) + ops := make([]string, len(a.Operations)) + for i, op := range a.Operations { + ops[i] = strings.Title(op) + } + a.OperationsString = strings.Join(ops, "") + + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", + fmt.Sprintf("%s_server", a.Server), + a.Resource.Resource, + a.Type, + fmt.Sprintf("%s_%s_handler.go", a.Resource.Resource, strings.Join(a.Operations, "_"))) + } + a.TemplateBody = addAdmissionHandlerTemplate + return a.Input, nil +} + +var addAdmissionHandlerTemplate = `{{ .Boilerplate }} + +package {{ .Type }} + +import ( + "context" + "net/http" + + {{ .Resource.Group}}{{ .Resource.Version }} "{{ .ResourcePackage }}/{{ .Resource.Group}}/{{ .Resource.Version }}" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +func init() { + webhookName := "{{ .BuilderName }}" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &{{ .Resource.Kind }}{{ .OperationsString }}Handler{}) +} + +// {{ .Resource.Kind }}{{ .OperationsString }}Handler handles {{ .Resource.Kind }} +type {{ .Resource.Kind }}{{ .OperationsString }}Handler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} +{{ if .Mutate }} +func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) {{ .Type }}{{ .Resource.Kind }}Fn(ctx context.Context, obj *{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}) error { + // TODO(user): implement your admission logic + return nil +} +{{ else }} +func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) {{ .Type }}{{ .Resource.Kind }}Fn(ctx context.Context, obj *{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}) (bool, string, error) { + // TODO(user): implement your admission logic + return true, "allowed to be admitted", nil +} +{{ end }} +var _ admission.Handler = &{{ .Resource.Kind }}{{ .OperationsString }}Handler{} +{{ if .Mutate }} +// Handle handles admission requests. +func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + copy := obj.DeepCopy() + + err = h.{{ .Type }}{{ .Resource.Kind }}Fn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(obj, copy) +} +{{ else }} +// Handle handles admission requests. +func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + + allowed, reason, err := h.{{ .Type }}{{ .Resource.Kind }}Fn(ctx, obj) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.ValidationResponse(allowed, reason) +} +{{ end }} +//var _ inject.Client = &{{ .Resource.Kind }}{{ .OperationsString }}Handler{} +// +//// InjectClient injects the client into the {{ .Resource.Kind }}{{ .OperationsString }}Handler +//func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &{{ .Resource.Kind }}{{ .OperationsString }}Handler{} + +// InjectDecoder injects the decoder into the {{ .Resource.Kind }}{{ .OperationsString }}Handler +func (h *{{ .Resource.Kind }}{{ .OperationsString }}Handler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} +` diff --git a/pkg/scaffold/webhook/admissionwebhooks.go b/pkg/scaffold/webhook/admissionwebhooks.go new file mode 100644 index 000000000..053f3c2e7 --- /dev/null +++ b/pkg/scaffold/webhook/admissionwebhooks.go @@ -0,0 +1,69 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &Server{} + +// AdmissionWebhooks scaffolds how to construct a webhook server and register webhooks. +type AdmissionWebhooks struct { + input.Input + + // Resource is a resource in the API group + Resource *resource.Resource + + Config +} + +// GetInput implements input.File +func (a *AdmissionWebhooks) GetInput() (input.Input, error) { + a.Server = strings.ToLower(a.Server) + a.Type = strings.ToLower(a.Type) + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", + fmt.Sprintf("%s_server", a.Server), + strings.ToLower(a.Resource.Resource), + a.Type, "webhooks.go") + } + a.TemplateBody = webhooksTemplate + return a.Input, nil +} + +var webhooksTemplate = `{{ .Boilerplate }} + +package {{ .Type }} + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + // Builders contain admission webhook builders + Builders = map[string]*builder.WebhookBuilder{} + // HandlerMap contains admission webhook handlers + HandlerMap = map[string][]admission.Handler{} +) +` diff --git a/pkg/scaffold/webhook/config.go b/pkg/scaffold/webhook/config.go new file mode 100644 index 000000000..dd48bf568 --- /dev/null +++ b/pkg/scaffold/webhook/config.go @@ -0,0 +1,27 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +// Config contains the information required to scaffold files for a webhook. +type Config struct { + // Server is the name of the server. + Server string + // Type is the type of the webhook. + Type string + // Operations that the webhook will intercept, e.g. create, update, delete, connect + Operations []string +} diff --git a/pkg/scaffold/webhook/server.go b/pkg/scaffold/webhook/server.go new file mode 100644 index 000000000..59461f360 --- /dev/null +++ b/pkg/scaffold/webhook/server.go @@ -0,0 +1,125 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +var _ input.File = &Server{} + +// Server scaffolds how to construct a webhook server and register webhooks. +type Server struct { + input.Input + + // Resource is a resource in the API group + Resource *resource.Resource + + Config +} + +// GetInput implements input.File +func (a *Server) GetInput() (input.Input, error) { + if a.Path == "" { + a.Path = filepath.Join("pkg", "webhook", fmt.Sprintf("%s_server", a.Server), "server.go") + } + a.TemplateBody = serverTemplate + return a.Input, nil +} + +var serverTemplate = `{{ .Boilerplate }} + +package {{ .Server }}server + +import ( + "fmt" + "os" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + log = logf.Log.WithName("{{ .Server }}_server") + builderMap = map[string]*builder.WebhookBuilder{} + // HandlerMap contains all admission webhook handlers. + HandlerMap = map[string][]admission.Handler{} +) + +// Add adds itself to the manager +func Add(mgr manager.Manager) error { + ns := os.Getenv("POD_NAMESPACE") + if len(ns) == 0 { + ns = "default" + } + secretName := os.Getenv("SECRET_NAME") + if len(secretName) == 0 { + secretName = "webhook-server-secret" + } + + svr, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{ + // TODO(user): change the configuration of ServerOptions based on your need. + Port: 9876, + CertDir: "/tmp/cert", + BootstrapOptions: &webhook.BootstrapOptions{ + Secret: &types.NamespacedName{ + Namespace: ns, + Name: secretName, + }, + + Service: &webhook.Service{ + Namespace: ns, + Name: "webhook-server-service", + // Selectors should select the pods that runs this webhook server. + Selectors: map[string]string{ + "control-plane": "controller-manager", + }, + }, + }, + }) + if err != nil { + return err + } + + var webhooks []webhook.Webhook + for k, builder := range builderMap { + handlers, ok := HandlerMap[k] + if !ok { + log.V(1).Info(fmt.Sprintf("can't find handlers for builder: %v", k)) + handlers = []admission.Handler{} + } + wh, err := builder. + Handlers(handlers...). + WithManager(mgr). + Build() + if err != nil { + return err + } + webhooks = append(webhooks, wh) + } + + return svr.Register(webhooks...) +} +` diff --git a/pkg/scaffold/webhook/util.go b/pkg/scaffold/webhook/util.go new file mode 100644 index 000000000..02129f88a --- /dev/null +++ b/pkg/scaffold/webhook/util.go @@ -0,0 +1,67 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "sigs.k8s.io/controller-tools/pkg/scaffold/input" + "sigs.k8s.io/controller-tools/pkg/scaffold/resource" +) + +// Use the k8s.io/api package for core resources +var coreGroups = map[string]string{ + "apps": "", + "admissionregistration": "k8s.io", + "apiextensions": "k8s.io", + "authentication": "k8s.io", + "autoscaling": "", + "batch": "", + "certificates": "k8s.io", + "core": "", + "extensions": "", + "metrics": "k8s.io", + "policy": "", + "rbac.authorization": "k8s.io", + "storage": "k8s.io", +} + +func builderName(config Config, resource string) string { + opsStr := strings.Join(config.Operations, "-") + return fmt.Sprintf("%s-%s-%s", config.Type, opsStr, resource) +} + +func getResourceInfo(coreGroups map[string]string, r *resource.Resource, in input.Input) (resourcePackage, groupDomain string) { + resourcePath := filepath.Join("pkg", "apis", r.Group, r.Version, + fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind))) + if _, err := os.Stat(resourcePath); os.IsNotExist(err) { + if domain, found := coreGroups[r.Group]; found { + resourcePackage := path.Join("k8s.io", "api") + groupDomain = r.Group + if domain != "" { + groupDomain = r.Group + "." + domain + } + return resourcePackage, groupDomain + } + // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath + } + return path.Join(in.Repo, "pkg", "apis"), r.Group + "." + in.Domain +} diff --git a/test/cmd/manager/main.go b/test/cmd/manager/main.go index c05b7462c..e71359f15 100644 --- a/test/cmd/manager/main.go +++ b/test/cmd/manager/main.go @@ -17,43 +17,64 @@ limitations under the License. package main import ( - "log" + "os" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" "sigs.k8s.io/controller-tools/test/pkg/apis" "sigs.k8s.io/controller-tools/test/pkg/controller" + "sigs.k8s.io/controller-tools/test/pkg/webhook" ) func main() { + logf.SetLogger(logf.ZapLogger(false)) + log := logf.Log.WithName("entrypoint") + // Get a config to talk to the apiserver + log.Info("setting up client for manager") cfg, err := config.GetConfig() if err != nil { - log.Fatal(err) + log.Error(err, "unable to set up client config") + os.Exit(1) } // Create a new Cmd to provide shared dependencies and start components + log.Info("setting up manager") mgr, err := manager.New(cfg, manager.Options{}) if err != nil { - log.Fatal(err) + log.Error(err, "unable to set up overall controller manager") + os.Exit(1) } - log.Printf("Registering Components.") + log.Info("Registering Components.") // Setup Scheme for all resources + log.Info("setting up scheme") if err := apis.AddToScheme(mgr.GetScheme()); err != nil { - log.Fatal(err) + log.Error(err, "unable add APIs to scheme") + os.Exit(1) } // Setup all Controllers + log.Info("Setting up controller") if err := controller.AddToManager(mgr); err != nil { - log.Fatal(err) + log.Error(err, "unable to register controllers to the manager") + os.Exit(1) } - log.Printf("Starting the Cmd.") + log.Info("setting up webhooks") + if err := webhook.AddToManager(mgr); err != nil { + log.Error(err, "unable to register webhooks to the manager") + os.Exit(1) + } // Start the Cmd - log.Fatal(mgr.Start(signals.SetupSignalHandler())) + log.Info("Starting the Cmd.") + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Error(err, "unable to run the manager") + os.Exit(1) + } } diff --git a/test/config/default/kustomization.yaml b/test/config/default/kustomization.yaml index 88392dcbe..69ee12d38 100644 --- a/test/config/default/kustomization.yaml +++ b/test/config/default/kustomization.yaml @@ -24,3 +24,10 @@ resources: patches: - manager_image_patch.yaml + +vars: +- name: WEBHOOK_SECRET_NAME + objref: + kind: Secret + name: webhook-server-secret + apiVersion: v1 diff --git a/test/config/manager/manager.yaml b/test/config/manager/manager.yaml index f469bcf19..5ca44d3d7 100644 --- a/test/config/manager/manager.yaml +++ b/test/config/manager/manager.yaml @@ -44,7 +44,15 @@ spec: - command: - /root/manager image: controller:latest + imagePullPolicy: Always name: manager + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SECRET_NAME + value: $(WEBHOOK_SECRET_NAME) resources: limits: cpu: 100m @@ -52,4 +60,23 @@ spec: requests: cpu: 100m memory: 20Mi + ports: + - containerPort: 9876 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/cert + name: cert + readOnly: true terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: webhook-server-secret + namespace: system diff --git a/test/config/rbac/rbac_role.yaml b/test/config/rbac/rbac_role.yaml index 7181f2761..240d29008 100644 --- a/test/config/rbac/rbac_role.yaml +++ b/test/config/rbac/rbac_role.yaml @@ -76,3 +76,40 @@ rules: - update - patch - delete +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - create + - update + - patch + - delete diff --git a/test/pkg/webhook/add_default_server.go b/test/pkg/webhook/add_default_server.go new file mode 100644 index 000000000..c2ecabf70 --- /dev/null +++ b/test/pkg/webhook/add_default_server.go @@ -0,0 +1,26 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + server "sigs.k8s.io/controller-tools/test/pkg/webhook/default_server" +) + +func init() { + // AddToManagerFuncs is a list of functions to create webhook servers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, server.Add) +} diff --git a/test/pkg/webhook/default_server/add_mutating_firstmate.go b/test/pkg/webhook/default_server/add_mutating_firstmate.go new file mode 100644 index 000000000..b8fbda855 --- /dev/null +++ b/test/pkg/webhook/default_server/add_mutating_firstmate.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaultserver + +import ( + "fmt" + + "sigs.k8s.io/controller-tools/test/pkg/webhook/default_server/firstmates/mutating" +) + +func init() { + for k, v := range mutating.Builders { + _, found := builderMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in builder map: %v", k)) + } + builderMap[k] = v + } + for k, v := range mutating.HandlerMap { + _, found := HandlerMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in handler map: %v", k)) + } + _, found = builderMap[k] + if !found { + log.V(1).Info(fmt.Sprintf( + "can't find webhook builder name %q in builder map", k)) + continue + } + HandlerMap[k] = v + } +} diff --git a/test/pkg/webhook/default_server/add_mutating_namespace.go b/test/pkg/webhook/default_server/add_mutating_namespace.go new file mode 100644 index 000000000..5afc28f1f --- /dev/null +++ b/test/pkg/webhook/default_server/add_mutating_namespace.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaultserver + +import ( + "fmt" + + "sigs.k8s.io/controller-tools/test/pkg/webhook/default_server/namespaces/mutating" +) + +func init() { + for k, v := range mutating.Builders { + _, found := builderMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in builder map: %v", k)) + } + builderMap[k] = v + } + for k, v := range mutating.HandlerMap { + _, found := HandlerMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in handler map: %v", k)) + } + _, found = builderMap[k] + if !found { + log.V(1).Info(fmt.Sprintf( + "can't find webhook builder name %q in builder map", k)) + continue + } + HandlerMap[k] = v + } +} diff --git a/test/pkg/webhook/default_server/add_validating_frigate.go b/test/pkg/webhook/default_server/add_validating_frigate.go new file mode 100644 index 000000000..9df27e08e --- /dev/null +++ b/test/pkg/webhook/default_server/add_validating_frigate.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaultserver + +import ( + "fmt" + + "sigs.k8s.io/controller-tools/test/pkg/webhook/default_server/frigates/validating" +) + +func init() { + for k, v := range validating.Builders { + _, found := builderMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in builder map: %v", k)) + } + builderMap[k] = v + } + for k, v := range validating.HandlerMap { + _, found := HandlerMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in handler map: %v", k)) + } + _, found = builderMap[k] + if !found { + log.V(1).Info(fmt.Sprintf( + "can't find webhook builder name %q in builder map", k)) + continue + } + HandlerMap[k] = v + } +} diff --git a/test/pkg/webhook/default_server/add_validating_kraken.go b/test/pkg/webhook/default_server/add_validating_kraken.go new file mode 100644 index 000000000..4051ade04 --- /dev/null +++ b/test/pkg/webhook/default_server/add_validating_kraken.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaultserver + +import ( + "fmt" + + "sigs.k8s.io/controller-tools/test/pkg/webhook/default_server/krakens/validating" +) + +func init() { + for k, v := range validating.Builders { + _, found := builderMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in builder map: %v", k)) + } + builderMap[k] = v + } + for k, v := range validating.HandlerMap { + _, found := HandlerMap[k] + if found { + log.V(1).Info(fmt.Sprintf( + "conflicting webhook builder names in handler map: %v", k)) + } + _, found = builderMap[k] + if !found { + log.V(1).Info(fmt.Sprintf( + "can't find webhook builder name %q in builder map", k)) + continue + } + HandlerMap[k] = v + } +} diff --git a/test/pkg/webhook/default_server/firstmates/mutating/create_update_webhook.go b/test/pkg/webhook/default_server/firstmates/mutating/create_update_webhook.go new file mode 100644 index 000000000..ac71423fd --- /dev/null +++ b/test/pkg/webhook/default_server/firstmates/mutating/create_update_webhook.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" + crewv1 "sigs.k8s.io/controller-tools/test/pkg/apis/crew/v1" +) + +func init() { + builderName := "mutating-create-update-firstmates" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName+".testproject.org"). + Path("/"+builderName). + Mutating(). + Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&crewv1.FirstMate{}) +} diff --git a/test/pkg/webhook/default_server/firstmates/mutating/delete_webhook.go b/test/pkg/webhook/default_server/firstmates/mutating/delete_webhook.go new file mode 100644 index 000000000..ae064d905 --- /dev/null +++ b/test/pkg/webhook/default_server/firstmates/mutating/delete_webhook.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" + crewv1 "sigs.k8s.io/controller-tools/test/pkg/apis/crew/v1" +) + +func init() { + builderName := "mutating-delete-firstmates" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName + ".testproject.org"). + Path("/" + builderName). + Mutating(). + Operations(admissionregistrationv1beta1.Delete). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&crewv1.FirstMate{}) +} diff --git a/test/pkg/webhook/default_server/firstmates/mutating/firstmates_create_update_handler.go b/test/pkg/webhook/default_server/firstmates/mutating/firstmates_create_update_handler.go new file mode 100644 index 000000000..335fc39cc --- /dev/null +++ b/test/pkg/webhook/default_server/firstmates/mutating/firstmates_create_update_handler.go @@ -0,0 +1,83 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + "context" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + crewv1 "sigs.k8s.io/controller-tools/test/pkg/apis/crew/v1" +) + +func init() { + webhookName := "mutating-create-update-firstmates" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &FirstMateCreateUpdateHandler{}) +} + +// FirstMateCreateUpdateHandler handles FirstMate +type FirstMateCreateUpdateHandler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} + +func (h *FirstMateCreateUpdateHandler) mutatingFirstMateFn(ctx context.Context, obj *crewv1.FirstMate) error { + // TODO(user): implement your admission logic + return nil +} + +var _ admission.Handler = &FirstMateCreateUpdateHandler{} + +// Handle handles admission requests. +func (h *FirstMateCreateUpdateHandler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &crewv1.FirstMate{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + copy := obj.DeepCopy() + + err = h.mutatingFirstMateFn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(obj, copy) +} + +//var _ inject.Client = &FirstMateCreateUpdateHandler{} +// +//// InjectClient injects the client into the FirstMateCreateUpdateHandler +//func (h *FirstMateCreateUpdateHandler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &FirstMateCreateUpdateHandler{} + +// InjectDecoder injects the decoder into the FirstMateCreateUpdateHandler +func (h *FirstMateCreateUpdateHandler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} diff --git a/test/pkg/webhook/default_server/firstmates/mutating/firstmates_delete_handler.go b/test/pkg/webhook/default_server/firstmates/mutating/firstmates_delete_handler.go new file mode 100644 index 000000000..85787c408 --- /dev/null +++ b/test/pkg/webhook/default_server/firstmates/mutating/firstmates_delete_handler.go @@ -0,0 +1,83 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + "context" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + crewv1 "sigs.k8s.io/controller-tools/test/pkg/apis/crew/v1" +) + +func init() { + webhookName := "mutating-delete-firstmates" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &FirstMateDeleteHandler{}) +} + +// FirstMateDeleteHandler handles FirstMate +type FirstMateDeleteHandler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} + +func (h *FirstMateDeleteHandler) mutatingFirstMateFn(ctx context.Context, obj *crewv1.FirstMate) error { + // TODO(user): implement your admission logic + return nil +} + +var _ admission.Handler = &FirstMateDeleteHandler{} + +// Handle handles admission requests. +func (h *FirstMateDeleteHandler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &crewv1.FirstMate{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + copy := obj.DeepCopy() + + err = h.mutatingFirstMateFn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(obj, copy) +} + +//var _ inject.Client = &FirstMateDeleteHandler{} +// +//// InjectClient injects the client into the FirstMateDeleteHandler +//func (h *FirstMateDeleteHandler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &FirstMateDeleteHandler{} + +// InjectDecoder injects the decoder into the FirstMateDeleteHandler +func (h *FirstMateDeleteHandler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} diff --git a/test/pkg/webhook/default_server/firstmates/mutating/webhooks.go b/test/pkg/webhook/default_server/firstmates/mutating/webhooks.go new file mode 100644 index 000000000..5356a6652 --- /dev/null +++ b/test/pkg/webhook/default_server/firstmates/mutating/webhooks.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + // Builders contain admission webhook builders + Builders = map[string]*builder.WebhookBuilder{} + // HandlerMap contains admission webhook handlers + HandlerMap = map[string][]admission.Handler{} +) diff --git a/test/pkg/webhook/default_server/frigates/validating/frigates_update_handler.go b/test/pkg/webhook/default_server/frigates/validating/frigates_update_handler.go new file mode 100644 index 000000000..7defe668b --- /dev/null +++ b/test/pkg/webhook/default_server/frigates/validating/frigates_update_handler.go @@ -0,0 +1,82 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + "context" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + shipv1beta1 "sigs.k8s.io/controller-tools/test/pkg/apis/ship/v1beta1" +) + +func init() { + webhookName := "validating-update-frigates" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &FrigateUpdateHandler{}) +} + +// FrigateUpdateHandler handles Frigate +type FrigateUpdateHandler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} + +func (h *FrigateUpdateHandler) validatingFrigateFn(ctx context.Context, obj *shipv1beta1.Frigate) (bool, string, error) { + // TODO(user): implement your admission logic + return true, "allowed to be admitted", nil +} + +var _ admission.Handler = &FrigateUpdateHandler{} + +// Handle handles admission requests. +func (h *FrigateUpdateHandler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &shipv1beta1.Frigate{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + + allowed, reason, err := h.validatingFrigateFn(ctx, obj) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.ValidationResponse(allowed, reason) +} + +//var _ inject.Client = &FrigateUpdateHandler{} +// +//// InjectClient injects the client into the FrigateUpdateHandler +//func (h *FrigateUpdateHandler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &FrigateUpdateHandler{} + +// InjectDecoder injects the decoder into the FrigateUpdateHandler +func (h *FrigateUpdateHandler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} diff --git a/test/pkg/webhook/default_server/frigates/validating/update_webhook.go b/test/pkg/webhook/default_server/frigates/validating/update_webhook.go new file mode 100644 index 000000000..9060b7609 --- /dev/null +++ b/test/pkg/webhook/default_server/frigates/validating/update_webhook.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" + shipv1beta1 "sigs.k8s.io/controller-tools/test/pkg/apis/ship/v1beta1" +) + +func init() { + builderName := "validating-update-frigates" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName + ".testproject.org"). + Path("/" + builderName). + Validating(). + Operations(admissionregistrationv1beta1.Update). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&shipv1beta1.Frigate{}) +} diff --git a/test/pkg/webhook/default_server/frigates/validating/webhooks.go b/test/pkg/webhook/default_server/frigates/validating/webhooks.go new file mode 100644 index 000000000..8a7530b38 --- /dev/null +++ b/test/pkg/webhook/default_server/frigates/validating/webhooks.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + // Builders contain admission webhook builders + Builders = map[string]*builder.WebhookBuilder{} + // HandlerMap contains admission webhook handlers + HandlerMap = map[string][]admission.Handler{} +) diff --git a/test/pkg/webhook/default_server/krakens/validating/create_webhook.go b/test/pkg/webhook/default_server/krakens/validating/create_webhook.go new file mode 100644 index 000000000..55b9e1090 --- /dev/null +++ b/test/pkg/webhook/default_server/krakens/validating/create_webhook.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" + creaturesv2alpha1 "sigs.k8s.io/controller-tools/test/pkg/apis/creatures/v2alpha1" +) + +func init() { + builderName := "validating-create-krakens" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName + ".testproject.org"). + Path("/" + builderName). + Validating(). + Operations(admissionregistrationv1beta1.Create). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&creaturesv2alpha1.Kraken{}) +} diff --git a/test/pkg/webhook/default_server/krakens/validating/krakens_create_handler.go b/test/pkg/webhook/default_server/krakens/validating/krakens_create_handler.go new file mode 100644 index 000000000..534f42cd6 --- /dev/null +++ b/test/pkg/webhook/default_server/krakens/validating/krakens_create_handler.go @@ -0,0 +1,82 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + "context" + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + creaturesv2alpha1 "sigs.k8s.io/controller-tools/test/pkg/apis/creatures/v2alpha1" +) + +func init() { + webhookName := "validating-create-krakens" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &KrakenCreateHandler{}) +} + +// KrakenCreateHandler handles Kraken +type KrakenCreateHandler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} + +func (h *KrakenCreateHandler) validatingKrakenFn(ctx context.Context, obj *creaturesv2alpha1.Kraken) (bool, string, error) { + // TODO(user): implement your admission logic + return true, "allowed to be admitted", nil +} + +var _ admission.Handler = &KrakenCreateHandler{} + +// Handle handles admission requests. +func (h *KrakenCreateHandler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &creaturesv2alpha1.Kraken{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + + allowed, reason, err := h.validatingKrakenFn(ctx, obj) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.ValidationResponse(allowed, reason) +} + +//var _ inject.Client = &KrakenCreateHandler{} +// +//// InjectClient injects the client into the KrakenCreateHandler +//func (h *KrakenCreateHandler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &KrakenCreateHandler{} + +// InjectDecoder injects the decoder into the KrakenCreateHandler +func (h *KrakenCreateHandler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} diff --git a/test/pkg/webhook/default_server/krakens/validating/webhooks.go b/test/pkg/webhook/default_server/krakens/validating/webhooks.go new file mode 100644 index 000000000..8a7530b38 --- /dev/null +++ b/test/pkg/webhook/default_server/krakens/validating/webhooks.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validating + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + // Builders contain admission webhook builders + Builders = map[string]*builder.WebhookBuilder{} + // HandlerMap contains admission webhook handlers + HandlerMap = map[string][]admission.Handler{} +) diff --git a/test/pkg/webhook/default_server/namespaces/mutating/namespaces_update_handler.go b/test/pkg/webhook/default_server/namespaces/mutating/namespaces_update_handler.go new file mode 100644 index 000000000..0470e1219 --- /dev/null +++ b/test/pkg/webhook/default_server/namespaces/mutating/namespaces_update_handler.go @@ -0,0 +1,83 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + "context" + "net/http" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +func init() { + webhookName := "mutating-update-namespaces" + if HandlerMap[webhookName] == nil { + HandlerMap[webhookName] = []admission.Handler{} + } + HandlerMap[webhookName] = append(HandlerMap[webhookName], &NamespaceUpdateHandler{}) +} + +// NamespaceUpdateHandler handles Namespace +type NamespaceUpdateHandler struct { + // Client client.Client + + // Decoder decodes objects + Decoder types.Decoder +} + +func (h *NamespaceUpdateHandler) mutatingNamespaceFn(ctx context.Context, obj *corev1.Namespace) error { + // TODO(user): implement your admission logic + return nil +} + +var _ admission.Handler = &NamespaceUpdateHandler{} + +// Handle handles admission requests. +func (h *NamespaceUpdateHandler) Handle(ctx context.Context, req types.Request) types.Response { + obj := &corev1.Namespace{} + + err := h.Decoder.Decode(req, obj) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + copy := obj.DeepCopy() + + err = h.mutatingNamespaceFn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(obj, copy) +} + +//var _ inject.Client = &NamespaceUpdateHandler{} +// +//// InjectClient injects the client into the NamespaceUpdateHandler +//func (h *NamespaceUpdateHandler) InjectClient(c client.Client) error { +// h.Client = c +// return nil +//} + +var _ inject.Decoder = &NamespaceUpdateHandler{} + +// InjectDecoder injects the decoder into the NamespaceUpdateHandler +func (h *NamespaceUpdateHandler) InjectDecoder(d types.Decoder) error { + h.Decoder = d + return nil +} diff --git a/test/pkg/webhook/default_server/namespaces/mutating/update_webhook.go b/test/pkg/webhook/default_server/namespaces/mutating/update_webhook.go new file mode 100644 index 000000000..336433081 --- /dev/null +++ b/test/pkg/webhook/default_server/namespaces/mutating/update_webhook.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +func init() { + builderName := "mutating-update-namespaces" + Builders[builderName] = builder. + NewWebhookBuilder(). + Name(builderName + ".testproject.org"). + Path("/" + builderName). + Mutating(). + Operations(admissionregistrationv1beta1.Update). + FailurePolicy(admissionregistrationv1beta1.Fail). + ForType(&corev1.Namespace{}) +} diff --git a/test/pkg/webhook/default_server/namespaces/mutating/webhooks.go b/test/pkg/webhook/default_server/namespaces/mutating/webhooks.go new file mode 100644 index 000000000..5356a6652 --- /dev/null +++ b/test/pkg/webhook/default_server/namespaces/mutating/webhooks.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mutating + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + // Builders contain admission webhook builders + Builders = map[string]*builder.WebhookBuilder{} + // HandlerMap contains admission webhook handlers + HandlerMap = map[string][]admission.Handler{} +) diff --git a/test/pkg/webhook/default_server/server.go b/test/pkg/webhook/default_server/server.go new file mode 100644 index 000000000..474f2ce25 --- /dev/null +++ b/test/pkg/webhook/default_server/server.go @@ -0,0 +1,91 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package defaultserver + +import ( + "fmt" + "os" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" +) + +var ( + log = logf.Log.WithName("default_server") + builderMap = map[string]*builder.WebhookBuilder{} + // HandlerMap contains all admission webhook handlers. + HandlerMap = map[string][]admission.Handler{} +) + +// Add adds itself to the manager +func Add(mgr manager.Manager) error { + ns := os.Getenv("POD_NAMESPACE") + if len(ns) == 0 { + ns = "default" + } + secretName := os.Getenv("SECRET_NAME") + if len(secretName) == 0 { + secretName = "webhook-server-secret" + } + + svr, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{ + // TODO(user): change the configuration of ServerOptions based on your need. + Port: 9876, + CertDir: "/tmp/cert", + BootstrapOptions: &webhook.BootstrapOptions{ + Secret: &types.NamespacedName{ + Namespace: ns, + Name: secretName, + }, + + Service: &webhook.Service{ + Namespace: ns, + Name: "webhook-server-service", + // Selectors should select the pods that runs this webhook server. + Selectors: map[string]string{ + "control-plane": "controller-manager", + }, + }, + }, + }) + if err != nil { + return err + } + + var webhooks []webhook.Webhook + for k, builder := range builderMap { + handlers, ok := HandlerMap[k] + if !ok { + log.V(1).Info(fmt.Sprintf("can't find handlers for builder: %v", k)) + handlers = []admission.Handler{} + } + wh, err := builder. + Handlers(handlers...). + WithManager(mgr). + Build() + if err != nil { + return err + } + webhooks = append(webhooks, wh) + } + + return svr.Register(webhooks...) +} diff --git a/test/pkg/webhook/webhook.go b/test/pkg/webhook/webhook.go new file mode 100644 index 000000000..423fdd846 --- /dev/null +++ b/test/pkg/webhook/webhook.go @@ -0,0 +1,37 @@ +/* +Copyright 2018 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// AddToManagerFuncs is a list of functions to add all Controllers to the Manager +var AddToManagerFuncs []func(manager.Manager) error + +// AddToManager adds all Controllers to the Manager +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=mutatingwebhookconfigurations;validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete +func AddToManager(m manager.Manager) error { + for _, f := range AddToManagerFuncs { + if err := f(m); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/google/uuid/.travis.yml b/vendor/github.com/google/uuid/.travis.yml new file mode 100644 index 000000000..d8156a60b --- /dev/null +++ b/vendor/github.com/google/uuid/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.4.3 + - 1.5.3 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 000000000..04fdf09f1 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 000000000..b4bb97f6b --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 000000000..5dc68268d --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 000000000..9d92c11f1 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,19 @@ +# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on +[RFC 4122](http://tools.ietf.org/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +`go get github.com/google/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 000000000..fa820b9d3 --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 000000000..5b8a4b9af --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 000000000..b17461631 --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) + h.Write(data) + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 000000000..7f9e0c6c0 --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,37 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err == nil { + *uuid = id + } + return err +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 000000000..3e4e90dc4 --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,89 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 000000000..24b78edc9 --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 000000000..0cbbcddbd --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 000000000..f326b54db --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 000000000..e6ef06cdc --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1 and 2 UUIDs. +func (uuid UUID) Time() Time { + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time) +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 000000000..5ea6c7378 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 000000000..7f3643fe9 --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,198 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// Parse decodes s into a UUID or returns an error. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) (UUID, error) { + var uuid UUID + if len(s) != 36 { + if len(s) != 36+9 { + return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) + } + if strings.ToLower(s[:9]) != "urn:uuid:" { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + if len(b) != 36 { + if len(b) != 36+9 { + return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) + } + if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + } + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst[:], uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 000000000..199a1ac65 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nodeMu.Unlock() + + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + copy(uuid[10:], nodeID[:]) + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 000000000..84af91c9f --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + var uuid UUID + _, err := io.ReadFull(rander, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/github.com/mattbaird/jsonpatch/.gitignore b/vendor/github.com/mattbaird/jsonpatch/.gitignore new file mode 100644 index 000000000..daf913b1b --- /dev/null +++ b/vendor/github.com/mattbaird/jsonpatch/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/mattbaird/jsonpatch/LICENSE b/vendor/github.com/mattbaird/jsonpatch/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/vendor/github.com/mattbaird/jsonpatch/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/mattbaird/jsonpatch/README.md b/vendor/github.com/mattbaird/jsonpatch/README.md new file mode 100644 index 000000000..91c03b3fc --- /dev/null +++ b/vendor/github.com/mattbaird/jsonpatch/README.md @@ -0,0 +1,46 @@ +# jsonpatch +As per http://jsonpatch.com/ JSON Patch is specified in RFC 6902 from the IETF. + +JSON Patch allows you to generate JSON that describes changes you want to make to a document, so you don't have to send the whole doc. JSON Patch format is supported by HTTP PATCH method, allowing for standards based partial updates via REST APIs. + +```bash +go get github.com/mattbaird/jsonpatch +``` + +I tried some of the other "jsonpatch" go implementations, but none of them could diff two json documents and +generate format like jsonpatch.com specifies. Here's an example of the patch format: + +```json +[ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} +] + +``` +The API is super simple +#example +```go +package main + +import ( + "fmt" + "github.com/mattbaird/jsonpatch" +) + +var simpleA = `{"a":100, "b":200, "c":"hello"}` +var simpleB = `{"a":100, "b":200, "c":"goodbye"}` + +func main() { + patch, e := jsonpatch.CreatePatch([]byte(simpleA), []byte(simpleA)) + if e != nil { + fmt.Printf("Error creating JSON patch:%v", e) + return + } + for _, operation := range patch { + fmt.Printf("%s\n", operation.Json()) + } +} +``` + +This code needs more tests, as it's a highly recursive, type-fiddly monster. It's not a lot of code, but it has to deal with a lot of complexity. diff --git a/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go b/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go new file mode 100644 index 000000000..295f260f5 --- /dev/null +++ b/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go @@ -0,0 +1,257 @@ +package jsonpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +var errBadJSONDoc = fmt.Errorf("Invalid JSON Document") + +type JsonPatchOperation struct { + Operation string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} + +func (j *JsonPatchOperation) Json() string { + b, _ := json.Marshal(j) + return string(b) +} + +func (j *JsonPatchOperation) MarshalJSON() ([]byte, error) { + var b bytes.Buffer + b.WriteString("{") + b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation)) + b.WriteString(fmt.Sprintf(`,"path":"%s"`, j.Path)) + // Consider omitting Value for non-nullable operations. + if j.Value != nil || j.Operation == "replace" || j.Operation == "add" { + v, err := json.Marshal(j.Value) + if err != nil { + return nil, err + } + b.WriteString(`,"value":`) + b.Write(v) + } + b.WriteString("}") + return b.Bytes(), nil +} + +type ByPath []JsonPatchOperation + +func (a ByPath) Len() int { return len(a) } +func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path } + +func NewPatch(operation, path string, value interface{}) JsonPatchOperation { + return JsonPatchOperation{Operation: operation, Path: path, Value: value} +} + +// CreatePatch creates a patch as specified in http://jsonpatch.com/ +// +// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content. +// The function will return an array of JsonPatchOperations +// +// An error will be returned if any of the two documents are invalid. +func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { + aI := map[string]interface{}{} + bI := map[string]interface{}{} + err := json.Unmarshal(a, &aI) + if err != nil { + return nil, errBadJSONDoc + } + err = json.Unmarshal(b, &bI) + if err != nil { + return nil, errBadJSONDoc + } + return diff(aI, bI, "", []JsonPatchOperation{}) +} + +// Returns true if the values matches (must be json types) +// The types of the values must match, otherwise it will always return false +// If two map[string]interface{} are given, all elements must match. +func matchesValue(av, bv interface{}) bool { + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + return false + } + switch at := av.(type) { + case string: + bt := bv.(string) + if bt == at { + return true + } + case float64: + bt := bv.(float64) + if bt == at { + return true + } + case bool: + bt := bv.(bool) + if bt == at { + return true + } + case map[string]interface{}: + bt := bv.(map[string]interface{}) + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + case []interface{}: + bt := bv.([]interface{}) + if len(bt) != len(at) { + return false + } + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + } + return false +} + +// From http://tools.ietf.org/html/rfc6901#section-4 : +// +// Evaluation of each reference token begins by decoding any escaped +// character sequence. This is performed by first transforming any +// occurrence of the sequence '~1' to '/', and then transforming any +// occurrence of the sequence '~0' to '~'. +// TODO decode support: +// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") + +var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") + +func makePath(path string, newPart interface{}) string { + key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart)) + if path == "" { + return "/" + key + } + if strings.HasSuffix(path, "/") { + return path + key + } + return path + "/" + key +} + +// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. +func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { + for key, bv := range b { + p := makePath(path, key) + av, ok := a[key] + // value was added + if !ok { + patch = append(patch, NewPatch("add", p, bv)) + continue + } + // If types have changed, replace completely + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + patch = append(patch, NewPatch("replace", p, bv)) + continue + } + // Types are the same, compare values + var err error + patch, err = handleValues(av, bv, p, patch) + if err != nil { + return nil, err + } + } + // Now add all deleted values as nil + for key := range a { + _, found := b[key] + if !found { + p := makePath(path, key) + + patch = append(patch, NewPatch("remove", p, nil)) + } + } + return patch, nil +} + +func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { + var err error + switch at := av.(type) { + case map[string]interface{}: + bt := bv.(map[string]interface{}) + patch, err = diff(at, bt, p, patch) + if err != nil { + return nil, err + } + case string, float64, bool: + if !matchesValue(av, bv) { + patch = append(patch, NewPatch("replace", p, bv)) + } + case []interface{}: + bt, ok := bv.([]interface{}) + if !ok { + // array replaced by non-array + patch = append(patch, NewPatch("replace", p, bv)) + } else if len(at) != len(bt) { + // arrays are not the same length + patch = append(patch, compareArray(at, bt, p)...) + + } else { + for i := range bt { + patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) + if err != nil { + return nil, err + } + } + } + case nil: + switch bv.(type) { + case nil: + // Both nil, fine. + default: + patch = append(patch, NewPatch("add", p, bv)) + } + default: + panic(fmt.Sprintf("Unknown type:%T ", av)) + } + return patch, nil +} + +func compareArray(av, bv []interface{}, p string) []JsonPatchOperation { + retval := []JsonPatchOperation{} + // var err error + for i, v := range av { + found := false + for _, v2 := range bv { + if reflect.DeepEqual(v, v2) { + found = true + break + } + } + if !found { + retval = append(retval, NewPatch("remove", makePath(p, i), nil)) + } + } + + for i, v := range bv { + found := false + for _, v2 := range av { + if reflect.DeepEqual(v, v2) { + found = true + break + } + } + if !found { + retval = append(retval, NewPatch("add", makePath(p, i), v)) + } + } + + return retval +} diff --git a/vendor/github.com/pborman/uuid/.travis.yml b/vendor/github.com/pborman/uuid/.travis.yml new file mode 100644 index 000000000..3deb4a124 --- /dev/null +++ b/vendor/github.com/pborman/uuid/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - "1.9" + - "1.10" + - "1.11" + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pborman/uuid/CONTRIBUTING.md b/vendor/github.com/pborman/uuid/CONTRIBUTING.md new file mode 100644 index 000000000..04fdf09f1 --- /dev/null +++ b/vendor/github.com/pborman/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/pborman/uuid/CONTRIBUTORS b/vendor/github.com/pborman/uuid/CONTRIBUTORS new file mode 100644 index 000000000..b382a04ed --- /dev/null +++ b/vendor/github.com/pborman/uuid/CONTRIBUTORS @@ -0,0 +1 @@ +Paul Borman diff --git a/vendor/github.com/pborman/uuid/LICENSE b/vendor/github.com/pborman/uuid/LICENSE new file mode 100644 index 000000000..5dc68268d --- /dev/null +++ b/vendor/github.com/pborman/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pborman/uuid/README.md b/vendor/github.com/pborman/uuid/README.md new file mode 100644 index 000000000..810ad40dc --- /dev/null +++ b/vendor/github.com/pborman/uuid/README.md @@ -0,0 +1,15 @@ +This project was automatically exported from code.google.com/p/go-uuid + +# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services. + +This package now leverages the github.com/google/uuid package (which is based off an earlier version of this package). + +###### Install +`go get github.com/pborman/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid) + +Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here: +http://godoc.org/github.com/pborman/uuid diff --git a/vendor/github.com/pborman/uuid/dce.go b/vendor/github.com/pborman/uuid/dce.go new file mode 100644 index 000000000..50a0f2d09 --- /dev/null +++ b/vendor/github.com/pborman/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/pborman/uuid/doc.go b/vendor/github.com/pborman/uuid/doc.go new file mode 100644 index 000000000..727d76167 --- /dev/null +++ b/vendor/github.com/pborman/uuid/doc.go @@ -0,0 +1,13 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The uuid package generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// This package is a partial wrapper around the github.com/google/uuid package. +// This package represents a UUID as []byte while github.com/google/uuid +// represents a UUID as [16]byte. +package uuid diff --git a/vendor/github.com/pborman/uuid/hash.go b/vendor/github.com/pborman/uuid/hash.go new file mode 100644 index 000000000..a0420c1ef --- /dev/null +++ b/vendor/github.com/pborman/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known Name Space IDs and UUIDs +var ( + NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + NIL = Parse("00000000-0000-0000-0000-000000000000") +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space) + h.Write([]byte(data)) + s := h.Sum(nil) + uuid := make([]byte, 16) + copy(uuid, s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/pborman/uuid/marshal.go b/vendor/github.com/pborman/uuid/marshal.go new file mode 100644 index 000000000..35b89352a --- /dev/null +++ b/vendor/github.com/pborman/uuid/marshal.go @@ -0,0 +1,85 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "errors" + "fmt" + + guuid "github.com/google/uuid" +) + +// MarshalText implements encoding.TextMarshaler. +func (u UUID) MarshalText() ([]byte, error) { + if len(u) != 16 { + return nil, nil + } + var js [36]byte + encodeHex(js[:], u) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *UUID) UnmarshalText(data []byte) error { + if len(data) == 0 { + return nil + } + id := Parse(string(data)) + if id == nil { + return errors.New("invalid UUID") + } + *u = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u UUID) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *UUID) UnmarshalBinary(data []byte) error { + if len(data) == 0 { + return nil + } + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + var id [16]byte + copy(id[:], data) + *u = id[:] + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (u Array) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], u[:]) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *Array) UnmarshalText(data []byte) error { + id, err := guuid.ParseBytes(data) + if err != nil { + return err + } + *u = Array(id) + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u Array) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *Array) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(u[:], data) + return nil +} diff --git a/vendor/github.com/pborman/uuid/node.go b/vendor/github.com/pborman/uuid/node.go new file mode 100644 index 000000000..e524e0101 --- /dev/null +++ b/vendor/github.com/pborman/uuid/node.go @@ -0,0 +1,50 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + guuid "github.com/google/uuid" +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + return guuid.NodeInterface() +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + return guuid.SetNodeInterface(name) +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + return guuid.NodeID() +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + return guuid.SetNodeID(id) +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + if len(uuid) != 16 { + return nil + } + node := make([]byte, 6) + copy(node, uuid[10:]) + return node +} diff --git a/vendor/github.com/pborman/uuid/sql.go b/vendor/github.com/pborman/uuid/sql.go new file mode 100644 index 000000000..929c3847e --- /dev/null +++ b/vendor/github.com/pborman/uuid/sql.go @@ -0,0 +1,68 @@ +// Copyright 2015 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "errors" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src.(type) { + case string: + // if an empty UUID comes from a table, we return a null UUID + if src.(string) == "" { + return nil + } + + // see uuid.Parse for required string format + parsed := Parse(src.(string)) + + if parsed == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = parsed + case []byte: + b := src.([]byte) + + // if an empty UUID comes from a table, we return a null UUID + if len(b) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(b) == 16 { + parsed := make([]byte, 16) + copy(parsed, b) + *uuid = UUID(parsed) + } else { + u := Parse(string(b)) + + if u == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = u + } + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/pborman/uuid/time.go b/vendor/github.com/pborman/uuid/time.go new file mode 100644 index 000000000..5c0960d87 --- /dev/null +++ b/vendor/github.com/pborman/uuid/time.go @@ -0,0 +1,57 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + + guuid "github.com/google/uuid" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time = guuid.Time + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { return guuid.GetTime() } + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence a new random +// clock sequence is generated the first time a clock sequence is requested by +// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated +// for +func ClockSequence() int { return guuid.ClockSequence() } + +// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { guuid.SetClockSequence(seq) } + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. It returns false if uuid is not valid. The time is only well defined +// for version 1 and 2 UUIDs. +func (uuid UUID) Time() (Time, bool) { + if len(uuid) != 16 { + return 0, false + } + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time), true +} + +// ClockSequence returns the clock sequence encoded in uuid. It returns false +// if uuid is not valid. The clock sequence is only well defined for version 1 +// and 2 UUIDs. +func (uuid UUID) ClockSequence() (int, bool) { + if len(uuid) != 16 { + return 0, false + } + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true +} diff --git a/vendor/github.com/pborman/uuid/util.go b/vendor/github.com/pborman/uuid/util.go new file mode 100644 index 000000000..255b5e248 --- /dev/null +++ b/vendor/github.com/pborman/uuid/util.go @@ -0,0 +1,32 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts the the first two hex bytes of x into a byte. +func xtob(x string) (byte, bool) { + b1 := xvalues[x[0]] + b2 := xvalues[x[1]] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/pborman/uuid/uuid.go b/vendor/github.com/pborman/uuid/uuid.go new file mode 100644 index 000000000..a2429b0dd --- /dev/null +++ b/vendor/github.com/pborman/uuid/uuid.go @@ -0,0 +1,163 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + + guuid "github.com/google/uuid" +) + +// Array is a pass-by-value UUID that can be used as an effecient key in a map. +type Array [16]byte + +// UUID converts uuid into a slice. +func (uuid Array) UUID() UUID { + return uuid[:] +} + +// String returns the string representation of uuid, +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (uuid Array) String() string { + return guuid.UUID(uuid).String() +} + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID []byte + +// A Version represents a UUIDs version. +type Version = guuid.Version + +// A Variant represents a UUIDs variant. +type Variant = guuid.Variant + +// Constants returned by Variant. +const ( + Invalid = guuid.Invalid // Invalid UUID + RFC4122 = guuid.RFC4122 // The variant specified in RFC4122 + Reserved = guuid.Reserved // Reserved, NCS backward compatibility. + Microsoft = guuid.Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future = guuid.Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// New returns a new random (version 4) UUID as a string. It is a convenience +// function for NewRandom().String(). +func New() string { + return NewRandom().String() +} + +// Parse decodes s into a UUID or returns nil. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) UUID { + gu, err := guuid.Parse(s) + if err == nil { + return gu[:] + } + return nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + gu, err := guuid.ParseBytes(b) + if err == nil { + return gu[:], nil + } + return nil, err +} + +// Equal returns true if uuid1 and uuid2 are equal. +func Equal(uuid1, uuid2 UUID) bool { + return bytes.Equal(uuid1, uuid2) +} + +// Array returns an array representation of uuid that can be used as a map key. +// Array panics if uuid is not valid. +func (uuid UUID) Array() Array { + if len(uuid) != 16 { + panic("invalid uuid") + } + var a Array + copy(a[:], uuid) + return a +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + if len(uuid) != 16 { + return "" + } + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + if len(uuid) != 16 { + return "" + } + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst[:], uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. It returns Invalid if +// uuid is invalid. +func (uuid UUID) Variant() Variant { + if len(uuid) != 16 { + return Invalid + } + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. It returns false if uuid is not +// valid. +func (uuid UUID) Version() (Version, bool) { + if len(uuid) != 16 { + return 0, false + } + return Version(uuid[6] >> 4), true +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + guuid.SetRand(r) +} diff --git a/vendor/github.com/pborman/uuid/version1.go b/vendor/github.com/pborman/uuid/version1.go new file mode 100644 index 000000000..7af948da7 --- /dev/null +++ b/vendor/github.com/pborman/uuid/version1.go @@ -0,0 +1,23 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + guuid "github.com/google/uuid" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil. +func NewUUID() UUID { + gu, err := guuid.NewUUID() + if err == nil { + return UUID(gu[:]) + } + return nil +} diff --git a/vendor/github.com/pborman/uuid/version4.go b/vendor/github.com/pborman/uuid/version4.go new file mode 100644 index 000000000..b459d46d1 --- /dev/null +++ b/vendor/github.com/pborman/uuid/version4.go @@ -0,0 +1,26 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import guuid "github.com/google/uuid" + +// Random returns a Random (Version 4) UUID or panics. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() UUID { + if gu, err := guuid.NewRandom(); err == nil { + return UUID(gu[:]) + } + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/CONTRIBUTING.md b/vendor/sigs.k8s.io/controller-runtime/CONTRIBUTING.md index 181384bf7..feecd43ea 100644 --- a/vendor/sigs.k8s.io/controller-runtime/CONTRIBUTING.md +++ b/vendor/sigs.k8s.io/controller-runtime/CONTRIBUTING.md @@ -2,12 +2,28 @@ ## Sign the CLA -Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests. Please see https://git.k8s.io/community/CLA.md for more info +Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests. + +Please see https://git.k8s.io/community/CLA.md for more info -### Contributing A Patch +## Contributing steps 1. Submit an issue describing your proposed change to the repo in question. 1. The [repo owners](OWNERS) will respond to your issue promptly. 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the desired repo, develop and test your code changes. 1. Submit a pull request. + +## Test locally + +1. Setup tools + ```bash + $ go get -u github.com/golang/dep/cmd/dep + $ go get -u gopkg.in/alecthomas/gometalinter.v2 + $ gometalinter.v2 --install # if can't load package, refer: https://github.com/alecthomas/gometalinter/issues/404 + ``` +1. Test + ```bash + TRACE=1 ./hack/check-everything.sh + ``` + diff --git a/vendor/sigs.k8s.io/controller-runtime/Gopkg.lock b/vendor/sigs.k8s.io/controller-runtime/Gopkg.lock index ba5b92db4..717e34cc3 100644 --- a/vendor/sigs.k8s.io/controller-runtime/Gopkg.lock +++ b/vendor/sigs.k8s.io/controller-runtime/Gopkg.lock @@ -466,6 +466,14 @@ pruneopts = "UT" revision = "60711f1a8329503b04e1c88535f419d0bb440bff" +[[projects]] + branch = "master" + digest = "1:fc2b04b0069d6b10bdef96d278fe20c345794009685ed3c8c7f1a6dc023eefec" + name = "github.com/mattbaird/jsonpatch" + packages = ["."] + pruneopts = "UT" + revision = "81af80346b1a01caae0cbc27fd3c1ba5b11e189f" + [[projects]] digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" name = "github.com/matttproud/golang_protobuf_extensions" @@ -1252,6 +1260,8 @@ "tools/clientcmd/api", "tools/clientcmd/api/latest", "tools/clientcmd/api/v1", + "tools/leaderelection", + "tools/leaderelection/resourcelock", "tools/metrics", "tools/pager", "tools/record", @@ -1306,6 +1316,7 @@ "github.com/go-logr/logr/testing", "github.com/go-logr/zapr", "github.com/go-openapi/spec", + "github.com/mattbaird/jsonpatch", "github.com/onsi/ginkgo", "github.com/onsi/ginkgo/config", "github.com/onsi/ginkgo/types", @@ -1334,6 +1345,7 @@ "k8s.io/apimachinery/pkg/types", "k8s.io/apimachinery/pkg/util/runtime", "k8s.io/apimachinery/pkg/util/sets", + "k8s.io/apimachinery/pkg/util/uuid", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/watch", "k8s.io/client-go/discovery", @@ -1347,6 +1359,8 @@ "k8s.io/client-go/testing", "k8s.io/client-go/tools/cache", "k8s.io/client-go/tools/clientcmd", + "k8s.io/client-go/tools/leaderelection", + "k8s.io/client-go/tools/leaderelection/resourcelock", "k8s.io/client-go/tools/record", "k8s.io/client-go/tools/reference", "k8s.io/client-go/util/cert", diff --git a/vendor/sigs.k8s.io/controller-runtime/README.md b/vendor/sigs.k8s.io/controller-runtime/README.md index 90a5f041e..0767c424a 100644 --- a/vendor/sigs.k8s.io/controller-runtime/README.md +++ b/vendor/sigs.k8s.io/controller-runtime/README.md @@ -25,6 +25,12 @@ You can reach the maintainers of this project at: - Slack channel: [#kubebuilder](http://slack.k8s.io/#kubebuilder) - Google Group: [kubebuilder@googlegroups.com](https://groups.google.com/forum/#!forum/kubebuilder) -### Code of conduct +## Contributing +Contributions are greatly appreciated. The maintainers actively manage the issues list, and try to highlight issues suitable for newcomers. +The project follows the typical GitHub pull request model. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details. +Before starting any work, please either comment on an existing issue, or file a new one. + +## Code of conduct Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). + diff --git a/vendor/sigs.k8s.io/controller-runtime/example/controller.go b/vendor/sigs.k8s.io/controller-runtime/example/controller.go new file mode 100644 index 000000000..9d94b97c7 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/example/controller.go @@ -0,0 +1,77 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + + "github.com/go-logr/logr" + + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// reconcileReplicaSet reconciles ReplicaSets +type reconcileReplicaSet struct { + // client can be used to retrieve objects from the APIServer. + client client.Client + log logr.Logger +} + +// Implement reconcile.Reconciler so the controller can reconcile objects +var _ reconcile.Reconciler = &reconcileReplicaSet{} + +func (r *reconcileReplicaSet) Reconcile(request reconcile.Request) (reconcile.Result, error) { + // set up a convinient log object so we don't have to type request over and over again + log := r.log.WithValues("request", request) + + // Fetch the ReplicaSet from the cache + rs := &appsv1.ReplicaSet{} + err := r.client.Get(context.TODO(), request.NamespacedName, rs) + if errors.IsNotFound(err) { + log.Error(nil, "Could not find ReplicaSet") + return reconcile.Result{}, nil + } + + if err != nil { + log.Error(err, "Could not fetch ReplicaSet") + return reconcile.Result{}, err + } + + // Print the ReplicaSet + log.Info("Reconciling ReplicaSet", "container name", rs.Spec.Template.Spec.Containers[0].Name) + + // Set the label if it is missing + if rs.Labels == nil { + rs.Labels = map[string]string{} + } + if rs.Labels["hello"] == "world" { + return reconcile.Result{}, nil + } + + // Update the ReplicaSet + rs.Labels["hello"] = "world" + err = r.client.Update(context.TODO(), rs) + if err != nil { + log.Error(err, "Could not write ReplicaSet") + return reconcile.Result{}, err + } + + return reconcile.Result{}, nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/example/main.go b/vendor/sigs.k8s.io/controller-runtime/example/main.go index a41f51b4f..738f70083 100644 --- a/vendor/sigs.k8s.io/controller-runtime/example/main.go +++ b/vendor/sigs.k8s.io/controller-runtime/example/main.go @@ -17,29 +17,26 @@ limitations under the License. package main import ( - "context" "flag" "os" - "github.com/go-logr/logr" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" + apitypes "k8s.io/apimachinery/pkg/types" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/runtime/signals" "sigs.k8s.io/controller-runtime/pkg/source" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" ) -var ( - log = logf.Log.WithName("example-controller") -) +var log = logf.Log.WithName("example-controller") func main() { flag.Parse() @@ -75,56 +72,64 @@ func main() { os.Exit(1) } - if err := mgr.Start(signals.SetupSignalHandler()); err != nil { - entryLog.Error(err, "unable to run manager") + // Setup webhooks + mutatingWebhook, err := builder.NewWebhookBuilder(). + Name("mutating.k8s.io"). + Mutating(). + Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update). + WithManager(mgr). + ForType(&corev1.Pod{}). + Handlers(&podAnnotator{}). + Build() + if err != nil { + entryLog.Error(err, "unable to setup mutating webhook") os.Exit(1) } -} - -// reconcileReplicaSet reconciles ReplicaSets -type reconcileReplicaSet struct { - client client.Client - log logr.Logger -} - -// Implement reconcile.Reconciler so the controller can reconcile objects -var _ reconcile.Reconciler = &reconcileReplicaSet{} - -func (r *reconcileReplicaSet) Reconcile(request reconcile.Request) (reconcile.Result, error) { - // set up a convinient log object so we don't have to type request over and over again - log := r.log.WithValues("request", request) - - // Fetch the ReplicaSet from the cache - rs := &appsv1.ReplicaSet{} - err := r.client.Get(context.TODO(), request.NamespacedName, rs) - if errors.IsNotFound(err) { - log.Error(nil, "Could not find ReplicaSet") - return reconcile.Result{}, nil - } + validatingWebhook, err := builder.NewWebhookBuilder(). + Name("validating.k8s.io"). + Validating(). + Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update). + WithManager(mgr). + ForType(&corev1.Pod{}). + Handlers(&podValidator{}). + Build() if err != nil { - log.Error(err, "Could not fetch ReplicaSet") - return reconcile.Result{}, err + entryLog.Error(err, "unable to setup validating webhook") + os.Exit(1) } - // Print the ReplicaSet - log.Info("Reconciling ReplicaSet", "container name", rs.Spec.Template.Spec.Containers[0].Name) - - // Set the label if it is missing - if rs.Labels == nil { - rs.Labels = map[string]string{} - } - if rs.Labels["hello"] == "world" { - return reconcile.Result{}, nil + as, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{ + Port: 9876, + CertDir: "/tmp/cert", + BootstrapOptions: &webhook.BootstrapOptions{ + Secret: &apitypes.NamespacedName{ + Namespace: "default", + Name: "foo-admission-server-secret", + }, + + Service: &webhook.Service{ + Namespace: "default", + Name: "foo-admission-server-service", + // Selectors should select the pods that runs this webhook server. + Selectors: map[string]string{ + "app": "foo-admission-server", + }, + }, + }, + }) + if err != nil { + entryLog.Error(err, "unable to create a new webhook server") + os.Exit(1) } - - // Update the ReplicaSet - rs.Labels["hello"] = "world" - err = r.client.Update(context.TODO(), rs) + err = as.Register(mutatingWebhook, validatingWebhook) if err != nil { - log.Error(err, "Could not write ReplicaSet") - return reconcile.Result{}, err + entryLog.Error(err, "unable to register webhooks in the admission server") + os.Exit(1) } - return reconcile.Result{}, nil + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + entryLog.Error(err, "unable to run manager") + os.Exit(1) + } } diff --git a/vendor/sigs.k8s.io/controller-runtime/example/mutatingwebhook.go b/vendor/sigs.k8s.io/controller-runtime/example/mutatingwebhook.go new file mode 100644 index 000000000..be9caf5f9 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/example/mutatingwebhook.go @@ -0,0 +1,83 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "net/http" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +// podAnnotator annotates Pods +type podAnnotator struct { + client client.Client + decoder types.Decoder +} + +// Implement admission.Handler so the controller can handle admission request. +var _ admission.Handler = &podAnnotator{} + +// podAnnotator adds an annotation to every incoming pods. +func (a *podAnnotator) Handle(ctx context.Context, req types.Request) types.Response { + pod := &corev1.Pod{} + + err := a.decoder.Decode(req, pod) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + copy := pod.DeepCopy() + + err = a.mutatePodsFn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(pod, copy) +} + +// mutatePodsFn add an annotation to the given pod +func (a *podAnnotator) mutatePodsFn(ctx context.Context, pod *corev1.Pod) error { + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + pod.Annotations["example-mutating-admission-webhook"] = "foo" + return nil +} + +// podValidator implements inject.Client. +// A client will be automatically injected. +var _ inject.Client = &podValidator{} + +// InjectClient injects the client. +func (v *podAnnotator) InjectClient(c client.Client) error { + v.client = c + return nil +} + +// podValidator implements inject.Decoder. +// A decoder will be automatically injected. +var _ inject.Decoder = &podValidator{} + +// InjectDecoder injects the decoder. +func (v *podAnnotator) InjectDecoder(d types.Decoder) error { + v.decoder = d + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/example/validatingwebhook.go b/vendor/sigs.k8s.io/controller-runtime/example/validatingwebhook.go new file mode 100644 index 000000000..5019cb611 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/example/validatingwebhook.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "net/http" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +// podValidator validates Pods +type podValidator struct { + client client.Client + decoder types.Decoder +} + +// Implement admission.Handler so the controller can handle admission request. +var _ admission.Handler = &podValidator{} + +// podValidator admits a pod iff a specific annotation exists. +func (v *podValidator) Handle(ctx context.Context, req types.Request) types.Response { + pod := &corev1.Pod{} + + err := v.decoder.Decode(req, pod) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + + allowed, reason, err := v.validatePodsFn(ctx, pod) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.ValidationResponse(allowed, reason) +} + +func (v *podValidator) validatePodsFn(ctx context.Context, pod *corev1.Pod) (bool, string, error) { + key := "example-mutating-admission-webhook" + anno, found := pod.Annotations[key] + switch { + case !found: + return found, fmt.Sprintf("failed to find annotation with key: %q", key), nil + case found && anno == "foo": + return found, "", nil + case found && anno != "foo": + return false, + fmt.Sprintf("the value associate with key %q is expected to be %q, but got %q", key, "foo", anno), nil + } + return false, "", nil +} + +// podValidator implements inject.Client. +// A client will be automatically injected. +var _ inject.Client = &podValidator{} + +// InjectClient injects the client. +func (v *podValidator) InjectClient(c client.Client) error { + v.client = c + return nil +} + +// podValidator implements inject.Decoder. +// A decoder will be automatically injected. +var _ inject.Decoder = &podValidator{} + +// InjectDecoder injects the decoder. +func (v *podValidator) InjectDecoder(d types.Decoder) error { + v.decoder = d + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/hack/verify.sh b/vendor/sigs.k8s.io/controller-runtime/hack/verify.sh index ded465eb2..57cf9401c 100755 --- a/vendor/sigs.k8s.io/controller-runtime/hack/verify.sh +++ b/vendor/sigs.k8s.io/controller-runtime/hack/verify.sh @@ -39,7 +39,6 @@ gometalinter.v2 --disable-all \ --enable=errcheck \ --enable=varcheck \ --enable=goconst \ - --enable=gosec \ --enable=unparam \ --enable=ineffassign \ --enable=nakedret \ @@ -53,5 +52,6 @@ gometalinter.v2 --disable-all \ --skip=atomic \ ./pkg/... # TODO: Enable these as we fix them to make them pass +# --enable=gosec \ # --enable=maligned \ # --enable=safesql \ diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/certwriter.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/certwriter.go deleted file mode 100644 index 0dce3c538..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/certwriter.go +++ /dev/null @@ -1,211 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package writer - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "net/url" - "time" - - admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/generator" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - // CACertName is the name of the CA certificate - CACertName = "ca-cert.pem" - // ServerKeyName is the name of the server private key - ServerKeyName = "key.pem" - // ServerCertName is the name of the serving certificate - ServerCertName = "cert.pem" -) - -// CertWriter provides method to handle webhooks. -type CertWriter interface { - // EnsureCert ensures that the webhooks have proper certificates. - EnsureCerts(runtime.Object) error -} - -// Options are options for configuring a CertWriter. -type Options struct { - Client client.Client - CertGenerator generator.CertGenerator -} - -// NewCertWriter builds a new CertWriter using the provided options. -// By default, it builds a MultiCertWriter that is composed of a SecretCertWriter and a FSCertWriter. -func NewCertWriter(ops Options) (CertWriter, error) { - if ops.CertGenerator == nil { - ops.CertGenerator = &generator.SelfSignedCertGenerator{} - } - if ops.Client == nil { - // TODO: default the client if possible - return nil, errors.New("Options.Client is required") - } - s := &SecretCertWriter{ - Client: ops.Client, - CertGenerator: ops.CertGenerator, - } - f := &FSCertWriter{ - CertGenerator: ops.CertGenerator, - } - return &MultiCertWriter{ - CertWriters: []CertWriter{ - s, - f, - }, - }, nil -} - -// handleCommon ensures the given webhook has a proper certificate. -// It uses the given certReadWriter to read and (or) write the certificate. -func handleCommon(webhook *admissionregistrationv1beta1.Webhook, ch certReadWriter) error { - if webhook == nil { - return nil - } - if ch == nil { - return errors.New("certReaderWriter should not be nil") - } - - certs, err := createIfNotExists(webhook.Name, ch) - if err != nil { - return err - } - - dnsName, err := dnsNameForWebhook(&webhook.ClientConfig) - if err != nil { - return err - } - // Recreate the cert if it's invalid. - valid := validCert(certs, dnsName) - if !valid { - log.Info("cert is invalid or expiring, regenerating a new one") - certs, err = ch.overwrite(webhook.Name) - if err != nil { - return err - } - } - - // Ensure the CA bundle in the webhook configuration has the signing CA. - caBundle := webhook.ClientConfig.CABundle - caCert := certs.CACert - if !bytes.Contains(caBundle, caCert) { - webhook.ClientConfig.CABundle = append(caBundle, caCert...) - } - return nil -} - -func createIfNotExists(webhookName string, ch certReadWriter) (*generator.Artifacts, error) { - // Try to read first - certs, err := ch.read(webhookName) - if isNotFound(err) { - // Create if not exists - certs, err = ch.write(webhookName) - switch { - // This may happen if there is another racer. - case isAlreadyExists(err): - certs, err = ch.read(webhookName) - if err != nil { - return certs, err - } - case err != nil: - return certs, err - } - } else if err != nil { - return certs, err - } - return certs, nil -} - -// certReadWriter provides methods for reading and writing certificates. -type certReadWriter interface { - // read reads a wehbook name and returns the certs for it. - read(webhookName string) (*generator.Artifacts, error) - // write writes the certs and return the certs it wrote. - write(webhookName string) (*generator.Artifacts, error) - // overwrite overwrites the existing certs and return the certs it wrote. - overwrite(webhookName string) (*generator.Artifacts, error) -} - -func validCert(certs *generator.Artifacts, dnsName string) bool { - if certs == nil { - return false - } - - // Verify key and cert are valid pair - _, err := tls.X509KeyPair(certs.Cert, certs.Key) - if err != nil { - return false - } - - // Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time. - pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(certs.CACert) { - return false - } - block, _ := pem.Decode([]byte(certs.Cert)) - if block == nil { - return false - } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return false - } - ops := x509.VerifyOptions{ - DNSName: dnsName, - Roots: pool, - CurrentTime: time.Now().AddDate(0, 6, 0), - } - _, err = cert.Verify(ops) - return err == nil -} - -func getWebhooksFromObject(obj runtime.Object) ([]admissionregistrationv1beta1.Webhook, error) { - switch typed := obj.(type) { - case *admissionregistrationv1beta1.MutatingWebhookConfiguration: - return typed.Webhooks, nil - case *admissionregistrationv1beta1.ValidatingWebhookConfiguration: - return typed.Webhooks, nil - //case *unstructured.Unstructured: - // TODO: implement this if needed - default: - return nil, fmt.Errorf("unsupported type: %T, only support v1beta1.MutatingWebhookConfiguration and v1beta1.ValidatingWebhookConfiguration", typed) - } -} - -func dnsNameForWebhook(config *admissionregistrationv1beta1.WebhookClientConfig) (string, error) { - if config.Service != nil && config.URL != nil { - return "", fmt.Errorf("service and URL can't be set at the same time in a webhook: %v", config) - } - if config.Service == nil && config.URL == nil { - return "", fmt.Errorf("one of service and URL need to be set in a webhook: %v", config) - } - if config.Service != nil { - return generator.ServiceToCommonName(config.Service.Namespace, config.Service.Name), nil - } - // config.URL != nil - u, err := url.Parse(*config.URL) - return u.Host, err - -} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/doc.go deleted file mode 100644 index 223e8b579..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/doc.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* -Package writer provides method to ensure each webhook has a working certificate and private key in the right -place for consuming. - -It will create the certificates if they don't exist. -It will ensure the certificates are valid and not expiring. If not, it will recreate them. - -Example Webhook Configuration - -There is an example annotation to get the webhook managed by the SecretCertWriter. -SecretCertProvisionAnnotationKeyPrefix is the prefix of the annotation key. - - secret.certprovisioner.kubernetes.io/webhook-1: namespace-bar/secret-foo - -The following is an example MutatingWebhookConfiguration in yaml. - - apiVersion: admissionregistration.k8s.io/v1beta1 - kind: MutatingWebhookConfiguration - metadata: - name: myMutatingWebhookConfiguration - annotations: - secret.certprovisioner.kubernetes.io/webhook-1: namespace-bar/secret-foo - webhooks: - - name: webhook-1 - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - "*" - resources: - - pods - clientConfig: - service: - namespace: service-ns-1 - name: service-foo - path: "/mutating-pods" - caBundle: [] # CA bundle here - -Create a default CertWriter - - writer, err := NewCertWriter(Options{client: client})) - if err != nil { - // handler error - } - -Create a SecretCertWriter - - writer, err := &SecretCertWriter{ - Client: client - } - if err != nil { - // handler error - } - -Provision the certificates using the CertWriter. The certificate will be available in the desired secrets or -the desired path. - - // writer can be either one of the CertWriters created above - err = writer.EnsureCerts(webhookConfiguration) // webhookConfiguration is an existing runtime.Object - if err != nil { - // handler error - } - -*/ -package writer - -import ( - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" -) - -var log = logf.KBLog.WithName("admission").WithName("cert").WithName("writer") diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/fs.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/fs.go deleted file mode 100644 index 6031341fa..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/fs.go +++ /dev/null @@ -1,239 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package writer - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - certgenerator "sigs.k8s.io/controller-runtime/pkg/admission/cert/generator" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/internal/atomic" -) - -const ( - // FSCertProvisionAnnotationKeyPrefix should be used in an annotation in the following format: - // fs.certprovisioner.kubernetes.io/: path/to/certs/ - // the webhook cert manager library will provision the certificate for the webhook by - // storing it under the specified path. - // format: fs.certprovisioner.kubernetes.io/webhookName: path/to/certs/ - FSCertProvisionAnnotationKeyPrefix = "fs.certprovisioner.kubernetes.io/" -) - -// FSCertWriter provisions the certificate by reading and writing to the filesystem. -type FSCertWriter struct { - CertGenerator certgenerator.CertGenerator -} - -var _ CertWriter = &FSCertWriter{} - -// EnsureCerts provisions certificates for a webhook configuration by writing them in the filesystem. -func (f *FSCertWriter) EnsureCerts(webhookConfig runtime.Object) error { - if webhookConfig == nil { - return errors.New("unexpected nil webhook configuration object") - } - - fsWebhookMap := map[string]*webhookAndPath{} - accessor, err := meta.Accessor(webhookConfig) - if err != nil { - return err - } - annotations := accessor.GetAnnotations() - // Parse the annotations to extract info - f.parseAnnotations(annotations, fsWebhookMap) - - webhooks, err := getWebhooksFromObject(webhookConfig) - if err != nil { - return err - } - for i, webhook := range webhooks { - if p, found := fsWebhookMap[webhook.Name]; found { - p.webhook = &webhooks[i] - } - } - - // validation - for k, v := range fsWebhookMap { - if v.webhook == nil { - return fmt.Errorf("expecting a webhook named %q", k) - } - } - - generator := f.CertGenerator - if f.CertGenerator == nil { - generator = &certgenerator.SelfSignedCertGenerator{} - } - - cw := &fsCertWriter{ - certGenerator: generator, - webhookConfig: webhookConfig, - webhookMap: fsWebhookMap, - } - return cw.ensureCert() -} - -func (f *FSCertWriter) parseAnnotations(annotations map[string]string, fsWebhookMap map[string]*webhookAndPath) { - for k, v := range annotations { - if strings.HasPrefix(k, FSCertProvisionAnnotationKeyPrefix) { - webhookName := strings.TrimPrefix(k, FSCertProvisionAnnotationKeyPrefix) - fsWebhookMap[webhookName] = &webhookAndPath{ - path: v, - } - } - } -} - -// fsCertWriter deals with writing to the local filesystem. -type fsCertWriter struct { - certGenerator certgenerator.CertGenerator - - webhookConfig runtime.Object - webhookMap map[string]*webhookAndPath -} - -type webhookAndPath struct { - webhook *admissionregistrationv1beta1.Webhook - path string -} - -var _ certReadWriter = &fsCertWriter{} - -func (f *fsCertWriter) ensureCert() error { - var err error - for _, v := range f.webhookMap { - err = handleCommon(v.webhook, f) - if err != nil { - return err - } - } - return nil -} - -func (f *fsCertWriter) write(webhookName string) (*certgenerator.Artifacts, error) { - return f.doWrite(webhookName) -} - -func (f *fsCertWriter) overwrite(webhookName string) (*certgenerator.Artifacts, error) { - return f.doWrite(webhookName) -} - -func (f *fsCertWriter) doWrite(webhookName string) (*certgenerator.Artifacts, error) { - v := f.webhookMap[webhookName] - commonName, err := dnsNameForWebhook(&v.webhook.ClientConfig) - if err != nil { - return nil, err - } - certs, err := f.certGenerator.Generate(commonName) - if err != nil { - return nil, err - } - aw, err := atomic.NewAtomicWriter(v.path, log.WithName("atomic-writer").WithValues("task", "processing webhook", "webhook", webhookName)) - if err != nil { - return nil, err - } - // AtomicWriter's algorithm only manages files using symbolic link. - // If a file is not a symbolic link, will ignore the update for it. - // We want to cleanup for AtomicWriter by removing old files that are not symbolic links. - prepareToWrite(v.path) - err = aw.Write(certToProjectionMap(certs)) - return certs, err -} - -func prepareToWrite(dir string) { - filenames := []string{CACertName, ServerCertName, ServerKeyName} - for _, f := range filenames { - abspath := path.Join(dir, f) - _, err := os.Stat(abspath) - if os.IsNotExist(err) { - continue - } else if err != nil { - log.Error(err, "unable to stat file", "file", abspath) - } - _, err = os.Readlink(abspath) - // if it's not a symbolic link - if err != nil { - err = os.Remove(abspath) - if err != nil { - log.Error(err, "unable to remove old file", "file", abspath) - } - } - } -} - -func (f *fsCertWriter) read(webhookName string) (*certgenerator.Artifacts, error) { - dir := f.webhookMap[webhookName].path - if err := ensureExist(dir); err != nil { - return nil, err - } - caBytes, err := ioutil.ReadFile(path.Join(dir, CACertName)) - if err != nil { - return nil, err - } - certBytes, err := ioutil.ReadFile(path.Join(dir, ServerCertName)) - if err != nil { - return nil, err - } - keyBytes, err := ioutil.ReadFile(path.Join(dir, ServerKeyName)) - if err != nil { - return nil, err - } - return &certgenerator.Artifacts{ - CACert: caBytes, - Cert: certBytes, - Key: keyBytes, - }, nil -} - -func ensureExist(dir string) error { - filenames := []string{CACertName, ServerCertName, ServerKeyName} - for _, filename := range filenames { - _, err := os.Stat(path.Join(dir, filename)) - switch { - case err == nil: - continue - case os.IsNotExist(err): - return notFoundError{err} - default: - return err - } - } - return nil -} - -func certToProjectionMap(cert *certgenerator.Artifacts) map[string]atomic.FileProjection { - // TODO: figure out if we can reduce the permission. (Now it's 0666) - return map[string]atomic.FileProjection{ - CACertName: { - Data: cert.CACert, - Mode: 0666, - }, - ServerCertName: { - Data: cert.Cert, - Mode: 0666, - }, - ServerKeyName: { - Data: cert.Key, - Mode: 0666, - }, - } -} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/multiple.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/multiple.go deleted file mode 100644 index 82baa679d..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/multiple.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package writer - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -// MultiCertWriter composes a slice of CertWriters. -// This is useful if you need both SecretCertWriter and FSCertWriter. -type MultiCertWriter struct { - CertWriters []CertWriter -} - -var _ CertWriter = &MultiCertWriter{} - -// EnsureCerts provisions certificates for a webhook configuration by invoking each CertWrite. -func (s *MultiCertWriter) EnsureCerts(webhookConfig runtime.Object) error { - var err error - for _, certWriter := range s.CertWriters { - err = certWriter.EnsureCerts(webhookConfig) - if err != nil { - return err - } - } - return nil -} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/secret.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/secret.go deleted file mode 100644 index 2c6c0c123..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/secret.go +++ /dev/null @@ -1,226 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package writer - -import ( - "errors" - "fmt" - "strings" - - admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/generator" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -const ( - // SecretCertProvisionAnnotationKeyPrefix should be used in an annotation in the following format: - // secret.certprovisioner.kubernetes.io/: / - // the webhook cert manager library will provision the certificate for the webhook by - // storing it in the specified secret. - SecretCertProvisionAnnotationKeyPrefix = "secret.certprovisioner.kubernetes.io/" -) - -// SecretCertWriter provisions the certificate by reading and writing to the k8s secrets. -type SecretCertWriter struct { - Client client.Client - CertGenerator generator.CertGenerator -} - -var _ CertWriter = &SecretCertWriter{} - -// EnsureCerts provisions certificates for a webhook configuration by writing them in k8s secrets. -func (s *SecretCertWriter) EnsureCerts(webhookConfig runtime.Object) error { - if webhookConfig == nil { - return errors.New("unexpected nil webhook configuration object") - } - - secretWebhookMap := map[string]*webhookAndSecret{} - accessor, err := meta.Accessor(webhookConfig) - if err != nil { - return err - } - annotations := accessor.GetAnnotations() - // Parse the annotations to extract info - s.parseAnnotations(annotations, secretWebhookMap) - - webhooks, err := getWebhooksFromObject(webhookConfig) - if err != nil { - return err - } - for i, webhook := range webhooks { - if s, found := secretWebhookMap[webhook.Name]; found { - s.webhook = &webhooks[i] - } - } - - // validation - for k, v := range secretWebhookMap { - if v.webhook == nil { - return fmt.Errorf("expecting a webhook named %q", k) - } - } - - certGenerator := s.CertGenerator - if s.CertGenerator == nil { - certGenerator = &generator.SelfSignedCertGenerator{} - } - - srw := &secretReadWriter{ - client: s.Client, - certGenerator: certGenerator, - webhookConfig: webhookConfig, - webhookMap: secretWebhookMap, - } - return srw.ensureCert() -} - -func (s *SecretCertWriter) parseAnnotations(annotations map[string]string, secretWebhookMap map[string]*webhookAndSecret) { - for k, v := range annotations { - if strings.HasPrefix(k, SecretCertProvisionAnnotationKeyPrefix) { - webhookName := strings.TrimPrefix(k, SecretCertProvisionAnnotationKeyPrefix) - secretWebhookMap[webhookName] = &webhookAndSecret{ - secret: newNamespacedNameFromString(v), - } - } - } -} - -func (s *secretReadWriter) ensureCert() error { - for _, v := range s.webhookMap { - err := handleCommon(v.webhook, s) - if err != nil { - return err - } - } - return nil -} - -// secretReadWriter deals with writing to the k8s secrets. -type secretReadWriter struct { - client client.Client - certGenerator generator.CertGenerator - - webhookConfig runtime.Object - webhookMap map[string]*webhookAndSecret -} - -type webhookAndSecret struct { - webhook *admissionregistrationv1beta1.Webhook - secret types.NamespacedName -} - -var _ certReadWriter = &secretReadWriter{} - -func (s *secretReadWriter) buildSecret(webhookName string) (*corev1.Secret, *generator.Artifacts, error) { - v := s.webhookMap[webhookName] - - webhook := v.webhook - commonName, err := dnsNameForWebhook(&webhook.ClientConfig) - if err != nil { - return nil, nil, err - } - certs, err := s.certGenerator.Generate(commonName) - if err != nil { - return nil, nil, err - } - secret := certsToSecret(certs, v.secret) - err = controllerutil.SetControllerReference(s.webhookConfig.(metav1.Object), secret, scheme.Scheme) - return secret, certs, err -} - -func (s *secretReadWriter) write(webhookName string) (*generator.Artifacts, error) { - secret, certs, err := s.buildSecret(webhookName) - if err != nil { - return nil, err - } - err = s.client.Create(nil, secret) - if apierrors.IsAlreadyExists(err) { - return nil, alreadyExistError{err} - } - return certs, err -} - -func (s *secretReadWriter) overwrite(webhookName string) ( - *generator.Artifacts, error) { - secret, certs, err := s.buildSecret(webhookName) - if err != nil { - return nil, err - } - err = s.client.Update(nil, secret) - return certs, err -} - -func (s *secretReadWriter) read(webhookName string) (*generator.Artifacts, error) { - v := s.webhookMap[webhookName] - secret := &corev1.Secret{} - err := s.client.Get(nil, v.secret, secret) - if apierrors.IsNotFound(err) { - return nil, notFoundError{err} - } - return secretToCerts(secret), err -} - -// newNamespacedNameFromString parses the provided string and returns a NamespacedName. -// The expected format is as per String() above. -// If the input string is invalid, the returned NamespacedName has all empty string field values. -// This allows a single-value return from this function, while still allowing error checks in the caller. -// Note that an input string which does not include exactly one Separator is not a valid input (as it could never -// have neem returned by String() ) -// NOTE: https://github.com/kubernetes/apimachinery/commit/2b167bb262168a225efe64d1fdc40ea97870ca9e removed this from -// "k8s.io/apimachinery/pkg/types". Code copied here. -func newNamespacedNameFromString(s string) types.NamespacedName { - nn := types.NamespacedName{} - result := strings.Split(s, string(types.Separator)) - if len(result) == 2 { - nn.Namespace = result[0] - nn.Name = result[1] - } - return nn -} - -func secretToCerts(secret *corev1.Secret) *generator.Artifacts { - if secret.Data == nil { - return nil - } - return &generator.Artifacts{ - CACert: secret.Data[CACertName], - Cert: secret.Data[ServerCertName], - Key: secret.Data[ServerKeyName], - } -} - -func certsToSecret(certs *generator.Artifacts, sec types.NamespacedName) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: sec.Namespace, - Name: sec.Name, - }, - Data: map[string][]byte{ - CACertName: certs.CACert, - ServerKeyName: certs.Key, - ServerCertName: certs.Cert, - }, - } -} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/controller.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/controller.go deleted file mode 100644 index 20b6eda17..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/controller.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package admission - -import ( - "fmt" - "reflect" - "sync" - - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/generator" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/writer" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" -) - -// CertProvisioner provisions certificates for webhook configurations and writes them to an output -// destination - such as a Secret or local file. CertProvisioner can update the CA field of -// certain resources with the CA of the certs. -type CertProvisioner struct { - Client client.Client - // CertGenerator generates certificate for a given common name. - CertGenerator generator.CertGenerator - CertWriter writer.CertWriter - - once sync.Once -} - -// Sync takes a runtime.Object which is expected to be either a MutatingWebhookConfiguration or -// a ValidatingWebhookConfiguration. -// It provisions certificate for each webhook in the webhookConfiguration, ensures the cert and CA are valid, -// and not expiring. It updates the CABundle in the webhook configuration if necessary. -func (cp *CertProvisioner) Sync(webhookConfiguration runtime.Object) error { - var err error - // Do the initialization for CertInput only once. - cp.once.Do(func() { - if cp.CertGenerator == nil { - cp.CertGenerator = &generator.SelfSignedCertGenerator{} - } - if cp.Client == nil { - cp.Client, err = client.New(config.GetConfigOrDie(), client.Options{}) - if err != nil { - return - } - } - if cp.CertWriter == nil { - cp.CertWriter, err = writer.NewCertWriter( - writer.Options{ - Client: cp.Client, - CertGenerator: cp.CertGenerator, - }) - if err != nil { - return - } - } - }) - if err != nil { - return fmt.Errorf("failed to default the CertProvision: %v", err) - } - - // Deepcopy the webhook configuration object before invoking EnsureCerts, - // since EnsureCerts will modify the provided object. - cloned := webhookConfiguration.DeepCopyObject() - err = cp.CertWriter.EnsureCerts(cloned) - if err != nil { - return err - } - - // If some fields have been changed, we will update the object. - // Mostly this is because of the CABundle field has been updated. - if reflect.DeepEqual(webhookConfiguration, cloned) { - return nil - } - return cp.Client.Update(nil, cloned) -} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/admission/doc.go deleted file mode 100644 index 815c85458..000000000 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/doc.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* -Package admission provides functions to manage webhooks certificates. - -There are 3 typical ways to use this library: - -* The sync function can be used as a Reconciler function. - -* Invoking it directly fromt eh webhook server at startup. - -* Deploying it as an init container along with the webhook server. - -Webhook Configuration - -The following is an example MutatingWebhookConfiguration in yaml. - - apiVersion: admissionregistration.k8s.io/v1beta1 - kind: MutatingWebhookConfiguration - metadata: - name: myMutatingWebhookConfiguration - annotations: - secret.certprovisioner.kubernetes.io/webhook-1: namespace-bar/secret-foo - secret.certprovisioner.kubernetes.io/webhook-2: default/secret-baz - webhooks: - - name: webhook-1 - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - "*" - resources: - - pods - clientConfig: - service: - namespace: service-ns-1 - name: service-foo - path: "/mutating-pods" - caBundle: [] # CA bundle here - - name: webhook-2 - rules: - - apiGroups: - - apps - apiVersions: - - v1 - operations: - - "*" - resources: - - deployments - clientConfig: - service: - namespace: service-ns-2 - name: service-bar - path: "/mutating-deployment" - caBundle: [] # CA bundle here - -Build the CertProvisioner - -You can choose to provide your own CertGenerator and CertWriter. -An easier way is to use an empty Options the package will default it with reasonable values. -The package will write self-signed certificates to secrets. - - // Build a client. You can also create a client with your own config.Config. - cl, err := client.New(config.GetConfigOrDie(), client.Options) - if err != nil { - // handle error - } - - // Build a CertProvisioner with unspecified CertGenerator and CertWriter. - cp := &CertProvisioner{client: cl} - -Provision certificates - -Provision certificates for webhook configuration objects' by calling Sync method. - - err = cp.Sync(mwc) - if err != nil { - // handler error - } - -When the above MutatingWebhookConfiguration is processed, the cert provisioner will create -the certificate and create a secret named "secret-foo" in namespace "namespace-bar" for webhook "webhook-1". -Similarly, it will create an secret named "secret-baz" in namespace "default" for webhook "webhook-2". -And it will also write the CA back to the WebhookConfiguration. -*/ -package admission diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go index 5435d2715..cb87d8f38 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go @@ -79,6 +79,10 @@ type Options struct { // Resync is the resync period. Defaults to defaultResyncTime. Resync *time.Duration + + // Namespace restricts the cache's ListWatch to the desired namespace + // Default watches all namespaces + Namespace string } var defaultResyncTime = 10 * time.Hour @@ -89,7 +93,7 @@ func New(config *rest.Config, opts Options) (Cache, error) { if err != nil { return nil, err } - im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync) + im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace) return &informerCache{InformersMap: im}, nil } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go index 03298baee..eedfcc6cf 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go @@ -23,6 +23,7 @@ import ( "strings" apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/cache" @@ -58,11 +59,6 @@ func (ip *informerCache) Get(ctx context.Context, key client.ObjectKey, out runt // List implements Reader func (ip *informerCache) List(ctx context.Context, opts *client.ListOptions, out runtime.Object) error { - itemsPtr, err := apimeta.GetItemsPtr(out) - if err != nil { - return nil - } - gvk, err := apiutil.GVKForObject(out, ip.Scheme) if err != nil { return err @@ -73,13 +69,25 @@ func (ip *informerCache) List(ctx context.Context, opts *client.ListOptions, out } // we need the non-list GVK, so chop off the "List" from the end of the kind gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] - - // http://knowyourmeme.com/memes/this-is-fine - elemType := reflect.Indirect(reflect.ValueOf(itemsPtr)).Type().Elem() - cacheTypeValue := reflect.Zero(reflect.PtrTo(elemType)) - cacheTypeObj, ok := cacheTypeValue.Interface().(runtime.Object) - if !ok { - return fmt.Errorf("cannot get cache for %T, its element %T is not a runtime.Object", out, cacheTypeValue.Interface()) + _, isUnstructured := out.(*unstructured.UnstructuredList) + var cacheTypeObj runtime.Object + if isUnstructured { + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(gvk) + cacheTypeObj = u + } else { + itemsPtr, err := apimeta.GetItemsPtr(out) + if err != nil { + return nil + } + // http://knowyourmeme.com/memes/this-is-fine + elemType := reflect.Indirect(reflect.ValueOf(itemsPtr)).Type().Elem() + cacheTypeValue := reflect.Zero(reflect.PtrTo(elemType)) + var ok bool + cacheTypeObj, ok = cacheTypeValue.Interface().(runtime.Object) + if !ok { + return fmt.Errorf("cannot get cache for %T, its element %T is not a runtime.Object", out, cacheTypeValue.Interface()) + } } cache, err := ip.InformersMap.Get(gvk, cacheTypeObj) diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go new file mode 100644 index 000000000..978002d9c --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go @@ -0,0 +1,96 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" +) + +// InformersMap create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs. +// It uses a standard parameter codec constructed based on the given generated Scheme. +type InformersMap struct { + // we abstract over the details of structured vs unstructured with the specificInformerMaps + + structured *specificInformersMap + unstructured *specificInformersMap + + // Scheme maps runtime.Objects to GroupVersionKinds + Scheme *runtime.Scheme +} + +// NewInformersMap creates a new InformersMap that can create informers for +// both structured and unstructured objects. +func NewInformersMap(config *rest.Config, + scheme *runtime.Scheme, + mapper meta.RESTMapper, + resync time.Duration, + namespace string) *InformersMap { + + return &InformersMap{ + structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace), + unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace), + + Scheme: scheme, + } +} + +// Start calls Run on each of the informers and sets started to true. Blocks on the stop channel. +func (m *InformersMap) Start(stop <-chan struct{}) error { + go m.structured.Start(stop) + go m.unstructured.Start(stop) + <-stop + return nil +} + +// WaitForCacheSync waits until all the caches have been synced. +func (m *InformersMap) WaitForCacheSync(stop <-chan struct{}) bool { + syncedFuncs := append([]cache.InformerSynced(nil), m.structured.HasSyncedFuncs()...) + syncedFuncs = append(syncedFuncs, m.unstructured.HasSyncedFuncs()...) + + return cache.WaitForCacheSync(stop, syncedFuncs...) +} + +// Get will create a new Informer and add it to the map of InformersMap if none exists. Returns +// the Informer from the map. +func (m *InformersMap) Get(gvk schema.GroupVersionKind, obj runtime.Object) (*MapEntry, error) { + _, isUnstructured := obj.(*unstructured.Unstructured) + _, isUnstructuredList := obj.(*unstructured.UnstructuredList) + isUnstructured = isUnstructured || isUnstructuredList + + if isUnstructured { + return m.unstructured.Get(gvk, obj) + } + + return m.structured.Get(gvk, obj) +} + +// newStructuredInformersMap creates a new InformersMap for structured objects. +func newStructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration, namespace string) *specificInformersMap { + return newSpecificInformersMap(config, scheme, mapper, resync, namespace, createStructuredListWatch) +} + +// newUnstructuredInformersMap creates a new InformersMap for unstructured objects. +func newUnstructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration, namespace string) *specificInformersMap { + return newSpecificInformersMap(config, scheme, mapper, resync, namespace, createUnstructuredListWatch) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go index 4564fc95e..9dea36966 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go @@ -27,24 +27,34 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) -// NewInformersMap returns a new InformersMap -func NewInformersMap(config *rest.Config, +// clientListWatcherFunc knows how to create a ListWatcher +type createListWatcherFunc func(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) + +// newSpecificInformersMap returns a new specificInformersMap (like +// the generical InformersMap, except that it doesn't implement WaitForCacheSync). +func newSpecificInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, - resync time.Duration) *InformersMap { - ip := &InformersMap{ - config: config, - Scheme: scheme, - mapper: mapper, - informersByGVK: make(map[schema.GroupVersionKind]*MapEntry), - codecs: serializer.NewCodecFactory(scheme), - paramCodec: runtime.NewParameterCodec(scheme), - resync: resync, + resync time.Duration, + namespace string, + createListWatcher createListWatcherFunc) *specificInformersMap { + ip := &specificInformersMap{ + config: config, + Scheme: scheme, + mapper: mapper, + informersByGVK: make(map[schema.GroupVersionKind]*MapEntry), + codecs: serializer.NewCodecFactory(scheme), + paramCodec: runtime.NewParameterCodec(scheme), + resync: resync, + createListWatcher: createListWatcher, + namespace: namespace, } return ip } @@ -58,9 +68,9 @@ type MapEntry struct { Reader CacheReader } -// InformersMap create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs. -//It uses a standard parameter codec constructed based on the given generated Scheme. -type InformersMap struct { +// specificInformersMap create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs. +// It uses a standard parameter codec constructed based on the given generated Scheme. +type specificInformersMap struct { // Scheme maps runtime.Objects to GroupVersionKinds Scheme *runtime.Scheme @@ -90,10 +100,20 @@ type InformersMap struct { // start is true if the informers have been started started bool + + // createClient knows how to create a client and a list object, + // and allows for abstracting over the particulars of structured vs + // unstructured objects. + createListWatcher createListWatcherFunc + + // namespace is the namespace that all ListWatches are restricted to + // default or empty string means all namespaces + namespace string } // Start calls Run on each of the informers and sets started to true. Blocks on the stop channel. -func (ip *InformersMap) Start(stop <-chan struct{}) error { +// It doesn't return start because it can't return an error, and it's not a runnable directly. +func (ip *specificInformersMap) Start(stop <-chan struct{}) { func() { ip.mu.Lock() defer ip.mu.Unlock() @@ -110,21 +130,22 @@ func (ip *InformersMap) Start(stop <-chan struct{}) error { ip.started = true }() <-stop - return nil } -// WaitForCacheSync waits until all the caches have been synced -func (ip *InformersMap) WaitForCacheSync(stop <-chan struct{}) bool { +// HasSyncedFuncs returns all the HasSynced functions for the informers in this map. +func (ip *specificInformersMap) HasSyncedFuncs() []cache.InformerSynced { + ip.mu.RLock() + defer ip.mu.RUnlock() syncedFuncs := make([]cache.InformerSynced, 0, len(ip.informersByGVK)) for _, informer := range ip.informersByGVK { syncedFuncs = append(syncedFuncs, informer.Informer.HasSynced) } - return cache.WaitForCacheSync(stop, syncedFuncs...) + return syncedFuncs } -// Get will create a new Informer and add it to the map of InformersMap if none exists. Returns +// Get will create a new Informer and add it to the map of specificInformersMap if none exists. Returns // the Informer from the map. -func (ip *InformersMap) Get(gvk schema.GroupVersionKind, obj runtime.Object) (*MapEntry, error) { +func (ip *specificInformersMap) Get(gvk schema.GroupVersionKind, obj runtime.Object) (*MapEntry, error) { // Return the informer if it is found i, ok := func() (*MapEntry, bool) { ip.mu.RLock() @@ -154,7 +175,7 @@ func (ip *InformersMap) Get(gvk schema.GroupVersionKind, obj runtime.Object) (*M // Create a NewSharedIndexInformer and add it to the map. var lw *cache.ListWatch - lw, err := ip.newListWatch(gvk) + lw, err := ip.createListWatcher(gvk, ip) if err != nil { return nil, err } @@ -191,14 +212,7 @@ func (ip *InformersMap) Get(gvk schema.GroupVersionKind, obj runtime.Object) (*M } // newListWatch returns a new ListWatch object that can be used to create a SharedIndexInformer. -func (ip *InformersMap) newListWatch(gvk schema.GroupVersionKind) (*cache.ListWatch, error) { - // Construct a RESTClient for the groupVersionKind that we will use to - // talk to the apiserver. - client, err := apiutil.RESTClientForGVK(gvk, ip.config, ip.codecs) - if err != nil { - return nil, err - } - +func createStructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) { // Kubernetes APIs work against Resources, not GroupVersionKinds. Map the // groupVersionKind to the Resource API we will use. mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) @@ -206,7 +220,10 @@ func (ip *InformersMap) newListWatch(gvk schema.GroupVersionKind) (*cache.ListWa return nil, err } - // Get a listObject for listing that the ListWatch can DeepCopy + client, err := apiutil.RESTClientForGVK(gvk, ip.config, ip.codecs) + if err != nil { + return nil, err + } listGVK := gvk.GroupVersion().WithKind(gvk.Kind + "List") listObj, err := ip.Scheme.New(listGVK) if err != nil { @@ -217,14 +234,48 @@ func (ip *InformersMap) newListWatch(gvk schema.GroupVersionKind) (*cache.ListWa return &cache.ListWatch{ ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { res := listObj.DeepCopyObject() - err := client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res) + isNamespaceScoped := ip.namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot + err := client.Get().NamespaceIfScoped(ip.namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res) return res, err }, // Setup the watch function WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { // Watch needs to be set to true separately opts.Watch = true - return client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Watch() + isNamespaceScoped := ip.namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot + return client.Get().NamespaceIfScoped(ip.namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Watch() + }, + }, nil +} + +func createUnstructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) { + // Kubernetes APIs work against Resources, not GroupVersionKinds. Map the + // groupVersionKind to the Resource API we will use. + mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + dynamicClient, err := dynamic.NewForConfig(ip.config) + if err != nil { + return nil, err + } + + // Create a new ListWatch for the obj + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + if ip.namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot { + return dynamicClient.Resource(mapping.Resource).Namespace(ip.namespace).List(opts) + } + return dynamicClient.Resource(mapping.Resource).List(opts) + }, + // Setup the watch function + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + // Watch needs to be set to true separately + opts.Watch = true + if ip.namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot { + return dynamicClient.Resource(mapping.Resource).Namespace(ip.namespace).Watch(opts) + } + return dynamicClient.Resource(mapping.Resource).Watch(opts) }, }, nil } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go index 9365eb2af..614d454f1 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go @@ -65,6 +65,13 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi // RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated // with the given GroupVersionKind. func RESTClientForGVK(gvk schema.GroupVersionKind, baseConfig *rest.Config, codecs serializer.CodecFactory) (rest.Interface, error) { + cfg := createRestConfig(gvk, baseConfig) + cfg.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: codecs} + return rest.RESTClientFor(cfg) +} + +//createRestConfig copies the base config and updates needed fields for a new rest config +func createRestConfig(gvk schema.GroupVersionKind, baseConfig *rest.Config) *rest.Config { gv := gvk.GroupVersion() cfg := rest.CopyConfig(baseConfig) @@ -74,9 +81,8 @@ func RESTClientForGVK(gvk schema.GroupVersionKind, baseConfig *rest.Config, code } else { cfg.APIPath = "/apis" } - cfg.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: codecs} if cfg.UserAgent == "" { cfg.UserAgent = rest.DefaultKubernetesUserAgent() } - return rest.RESTClientFor(cfg) + return cfg } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go index a7854ef62..05b9eba2b 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go @@ -22,8 +22,10 @@ import ( "reflect" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" @@ -58,15 +60,26 @@ func New(config *rest.Config, options Options) (Client, error) { } } + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return nil, err + } + c := &client{ - cache: clientCache{ - config: config, - scheme: options.Scheme, - mapper: options.Mapper, - codecs: serializer.NewCodecFactory(options.Scheme), - resourceByType: make(map[reflect.Type]*resourceMeta), + typedClient: typedClient{ + cache: clientCache{ + config: config, + scheme: options.Scheme, + mapper: options.Mapper, + codecs: serializer.NewCodecFactory(options.Scheme), + resourceByType: make(map[reflect.Type]*resourceMeta), + }, + paramCodec: runtime.NewParameterCodec(options.Scheme), + }, + unstructuredClient: unstructuredClient{ + client: dynamicClient, + restMapper: options.Mapper, }, - paramCodec: runtime.NewParameterCodec(options.Scheme), } return c, nil @@ -77,82 +90,53 @@ var _ Client = &client{} // client is a client.Client that reads and writes directly from/to an API server. It lazily initializes // new clients at the time they are used, and caches the client. type client struct { - cache clientCache - paramCodec runtime.ParameterCodec + typedClient typedClient + unstructuredClient unstructuredClient } // Create implements client.Client func (c *client) Create(ctx context.Context, obj runtime.Object) error { - o, err := c.cache.getObjMeta(obj) - if err != nil { - return err + _, ok := obj.(*unstructured.Unstructured) + if ok { + return c.unstructuredClient.Create(ctx, obj) } - return o.Post(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). - Resource(o.resource()). - Body(obj). - Do(). - Into(obj) + return c.typedClient.Create(ctx, obj) } // Update implements client.Client func (c *client) Update(ctx context.Context, obj runtime.Object) error { - o, err := c.cache.getObjMeta(obj) - if err != nil { - return err + _, ok := obj.(*unstructured.Unstructured) + if ok { + return c.unstructuredClient.Update(ctx, obj) } - return o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). - Resource(o.resource()). - Name(o.GetName()). - Body(obj). - Do(). - Into(obj) + return c.typedClient.Update(ctx, obj) } // Delete implements client.Client -func (c *client) Delete(ctx context.Context, obj runtime.Object) error { - o, err := c.cache.getObjMeta(obj) - if err != nil { - return err +func (c *client) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error { + _, ok := obj.(*unstructured.Unstructured) + if ok { + return c.unstructuredClient.Delete(ctx, obj, opts...) } - return o.Delete(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). - Resource(o.resource()). - Name(o.GetName()). - Do(). - Error() + return c.typedClient.Delete(ctx, obj, opts...) } // Get implements client.Client func (c *client) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error { - r, err := c.cache.getResource(obj) - if err != nil { - return err + _, ok := obj.(*unstructured.Unstructured) + if ok { + return c.unstructuredClient.Get(ctx, key, obj) } - return r.Get(). - NamespaceIfScoped(key.Namespace, r.isNamespaced()). - Resource(r.resource()). - Name(key.Name).Do().Into(obj) + return c.typedClient.Get(ctx, key, obj) } // List implements client.Client func (c *client) List(ctx context.Context, opts *ListOptions, obj runtime.Object) error { - r, err := c.cache.getResource(obj) - if err != nil { - return err + _, ok := obj.(*unstructured.UnstructuredList) + if ok { + return c.unstructuredClient.List(ctx, opts, obj) } - namespace := "" - if opts != nil { - namespace = opts.Namespace - } - return r.Get(). - NamespaceIfScoped(namespace, r.isNamespaced()). - Resource(r.resource()). - Body(obj). - VersionedParams(opts.AsListOptions(), c.paramCodec). - Do(). - Into(obj) + return c.typedClient.List(ctx, opts, obj) } // Status implements client.StatusClient @@ -169,21 +153,10 @@ type statusWriter struct { var _ StatusWriter = &statusWriter{} // Update implements client.StatusWriter -func (sw *statusWriter) Update(_ context.Context, obj runtime.Object) error { - o, err := sw.client.cache.getObjMeta(obj) - if err != nil { - return err +func (sw *statusWriter) Update(ctx context.Context, obj runtime.Object) error { + _, ok := obj.(*unstructured.Unstructured) + if ok { + return sw.client.unstructuredClient.UpdateStatus(ctx, obj) } - // TODO(droot): examine the returned error and check if it error needs to be - // wrapped to improve the UX ? - // It will be nice to receive an error saying the object doesn't implement - // status subresource and check CRD definition - return o.Put(). - NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). - Resource(o.resource()). - Name(o.GetName()). - SubResource("status"). - Body(obj). - Do(). - Into(obj) + return sw.client.typedClient.UpdateStatus(ctx, obj) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go index 598cfd48a..8597cbde6 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go @@ -105,7 +105,7 @@ func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error { return c.tracker.Create(gvr, obj, accessor.GetNamespace()) } -func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object) error { +func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error { gvr, err := getGVRFromObject(obj) if err != nil { return err @@ -114,6 +114,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object) error { if err != nil { return err } + //TODO: implement propagation return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName()) } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go index 732ec1061..36d0fce62 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go @@ -19,6 +19,7 @@ package client import ( "context" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -29,6 +30,15 @@ import ( // ObjectKey identifies a Kubernetes Object. type ObjectKey = types.NamespacedName +// ObjectKeyFromObject returns the ObjectKey given a runtime.Object +func ObjectKeyFromObject(obj runtime.Object) (ObjectKey, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return ObjectKey{}, err + } + return ObjectKey{Namespace: accessor.GetNamespace(), Name: accessor.GetName()}, nil +} + // TODO(directxman12): is there a sane way to deal with get/delete options? // Reader knows how to read and list Kubernetes objects. @@ -50,7 +60,7 @@ type Writer interface { Create(ctx context.Context, obj runtime.Object) error // Delete deletes the given obj from Kubernetes cluster. - Delete(ctx context.Context, obj runtime.Object) error + Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error // Update updates the given obj in the Kubernetes cluster. obj must be a // struct pointer so that obj can be updated with the content returned by the Server. @@ -93,6 +103,88 @@ type FieldIndexer interface { IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error } +// DeleteOptions contains options for delete requests. It's generally a subset +// of metav1.DeleteOptions. +type DeleteOptions struct { + // GracePeriodSeconds is the duration in seconds before the object should be + // deleted. Value must be non-negative integer. The value zero indicates + // delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + GracePeriodSeconds *int64 + + // Preconditions must be fulfilled before a deletion is carried out. If not + // possible, a 409 Conflict status will be returned. + Preconditions *metav1.Preconditions + + // PropagationPolicy determined whether and how garbage collection will be + // performed. Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + PropagationPolicy *metav1.DeletionPropagation + + // Raw represents raw DeleteOptions, as passed to the API server. + Raw *metav1.DeleteOptions +} + +// AsDeleteOptions returns these options as a metav1.DeleteOptions. +// This may mutate the Raw field. +func (o *DeleteOptions) AsDeleteOptions() *metav1.DeleteOptions { + + if o == nil { + return &metav1.DeleteOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.DeleteOptions{} + } + + o.Raw.GracePeriodSeconds = o.GracePeriodSeconds + o.Raw.Preconditions = o.Preconditions + o.Raw.PropagationPolicy = o.PropagationPolicy + return o.Raw +} + +// ApplyOptions executes the given DeleteOptionFuncs and returns the mutated +// DeleteOptions. +func (o *DeleteOptions) ApplyOptions(optFuncs []DeleteOptionFunc) *DeleteOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// DeleteOptionFunc is a function that mutates a DeleteOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type DeleteOptionFunc func(*DeleteOptions) + +// GracePeriodSeconds is a functional option that sets the GracePeriodSeconds +// field of a DeleteOptions struct. +func GracePeriodSeconds(gp int64) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.GracePeriodSeconds = &gp + } +} + +// Preconditions is a functional option that sets the Preconditions field of a +// DeleteOptions struct. +func Preconditions(p *metav1.Preconditions) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.Preconditions = p + } +} + +// PropagationPolicy is a functional option that sets the PropagationPolicy +// field of a DeleteOptions struct. +func PropagationPolicy(p metav1.DeletionPropagation) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.PropagationPolicy = &p + } +} + // ListOptions contains options for limitting or filtering results. // It's generally a subset of metav1.ListOptions, with support for // pre-parsed selectors (since generally, selectors will be executed diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go index 24e49071b..efcf6d4c3 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go @@ -16,6 +16,13 @@ limitations under the License. package client +import ( + "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + // DelegatingClient forms an interface Client by composing separate // reader, writer and statusclient interfaces. This way, you can have an Client that // reads from a cache and writes to the API server. @@ -24,3 +31,29 @@ type DelegatingClient struct { Writer StatusClient } + +// DelegatingReader forms a interface Reader that will cause Get and List +// requests for unstructured types to use the ClientReader while +// requests for any other type of object with use the CacheReader. +type DelegatingReader struct { + CacheReader Reader + ClientReader Reader +} + +// Get retrieves an obj for a given object key from the Kubernetes Cluster. +func (d *DelegatingReader) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error { + _, isUnstructured := obj.(*unstructured.Unstructured) + if isUnstructured { + return d.ClientReader.Get(ctx, key, obj) + } + return d.CacheReader.Get(ctx, key, obj) +} + +// List retrieves list of objects for a given namespace and list options. +func (d *DelegatingReader) List(ctx context.Context, opts *ListOptions, list runtime.Object) error { + _, isUnstructured := list.(*unstructured.UnstructuredList) + if isUnstructured { + return d.ClientReader.List(ctx, opts, list) + } + return d.CacheReader.List(ctx, opts, list) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go new file mode 100644 index 000000000..63c232fa9 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go @@ -0,0 +1,127 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" +) + +// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes +// new clients at the time they are used, and caches the client. +type typedClient struct { + cache clientCache + paramCodec runtime.ParameterCodec +} + +// Create implements client.Client +func (c *typedClient) Create(_ context.Context, obj runtime.Object) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + return o.Post(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Body(obj). + Do(). + Into(obj) +} + +// Update implements client.Client +func (c *typedClient) Update(_ context.Context, obj runtime.Object) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + return o.Put(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + Body(obj). + Do(). + Into(obj) +} + +// Delete implements client.Client +func (c *typedClient) Delete(_ context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + + deleteOpts := DeleteOptions{} + return o.Delete(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + Body(deleteOpts.ApplyOptions(opts).AsDeleteOptions()). + Do(). + Error() +} + +// Get implements client.Client +func (c *typedClient) Get(_ context.Context, key ObjectKey, obj runtime.Object) error { + r, err := c.cache.getResource(obj) + if err != nil { + return err + } + return r.Get(). + NamespaceIfScoped(key.Namespace, r.isNamespaced()). + Resource(r.resource()). + Name(key.Name).Do().Into(obj) +} + +// List implements client.Client +func (c *typedClient) List(_ context.Context, opts *ListOptions, obj runtime.Object) error { + r, err := c.cache.getResource(obj) + if err != nil { + return err + } + namespace := "" + if opts != nil { + namespace = opts.Namespace + } + return r.Get(). + NamespaceIfScoped(namespace, r.isNamespaced()). + Resource(r.resource()). + Body(obj). + VersionedParams(opts.AsListOptions(), c.paramCodec). + Do(). + Into(obj) +} + +// UpdateStatus used by StatusWriter to write status. +func (c *typedClient) UpdateStatus(_ context.Context, obj runtime.Object) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + // TODO(droot): examine the returned error and check if it error needs to be + // wrapped to improve the UX ? + // It will be nice to receive an error saying the object doesn't implement + // status subresource and check CRD definition + return o.Put(). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource("status"). + Body(obj). + Do(). + Into(obj) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go new file mode 100644 index 000000000..13217b07d --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go @@ -0,0 +1,162 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" +) + +// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes +// new clients at the time they are used, and caches the client. +type unstructuredClient struct { + client dynamic.Interface + restMapper meta.RESTMapper +} + +// Create implements client.Client +func (uc *unstructuredClient) Create(_ context.Context, obj runtime.Object) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace()) + if err != nil { + return err + } + i, err := r.Create(u) + if err != nil { + return err + } + u.Object = i.Object + return nil +} + +// Update implements client.Client +func (uc *unstructuredClient) Update(_ context.Context, obj runtime.Object) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace()) + if err != nil { + return err + } + i, err := r.Update(u) + if err != nil { + return err + } + u.Object = i.Object + return nil +} + +// Delete implements client.Client +func (uc *unstructuredClient) Delete(_ context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace()) + if err != nil { + return err + } + deleteOpts := DeleteOptions{} + err = r.Delete(u.GetName(), deleteOpts.ApplyOptions(opts).AsDeleteOptions()) + return err +} + +// Get implements client.Client +func (uc *unstructuredClient) Get(_ context.Context, key ObjectKey, obj runtime.Object) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), key.Namespace) + if err != nil { + return err + } + i, err := r.Get(key.Name, metav1.GetOptions{}) + if err != nil { + return err + } + u.Object = i.Object + return nil +} + +// List implements client.Client +func (uc *unstructuredClient) List(_ context.Context, opts *ListOptions, obj runtime.Object) error { + u, ok := obj.(*unstructured.UnstructuredList) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + gvk := u.GroupVersionKind() + if strings.HasSuffix(gvk.Kind, "List") { + gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] + } + namespace := "" + if opts != nil { + namespace = opts.Namespace + } + r, err := uc.getResourceInterface(gvk, namespace) + if err != nil { + return err + } + + i, err := r.List(*opts.AsListOptions()) + if err != nil { + return err + } + u.Items = i.Items + u.Object = i.Object + return nil +} + +func (uc *unstructuredClient) UpdateStatus(_ context.Context, obj runtime.Object) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace()) + if err != nil { + return err + } + i, err := r.UpdateStatus(u) + if err != nil { + return err + } + u.Object = i.Object + return nil +} + +func (uc *unstructuredClient) getResourceInterface(gvk schema.GroupVersionKind, ns string) (dynamic.ResourceInterface, error) { + mapping, err := uc.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + if mapping.Scope.Name() == meta.RESTScopeNameRoot { + return uc.client.Resource(mapping.Resource), nil + } + return uc.client.Resource(mapping.Resource).Namespace(ns), nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go index 0c7a5e390..d918eeaa4 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go @@ -17,11 +17,15 @@ limitations under the License. package controllerutil import ( + "context" "fmt" + "reflect" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) @@ -97,3 +101,78 @@ func referSameObject(a, b v1.OwnerReference) bool { return aGV == bGV && a.Kind == b.Kind && a.Name == b.Name } + +// OperationResult is the action result of a CreateOrUpdate call +type OperationResult string + +const ( // They should complete the sentence "Deployment default/foo has been ..." + // OperationResultNone means that the resource has not been changed + OperationResultNone OperationResult = "unchanged" + // OperationResultCreated means that a new resource is created + OperationResultCreated OperationResult = "created" + // OperationResultUpdated means that an existing resource is updated + OperationResultUpdated OperationResult = "updated" +) + +// CreateOrUpdate creates or updates the given object obj in the Kubernetes +// cluster. The object's desired state should be reconciled with the existing +// state using the passed in ReconcileFn. obj must be a struct pointer so that +// obj can be updated with the content returned by the Server. +// +// It returns the executed operation and an error. +func CreateOrUpdate(ctx context.Context, c client.Client, obj runtime.Object, f MutateFn) (OperationResult, error) { + // op is the operation we are going to attempt + op := OperationResultNone + + // get the existing object meta + metaObj, ok := obj.(v1.Object) + if !ok { + return OperationResultNone, fmt.Errorf("%T does not implement metav1.Object interface", obj) + } + + // retrieve the existing object + key := client.ObjectKey{ + Name: metaObj.GetName(), + Namespace: metaObj.GetNamespace(), + } + err := c.Get(ctx, key, obj) + + // reconcile the existing object + existing := obj.DeepCopyObject() + existingObjMeta := existing.(v1.Object) + existingObjMeta.SetName(metaObj.GetName()) + existingObjMeta.SetNamespace(metaObj.GetNamespace()) + + if e := f(obj); e != nil { + return OperationResultNone, e + } + + if metaObj.GetName() != existingObjMeta.GetName() { + return OperationResultNone, fmt.Errorf("ReconcileFn cannot mutate objects name") + } + + if metaObj.GetNamespace() != existingObjMeta.GetNamespace() { + return OperationResultNone, fmt.Errorf("ReconcileFn cannot mutate objects namespace") + } + + if errors.IsNotFound(err) { + err = c.Create(ctx, obj) + op = OperationResultCreated + } else if err == nil { + if reflect.DeepEqual(existing, obj) { + return OperationResultNone, nil + } + err = c.Update(ctx, obj) + op = OperationResultUpdated + } else { + return OperationResultNone, err + } + + if err != nil { + op = OperationResultNone + } + return op, err +} + +// MutateFn is a function which mutates the existing object into it's desired state. +type MutateFn func(existing runtime.Object) error diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/doc.go index 951a33e86..148b992fe 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/doc.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/doc.go @@ -49,6 +49,19 @@ system must be read for each Reconciler. * Controller require Watches to be configured to enqueue reconcile.Requests in response to events. +Webhook + +Admission Webhooks are a mechanism for extending kubernetes APIs. Webhooks can be configured with target +event type (object Create, Update, Delete), the API server will send AdmissionRequests to them +when certain events happen. The webhooks may mutate and (or) validate the object embedded in +the AdmissionReview requests and send back the response to the API server. + +There are 2 types of admission webhook: mutating and validating admission webhook. +Mutating webhook is used to mutate a core API object or a CRD instance before the API server admits it. +Validating webhook is used to validate if an object meets certain requirements. + +* Admission Webhooks require Handler(s) to be provided to process the received AdmissionReview requests. + Reconciler Reconciler is a function provided to a Controller that may be called at anytime with the Name and Namespace of an object. diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/internal/controller/controller.go b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/controller/controller.go index 522f5c23c..17846a1ab 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/internal/controller/controller.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/controller/controller.go @@ -209,6 +209,9 @@ func (c *Controller) processNextWorkItem() bool { log.Error(err, "Reconciler error", "Controller", c.Name, "Request", req) return false + } else if result.RequeueAfter > 0 { + c.Queue.AddAfter(req, result.RequeueAfter) + return true } else if result.Requeue { c.Queue.AddRateLimited(req) return true diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/doc.go new file mode 100644 index 000000000..3f0f52a9b --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package leaderelection contains a constructors for a leader election resource lock +*/ +package leaderelection diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/doc.go new file mode 100644 index 000000000..3fff7c718 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package fake mocks a resource lock for testing purposes. +Always returns leadership. +*/ +package fake diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/leader_election.go b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/leader_election.go new file mode 100644 index 000000000..b0df56d8e --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/fake/leader_election.go @@ -0,0 +1,90 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "os" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "sigs.k8s.io/controller-runtime/pkg/leaderelection" + "sigs.k8s.io/controller-runtime/pkg/recorder" +) + +// NewResourceLock creates a new ResourceLock for use in testing +// leader election. +func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) { + // Leader id, needs to be unique + id, err := os.Hostname() + if err != nil { + return nil, err + } + id = id + "_" + string(uuid.NewUUID()) + + return &ResourceLock{ + id: id, + record: resourcelock.LeaderElectionRecord{ + HolderIdentity: id, + LeaseDurationSeconds: 15, + AcquireTime: metav1.NewTime(time.Now()), + RenewTime: metav1.NewTime(time.Now().Add(15 * time.Second)), + LeaderTransitions: 1, + }, + }, nil +} + +// ResourceLock implements the ResourceLockInterface. +// By default returns that the current identity holds the lock. +type ResourceLock struct { + id string + record resourcelock.LeaderElectionRecord +} + +// Get implements the ResourceLockInterface. +func (f *ResourceLock) Get() (*resourcelock.LeaderElectionRecord, error) { + return &f.record, nil +} + +// Create implements the ResourceLockInterface. +func (f *ResourceLock) Create(ler resourcelock.LeaderElectionRecord) error { + f.record = ler + return nil +} + +// Update implements the ResourceLockInterface. +func (f *ResourceLock) Update(ler resourcelock.LeaderElectionRecord) error { + f.record = ler + return nil +} + +// RecordEvent implements the ResourceLockInterface. +func (f *ResourceLock) RecordEvent(s string) { + return +} + +// Identity implements the ResourceLockInterface. +func (f *ResourceLock) Identity() string { + return f.id +} + +// Describe implements the ResourceLockInterface. +func (f *ResourceLock) Describe() string { + return f.id +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/leader_election.go b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/leader_election.go new file mode 100644 index 000000000..fb8019abe --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/leaderelection/leader_election.go @@ -0,0 +1,109 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package leaderelection + +import ( + "fmt" + "io/ioutil" + "os" + + "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "sigs.k8s.io/controller-runtime/pkg/recorder" +) + +const inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + +// Options provides the required configuration to create a new resource lock +type Options struct { + // LeaderElection determines whether or not to use leader election when + // starting the manager. + LeaderElection bool + + // LeaderElectionNamespace determines the namespace in which the leader + // election configmap will be created. + LeaderElectionNamespace string + + // LeaderElectionID determines the name of the configmap that leader election + // will use for holding the leader lock. + LeaderElectionID string +} + +// NewResourceLock creates a new config map resource lock for use in a leader +// election loop +func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options Options) (resourcelock.Interface, error) { + if !options.LeaderElection { + return nil, nil + } + + // Default the LeaderElectionID + if options.LeaderElectionID == "" { + options.LeaderElectionID = "controller-leader-election-helper" + } + + // Default the namespace (if running in cluster) + if options.LeaderElectionNamespace == "" { + var err error + options.LeaderElectionNamespace, err = getInClusterNamespace() + if err != nil { + return nil, fmt.Errorf("unable to find leader election namespace: %v", err) + } + } + + // Leader id, needs to be unique + id, err := os.Hostname() + if err != nil { + return nil, err + } + id = id + "_" + string(uuid.NewUUID()) + + // Construct client for leader election + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + // TODO(JoelSpeed): switch to leaderelection object in 1.12 + return resourcelock.New(resourcelock.ConfigMapsResourceLock, + options.LeaderElectionNamespace, + options.LeaderElectionID, + client.CoreV1(), + resourcelock.ResourceLockConfig{ + Identity: id, + EventRecorder: recorderProvider.GetEventRecorderFor(id), + }) +} + +func getInClusterNamespace() (string, error) { + // Check whether the namespace file exists. + // If not, we are not running in cluster so can't guess the namespace. + _, err := os.Stat(inClusterNamespacePath) + if os.IsNotExist(err) { + return "", fmt.Errorf("not running in-cluster, please specify LeaderElectionNamespace") + } else if err != nil { + return "", fmt.Errorf("error checking namespace file: %v", err) + } + + // Load the namespace file and return itss content + namespace, err := ioutil.ReadFile(inClusterNamespacePath) + if err != nil { + return "", fmt.Errorf("error reading namespace file: %v", err) + } + return string(namespace), nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/internal.go b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/internal.go index 38c70748c..87108df30 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/internal.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/internal.go @@ -17,16 +17,22 @@ limitations under the License. package manager import ( + "fmt" "sync" + "time" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/recorder" "sigs.k8s.io/controller-runtime/pkg/runtime/inject" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" ) var log = logf.KBLog.WithName("manager") @@ -38,6 +44,8 @@ type controllerManager struct { // scheme is the scheme injected into Controllers, EventHandlers, Sources and Predicates. Defaults // to scheme.scheme. scheme *runtime.Scheme + // admissionDecoder is used to decode an admission.Request. + admissionDecoder types.Decoder // runnables is the set of Controllers that the controllerManager injects deps into and Starts. runnables []Runnable @@ -56,6 +64,12 @@ type controllerManager struct { // (and EventHandlers, Sources and Predicates). recorderProvider recorder.Provider + // resourceLock + resourceLock resourcelock.Interface + + // mapper is used to map resources to kind, and map kind and version. + mapper meta.RESTMapper + mu sync.Mutex started bool errChan chan error @@ -105,6 +119,9 @@ func (cm *controllerManager) SetFields(i interface{}) error { if _, err := inject.StopChannelInto(cm.stop, i); err != nil { return err } + if _, err := inject.DecoderInto(cm.admissionDecoder, i); err != nil { + return err + } return nil } @@ -120,6 +137,10 @@ func (cm *controllerManager) GetScheme() *runtime.Scheme { return cm.scheme } +func (cm *controllerManager) GetAdmissionDecoder() types.Decoder { + return cm.admissionDecoder +} + func (cm *controllerManager) GetFieldIndexer() client.FieldIndexer { return cm.fieldIndexes } @@ -132,7 +153,57 @@ func (cm *controllerManager) GetRecorder(name string) record.EventRecorder { return cm.recorderProvider.GetEventRecorderFor(name) } +func (cm *controllerManager) GetRESTMapper() meta.RESTMapper { + return cm.mapper +} + func (cm *controllerManager) Start(stop <-chan struct{}) error { + if cm.resourceLock == nil { + go cm.start(stop) + select { + case <-stop: + // we are done + return nil + case err := <-cm.errChan: + // Error starting a controller + return err + } + } + + l, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{ + Lock: cm.resourceLock, + // Values taken from: https://github.com/kubernetes/apiserver/blob/master/pkg/apis/config/v1alpha1/defaults.go + // TODO(joelspeed): These timings should be configurable + LeaseDuration: 15 * time.Second, + RenewDeadline: 10 * time.Second, + RetryPeriod: 2 * time.Second, + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: cm.start, + OnStoppedLeading: func() { + // Most implementations of leader election log.Fatal() here. + // Since Start is wrapped in log.Fatal when called, we can just return + // an error here which will cause the program to exit. + cm.errChan <- fmt.Errorf("leader election lost") + }, + }, + }) + if err != nil { + return err + } + + go l.Run() + + select { + case <-stop: + // We are done + return nil + case err := <-cm.errChan: + // Error starting a controller + return err + } +} + +func (cm *controllerManager) start(stop <-chan struct{}) { func() { cm.mu.Lock() defer cm.mu.Unlock() @@ -169,9 +240,6 @@ func (cm *controllerManager) Start(stop <-chan struct{}) error { select { case <-stop: // We are done - return nil - case err := <-cm.errChan: - // Error starting a controller - return err + return } } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go index cc34ce419..be83eb973 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/manager/manager.go @@ -21,16 +21,21 @@ import ( "time" "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" internalrecorder "sigs.k8s.io/controller-runtime/pkg/internal/recorder" + "sigs.k8s.io/controller-runtime/pkg/leaderelection" "sigs.k8s.io/controller-runtime/pkg/recorder" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" ) // Manager initializes shared dependencies such as Caches and Clients, and provides them to Runnables. @@ -55,6 +60,9 @@ type Manager interface { // GetScheme returns and initialized Scheme GetScheme() *runtime.Scheme + // GetAdmissionDecoder returns the runtime.Decoder based on the scheme. + GetAdmissionDecoder() types.Decoder + // GetClient returns a client configured with the Config GetClient() client.Client @@ -66,6 +74,9 @@ type Manager interface { // GetRecorder returns a new EventRecorder for the provided name GetRecorder(name string) record.EventRecorder + + // GetRESTMapper returns a RESTMapper + GetRESTMapper() meta.RESTMapper } // Options are the arguments for creating a new Manager @@ -83,10 +94,30 @@ type Options struct { // value only if you know what you are doing. Defaults to 10 hours if unset. SyncPeriod *time.Duration + // LeaderElection determines whether or not to use leader election when + // starting the manager. + LeaderElection bool + + // LeaderElectionNamespace determines the namespace in which the leader + // election configmap will be created. + LeaderElectionNamespace string + + // LeaderElectionID determines the name of the configmap that leader election + // will use for holding the leader lock. + LeaderElectionID string + + // Namespace if specified restricts the manager's cache to watch objects in the desired namespace + // Defaults to all namespaces + // Note: If a namespace is specified then controllers can still Watch for a cluster-scoped resource e.g Node + // For namespaced resources the cache will only hold objects from the desired namespace. + Namespace string + // Dependency injection for testing newCache func(config *rest.Config, opts cache.Options) (cache.Cache, error) newClient func(config *rest.Config, options client.Options) (client.Client, error) newRecorderProvider func(config *rest.Config, scheme *runtime.Scheme, logger logr.Logger) (recorder.Provider, error) + newResourceLock func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error) + newAdmissionDecoder func(scheme *runtime.Scheme) (types.Decoder, error) } // Runnable allows a component to be started. @@ -128,7 +159,7 @@ func New(config *rest.Config, options Options) (Manager, error) { } // Create the cache for the cached read client and registering informers - cache, err := options.newCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod}) + cache, err := options.newCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod, Namespace: options.Namespace}) if err != nil { return nil, err } @@ -140,14 +171,39 @@ func New(config *rest.Config, options Options) (Manager, error) { return nil, err } + // Create the resource lock to enable leader election) + resourceLock, err := options.newResourceLock(config, recorderProvider, leaderelection.Options{ + LeaderElection: options.LeaderElection, + LeaderElectionID: options.LeaderElectionID, + LeaderElectionNamespace: options.LeaderElectionNamespace, + }) + if err != nil { + return nil, err + } + + admissionDecoder, err := options.newAdmissionDecoder(options.Scheme) + if err != nil { + return nil, err + } + return &controllerManager{ config: config, scheme: options.Scheme, + admissionDecoder: admissionDecoder, errChan: make(chan error), cache: cache, fieldIndexes: cache, - client: client.DelegatingClient{Reader: cache, Writer: writeObj, StatusClient: writeObj}, + client: client.DelegatingClient{ + Reader: &client.DelegatingReader{ + CacheReader: cache, + ClientReader: writeObj, + }, + Writer: writeObj, + StatusClient: writeObj, + }, recorderProvider: recorderProvider, + resourceLock: resourceLock, + mapper: mapper, }, nil } @@ -177,5 +233,14 @@ func setOptionsDefaults(options Options) Options { options.newRecorderProvider = internalrecorder.NewProvider } + // Allow newResourceLock to be mocked + if options.newResourceLock == nil { + options.newResourceLock = leaderelection.NewResourceLock + } + + if options.newAdmissionDecoder == nil { + options.newAdmissionDecoder = admission.NewDecoder + } + return options } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/patch/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/patch/doc.go new file mode 100644 index 000000000..0e1d2a9f5 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/patch/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package patch provides method to calculate JSON patch between 2 k8s objects. + +Calculate JSON patch + + oldDeployment := appsv1.Deployment{ + // some fields + } + newDeployment := appsv1.Deployment{ + // some different fields + } + patch, err := NewJSONPatch(oldDeployment, newDeployment) + if err != nil { + // handle error + } +*/ +package patch diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/patch/patch.go b/vendor/sigs.k8s.io/controller-runtime/pkg/patch/patch.go new file mode 100644 index 000000000..c3153f08a --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/patch/patch.go @@ -0,0 +1,45 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package patch + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/mattbaird/jsonpatch" + + "k8s.io/apimachinery/pkg/runtime" +) + +// NewJSONPatch calculates the JSON patch between original and current objects. +func NewJSONPatch(original, current runtime.Object) ([]jsonpatch.JsonPatchOperation, error) { + originalGVK := original.GetObjectKind().GroupVersionKind() + currentGVK := current.GetObjectKind().GroupVersionKind() + if !reflect.DeepEqual(originalGVK, currentGVK) { + return nil, fmt.Errorf("GroupVersionKind %#v is expected to match %#v", originalGVK, currentGVK) + } + ori, err := json.Marshal(original) + if err != nil { + return nil, err + } + cur, err := json.Marshal(current) + if err != nil { + return nil, err + } + return jsonpatch.CreatePatch(ori, cur) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/reconcile/reconcile.go b/vendor/sigs.k8s.io/controller-runtime/pkg/reconcile/reconcile.go index cce7ba2cc..fec8e3020 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/reconcile/reconcile.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/reconcile/reconcile.go @@ -17,6 +17,8 @@ limitations under the License. package reconcile import ( + "time" + "k8s.io/apimachinery/pkg/types" ) @@ -24,6 +26,9 @@ import ( type Result struct { // Requeue tells the Controller to requeue the reconcile key. Defaults to false. Requeue bool + + // RequeueAfter if greater than 0, tells the Controller to requeue the reconcile key after the Duration. + RequeueAfter time.Duration } // Request contains the information necessary to reconcile a Kubernetes object. This includes the diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/runtime/inject/inject.go b/vendor/sigs.k8s.io/controller-runtime/pkg/runtime/inject/inject.go index c04814001..da7f1da45 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/runtime/inject/inject.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/runtime/inject/inject.go @@ -21,6 +21,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" ) // Cache is used by the ControllerManager to inject Cache into Sources, EventHandlers, Predicates, and @@ -59,8 +60,8 @@ type Client interface { InjectClient(client.Client) error } -// ClientInto will set client on i and return the result if it implements client. Returns -//// false if i does not implement client. +// ClientInto will set client on i and return the result if it implements Client. Returns +// false if i does not implement Client. func ClientInto(client client.Client, i interface{}) (bool, error) { if s, ok := i.(Client); ok { return true, s.InjectClient(client) @@ -68,6 +69,20 @@ func ClientInto(client client.Client, i interface{}) (bool, error) { return false, nil } +// Decoder is used by the ControllerManager to inject decoder into webhook handlers. +type Decoder interface { + InjectDecoder(types.Decoder) error +} + +// DecoderInto will set decoder on i and return the result if it implements Decoder. Returns +// false if i does not implement Decoder. +func DecoderInto(decoder types.Decoder, i interface{}) (bool, error) { + if s, ok := i.(Decoder); ok { + return true, s.InjectDecoder(decoder) + } + return false, nil +} + // Scheme is used by the ControllerManager to inject Scheme into Sources, EventHandlers, Predicates, and // Reconciles type Scheme interface { diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/builder.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/builder.go new file mode 100644 index 000000000..26f91ceba --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/builder.go @@ -0,0 +1,223 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package builder + +import ( + "errors" + "fmt" + + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/types" +) + +// WebhookBuilder builds a webhook based on the provided options. +type WebhookBuilder struct { + // name specifies the name of the webhook. It must be unique among all webhooks. + name string + + // path is the URL Path to register this webhook. e.g. "/mutate-pods". + path string + + // handlers handle admission requests. + // A WebhookBuilder may have multiple handlers. + // For example, handlers[0] mutates a pod for feature foo. + // handlers[1] mutates a pod for a different feature bar. + handlers []admission.Handler + + // t specifies the type of the webhook. + // Currently, Mutating and Validating are supported. + t *types.WebhookType + + // operations define the operations this webhook cares. + // only one of operations and Rules can be set. + operations []admissionregistrationv1beta1.OperationType + // apiType represents the resource that this webhook cares. + // Only one of apiType and Rules can be set. + apiType runtime.Object + // rules contain a list of admissionregistrationv1beta1.RuleWithOperations + // It overrides operations and apiType. + rules []admissionregistrationv1beta1.RuleWithOperations + + // failurePolicy maps to the FailurePolicy in the admissionregistrationv1beta1.Webhook + failurePolicy *admissionregistrationv1beta1.FailurePolicyType + + // namespaceSelector maps to the NamespaceSelector in the admissionregistrationv1beta1.Webhook + namespaceSelector *metav1.LabelSelector + + // manager is the manager for the webhook. + // It is used for provisioning various dependencies for the webhook. e.g. RESTMapper. + manager manager.Manager +} + +// NewWebhookBuilder creates an empty WebhookBuilder. +func NewWebhookBuilder() *WebhookBuilder { + return &WebhookBuilder{} +} + +// Name sets the name of the webhook. +// This is optional +func (b *WebhookBuilder) Name(name string) *WebhookBuilder { + b.name = name + return b +} + +// Mutating sets the type to mutating admission webhook +// Only one of Mutating and Validating can be invoked. +func (b *WebhookBuilder) Mutating() *WebhookBuilder { + m := types.WebhookTypeMutating + b.t = &m + return b +} + +// Validating sets the type to validating admission webhook +// Only one of Mutating and Validating can be invoked. +func (b *WebhookBuilder) Validating() *WebhookBuilder { + m := types.WebhookTypeValidating + b.t = &m + return b +} + +// Path sets the path for the webhook. +// Path needs to be unique among different webhooks. +// This is optional. If not set, it will be built from the type and resource name. +// For example, a webhook that mutates pods has a default path of "/mutate-pods" +// If the defaulting logic can't find a unique path for it, user need to set it manually. +func (b *WebhookBuilder) Path(path string) *WebhookBuilder { + b.path = path + return b +} + +// Operations sets the operations that this webhook cares. +// It will be overridden by Rules if Rules are not empty. +// This is optional +func (b *WebhookBuilder) Operations(ops ...admissionregistrationv1beta1.OperationType) *WebhookBuilder { + b.operations = ops + return b +} + +// ForType sets the type of resources that the webhook will operate. +// It will be overridden by Rules if Rules are not empty. +func (b *WebhookBuilder) ForType(obj runtime.Object) *WebhookBuilder { + b.apiType = obj + return b +} + +// Rules sets the RuleWithOperations for the webhook. +// It overrides ForType and Operations. +// This is optional and for advanced user. +func (b *WebhookBuilder) Rules(rules ...admissionregistrationv1beta1.RuleWithOperations) *WebhookBuilder { + b.rules = rules + return b +} + +// FailurePolicy sets the FailurePolicy of the webhook. +// If not set, it will be defaulted by the server. +// This is optional +func (b *WebhookBuilder) FailurePolicy(policy admissionregistrationv1beta1.FailurePolicyType) *WebhookBuilder { + b.failurePolicy = &policy + return b +} + +// NamespaceSelector sets the NamespaceSelector for the webhook. +// This is optional +func (b *WebhookBuilder) NamespaceSelector(namespaceSelector *metav1.LabelSelector) *WebhookBuilder { + b.namespaceSelector = namespaceSelector + return b +} + +// WithManager set the manager for the webhook for provisioning various dependencies. e.g. client etc. +func (b *WebhookBuilder) WithManager(mgr manager.Manager) *WebhookBuilder { + b.manager = mgr + return b +} + +// Handlers sets the handlers of the webhook. +func (b *WebhookBuilder) Handlers(handlers ...admission.Handler) *WebhookBuilder { + b.handlers = handlers + return b +} + +func (b *WebhookBuilder) validate() error { + if b.t == nil { + return errors.New("webhook type cannot be nil") + } + if b.rules == nil && b.apiType == nil { + return fmt.Errorf("ForType should be set") + } + if b.rules != nil && b.apiType != nil { + return fmt.Errorf("at most one of ForType and Rules can be set") + } + return nil +} + +// Build creates the Webhook based on the options provided. +func (b *WebhookBuilder) Build() (*admission.Webhook, error) { + err := b.validate() + if err != nil { + return nil, err + } + + w := &admission.Webhook{ + Name: b.name, + Type: *b.t, + Path: b.path, + FailurePolicy: b.failurePolicy, + NamespaceSelector: b.namespaceSelector, + Handlers: b.handlers, + } + + if b.rules != nil { + w.Rules = b.rules + } else { + if b.manager == nil { + return nil, errors.New("manager should be set using WithManager") + } + gvk, err := apiutil.GVKForObject(b.apiType, b.manager.GetScheme()) + if err != nil { + return nil, err + } + mapper := b.manager.GetRESTMapper() + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, err + } + + if b.operations == nil { + b.operations = []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + } + } + w.Rules = []admissionregistrationv1beta1.RuleWithOperations{ + { + Operations: b.operations, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{gvk.Group}, + APIVersions: []string{gvk.Version}, + Resources: []string{mapping.Resource.Resource}, + }, + }, + } + } + + return w, nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/doc.go new file mode 100644 index 000000000..411016ee7 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder/doc.go @@ -0,0 +1,107 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package builder provides methods to build admission webhooks. + +The following are 2 examples for building mutating webhook and validating webhook. + + webhook1, err := NewWebhookBuilder(). + Mutating(). + Operations(admissionregistrationv1beta1.Create). + ForType(&corev1.Pod{}). + WithManager(mgr). + Handlers(mutatingHandler11, mutatingHandler12). + Build() + if err != nil { + // handle error + } + + webhook2, err := NewWebhookBuilder(). + Validating(). + Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update). + ForType(&appsv1.Deployment{}). + WithManager(mgr). + Handlers(validatingHandler21). + Build() + if err != nil { + // handle error + } + +Note: To build a webhook for a CRD, you need to ensure the manager uses the scheme that understands your CRD. +This is necessary, because if the scheme doesn't understand your CRD types, the decoder won't be able to decode +the CR object from the admission review request. + +The following snippet shows how to register CRD types with manager's scheme. + + mgr, err := manager.New(cfg, manager.Options{}) + if err != nil { + // handle error + } + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "crew.k8s.io", Version: "v1"} + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + // Register your CRD types. + SchemeBuilder.Register(&Kraken{}, &KrakenList{}) + // Register your CRD types with the manager's scheme. + err = SchemeBuilder.AddToScheme(mgr.GetScheme()) + if err != nil { + // handle error + } + +There are more options for configuring a webhook. e.g. Name, Path, FailurePolicy, NamespaceSelector. +Here is another example: + + webhook3, err := NewWebhookBuilder(). + Name("foo.example.com"). + Path("/mutatepods"). + Mutating(). + Operations(admissionregistrationv1beta1.Create). + ForType(&corev1.Pod{}). + FailurePolicy(admissionregistrationv1beta1.Fail). + WithManager(mgr). + Handlers(mutatingHandler31, mutatingHandler32). + Build() + if err != nil { + // handle error + } + +For most users, we recommend to use Operations and ForType instead of Rules to construct a webhook, +since it is more intuitive and easier to pass the target operations to Operations method and +a empty target object to ForType method than passing a complex RuleWithOperations struct to Rules method. + +Rules may be useful for some more advanced use cases like subresources, wildcard resources etc. +Here is an example: + + webhook4, err := NewWebhookBuilder(). + Validating(). + Rules(admissionregistrationv1beta1.RuleWithOperations{ + Operations: []admissionregistrationv1beta1.OperationType{admissionregistrationv1beta1.Create}, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{"apps", "batch"}, + APIVersions: []string{"v1"}, + Resources: []string{"*"}, + }, + }). + WithManager(mgr). + Handlers(validatingHandler41). + Build() + if err != nil { + // handle error + } +*/ +package builder diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/decode.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/decode.go new file mode 100644 index 000000000..1aea214cf --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/decode.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package admission + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +// DecodeFunc is a function that implements the Decoder interface. +type DecodeFunc func(types.Request, runtime.Object) error + +var _ types.Decoder = DecodeFunc(nil) + +// Decode implements the Decoder interface. +func (f DecodeFunc) Decode(req types.Request, obj runtime.Object) error { + return f(req, obj) +} + +type decoder struct { + codecs serializer.CodecFactory +} + +// NewDecoder creates a Decoder given the runtime.Scheme +func NewDecoder(scheme *runtime.Scheme) (types.Decoder, error) { + return decoder{codecs: serializer.NewCodecFactory(scheme)}, nil +} + +// Decode decodes the inlined object in the AdmissionRequest into the passed-in runtime.Object. +func (d decoder) Decode(req types.Request, into runtime.Object) error { + deserializer := d.codecs.UniversalDeserializer() + return runtime.DecodeInto(deserializer, req.AdmissionRequest.Object.Raw, into) +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/doc.go new file mode 100644 index 000000000..44a2b1529 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/doc.go @@ -0,0 +1,101 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package admission provides implementation for admission webhook and methods to implement admission webhook handlers. + +The following snippet is an example implementation of mutating handler. + + type Mutator struct { + client client.Client + decoder types.Decoder + } + + func (m *Mutator) mutatePodsFn(ctx context.Context, pod *corev1.Pod) error { + // your logic to mutate the passed-in pod. + } + + func (m *Mutator) Handle(ctx context.Context, req types.Request) types.Response { + pod := &corev1.Pod{} + err := m.decoder.Decode(req, pod) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + // Do deepcopy before actually mutate the object. + copy := pod.DeepCopy() + err = m.mutatePodsFn(ctx, copy) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.PatchResponse(pod, copy) + } + + // InjectClient is called by the Manager and provides a client.Client to the Mutator instance. + func (m *Mutator) InjectClient(c client.Client) error { + h.client = c + return nil + } + + // InjectDecoder is called by the Manager and provides a types.Decoder to the Mutator instance. + func (m *Mutator) InjectDecoder(d types.Decoder) error { + h.decoder = d + return nil + } + +The following snippet is an example implementation of validating handler. + + type Handler struct { + client client.Client + decoder types.Decoder + } + + func (v *Validator) validatePodsFn(ctx context.Context, pod *corev1.Pod) (bool, string, error) { + // your business logic + } + + func (v *Validator) Handle(ctx context.Context, req types.Request) types.Response { + pod := &corev1.Pod{} + err := h.decoder.Decode(req, pod) + if err != nil { + return admission.ErrorResponse(http.StatusBadRequest, err) + } + + allowed, reason, err := h.validatePodsFn(ctx, pod) + if err != nil { + return admission.ErrorResponse(http.StatusInternalServerError, err) + } + return admission.ValidationResponse(allowed, reason) + } + + // InjectClient is called by the Manager and provides a client.Client to the Validator instance. + func (v *Validator) InjectClient(c client.Client) error { + h.client = c + return nil + } + + // InjectDecoder is called by the Manager and provides a types.Decoder to the Validator instance. + func (v *Validator) InjectDecoder(d types.Decoder) error { + h.decoder = d + return nil + } +*/ +package admission + +import ( + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" +) + +var log = logf.KBLog.WithName("admission") diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/http.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/http.go new file mode 100644 index 000000000..b85eebf81 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/http.go @@ -0,0 +1,102 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package admission + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + + "k8s.io/api/admission/v1beta1" + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +var admissionv1beta1scheme = runtime.NewScheme() +var admissionv1beta1schemecodecs = serializer.NewCodecFactory(admissionv1beta1scheme) + +func init() { + addToScheme(admissionv1beta1scheme) +} + +func addToScheme(scheme *runtime.Scheme) { + utilruntime.Must(admissionv1beta1.AddToScheme(scheme)) +} + +var _ http.Handler = &Webhook{} + +func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var body []byte + var err error + + var reviewResponse types.Response + if r.Body != nil { + if body, err = ioutil.ReadAll(r.Body); err != nil { + log.Error(err, "unable to read the body from the incoming request") + reviewResponse = ErrorResponse(http.StatusBadRequest, err) + writeResponse(w, reviewResponse) + return + } + } else { + err = errors.New("request body is empty") + log.Error(err, "bad request") + reviewResponse = ErrorResponse(http.StatusBadRequest, err) + writeResponse(w, reviewResponse) + return + } + + // verify the content type is accurate + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + err = fmt.Errorf("contentType=%s, expect application/json", contentType) + log.Error(err, "unable to process a request with an unknown content type") + reviewResponse = ErrorResponse(http.StatusBadRequest, err) + writeResponse(w, reviewResponse) + return + } + + ar := v1beta1.AdmissionReview{} + if _, _, err := admissionv1beta1schemecodecs.UniversalDeserializer().Decode(body, nil, &ar); err != nil { + log.Error(err, "unable to decode the request") + reviewResponse = ErrorResponse(http.StatusBadRequest, err) + writeResponse(w, reviewResponse) + return + } + + // TODO: add panic-recovery for Handle + reviewResponse = wh.Handle(context.Background(), types.Request{AdmissionRequest: ar.Request}) + writeResponse(w, reviewResponse) +} + +func writeResponse(w io.Writer, response types.Response) { + encoder := json.NewEncoder(w) + responseAdmissionReview := v1beta1.AdmissionReview{ + Response: response.Response, + } + err := encoder.Encode(responseAdmissionReview) + if err != nil { + log.Error(err, "unable to encode the response") + writeResponse(w, ErrorResponse(http.StatusInternalServerError, err)) + } +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/response.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/response.go new file mode 100644 index 000000000..bf7c903ba --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/response.go @@ -0,0 +1,70 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package admission + +import ( + "net/http" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/patch" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" +) + +// ErrorResponse creates a new Response for error-handling a request. +func ErrorResponse(code int32, err error) types.Response { + return types.Response{ + Response: &admissionv1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: code, + Message: err.Error(), + }, + }, + } +} + +// ValidationResponse returns a response for admitting a request. +func ValidationResponse(allowed bool, reason string) types.Response { + resp := types.Response{ + Response: &admissionv1beta1.AdmissionResponse{ + Allowed: allowed, + }, + } + if len(reason) > 0 { + resp.Response.Result = &metav1.Status{ + Reason: metav1.StatusReason(reason), + } + } + return resp +} + +// PatchResponse returns a new response with json patch. +func PatchResponse(original, current runtime.Object) types.Response { + patches, err := patch.NewJSONPatch(original, current) + if err != nil { + return ErrorResponse(http.StatusInternalServerError, err) + } + return types.Response{ + Patches: patches, + Response: &admissionv1beta1.AdmissionResponse{ + Allowed: true, + PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(), + }, + } +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/types/types.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/types/types.go new file mode 100644 index 000000000..19a808fa5 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/types/types.go @@ -0,0 +1,44 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "github.com/mattbaird/jsonpatch" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/runtime" +) + +// Request is the input of Handler +type Request struct { + AdmissionRequest *admissionv1beta1.AdmissionRequest +} + +// Response is the output of admission.Handler +type Response struct { + // Patches are the JSON patches for mutating webhooks. + // Using this instead of setting Response.Patch to minimize the overhead of serialization and deserialization. + Patches []jsonpatch.JsonPatchOperation + // Response is the admission response. Don't set the Patch field in it. + Response *admissionv1beta1.AdmissionResponse +} + +// Decoder is used to decode AdmissionRequest. +type Decoder interface { + // Decode decodes the raw byte object from the AdmissionRequest to the passed-in runtime.Object. + Decode(Request, runtime.Object) error +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/webhook.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/webhook.go new file mode 100644 index 000000000..0ae7a9417 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/webhook.go @@ -0,0 +1,239 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package admission + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "regexp" + "strings" + "sync" + + "github.com/mattbaird/jsonpatch" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + "sigs.k8s.io/controller-runtime/pkg/webhook/types" +) + +// Handler can handle an AdmissionRequest. +type Handler interface { + Handle(context.Context, atypes.Request) atypes.Response +} + +// HandlerFunc implements Handler interface using a single function. +type HandlerFunc func(context.Context, atypes.Request) atypes.Response + +var _ Handler = HandlerFunc(nil) + +// Handle process the AdmissionRequest by invoking the underlying function. +func (f HandlerFunc) Handle(ctx context.Context, req atypes.Request) atypes.Response { + return f(ctx, req) +} + +// Webhook represents each individual webhook. +type Webhook struct { + // Name is the name of the webhook + Name string + // Type is the webhook type, i.e. mutating, validating + Type types.WebhookType + // Path is the path this webhook will serve. + Path string + // Rules maps to the Rules field in admissionregistrationv1beta1.Webhook + Rules []admissionregistrationv1beta1.RuleWithOperations + // FailurePolicy maps to the FailurePolicy field in admissionregistrationv1beta1.Webhook + // This optional. If not set, will be defaulted to Ignore (fail-open) by the server. + // More details: https://github.com/kubernetes/api/blob/f5c295feaba2cbc946f0bbb8b535fc5f6a0345ee/admissionregistration/v1beta1/types.go#L144-L147 + FailurePolicy *admissionregistrationv1beta1.FailurePolicyType + // NamespaceSelector maps to the NamespaceSelector field in admissionregistrationv1beta1.Webhook + // This optional. + NamespaceSelector *metav1.LabelSelector + // Handlers contains a list of handlers. Each handler may only contains the business logic for its own feature. + // For example, feature foo and bar can be in the same webhook if all the other configurations are the same. + // The handler will be invoked sequentially as the order in the list. + // Note: if you are using mutating webhook with multiple handlers, it's your responsibility to + // ensure the handlers are not generating conflicting JSON patches. + Handlers []Handler + + once sync.Once +} + +func (w *Webhook) setDefaults() { + if len(w.Path) == 0 { + if len(w.Rules) == 0 || len(w.Rules[0].Resources) == 0 { + // can't do defaulting, skip it. + return + } + if w.Type == types.WebhookTypeMutating { + w.Path = "/mutate-" + w.Rules[0].Resources[0] + } else if w.Type == types.WebhookTypeValidating { + w.Path = "/validate-" + w.Rules[0].Resources[0] + } + } + if len(w.Name) == 0 { + reg := regexp.MustCompile("[^a-zA-Z0-9]+") + processedPath := strings.ToLower(reg.ReplaceAllString(w.Path, "")) + w.Name = processedPath + ".example.com" + } +} + +// Add adds additional handler(s) in the webhook +func (w *Webhook) Add(handlers ...Handler) { + w.Handlers = append(w.Handlers, handlers...) +} + +// Webhook implements Handler interface. +var _ Handler = &Webhook{} + +// Handle processes AdmissionRequest. +// If the webhook is mutating type, it delegates the AdmissionRequest to each handler and merge the patches. +// If the webhook is validating type, it delegates the AdmissionRequest to each handler and +// deny the request if anyone denies. +func (w *Webhook) Handle(ctx context.Context, req atypes.Request) atypes.Response { + if req.AdmissionRequest == nil { + return ErrorResponse(http.StatusBadRequest, errors.New("got an empty AdmissionRequest")) + } + var resp atypes.Response + switch w.Type { + case types.WebhookTypeMutating: + resp = w.handleMutating(ctx, req) + case types.WebhookTypeValidating: + resp = w.handleValidating(ctx, req) + default: + return ErrorResponse(http.StatusInternalServerError, errors.New("you must specify your webhook type")) + } + resp.Response.UID = req.AdmissionRequest.UID + return resp +} + +func (w *Webhook) handleMutating(ctx context.Context, req atypes.Request) atypes.Response { + patches := []jsonpatch.JsonPatchOperation{} + for _, handler := range w.Handlers { + resp := handler.Handle(ctx, req) + if !resp.Response.Allowed { + return resp + } + if resp.Response.PatchType != nil && *resp.Response.PatchType != admissionv1beta1.PatchTypeJSONPatch { + return ErrorResponse(http.StatusInternalServerError, + fmt.Errorf("unexpected patch type returned by the handler: %v, only allow: %v", + resp.Response.PatchType, admissionv1beta1.PatchTypeJSONPatch)) + } + patches = append(patches, resp.Patches...) + } + var err error + marshaledPatch, err := json.Marshal(patches) + if err != nil { + return ErrorResponse(http.StatusBadRequest, fmt.Errorf("error when marshaling the patch: %v", err)) + } + return atypes.Response{ + Response: &admissionv1beta1.AdmissionResponse{ + Allowed: true, + Patch: marshaledPatch, + PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(), + }, + } +} + +func (w *Webhook) handleValidating(ctx context.Context, req atypes.Request) atypes.Response { + for _, handler := range w.Handlers { + resp := handler.Handle(ctx, req) + if !resp.Response.Allowed { + return resp + } + } + return atypes.Response{ + Response: &admissionv1beta1.AdmissionResponse{ + Allowed: true, + }, + } +} + +// GetName returns the name of the webhook. +func (w *Webhook) GetName() string { + w.once.Do(w.setDefaults) + return w.Name +} + +// GetPath returns the path that the webhook registered. +func (w *Webhook) GetPath() string { + w.once.Do(w.setDefaults) + return w.Path +} + +// GetType returns the type of the webhook. +func (w *Webhook) GetType() types.WebhookType { + w.once.Do(w.setDefaults) + return w.Type +} + +// Handler returns a http.Handler for the webhook +func (w *Webhook) Handler() http.Handler { + w.once.Do(w.setDefaults) + return w +} + +// Validate validates if the webhook is valid. +func (w *Webhook) Validate() error { + w.once.Do(w.setDefaults) + if len(w.Rules) == 0 { + return errors.New("field Rules should not be empty") + } + if len(w.Name) == 0 { + return errors.New("field Name should not be empty") + } + if w.Type != types.WebhookTypeMutating && w.Type != types.WebhookTypeValidating { + return fmt.Errorf("unsupported Type: %v, only WebhookTypeMutating and WebhookTypeValidating are supported", w.Type) + } + if len(w.Path) == 0 { + return errors.New("field Path should not be empty") + } + if len(w.Handlers) == 0 { + return errors.New("field Handler should not be empty") + } + return nil +} + +var _ inject.Client = &Webhook{} + +// InjectClient injects the client into the handlers +func (w *Webhook) InjectClient(c client.Client) error { + for _, handler := range w.Handlers { + if _, err := inject.ClientInto(c, handler); err != nil { + return err + } + } + return nil +} + +var _ inject.Decoder = &Webhook{} + +// InjectDecoder injects the decoder into the handlers +func (w *Webhook) InjectDecoder(d atypes.Decoder) error { + for _, handler := range w.Handlers { + if _, err := inject.DecoderInto(d, handler); err != nil { + return err + } + } + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go new file mode 100644 index 000000000..0f16dee92 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/bootstrap.go @@ -0,0 +1,362 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "errors" + "fmt" + "net" + "net/http" + "net/url" + "os" + "path" + "strconv" + + "github.com/ghodss/yaml" + + "k8s.io/api/admissionregistration/v1beta1" + admissionregistration "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer" + "sigs.k8s.io/controller-runtime/pkg/webhook/types" +) + +// setDefault does defaulting for the Server. +func (s *Server) setDefault() { + s.setServerDefault() + s.setBootstrappingDefault() +} + +// setServerDefault does defaulting for the ServerOptions. +func (s *Server) setServerDefault() { + if len(s.Name) == 0 { + s.Name = "default-k8s-webhook-server" + } + if s.registry == nil { + s.registry = map[string]Webhook{} + } + if s.sMux == nil { + s.sMux = http.DefaultServeMux + } + if s.Port <= 0 { + s.Port = 443 + } + if len(s.CertDir) == 0 { + s.CertDir = path.Join("k8s-webhook-server", "cert") + } + + if s.Client == nil { + cfg, err := config.GetConfig() + if err != nil { + s.err = err + return + } + s.Client, err = client.New(cfg, client.Options{}) + if err != nil { + s.err = err + return + } + } +} + +// setBootstrappingDefault does defaulting for the Server bootstrapping. +func (s *Server) setBootstrappingDefault() { + if len(s.MutatingWebhookConfigName) == 0 { + s.MutatingWebhookConfigName = "mutating-webhook-configuration" + } + if len(s.ValidatingWebhookConfigName) == 0 { + s.ValidatingWebhookConfigName = "validating-webhook-configuration" + } + if s.Host == nil && s.Service == nil { + varString := "localhost" + s.Host = &varString + } + + var certWriter writer.CertWriter + var err error + if s.Secret != nil { + certWriter, err = writer.NewSecretCertWriter( + writer.SecretCertWriterOptions{ + Secret: s.Secret, + Client: s.Client, + }) + } else { + certWriter, err = writer.NewFSCertWriter( + writer.FSCertWriterOptions{ + Path: s.CertDir, + }) + } + if err != nil { + s.err = err + return + } + s.certProvisioner = &cert.Provisioner{ + CertWriter: certWriter, + } + if s.Writer == nil { + s.Writer = os.Stdout + } +} + +// installWebhookConfig writes the configuration of admissionWebhookConfiguration in yaml format if dryrun is true. +// Otherwise, it creates the the admissionWebhookConfiguration objects and service if any. +// It also provisions the certificate for the admission server. +func (s *Server) installWebhookConfig() error { + // do defaulting if necessary + s.once.Do(s.setDefault) + if s.err != nil { + return s.err + } + + var err error + s.webhookConfigurations, err = s.whConfigs() + if err != nil { + return err + } + svc := s.service() + objects := append(s.webhookConfigurations, svc) + + cc, err := s.getClientConfig() + if err != nil { + return err + } + // Provision the cert by creating new one or refreshing existing one. + _, err = s.certProvisioner.Provision(cert.Options{ + ClientConfig: cc, + Objects: s.webhookConfigurations, + Dryrun: s.Dryrun, + }) + if err != nil { + return err + } + + if s.Dryrun { + // TODO: print here + // if dryrun, return the AdmissionWebhookConfiguration in yaml format. + return s.genYamlConfig(objects) + } + + return batchCreateOrReplace(s.Client, objects...) +} + +// genYamlConfig generates yaml config for admissionWebhookConfiguration +func (s *Server) genYamlConfig(objs []runtime.Object) error { + for _, obj := range objs { + _, err := s.Writer.Write([]byte("---")) + if err != nil { + return err + } + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + _, err = s.Writer.Write(b) + if err != nil { + return err + } + } + return nil +} + +func (s *Server) getClientConfig() (*admissionregistration.WebhookClientConfig, error) { + if s.Host != nil && s.Service != nil { + return nil, errors.New("URL and Service can't be set at the same time") + } + cc := &admissionregistration.WebhookClientConfig{ + CABundle: []byte{}, + } + if s.Host != nil { + u := url.URL{ + Scheme: "https", + Host: net.JoinHostPort(*s.Host, strconv.Itoa(int(s.Port))), + } + urlString := u.String() + cc.URL = &urlString + } + if s.Service != nil { + cc.Service = &admissionregistration.ServiceReference{ + Name: s.Service.Name, + Namespace: s.Service.Namespace, + // Path will be set later + } + } + return cc, nil +} + +// getClientConfigWithPath constructs a WebhookClientConfig based on the server options. +// It will use path to the set the path in WebhookClientConfig. +func (s *Server) getClientConfigWithPath(path string) (*admissionregistration.WebhookClientConfig, error) { + cc, err := s.getClientConfig() + if err != nil { + return nil, err + } + return cc, setPath(cc, path) +} + +// setPath sets the path in the WebhookClientConfig. +func setPath(cc *admissionregistration.WebhookClientConfig, path string) error { + if cc.URL != nil { + u, err := url.Parse(*cc.URL) + if err != nil { + return err + } + u.Path = path + urlString := u.String() + cc.URL = &urlString + } + if cc.Service != nil { + cc.Service.Path = &path + } + return nil +} + +// whConfigs creates a mutatingWebhookConfiguration and(or) a validatingWebhookConfiguration based on registry. +// For the same type of webhook configuration, it generates a webhook entry per endpoint. +func (s *Server) whConfigs() ([]runtime.Object, error) { + objs := []runtime.Object{} + mutatingWH, err := s.mutatingWHConfigs() + if err != nil { + return nil, err + } + if mutatingWH != nil { + objs = append(objs, mutatingWH) + } + validatingWH, err := s.validatingWHConfigs() + if err != nil { + return nil, err + } + if validatingWH != nil { + objs = append(objs, validatingWH) + } + return objs, nil +} + +func (s *Server) mutatingWHConfigs() (runtime.Object, error) { + mutatingWebhooks := []v1beta1.Webhook{} + for path, webhook := range s.registry { + if webhook.GetType() != types.WebhookTypeMutating { + continue + } + + admissionWebhook := webhook.(*admission.Webhook) + wh, err := s.admissionWebhook(path, admissionWebhook) + if err != nil { + return nil, err + } + mutatingWebhooks = append(mutatingWebhooks, *wh) + } + + if len(mutatingWebhooks) > 0 { + return &admissionregistration.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", admissionregistration.GroupName, "v1beta1"), + Kind: "MutatingWebhookConfiguration", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.MutatingWebhookConfigName, + }, + Webhooks: mutatingWebhooks, + }, nil + } + return nil, nil +} + +func (s *Server) validatingWHConfigs() (runtime.Object, error) { + validatingWebhooks := []v1beta1.Webhook{} + for path, webhook := range s.registry { + var admissionWebhook *admission.Webhook + if webhook.GetType() != types.WebhookTypeValidating { + continue + } + + admissionWebhook = webhook.(*admission.Webhook) + wh, err := s.admissionWebhook(path, admissionWebhook) + if err != nil { + return nil, err + } + validatingWebhooks = append(validatingWebhooks, *wh) + } + + if len(validatingWebhooks) > 0 { + return &admissionregistration.ValidatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", admissionregistration.GroupName, "v1beta1"), + Kind: "ValidatingWebhookConfiguration", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.ValidatingWebhookConfigName, + }, + Webhooks: validatingWebhooks, + }, nil + } + return nil, nil +} + +func (s *Server) admissionWebhook(path string, wh *admission.Webhook) (*admissionregistration.Webhook, error) { + webhook := &admissionregistration.Webhook{ + Name: wh.GetName(), + Rules: wh.Rules, + FailurePolicy: wh.FailurePolicy, + NamespaceSelector: wh.NamespaceSelector, + ClientConfig: admissionregistration.WebhookClientConfig{ + // The reason why we assign an empty byte array to CABundle is that + // CABundle field will be updated by the Provisioner. + CABundle: []byte{}, + }, + } + cc, err := s.getClientConfigWithPath(path) + if err != nil { + return nil, err + } + webhook.ClientConfig = *cc + return webhook, nil +} + +// service creates a corev1.service object fronting the admission server. +func (s *Server) service() runtime.Object { + if s.Service == nil { + return nil + } + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.Service.Name, + Namespace: s.Service.Namespace, + }, + Spec: corev1.ServiceSpec{ + Selector: s.Service.Selectors, + Ports: []corev1.ServicePort{ + { + // When using service, kube-apiserver will send admission request to port 443. + Port: 443, + TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: s.Port}, + }, + }, + }, + } + return svc +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go new file mode 100644 index 000000000..2c9eba2a3 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/doc.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package webhook provides methods to build and bootstrap a webhook server. + +Currently, it only supports admission webhooks. It will support CRD conversion webhooks in the near future. + +Build webhooks + + // mgr is the manager that runs the server. + webhook1, err := NewWebhookBuilder(). + Name("foo.k8s.io"). + Mutating(). + Path("/mutating-pods"). + Operations(admissionregistrationv1beta1.Create). + ForType(&corev1.Pod{}). + WithManager(mgr). + Handlers(mutatingHandler1, mutatingHandler2). + Build() + if err != nil { + // handle error + } + + webhook2, err := NewWebhookBuilder(). + Name("bar.k8s.io"). + Validating(). + Path("/validating-deployment"). + Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update). + ForType(&appsv1.Deployment{}). + WithManager(mgr). + Handlers(validatingHandler1). + Build() + if err != nil { + // handle error + } + +Create a webhook server. + + as, err := NewServer("baz-admission-server", mgr, ServerOptions{ + CertDir: "/tmp/cert", + BootstrapOptions: &BootstrapOptions{ + Secret: &apitypes.NamespacedName{ + Namespace: "default", + Name: "foo-admission-server-secret", + }, + Service: &Service{ + Namespace: "default", + Name: "foo-admission-server-service", + // Selectors should select the pods that runs this webhook server. + Selectors: map[string]string{ + "app": "foo-admission-server", + }, + }, + }, + }) + if err != nil { + // handle error + } + +Register the webhooks in the server. + + err = as.Register(webhook1, webhook2) + if err != nil { + // handle error + } + +Start the server by starting the manager + + err := mrg.Start(signals.SetupSignalHandler()) + if err != nil { + // handle error + } +*/ +package webhook + +import ( + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" +) + +var log = logf.KBLog.WithName("webhook") diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go new file mode 100644 index 000000000..5929246f0 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package cert provides functions to manage certificates for webhookClientConfiguration. + +Create a Provisioner with a CertWriter. + + provisioner := Provisioner{ + CertWriter: admission.NewSecretCertWriter(admission.SecretCertWriterOptions{...}), + } + +Provision the certificates for the webhookClientConfig + + err := provisioner.Provision(Options{ + ClientConfig: webhookClientConfig, + Objects: []runtime.Object{mutatingWebhookConfiguration, validatingWebhookConfiguration} + }) + if err != nil { + // handle error + } +*/ +package cert diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/certgenerator.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/certgenerator.go similarity index 100% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/certgenerator.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/certgenerator.go diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/doc.go similarity index 95% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/doc.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/doc.go index 798cfa2e4..9d814e428 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/doc.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/doc.go @@ -17,7 +17,7 @@ limitations under the License. /* Package generator provides an interface and implementation to provision certificates. -Create an instance of CertGenerator. +Create an instance of certGenerator. cg := SelfSignedCertGenerator{} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/fake/certgenerator.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/fake/certgenerator.go similarity index 84% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/fake/certgenerator.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/fake/certgenerator.go index a6f3083ec..d631dd216 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/fake/certgenerator.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/fake/certgenerator.go @@ -19,10 +19,10 @@ package fake import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/admission/cert/generator" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator" ) -// CertGenerator is a CertGenerator for testing. +// CertGenerator is a certGenerator for testing. type CertGenerator struct { DNSNameToCertArtifacts map[string]*generator.Artifacts } @@ -33,7 +33,7 @@ var _ generator.CertGenerator = &CertGenerator{} func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) { certs, found := cp.DNSNameToCertArtifacts[commonName] if !found { - return nil, fmt.Errorf("failed to find common name %q in the CertGenerator", commonName) + return nil, fmt.Errorf("failed to find common name %q in the certGenerator", commonName) } return certs, nil } diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/selfsigned.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/selfsigned.go similarity index 97% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/selfsigned.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/selfsigned.go index af414f477..0d190127e 100644 --- a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/generator/selfsigned.go +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator/selfsigned.go @@ -28,7 +28,7 @@ func ServiceToCommonName(serviceNamespace, serviceName string) string { return fmt.Sprintf("%s.%s.svc", serviceName, serviceNamespace) } -// SelfSignedCertGenerator implements the CertGenerator interface. +// SelfSignedCertGenerator implements the certGenerator interface. // It provisions self-signed certificates. type SelfSignedCertGenerator struct{} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go new file mode 100644 index 000000000..2383038ce --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/provisioner.go @@ -0,0 +1,139 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cert + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/url" + + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer" +) + +// Provisioner provisions certificates for webhook configurations and writes them to an output +// destination - such as a Secret or local file. Provisioner can update the CA field of +// certain resources with the CA of the certs. +type Provisioner struct { + // CertWriter knows how to persist the certificate. + CertWriter writer.CertWriter +} + +// Options are options for provisioning the certificate. +type Options struct { + // ClientConfig is the WebhookClientCert that contains the information to generate + // the certificate. The CA Certificate will be updated in the ClientConfig. + // The updated ClientConfig will be used to inject into other runtime.Objects, + // e.g. MutatingWebhookConfiguration and ValidatingWebhookConfiguration. + ClientConfig *admissionregistrationv1beta1.WebhookClientConfig + // Objects are the objects that will use the ClientConfig above. + Objects []runtime.Object + // Dryrun controls if the objects are sent to the API server or write to io.Writer + Dryrun bool +} + +// Provision provisions certificates for for the WebhookClientConfig. +// It ensures the cert and CA are valid and not expiring. +// It updates the CABundle in the webhookClientConfig if necessary. +// It inject the WebhookClientConfig into options.Objects. +func (cp *Provisioner) Provision(options Options) (bool, error) { + if cp.CertWriter == nil { + return false, errors.New("CertWriter need to be set") + } + // If the objects need to be updated, just be lazy and return. + if len(options.Objects) == 0 { + return false, nil + } + + dnsName, err := dnsNameFromClientConfig(options.ClientConfig) + if err != nil { + return false, err + } + + certs, changed, err := cp.CertWriter.EnsureCert(dnsName, options.Dryrun) + if err != nil { + return false, err + } + + caBundle := options.ClientConfig.CABundle + caCert := certs.CACert + // TODO(mengqiy): limit the size of the CABundle by GC the old CA certificate + // this is important since the max record size in etcd is 1MB (latest version is 1.5MB). + if !bytes.Contains(caBundle, caCert) { + // Ensure the CA bundle in the webhook configuration has the signing CA. + options.ClientConfig.CABundle = append(caBundle, caCert...) + changed = true + } + return changed, cp.inject(options.ClientConfig, options.Objects) +} + +// Inject the ClientConfig to the objects. +// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration. +func (cp *Provisioner) inject(cc *admissionregistrationv1beta1.WebhookClientConfig, objs []runtime.Object) error { + if cc == nil { + return nil + } + for i := range objs { + switch typed := objs[i].(type) { + case *admissionregistrationv1beta1.MutatingWebhookConfiguration: + injectForEachWebhook(cc, typed.Webhooks) + case *admissionregistrationv1beta1.ValidatingWebhookConfiguration: + injectForEachWebhook(cc, typed.Webhooks) + default: + return fmt.Errorf("%#v is not supported for injecting a webhookClientConfig", + objs[i].GetObjectKind().GroupVersionKind()) + } + } + return cp.CertWriter.Inject(objs...) +} + +func injectForEachWebhook( + cc *admissionregistrationv1beta1.WebhookClientConfig, + webhooks []admissionregistrationv1beta1.Webhook) { + for i := range webhooks { + // only replacing the CA bundle to preserve the path in the WebhookClientConfig + webhooks[i].ClientConfig.CABundle = cc.CABundle + } +} + +func dnsNameFromClientConfig(config *admissionregistrationv1beta1.WebhookClientConfig) (string, error) { + if config == nil { + return "", errors.New("clientConfig should not be empty") + } + if config.Service != nil && config.URL != nil { + return "", fmt.Errorf("service and URL can't be set at the same time in a webhook: %v", config) + } + if config.Service == nil && config.URL == nil { + return "", fmt.Errorf("one of service and URL need to be set in a webhook: %v", config) + } + if config.Service != nil { + return generator.ServiceToCommonName(config.Service.Namespace, config.Service.Name), nil + } + u, err := url.Parse(*config.URL) + if err != nil { + return "", err + } + host, _, err := net.SplitHostPort(u.Host) + if err != nil { + return u.Host, nil + } + return host, err +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/internal/atomic/atomic_writer.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic/atomic_writer.go similarity index 100% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/internal/atomic/atomic_writer.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic/atomic_writer.go diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go new file mode 100644 index 000000000..63d0d5e62 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/certwriter.go @@ -0,0 +1,135 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package writer + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator" +) + +const ( + // CACertName is the name of the CA certificate + CACertName = "ca-cert.pem" + // ServerKeyName is the name of the server private key + ServerKeyName = "key.pem" + // ServerCertName is the name of the serving certificate + ServerCertName = "cert.pem" +) + +// CertWriter provides method to handle webhooks. +type CertWriter interface { + // EnsureCert provisions the cert for the webhookClientConfig. + EnsureCert(dnsName string, dryrun bool) (*generator.Artifacts, bool, error) + // Inject injects the necessary information given the objects. + // It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration. + Inject(objs ...runtime.Object) error +} + +// handleCommon ensures the given webhook has a proper certificate. +// It uses the given certReadWriter to read and (or) write the certificate. +func handleCommon(dnsName string, ch certReadWriter) (*generator.Artifacts, bool, error) { + if len(dnsName) == 0 { + return nil, false, errors.New("dnsName should not be empty") + } + if ch == nil { + return nil, false, errors.New("certReaderWriter should not be nil") + } + + certs, changed, err := createIfNotExists(ch) + if err != nil { + return nil, changed, err + } + + // Recreate the cert if it's invalid. + valid := validCert(certs, dnsName) + if !valid { + log.Info("cert is invalid or expiring, regenerating a new one") + certs, err = ch.overwrite() + if err != nil { + return nil, false, err + } + changed = true + } + return certs, changed, nil +} + +func createIfNotExists(ch certReadWriter) (*generator.Artifacts, bool, error) { + // Try to read first + certs, err := ch.read() + if isNotFound(err) { + // Create if not exists + certs, err = ch.write() + switch { + // This may happen if there is another racer. + case isAlreadyExists(err): + certs, err = ch.read() + return certs, true, err + default: + return certs, true, err + } + } + return certs, false, err +} + +// certReadWriter provides methods for reading and writing certificates. +type certReadWriter interface { + // read reads a wehbook name and returns the certs for it. + read() (*generator.Artifacts, error) + // write writes the certs and return the certs it wrote. + write() (*generator.Artifacts, error) + // overwrite overwrites the existing certs and return the certs it wrote. + overwrite() (*generator.Artifacts, error) +} + +func validCert(certs *generator.Artifacts, dnsName string) bool { + if certs == nil { + return false + } + + // Verify key and cert are valid pair + _, err := tls.X509KeyPair(certs.Cert, certs.Key) + if err != nil { + return false + } + + // Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time. + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(certs.CACert) { + return false + } + block, _ := pem.Decode([]byte(certs.Cert)) + if block == nil { + return false + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return false + } + ops := x509.VerifyOptions{ + DNSName: dnsName, + Roots: pool, + CurrentTime: time.Now().AddDate(0, 6, 0), + } + _, err = cert.Verify(ops) + return err == nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go new file mode 100644 index 000000000..91aa07ae4 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/doc.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package writer provides method to provision and persist the certificates. + +It will create the certificates if they don't exist. +It will ensure the certificates are valid and not expiring. If not, it will recreate them. + +Create a CertWriter that can write the certificate to secret + + writer, err := NewSecretCertWriter(SecretCertWriterOptions{ + Secret: types.NamespacedName{Namespace: "foo", Name: "bar"}, + Client: client, + }) + if err != nil { + // handler error + } + +Create a CertWriter that can write the certificate to the filesystem. + + writer, err := NewFSCertWriter(FSCertWriterOptions{ + Path: "path/to/cert/", + }) + if err != nil { + // handler error + } + +Provision the certificates using the CertWriter. The certificate will be available in the desired secret or +the desired path. + + // writer can be either one of the CertWriters created above + certs, changed, err := writer.EnsureCerts("admissionwebhook.k8s.io", false) + if err != nil { + // handler error + } + +Inject necessary information given the objects. + + err = writer.Inject(objs...) + if err != nil { + // handler error + } +*/ +package writer + +import ( + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" +) + +var log = logf.KBLog.WithName("admission").WithName("cert").WithName("writer") diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/error.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go similarity index 100% rename from vendor/sigs.k8s.io/controller-runtime/pkg/admission/cert/writer/error.go rename to vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/error.go diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go new file mode 100644 index 000000000..072d8d6a8 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/fs.go @@ -0,0 +1,208 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package writer + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/atomic" +) + +// fsCertWriter provisions the certificate by reading and writing to the filesystem. +type fsCertWriter struct { + // dnsName is the DNS name that the certificate is for. + dnsName string + + *FSCertWriterOptions +} + +// FSCertWriterOptions are options for constructing a FSCertWriter. +type FSCertWriterOptions struct { + // certGenerator generates the certificates. + CertGenerator generator.CertGenerator + // path is the directory that the certificate and private key and CA certificate will be written. + Path string +} + +var _ CertWriter = &fsCertWriter{} + +func (ops *FSCertWriterOptions) setDefaults() { + if ops.CertGenerator == nil { + ops.CertGenerator = &generator.SelfSignedCertGenerator{} + } +} + +func (ops *FSCertWriterOptions) validate() error { + if len(ops.Path) == 0 { + return errors.New("path must be set in FSCertWriterOptions") + } + return nil +} + +// NewFSCertWriter constructs a CertWriter that persists the certificate on filesystem. +func NewFSCertWriter(ops FSCertWriterOptions) (CertWriter, error) { + ops.setDefaults() + err := ops.validate() + if err != nil { + return nil, err + } + return &fsCertWriter{ + FSCertWriterOptions: &ops, + }, nil +} + +// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates in the filesystem. +// fsCertWriter doesn't support dryrun. +func (f *fsCertWriter) EnsureCert(dnsName string, _ bool) (*generator.Artifacts, bool, error) { + // create or refresh cert and write it to fs + f.dnsName = dnsName + return handleCommon(f.dnsName, f) +} + +func (f *fsCertWriter) write() (*generator.Artifacts, error) { + return f.doWrite() +} + +func (f *fsCertWriter) overwrite() (*generator.Artifacts, error) { + return f.doWrite() +} + +func (f *fsCertWriter) doWrite() (*generator.Artifacts, error) { + certs, err := f.CertGenerator.Generate(f.dnsName) + if err != nil { + return nil, err + } + + // AtomicWriter's algorithm only manages files using symbolic link. + // If a file is not a symbolic link, will ignore the update for it. + // We want to cleanup for AtomicWriter by removing old files that are not symbolic links. + err = prepareToWrite(f.Path) + if err != nil { + return nil, err + } + + aw, err := atomic.NewAtomicWriter(f.Path, log.WithName("atomic-writer"). + WithValues("task", "processing webhook")) + if err != nil { + return nil, err + } + err = aw.Write(certToProjectionMap(certs)) + return certs, err +} + +// prepareToWrite ensures it directory is compatible with the atomic.Writer library. +func prepareToWrite(dir string) error { + _, err := os.Stat(dir) + switch { + case os.IsNotExist(err): + log.Info(fmt.Sprintf("cert directory %v doesn't exist, creating", dir)) + // TODO: figure out if we can reduce the permission. (Now it's 0777) + err = os.MkdirAll(dir, 0777) + if err != nil { + return fmt.Errorf("can't create dir: %v", dir) + } + case err != nil: + return err + } + + filenames := []string{CACertName, ServerCertName, ServerKeyName} + for _, f := range filenames { + abspath := path.Join(dir, f) + _, err := os.Stat(abspath) + if os.IsNotExist(err) { + continue + } else if err != nil { + log.Error(err, "unable to stat file", "file", abspath) + } + _, err = os.Readlink(abspath) + // if it's not a symbolic link + if err != nil { + err = os.Remove(abspath) + if err != nil { + log.Error(err, "unable to remove old file", "file", abspath) + } + } + } + return nil +} + +func (f *fsCertWriter) read() (*generator.Artifacts, error) { + if err := ensureExist(f.Path); err != nil { + return nil, err + } + caBytes, err := ioutil.ReadFile(path.Join(f.Path, CACertName)) + if err != nil { + return nil, err + } + certBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerCertName)) + if err != nil { + return nil, err + } + keyBytes, err := ioutil.ReadFile(path.Join(f.Path, ServerKeyName)) + if err != nil { + return nil, err + } + return &generator.Artifacts{ + CACert: caBytes, + Cert: certBytes, + Key: keyBytes, + }, nil +} + +func ensureExist(dir string) error { + filenames := []string{CACertName, ServerCertName, ServerKeyName} + for _, filename := range filenames { + _, err := os.Stat(path.Join(dir, filename)) + switch { + case err == nil: + continue + case os.IsNotExist(err): + return notFoundError{err} + default: + return err + } + } + return nil +} + +func certToProjectionMap(cert *generator.Artifacts) map[string]atomic.FileProjection { + // TODO: figure out if we can reduce the permission. (Now it's 0666) + return map[string]atomic.FileProjection{ + CACertName: { + Data: cert.CACert, + Mode: 0666, + }, + ServerCertName: { + Data: cert.Cert, + Mode: 0666, + }, + ServerKeyName: { + Data: cert.Key, + Mode: 0666, + }, + } +} + +func (f *fsCertWriter) Inject(objs ...runtime.Object) error { + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go new file mode 100644 index 000000000..d6c9e368a --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer/secret.go @@ -0,0 +1,198 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package writer + +import ( + "errors" + "io" + "os" + + "github.com/ghodss/yaml" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator" +) + +// secretCertWriter provisions the certificate by reading and writing to the k8s secrets. +type secretCertWriter struct { + *SecretCertWriterOptions + + // dnsName is the DNS name that the certificate is for. + dnsName string + // dryrun indicates sending the create/update request to the server or output to the writer in yaml format. + dryrun bool +} + +// SecretCertWriterOptions is options for constructing a secretCertWriter. +type SecretCertWriterOptions struct { + // client talks to a kubernetes cluster for creating the secret. + Client client.Client + // certGenerator generates the certificates. + CertGenerator generator.CertGenerator + // secret points the secret that contains certificates that written by the CertWriter. + Secret *types.NamespacedName + // Writer is used in dryrun mode for writing the objects in yaml format. + Writer io.Writer +} + +var _ CertWriter = &secretCertWriter{} + +func (ops *SecretCertWriterOptions) setDefaults() { + if ops.CertGenerator == nil { + ops.CertGenerator = &generator.SelfSignedCertGenerator{} + } + if ops.Writer == nil { + ops.Writer = os.Stdout + } +} + +func (ops *SecretCertWriterOptions) validate() error { + if ops.Client == nil { + return errors.New("client must be set in SecretCertWriterOptions") + } + if ops.Secret == nil { + return errors.New("secret must be set in SecretCertWriterOptions") + } + return nil +} + +// NewSecretCertWriter constructs a CertWriter that persists the certificate in a k8s secret. +func NewSecretCertWriter(ops SecretCertWriterOptions) (CertWriter, error) { + ops.setDefaults() + err := ops.validate() + if err != nil { + return nil, err + } + return &secretCertWriter{ + SecretCertWriterOptions: &ops, + }, nil +} + +// EnsureCert provisions certificates for a webhookClientConfig by writing the certificates to a k8s secret. +func (s *secretCertWriter) EnsureCert(dnsName string, dryrun bool) (*generator.Artifacts, bool, error) { + // Create or refresh the certs based on clientConfig + s.dryrun = dryrun + s.dnsName = dnsName + return handleCommon(s.dnsName, s) +} + +var _ certReadWriter = &secretCertWriter{} + +func (s *secretCertWriter) buildSecret() (*corev1.Secret, *generator.Artifacts, error) { + certs, err := s.CertGenerator.Generate(s.dnsName) + if err != nil { + return nil, nil, err + } + secret := certsToSecret(certs, *s.Secret) + return secret, certs, err +} + +func (s *secretCertWriter) write() (*generator.Artifacts, error) { + secret, certs, err := s.buildSecret() + if err != nil { + return nil, err + } + if s.dryrun { + return certs, s.dryrunWrite(secret) + } + err = s.Client.Create(nil, secret) + if apierrors.IsAlreadyExists(err) { + return nil, alreadyExistError{err} + } + return certs, err +} + +func (s *secretCertWriter) overwrite() ( + *generator.Artifacts, error) { + secret, certs, err := s.buildSecret() + if err != nil { + return nil, err + } + if s.dryrun { + return certs, s.dryrunWrite(secret) + } + err = s.Client.Update(nil, secret) + return certs, err +} + +func (s *secretCertWriter) dryrunWrite(secret *corev1.Secret) error { + sec, err := yaml.Marshal(secret) + if err != nil { + return err + } + _, err = s.Writer.Write(sec) + return err +} + +func (s *secretCertWriter) read() (*generator.Artifacts, error) { + if s.dryrun { + return nil, notFoundError{} + } + secret := &corev1.Secret{} + err := s.Client.Get(nil, *s.Secret, secret) + if apierrors.IsNotFound(err) { + return nil, notFoundError{err} + } + return secretToCerts(secret), err +} + +func secretToCerts(secret *corev1.Secret) *generator.Artifacts { + if secret.Data == nil { + return nil + } + return &generator.Artifacts{ + CACert: secret.Data[CACertName], + Cert: secret.Data[ServerCertName], + Key: secret.Data[ServerKeyName], + } +} + +func certsToSecret(certs *generator.Artifacts, sec types.NamespacedName) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: sec.Namespace, + Name: sec.Name, + }, + Data: map[string][]byte{ + CACertName: certs.CACert, + ServerKeyName: certs.Key, + ServerCertName: certs.Cert, + }, + } +} + +// Inject sets the ownerReference in the secret. +func (s *secretCertWriter) Inject(objs ...runtime.Object) error { + // TODO: figure out how to get the UID + //for i := range objs { + // accessor, err := meta.Accessor(objs[i]) + // if err != nil { + // return err + // } + // err = controllerutil.SetControllerReference(accessor, s.sec, scheme.Scheme) + // if err != nil { + // return err + // } + //} + //return s.client.Update(context.Background(), s.sec) + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go new file mode 100644 index 000000000..3c547c42e --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/server.go @@ -0,0 +1,278 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "context" + "fmt" + "io" + "net/http" + "path" + "sync" + "time" + + "k8s.io/apimachinery/pkg/runtime" + apitypes "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/runtime/inject" + atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert" + "sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer" + "sigs.k8s.io/controller-runtime/pkg/webhook/types" +) + +// ServerOptions are options for configuring an admission webhook server. +type ServerOptions struct { + // Port is the port number that the server will serve. + // It will be defaulted to 443 if unspecified. + Port int32 + + // CertDir is the directory that contains the server key and certificate. + // If using FSCertWriter in Provisioner, the server itself will provision the certificate and + // store it in this directory. + // If using SecretCertWriter in Provisioner, the server will provision the certificate in a secret, + // the user is responsible to mount the secret to the this location for the server to consume. + CertDir string + + // Client is a client defined in controller-runtime instead of a client-go client. + // It knows how to talk to a kubernetes cluster. + // Client will be injected by the manager if not set. + Client client.Client + + // Dryrun controls if the server will install the webhookConfiguration and service if any. + // If true, it will print the objects in yaml format. + // If false, it will install the objects in the cluster. + Dryrun bool + + // BootstrapOptions contains the options for bootstrapping the admission server. + *BootstrapOptions +} + +// BootstrapOptions are options for bootstrapping an admission webhook server. +type BootstrapOptions struct { + // MutatingWebhookConfigName is the name that used for creating the MutatingWebhookConfiguration object. + MutatingWebhookConfigName string + // ValidatingWebhookConfigName is the name that used for creating the ValidatingWebhookConfiguration object. + ValidatingWebhookConfigName string + + // Secret is the location for storing the certificate for the admission server. + // The server should have permission to create a secret in the namespace. + // This is optional. If unspecified, it will write to the filesystem. + // It the secret already exists and is different from the desired, it will be replaced. + Secret *apitypes.NamespacedName + // Writer is used in dryrun mode for writing the objects in yaml format. + Writer io.Writer + + // Service is k8s service fronting the webhook server pod(s). + // This field is optional. But one and only one of Service and Host need to be set. + // This maps to field .webhooks.getClientConfig.service + // https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L260 + Service *Service + // Host is the host name of .webhooks.clientConfig.url + // https://github.com/kubernetes/api/blob/183f3326a9353bd6d41430fc80f96259331d029c/admissionregistration/v1beta1/types.go#L250 + // This field is optional. But one and only one of Service and Host need to be set. + // If neither Service nor Host is unspecified, Host will be defaulted to "localhost". + Host *string + + // certProvisioner is constructed using certGenerator and certWriter + certProvisioner *cert.Provisioner // nolint: structcheck + + // err will be non-nil if there is an error occur during initialization. + err error // nolint: structcheck +} + +// Service contains information for creating a service +type Service struct { + // Name of the service + Name string + // Namespace of the service + Namespace string + // Selectors is the selector of the service. + // This must select the pods that runs this webhook server. + Selectors map[string]string +} + +// Server is an admission webhook server that can serve traffic and +// generates related k8s resources for deploying. +type Server struct { + // Name is the name of server + Name string + + // ServerOptions contains options for configuring the admission server. + ServerOptions + + sMux *http.ServeMux + // registry maps a path to a http.Handler. + registry map[string]Webhook + + // mutatingWebhookConfiguration and validatingWebhookConfiguration are populated during server bootstrapping. + // They can be nil, if there is no webhook registered under it. + webhookConfigurations []runtime.Object + + // manager is the manager that this webhook server will be registered. + manager manager.Manager + + once sync.Once +} + +// Webhook defines the basics that a webhook should support. +type Webhook interface { + // GetName returns the name of the webhook. + GetName() string + // GetPath returns the path that the webhook registered. + GetPath() string + // GetType returns the Type of the webhook. + // e.g. mutating or validating + GetType() types.WebhookType + // Handler returns a http.Handler for the webhook. + Handler() http.Handler + // Validate validates if the webhook itself is valid. + // If invalid, a non-nil error will be returned. + Validate() error +} + +// NewServer creates a new admission webhook server. +func NewServer(name string, mgr manager.Manager, options ServerOptions) (*Server, error) { + as := &Server{ + Name: name, + sMux: http.NewServeMux(), + registry: map[string]Webhook{}, + ServerOptions: options, + manager: mgr, + } + + return as, nil +} + +// Register validates and registers webhook(s) in the server +func (s *Server) Register(webhooks ...Webhook) error { + for i, webhook := range webhooks { + // validate the webhook before registering it. + err := webhook.Validate() + if err != nil { + return err + } + _, found := s.registry[webhook.GetPath()] + if found { + return fmt.Errorf("can't register duplicate path: %v", webhook.GetPath()) + } + s.registry[webhook.GetPath()] = webhooks[i] + s.sMux.Handle(webhook.GetPath(), webhook.Handler()) + } + + // Lazily add Server to manager. + // Because the all webhook handlers to be in place, so we can inject the things they need. + return s.manager.Add(s) +} + +// Handle registers a http.Handler for the given pattern. +func (s *Server) Handle(pattern string, handler http.Handler) { + s.sMux.Handle(pattern, handler) +} + +var _ manager.Runnable = &Server{} + +// Start runs the server if s.Dryrun is false. +// Otherwise, it will print the objects in yaml format. +func (s *Server) Start(stop <-chan struct{}) error { + err := s.installWebhookConfig() + // if encounter an error or it's in dryrun mode, return. + if err != nil || s.Dryrun { + return err + } + + srv := &http.Server{ + Addr: fmt.Sprintf(":%v", s.Port), + Handler: s.sMux, + } + errCh := make(chan error) + serveFn := func() { + errCh <- srv.ListenAndServeTLS(path.Join(s.CertDir, writer.ServerCertName), path.Join(s.CertDir, writer.ServerKeyName)) + } + + go serveFn() + for { + // TODO(mengqiy): add jitter to the timer + // Could use https://godoc.org/k8s.io/apimachinery/pkg/util/wait#Jitter + timer := time.Tick(6 * 30 * 24 * time.Hour) + select { + case <-timer: + changed, err := s.RefreshCert() + if err != nil { + log.Error(err, "encountering error when refreshing the certificate") + return err + } + if !changed { + continue + } + log.Info("server is shutting down to reload the certificates.") + err = srv.Shutdown(context.Background()) + if err != nil { + log.Error(err, "encountering error when shutting down") + return err + } + go serveFn() + case <-stop: + return nil + case e := <-errCh: + return e + } + } +} + +// RefreshCert refreshes the certificate using Server's Provisioner if the certificate is expiring. +func (s *Server) RefreshCert() (bool, error) { + cc, err := s.getClientConfig() + if err != nil { + return false, err + } + changed, err := s.certProvisioner.Provision(cert.Options{ + ClientConfig: cc, + Objects: s.webhookConfigurations, + }) + if err != nil { + return false, err + } + + return changed, batchCreateOrReplace(s.Client, s.webhookConfigurations...) +} + +var _ inject.Client = &Server{} + +// InjectClient injects the client into the server +func (s *Server) InjectClient(c client.Client) error { + s.Client = c + for _, wh := range s.registry { + if _, err := inject.ClientInto(c, wh.Handler()); err != nil { + return err + } + } + return nil +} + +var _ inject.Decoder = &Server{} + +// InjectDecoder injects the client into the server +func (s *Server) InjectDecoder(d atypes.Decoder) error { + for _, wh := range s.registry { + if _, err := inject.DecoderInto(d, wh.Handler()); err != nil { + return err + } + } + return nil +} diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/types/webhook.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/types/webhook.go new file mode 100644 index 000000000..2ad1253f2 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/types/webhook.go @@ -0,0 +1,28 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +// WebhookType defines the type of a webhook +type WebhookType int + +const ( + _ = iota + // WebhookTypeMutating represents mutating type webhook + WebhookTypeMutating WebhookType = iota + // WebhookTypeValidating represents validating type webhook + WebhookTypeValidating +) diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go new file mode 100644 index 000000000..8ca0c270c --- /dev/null +++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/util.go @@ -0,0 +1,110 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "context" + "fmt" + + admissionregistration "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type mutateFn func(current, desired runtime.Object) error + +var serviceFn = func(current, desired runtime.Object) error { + typedC := current.(*corev1.Service) + typedD := desired.(*corev1.Service) + typedC.Spec.Selector = typedD.Spec.Selector + return nil +} + +var mutatingWebhookConfigFn = func(current, desired runtime.Object) error { + typedC := current.(*admissionregistration.MutatingWebhookConfiguration) + typedD := desired.(*admissionregistration.MutatingWebhookConfiguration) + typedC.Webhooks = typedD.Webhooks + return nil +} + +var validatingWebhookConfigFn = func(current, desired runtime.Object) error { + typedC := current.(*admissionregistration.ValidatingWebhookConfiguration) + typedD := desired.(*admissionregistration.ValidatingWebhookConfiguration) + typedC.Webhooks = typedD.Webhooks + return nil +} + +// createOrReplaceHelper creates the object if it doesn't exist; +// otherwise, it will replace it. +// When replacing, fn should know how to preserve existing fields in the object GET from the APIServer. +// TODO: use the helper in #98 when it merges. +func createOrReplaceHelper(c client.Client, obj runtime.Object, fn mutateFn) error { + if obj == nil { + return nil + } + err := c.Create(context.Background(), obj) + if apierrors.IsAlreadyExists(err) { + // TODO: retry mutiple times with backoff if necessary. + existing := obj.DeepCopyObject() + objectKey, err := client.ObjectKeyFromObject(obj) + if err != nil { + return err + } + err = c.Get(context.Background(), objectKey, existing) + if err != nil { + return err + } + err = fn(existing, obj) + if err != nil { + return err + } + return c.Update(context.Background(), existing) + } + return err +} + +// createOrReplace creates the object if it doesn't exist; +// otherwise, it will replace it. +// When replacing, it knows how to preserve existing fields in the object GET from the APIServer. +// It currently only support MutatingWebhookConfiguration, ValidatingWebhookConfiguration and Service. +func createOrReplace(c client.Client, obj runtime.Object) error { + if obj == nil { + return nil + } + switch obj.(type) { + case *admissionregistration.MutatingWebhookConfiguration: + return createOrReplaceHelper(c, obj, mutatingWebhookConfigFn) + case *admissionregistration.ValidatingWebhookConfiguration: + return createOrReplaceHelper(c, obj, validatingWebhookConfigFn) + case *corev1.Service: + return createOrReplaceHelper(c, obj, serviceFn) + default: + return fmt.Errorf("unsupported GroupVersionKind: %#v", obj.GetObjectKind().GroupVersionKind()) + } +} + +func batchCreateOrReplace(c client.Client, objs ...runtime.Object) error { + for i := range objs { + err := createOrReplace(c, objs[i]) + if err != nil { + return err + } + } + return nil +}