Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
69
vendor/k8s.io/apiserver/pkg/admission/BUILD
generated
vendored
Normal file
69
vendor/k8s.io/apiserver/pkg/admission/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"chain_test.go",
|
||||
"config_test.go",
|
||||
"errors_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attributes.go",
|
||||
"chain.go",
|
||||
"config.go",
|
||||
"errors.go",
|
||||
"handler.go",
|
||||
"interfaces.go",
|
||||
"plugins.go",
|
||||
],
|
||||
deps = [
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/install:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
85
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
Normal file
85
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2014 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/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type attributesRecord struct {
|
||||
kind schema.GroupVersionKind
|
||||
namespace string
|
||||
name string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
operation Operation
|
||||
object runtime.Object
|
||||
oldObject runtime.Object
|
||||
userInfo user.Info
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, userInfo user.Info) Attributes {
|
||||
return &attributesRecord{
|
||||
kind: kind,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
resource: resource,
|
||||
subresource: subresource,
|
||||
operation: operation,
|
||||
object: object,
|
||||
oldObject: oldObject,
|
||||
userInfo: userInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetKind() schema.GroupVersionKind {
|
||||
return record.kind
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetNamespace() string {
|
||||
return record.namespace
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetName() string {
|
||||
return record.name
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetResource() schema.GroupVersionResource {
|
||||
return record.resource
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetSubresource() string {
|
||||
return record.subresource
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperation() Operation {
|
||||
return record.operation
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetObject() runtime.Object {
|
||||
return record.object
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOldObject() runtime.Object {
|
||||
return record.oldObject
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetUserInfo() user.Info {
|
||||
return record.userInfo
|
||||
}
|
||||
49
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
Normal file
49
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2014 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
|
||||
|
||||
// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers
|
||||
type chainAdmissionHandler []Interface
|
||||
|
||||
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
||||
func NewChainHandler(handlers ...Interface) Interface {
|
||||
return chainAdmissionHandler(handlers)
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
err := handler.Admit(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles will return true if any of the handlers handles the given operation
|
||||
func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool {
|
||||
for _, handler := range admissionHandler {
|
||||
if handler.Handles(operation) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
154
vendor/k8s.io/apiserver/pkg/admission/chain_test.go
generated
vendored
Normal file
154
vendor/k8s.io/apiserver/pkg/admission/chain_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
Copyright 2015 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"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type FakeHandler struct {
|
||||
*Handler
|
||||
name string
|
||||
admit bool
|
||||
admitCalled bool
|
||||
}
|
||||
|
||||
func (h *FakeHandler) Admit(a Attributes) (err error) {
|
||||
h.admitCalled = true
|
||||
if h.admit {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Don't admit")
|
||||
}
|
||||
|
||||
func makeHandler(name string, admit bool, ops ...Operation) Interface {
|
||||
return &FakeHandler{
|
||||
name: name,
|
||||
admit: admit,
|
||||
Handler: NewHandler(ops...),
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operation Operation
|
||||
chain chainAdmissionHandler
|
||||
accept bool
|
||||
calls map[string]bool
|
||||
}{
|
||||
{
|
||||
name: "all accept",
|
||||
operation: Create,
|
||||
chain: []Interface{
|
||||
makeHandler("a", true, Update, Delete, Create),
|
||||
makeHandler("b", true, Delete, Create),
|
||||
makeHandler("c", true, Create),
|
||||
},
|
||||
calls: map[string]bool{"a": true, "b": true, "c": true},
|
||||
accept: true,
|
||||
},
|
||||
{
|
||||
name: "ignore handler",
|
||||
operation: Create,
|
||||
chain: []Interface{
|
||||
makeHandler("a", true, Update, Delete, Create),
|
||||
makeHandler("b", false, Delete),
|
||||
makeHandler("c", true, Create),
|
||||
},
|
||||
calls: map[string]bool{"a": true, "c": true},
|
||||
accept: true,
|
||||
},
|
||||
{
|
||||
name: "ignore all",
|
||||
operation: Connect,
|
||||
chain: []Interface{
|
||||
makeHandler("a", true, Update, Delete, Create),
|
||||
makeHandler("b", false, Delete),
|
||||
makeHandler("c", true, Create),
|
||||
},
|
||||
calls: map[string]bool{},
|
||||
accept: true,
|
||||
},
|
||||
{
|
||||
name: "reject one",
|
||||
operation: Delete,
|
||||
chain: []Interface{
|
||||
makeHandler("a", true, Update, Delete, Create),
|
||||
makeHandler("b", false, Delete),
|
||||
makeHandler("c", true, Create),
|
||||
},
|
||||
calls: map[string]bool{"a": true, "b": true},
|
||||
accept: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", test.operation, nil))
|
||||
accepted := (err == nil)
|
||||
if accepted != test.accept {
|
||||
t.Errorf("%s: unexpected result of admit call: %v\n", test.name, accepted)
|
||||
}
|
||||
for _, h := range test.chain {
|
||||
fake := h.(*FakeHandler)
|
||||
_, shouldBeCalled := test.calls[fake.name]
|
||||
if shouldBeCalled != fake.admitCalled {
|
||||
t.Errorf("%s: handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
chain := chainAdmissionHandler{
|
||||
makeHandler("a", true, Update, Delete, Create),
|
||||
makeHandler("b", true, Delete, Create),
|
||||
makeHandler("c", true, Create),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
operation Operation
|
||||
chain chainAdmissionHandler
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "all handle",
|
||||
operation: Create,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "none handle",
|
||||
operation: Connect,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "some handle",
|
||||
operation: Delete,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
handles := chain.Handles(test.operation)
|
||||
if handles != test.expected {
|
||||
t.Errorf("Unexpected handles result. Expected: %v. Actual: %v", test.expected, handles)
|
||||
}
|
||||
}
|
||||
}
|
||||
193
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
Normal file
193
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2017 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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"bytes"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||
"k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||
apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(groupFactoryRegistry, registry, scheme)
|
||||
}
|
||||
|
||||
func makeAbs(path, base string) (string, error) {
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
if len(base) == 0 || base == "." {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
base = cwd
|
||||
}
|
||||
return filepath.Join(base, path), nil
|
||||
}
|
||||
|
||||
// ReadAdmissionConfiguration reads the admission configuration at the specified path.
|
||||
// It returns the loaded admission configuration if the input file aligns with the required syntax.
|
||||
// If it does not align with the provided syntax, it returns a default configuration for the enumerated
|
||||
// set of pluginNames whose config location references the specified configFilePath.
|
||||
// It does this to preserve backward compatibility when admission control files were opaque.
|
||||
// It returns an error if the file did not exist.
|
||||
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string) (ConfigProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
|
||||
}
|
||||
// a file was provided, so we just read it.
|
||||
data, err := ioutil.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
|
||||
}
|
||||
decoder := codecs.UniversalDecoder()
|
||||
decodedObj, err := runtime.Decode(decoder, data)
|
||||
// we were able to decode the file successfully
|
||||
if err == nil {
|
||||
decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||
}
|
||||
baseDir := path.Dir(configFilePath)
|
||||
for i := range decodedConfig.Plugins {
|
||||
if decodedConfig.Plugins[i].Path == "" {
|
||||
continue
|
||||
}
|
||||
// we update relative file paths to absolute paths
|
||||
absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedConfig.Plugins[i].Path = absPath
|
||||
}
|
||||
return configProvider{config: decodedConfig}, nil
|
||||
}
|
||||
// we got an error where the decode wasn't related to a missing type
|
||||
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
|
||||
return nil, err
|
||||
}
|
||||
// convert the legacy format to the new admission control format
|
||||
// in order to preserve backwards compatibility, we set plugins that
|
||||
// previously read input from a non-versioned file configuration to the
|
||||
// current input file.
|
||||
legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector")
|
||||
externalConfig := &apiserverv1alpha1.AdmissionConfiguration{}
|
||||
for _, pluginName := range pluginNames {
|
||||
if legacyPluginsWithUnversionedConfig.Has(pluginName) {
|
||||
externalConfig.Plugins = append(externalConfig.Plugins,
|
||||
apiserverv1alpha1.AdmissionPluginConfiguration{
|
||||
Name: pluginName,
|
||||
Path: configFilePath})
|
||||
}
|
||||
}
|
||||
scheme.Default(externalConfig)
|
||||
internalConfig := &apiserver.AdmissionConfiguration{}
|
||||
if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configProvider{config: internalConfig}, nil
|
||||
}
|
||||
|
||||
type configProvider struct {
|
||||
config *apiserver.AdmissionConfiguration
|
||||
}
|
||||
|
||||
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
|
||||
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
|
||||
// if there is nothing nested in the object, we return the named location
|
||||
obj := pluginCfg.Configuration
|
||||
if obj != nil {
|
||||
// serialize the configuration and build a reader for it
|
||||
content, err := writeYAML(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(content), nil
|
||||
}
|
||||
// there is nothing nested, so we delegate to path
|
||||
if pluginCfg.Path != "" {
|
||||
content, err := ioutil.ReadFile(pluginCfg.Path)
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err)
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(content), nil
|
||||
}
|
||||
// there is no special config at all
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetAdmissionPluginConfiguration takes the admission configuration and returns a reader
|
||||
// for the specified plugin. If no specific configuration is present, we return a nil reader.
|
||||
func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
|
||||
// there is no config, so there is no potential config
|
||||
if p.config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// look for matching plugin and get configuration
|
||||
for _, pluginCfg := range p.config.Plugins {
|
||||
if pluginName != pluginCfg.Name {
|
||||
continue
|
||||
}
|
||||
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pluginConfig, nil
|
||||
}
|
||||
// there is no registered config that matches on plugin name.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// writeYAML writes the specified object to a byte array as yaml.
|
||||
func writeYAML(obj runtime.Object) ([]byte, error) {
|
||||
json, err := runtime.Encode(codecs.LegacyCodec(), obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err := yaml.JSONToYAML(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return content, err
|
||||
}
|
||||
147
vendor/k8s.io/apiserver/pkg/admission/config_test.go
generated
vendored
Normal file
147
vendor/k8s.io/apiserver/pkg/admission/config_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
Copyright 2017 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||
)
|
||||
|
||||
func TestReadAdmissionConfiguration(t *testing.T) {
|
||||
// create a place holder file to hold per test config
|
||||
configFile, err := ioutil.TempFile("", "admission-plugin-config")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if err = configFile.Close(); err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
configFileName := configFile.Name()
|
||||
// the location that will be fixed up to be relative to the test config file.
|
||||
imagePolicyWebhookFile, err := makeAbs("image-policy-webhook.json", os.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
// individual test scenarios
|
||||
testCases := map[string]struct {
|
||||
ConfigBody string
|
||||
ExpectedAdmissionConfig *apiserver.AdmissionConfiguration
|
||||
PluginNames []string
|
||||
}{
|
||||
"v1Alpha1 configuration - path fixup": {
|
||||
ConfigBody: `{
|
||||
"apiVersion": "apiserver.k8s.io/v1alpha1",
|
||||
"kind": "AdmissionConfiguration",
|
||||
"plugins": [
|
||||
{"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"},
|
||||
{"name": "ResourceQuota"}
|
||||
]}`,
|
||||
ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
|
||||
Plugins: []apiserver.AdmissionPluginConfiguration{
|
||||
{
|
||||
Name: "ImagePolicyWebhook",
|
||||
Path: imagePolicyWebhookFile,
|
||||
},
|
||||
{
|
||||
Name: "ResourceQuota",
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginNames: []string{},
|
||||
},
|
||||
"v1Alpha1 configuration - abspath": {
|
||||
ConfigBody: `{
|
||||
"apiVersion": "apiserver.k8s.io/v1alpha1",
|
||||
"kind": "AdmissionConfiguration",
|
||||
"plugins": [
|
||||
{"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"},
|
||||
{"name": "ResourceQuota"}
|
||||
]}`,
|
||||
ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
|
||||
Plugins: []apiserver.AdmissionPluginConfiguration{
|
||||
{
|
||||
Name: "ImagePolicyWebhook",
|
||||
Path: "/tmp/image-policy-webhook.json",
|
||||
},
|
||||
{
|
||||
Name: "ResourceQuota",
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginNames: []string{},
|
||||
},
|
||||
"legacy configuration with using legacy plugins": {
|
||||
ConfigBody: `{
|
||||
"imagePolicy": {
|
||||
"kubeConfigFile": "/home/user/.kube/config",
|
||||
"allowTTL": 30,
|
||||
"denyTTL": 30,
|
||||
"retryBackoff": 500,
|
||||
"defaultAllow": true
|
||||
},
|
||||
"podNodeSelectorPluginConfig": {
|
||||
"clusterDefaultNodeSelector": ""
|
||||
}
|
||||
}`,
|
||||
ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
|
||||
Plugins: []apiserver.AdmissionPluginConfiguration{
|
||||
{
|
||||
Name: "ImagePolicyWebhook",
|
||||
Path: configFileName,
|
||||
},
|
||||
{
|
||||
Name: "PodNodeSelector",
|
||||
Path: configFileName,
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginNames: []string{"ImagePolicyWebhook", "PodNodeSelector"},
|
||||
},
|
||||
"legacy configuration not using legacy plugins": {
|
||||
ConfigBody: `{
|
||||
"imagePolicy": {
|
||||
"kubeConfigFile": "/home/user/.kube/config",
|
||||
"allowTTL": 30,
|
||||
"denyTTL": 30,
|
||||
"retryBackoff": 500,
|
||||
"defaultAllow": true
|
||||
},
|
||||
"podNodeSelectorPluginConfig": {
|
||||
"clusterDefaultNodeSelector": ""
|
||||
}
|
||||
}`,
|
||||
ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{},
|
||||
PluginNames: []string{"NamespaceLifecycle", "InitialResources"},
|
||||
},
|
||||
}
|
||||
for testName, testCase := range testCases {
|
||||
if err = ioutil.WriteFile(configFileName, []byte(testCase.ConfigBody), 0644); err != nil {
|
||||
t.Fatalf("unexpected err writing temp file: %v", err)
|
||||
}
|
||||
config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(config.(configProvider).config, testCase.ExpectedAdmissionConfig) {
|
||||
t.Errorf("%s: Expected:\n\t%#v\nGot:\n\t%#v", testName, testCase.ExpectedAdmissionConfig, config.(configProvider).config)
|
||||
}
|
||||
}
|
||||
}
|
||||
72
vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
Normal file
72
vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2015 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 (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
func extractResourceName(a Attributes) (name string, resource schema.GroupResource, err error) {
|
||||
resource = a.GetResource().GroupResource()
|
||||
|
||||
if len(a.GetName()) > 0 {
|
||||
return a.GetName(), resource, nil
|
||||
}
|
||||
|
||||
name = "Unknown"
|
||||
obj := a.GetObject()
|
||||
if obj != nil {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
// not all object have ObjectMeta. If we don't, return a name with a slash (always illegal)
|
||||
return "Unknown/errorGettingName", resource, nil
|
||||
}
|
||||
|
||||
// this is necessary because name object name generation has not occurred yet
|
||||
if len(accessor.GetName()) > 0 {
|
||||
name = accessor.GetName()
|
||||
} else if len(accessor.GetGenerateName()) > 0 {
|
||||
name = accessor.GetGenerateName()
|
||||
}
|
||||
}
|
||||
return name, resource, nil
|
||||
}
|
||||
|
||||
// NewForbidden is a utility function to return a well-formatted admission control error response
|
||||
func NewForbidden(a Attributes, internalError error) error {
|
||||
// do not double wrap an error of same type
|
||||
if apierrors.IsForbidden(internalError) {
|
||||
return internalError
|
||||
}
|
||||
name, resource, err := extractResourceName(a)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err}))
|
||||
}
|
||||
return apierrors.NewForbidden(resource, name, internalError)
|
||||
}
|
||||
|
||||
// NewNotFound is a utility function to return a well-formatted admission control error response
|
||||
func NewNotFound(a Attributes) error {
|
||||
name, resource, err := extractResourceName(a)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
return apierrors.NewNotFound(resource, name)
|
||||
}
|
||||
62
vendor/k8s.io/apiserver/pkg/admission/errors_test.go
generated
vendored
Normal file
62
vendor/k8s.io/apiserver/pkg/admission/errors_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2015 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 (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// makes sure that we never get:
|
||||
// Internal error occurred: [some error, object does not implement the Object interfaces]
|
||||
func TestNewForbidden(t *testing.T) {
|
||||
attributes := NewAttributesRecord(
|
||||
&fakeObj{},
|
||||
nil,
|
||||
schema.GroupVersionKind{Group: "foo", Version: "bar", Kind: "Baz"},
|
||||
"",
|
||||
"",
|
||||
schema.GroupVersionResource{Group: "foo", Version: "bar", Resource: "baz"},
|
||||
"",
|
||||
Create,
|
||||
nil)
|
||||
err := errors.New("some error")
|
||||
expectedErr := `baz.foo "Unknown/errorGettingName" is forbidden: some error`
|
||||
|
||||
actualErr := NewForbidden(attributes, err)
|
||||
if actualErr.Error() != expectedErr {
|
||||
t.Errorf("expected %v, got %v", expectedErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeObj struct{}
|
||||
type fakeObjKind struct{}
|
||||
|
||||
func (f *fakeObj) GetObjectKind() schema.ObjectKind {
|
||||
return &fakeObjKind{}
|
||||
}
|
||||
func (f *fakeObj) DeepCopyObject() runtime.Object {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *fakeObjKind) SetGroupVersionKind(kind schema.GroupVersionKind) {}
|
||||
func (f *fakeObjKind) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{Group: "foo", Version: "bar", Kind: "Baz"}
|
||||
}
|
||||
85
vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
Normal file
85
vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2015 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 (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
// timeToWaitForReady is the amount of time to wait to let an admission controller to be ready to satisfy a request.
|
||||
// this is useful when admission controllers need to warm their caches before letting requests through.
|
||||
timeToWaitForReady = 10 * time.Second
|
||||
)
|
||||
|
||||
// ReadyFunc is a function that returns true if the admission controller is ready to handle requests.
|
||||
type ReadyFunc func() bool
|
||||
|
||||
// Handler is a base for admission control handlers that
|
||||
// support a predefined set of operations
|
||||
type Handler struct {
|
||||
operations sets.String
|
||||
readyFunc ReadyFunc
|
||||
}
|
||||
|
||||
// Handles returns true for methods that this handler supports
|
||||
func (h *Handler) Handles(operation Operation) bool {
|
||||
return h.operations.Has(string(operation))
|
||||
}
|
||||
|
||||
// NewHandler creates a new base handler that handles the passed
|
||||
// in operations
|
||||
func NewHandler(ops ...Operation) *Handler {
|
||||
operations := sets.NewString()
|
||||
for _, op := range ops {
|
||||
operations.Insert(string(op))
|
||||
}
|
||||
return &Handler{
|
||||
operations: operations,
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadyFunc allows late registration of a ReadyFunc to know if the handler is ready to process requests.
|
||||
func (h *Handler) SetReadyFunc(readyFunc ReadyFunc) {
|
||||
h.readyFunc = readyFunc
|
||||
}
|
||||
|
||||
// WaitForReady will wait for the readyFunc (if registered) to return ready, and in case of timeout, will return false.
|
||||
func (h *Handler) WaitForReady() bool {
|
||||
// there is no ready func configured, so we return immediately
|
||||
if h.readyFunc == nil {
|
||||
return true
|
||||
}
|
||||
return h.waitForReadyInternal(time.After(timeToWaitForReady))
|
||||
}
|
||||
|
||||
func (h *Handler) waitForReadyInternal(timeout <-chan time.Time) bool {
|
||||
// there is no configured ready func, so return immediately
|
||||
if h.readyFunc == nil {
|
||||
return true
|
||||
}
|
||||
for !h.readyFunc() {
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-timeout:
|
||||
return h.readyFunc()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
47
vendor/k8s.io/apiserver/pkg/admission/initializer/BUILD
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/pkg/admission/initializer/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"initializer.go",
|
||||
"interfaces.go",
|
||||
],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["initializer_test.go"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
57
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
Normal file
57
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2017 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 initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type pluginInitializer struct {
|
||||
externalClient kubernetes.Interface
|
||||
externalInformers informers.SharedInformerFactory
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
// New creates an instance of admission plugins initializer.
|
||||
func New(extClientset kubernetes.Interface, extInformers informers.SharedInformerFactory, authz authorizer.Authorizer) (pluginInitializer, error) {
|
||||
return pluginInitializer{
|
||||
externalClient: extClientset,
|
||||
externalInformers: extInformers,
|
||||
authorizer: authz,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Initialize checks the initialization interfaces implemented by a plugin
|
||||
// and provide the appropriate initialization data
|
||||
func (i pluginInitializer) Initialize(plugin admission.Interface) {
|
||||
if wants, ok := plugin.(WantsExternalKubeClientSet); ok {
|
||||
wants.SetExternalKubeClientSet(i.externalClient)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsExternalKubeInformerFactory); ok {
|
||||
wants.SetExternalKubeInformerFactory(i.externalInformers)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsAuthorizer); ok {
|
||||
wants.SetAuthorizer(i.authorizer)
|
||||
}
|
||||
}
|
||||
|
||||
var _ admission.PluginInitializer = pluginInitializer{}
|
||||
122
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go
generated
vendored
Normal file
122
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Copyright 2017 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 initializer_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
// TestWantsAuthorizer ensures that the authorizer is injected
|
||||
// when the WantsAuthorizer interface is implemented by a plugin.
|
||||
func TestWantsAuthorizer(t *testing.T) {
|
||||
target, err := initializer.New(nil, nil, &TestAuthorizer{})
|
||||
if err != nil {
|
||||
t.Fatalf("expected to create an instance of initializer but got an error = %s", err.Error())
|
||||
}
|
||||
wantAuthorizerAdmission := &WantAuthorizerAdmission{}
|
||||
target.Initialize(wantAuthorizerAdmission)
|
||||
if wantAuthorizerAdmission.auth == nil {
|
||||
t.Errorf("expected authorizer to be initialized but found nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWantsExternalKubeClientSet ensures that the clienset is injected
|
||||
// when the WantsExternalKubeClientSet interface is implemented by a plugin.
|
||||
func TestWantsExternalKubeClientSet(t *testing.T) {
|
||||
cs := &fake.Clientset{}
|
||||
target, err := initializer.New(cs, nil, &TestAuthorizer{})
|
||||
if err != nil {
|
||||
t.Fatalf("expected to create an instance of initializer but got an error = %s", err.Error())
|
||||
}
|
||||
wantExternalKubeClientSet := &WantExternalKubeClientSet{}
|
||||
target.Initialize(wantExternalKubeClientSet)
|
||||
if wantExternalKubeClientSet.cs != cs {
|
||||
t.Errorf("expected clientset to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWantsExternalKubeInformerFactory ensures that the informer factory is injected
|
||||
// when the WantsExternalKubeInformerFactory interface is implemented by a plugin.
|
||||
func TestWantsExternalKubeInformerFactory(t *testing.T) {
|
||||
cs := &fake.Clientset{}
|
||||
sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second)
|
||||
target, err := initializer.New(cs, sf, &TestAuthorizer{})
|
||||
if err != nil {
|
||||
t.Fatalf("expected to create an instance of initializer but got an error = %s", err.Error())
|
||||
}
|
||||
wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{}
|
||||
target.Initialize(wantExternalKubeInformerFactory)
|
||||
if wantExternalKubeInformerFactory.sf != sf {
|
||||
t.Errorf("expected informer factory to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
// WantExternalKubeInformerFactory is a test stub that fulfills the WantsExternalKubeInformerFactory interface
|
||||
type WantExternalKubeInformerFactory struct {
|
||||
sf informers.SharedInformerFactory
|
||||
}
|
||||
|
||||
func (self *WantExternalKubeInformerFactory) SetExternalKubeInformerFactory(sf informers.SharedInformerFactory) {
|
||||
self.sf = sf
|
||||
}
|
||||
func (self *WantExternalKubeInformerFactory) Admit(a admission.Attributes) error { return nil }
|
||||
func (self *WantExternalKubeInformerFactory) Handles(o admission.Operation) bool { return false }
|
||||
func (self *WantExternalKubeInformerFactory) Validate() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantExternalKubeInformerFactory{}
|
||||
var _ initializer.WantsExternalKubeInformerFactory = &WantExternalKubeInformerFactory{}
|
||||
|
||||
// WantExternalKubeClientSet is a test stub that fulfills the WantsExternalKubeClientSet interface
|
||||
type WantExternalKubeClientSet struct {
|
||||
cs kubernetes.Interface
|
||||
}
|
||||
|
||||
func (self *WantExternalKubeClientSet) SetExternalKubeClientSet(cs kubernetes.Interface) { self.cs = cs }
|
||||
func (self *WantExternalKubeClientSet) Admit(a admission.Attributes) error { return nil }
|
||||
func (self *WantExternalKubeClientSet) Handles(o admission.Operation) bool { return false }
|
||||
func (self *WantExternalKubeClientSet) Validate() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantExternalKubeClientSet{}
|
||||
var _ initializer.WantsExternalKubeClientSet = &WantExternalKubeClientSet{}
|
||||
|
||||
// WantAuthorizerAdmission is a test stub that fulfills the WantsAuthorizer interface.
|
||||
type WantAuthorizerAdmission struct {
|
||||
auth authorizer.Authorizer
|
||||
}
|
||||
|
||||
func (self *WantAuthorizerAdmission) SetAuthorizer(a authorizer.Authorizer) { self.auth = a }
|
||||
func (self *WantAuthorizerAdmission) Admit(a admission.Attributes) error { return nil }
|
||||
func (self *WantAuthorizerAdmission) Handles(o admission.Operation) bool { return false }
|
||||
func (self *WantAuthorizerAdmission) Validate() error { return nil }
|
||||
|
||||
var _ admission.Interface = &WantAuthorizerAdmission{}
|
||||
var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{}
|
||||
|
||||
// TestAuthorizer is a test stub for testing that fulfills the authorizer interface.
|
||||
type TestAuthorizer struct{}
|
||||
|
||||
func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
return false, "", nil
|
||||
}
|
||||
42
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
Normal file
42
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2017 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 initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
||||
type WantsExternalKubeClientSet interface {
|
||||
SetExternalKubeClientSet(kubernetes.Interface)
|
||||
admission.Validator
|
||||
}
|
||||
|
||||
// WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it
|
||||
type WantsExternalKubeInformerFactory interface {
|
||||
SetExternalKubeInformerFactory(informers.SharedInformerFactory)
|
||||
admission.Validator
|
||||
}
|
||||
|
||||
// WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it.
|
||||
type WantsAuthorizer interface {
|
||||
SetAuthorizer(authorizer.Authorizer)
|
||||
admission.Validator
|
||||
}
|
||||
90
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
Normal file
90
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2014 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 (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// Attributes is an interface used by AdmissionController to get information about a request
|
||||
// that is used to make an admission decision.
|
||||
type Attributes interface {
|
||||
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
|
||||
// may omit name and rely on the server to generate the name. If that is the case, this method will return
|
||||
// the empty string
|
||||
GetName() string
|
||||
// GetNamespace is the namespace associated with the request (if any)
|
||||
GetNamespace() string
|
||||
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
GetResource() schema.GroupVersionResource
|
||||
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||
GetSubresource() string
|
||||
// GetOperation is the operation being performed
|
||||
GetOperation() Operation
|
||||
// GetObject is the object from the incoming request prior to default values being applied
|
||||
GetObject() runtime.Object
|
||||
// GetOldObject is the existing object. Only populated for UPDATE requests.
|
||||
GetOldObject() runtime.Object
|
||||
// GetKind is the type of object being manipulated. For example: Pod
|
||||
GetKind() schema.GroupVersionKind
|
||||
// GetUserInfo is information about the requesting user
|
||||
GetUserInfo() user.Info
|
||||
}
|
||||
|
||||
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||
type Interface interface {
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
Admit(a Attributes) (err error)
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
Handles(operation Operation) bool
|
||||
}
|
||||
|
||||
// Operation is the type of resource operation being checked for admission control
|
||||
type Operation string
|
||||
|
||||
// Operation constants
|
||||
const (
|
||||
Create Operation = "CREATE"
|
||||
Update Operation = "UPDATE"
|
||||
Delete Operation = "DELETE"
|
||||
Connect Operation = "CONNECT"
|
||||
)
|
||||
|
||||
// PluginInitializer is used for initialization of shareable resources between admission plugins.
|
||||
// After initialization the resources have to be set separately
|
||||
type PluginInitializer interface {
|
||||
Initialize(plugin Interface)
|
||||
}
|
||||
|
||||
// Validator holds Validate functions, which are responsible for validation of initialized shared resources
|
||||
// and should be implemented on admission plugins
|
||||
type Validator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
|
||||
type ConfigProvider interface {
|
||||
ConfigFor(pluginName string) (io.Reader, error)
|
||||
}
|
||||
61
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD
generated
vendored
Normal file
61
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
Copyright 2015 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 lifecycle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of admission plug-in
|
||||
PluginName = "NamespaceLifecycle"
|
||||
// how long a namespace stays in the force live lookup cache before expiration.
|
||||
forceLiveLookupTTL = 30 * time.Second
|
||||
// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
|
||||
// this accomplishes two things:
|
||||
// 1. It allows a watch-fed cache time to observe a namespace creation event
|
||||
// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
|
||||
// so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
|
||||
missingNamespaceWait = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
|
||||
})
|
||||
}
|
||||
|
||||
// lifecycle is an implementation of admission.Interface.
|
||||
// It enforces life-cycle constraints around a Namespace depending on its Phase
|
||||
type lifecycle struct {
|
||||
*admission.Handler
|
||||
client kubernetes.Interface
|
||||
immortalNamespaces sets.String
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
|
||||
// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
|
||||
forceLiveLookupCache *utilcache.LRUExpireCache
|
||||
}
|
||||
|
||||
type forceLiveLookupEntry struct {
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
var _ = initializer.WantsExternalKubeInformerFactory(&lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&lifecycle{})
|
||||
|
||||
func makeNamespaceKey(namespace string) *v1.Namespace {
|
||||
return &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lifecycle) Admit(a admission.Attributes) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
}
|
||||
|
||||
// always allow non-namespaced resources
|
||||
if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
// if a namespace is deleted, we want to prevent all further creates into it
|
||||
// while it is undergoing termination. to reduce incidences where the cache
|
||||
// is slow to update, we add the namespace into a force live lookup list to ensure
|
||||
// we are not looking at stale state.
|
||||
if a.GetOperation() == admission.Delete {
|
||||
l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
|
||||
}
|
||||
// allow all operations to namespaces
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow deletion of other resources
|
||||
if a.GetOperation() == admission.Delete {
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow access review checks. Returning status about the namespace would be leaking information
|
||||
if isAccessReview(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !l.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
var (
|
||||
exists bool
|
||||
err error
|
||||
)
|
||||
|
||||
namespace, err := l.namespaceLister.Get(a.GetNamespace())
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
exists = true
|
||||
}
|
||||
|
||||
if !exists && a.GetOperation() == admission.Create {
|
||||
// give the cache time to observe the namespace before rejecting a create.
|
||||
// this helps when creating a namespace and immediately creating objects within it.
|
||||
time.Sleep(missingNamespaceWait)
|
||||
namespace, err = l.namespaceLister.Get(a.GetNamespace())
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// no-op
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
default:
|
||||
exists = true
|
||||
}
|
||||
if exists {
|
||||
glog.V(4).Infof("found %s in cache after waiting", a.GetNamespace())
|
||||
}
|
||||
}
|
||||
|
||||
// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
|
||||
forceLiveLookup := false
|
||||
if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
|
||||
// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
|
||||
forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
|
||||
}
|
||||
|
||||
// refuse to operate on non-existent namespaces
|
||||
if !exists || forceLiveLookup {
|
||||
// as a last resort, make a call directly to storage
|
||||
namespace, err = l.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
return err
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
glog.V(4).Infof("found %s via storage lookup", a.GetNamespace())
|
||||
}
|
||||
|
||||
// ensure that we're not trying to create objects in terminating namespaces
|
||||
if a.GetOperation() == admission.Create {
|
||||
if namespace.Status.Phase != v1.NamespaceTerminating {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: This should probably not be a 403
|
||||
return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated.", a.GetNamespace()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewLifecycle creates a new namespace lifecycle admission control handler
|
||||
func NewLifecycle(immortalNamespaces sets.String) (admission.Interface, error) {
|
||||
return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (admission.Interface, error) {
|
||||
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
|
||||
return &lifecycle{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
immortalNamespaces: immortalNamespaces,
|
||||
forceLiveLookupCache: forceLiveLookupCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().V1().Namespaces()
|
||||
l.namespaceLister = namespaceInformer.Lister()
|
||||
l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
func (l *lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
l.client = client
|
||||
}
|
||||
|
||||
func (l *lifecycle) Validate() error {
|
||||
if l.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if l.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// accessReviewResources are resources which give a view into permissions in a namespace. Users must be allowed to create these
|
||||
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
|
||||
var accessReviewResources = map[schema.GroupResource]bool{
|
||||
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
|
||||
}
|
||||
|
||||
func isAccessReview(a admission.Attributes) bool {
|
||||
return accessReviewResources[a.GetResource().GroupResource()]
|
||||
}
|
||||
298
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go
generated
vendored
Normal file
298
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
Copyright 2015 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 lifecycle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
kubeadmission "k8s.io/apiserver/pkg/admission/initializer"
|
||||
informers "k8s.io/client-go/informers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns a configured handler for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
return newHandlerForTestWithClock(c, clock.RealClock{})
|
||||
}
|
||||
|
||||
// newHandlerForTestWithClock returns a configured handler for testing.
|
||||
func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler, err := newLifecycleWithClock(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem), cacheClock)
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
pluginInitializer, err := kubeadmission.New(c, f, nil)
|
||||
if err != nil {
|
||||
return handler, f, err
|
||||
}
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.Validate(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces with the specified phase.
|
||||
func newMockClientForTest(namespaces map[string]v1.NamespacePhase) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &v1.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
index := 0
|
||||
for name, phase := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
ResourceVersion: fmt.Sprintf("%d", index),
|
||||
},
|
||||
Status: v1.NamespaceStatus{
|
||||
Phase: phase,
|
||||
},
|
||||
})
|
||||
index++
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) v1.Pod {
|
||||
return v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{{Name: "vol"}},
|
||||
Containers: []v1.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessReviewCheckOnMissingNamespace(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]v1.NamespacePhase{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{Group: "authorization.k8s.io", Version: "v1", Kind: "LocalSubjectAccesReview"}, namespace, "", schema.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1", Resource: "localsubjectaccessreviews"}, "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist.
|
||||
func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]v1.NamespacePhase{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
actions := ""
|
||||
for _, action := range mockClient.Actions() {
|
||||
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
|
||||
}
|
||||
t.Errorf("expected error returned from admission handler: %v", actions)
|
||||
}
|
||||
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace when it is missing")
|
||||
}
|
||||
|
||||
// verify update operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting updates in a namespace when it is missing")
|
||||
}
|
||||
|
||||
// verify delete operations in the namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceActive verifies a resource is admitted when the namespace is active.
|
||||
func TestAdmissionNamespaceActive(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]v1.NamespacePhase{
|
||||
namespace: v1.NamespaceActive,
|
||||
})
|
||||
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceTerminating verifies a resource is not created when the namespace is active.
|
||||
func TestAdmissionNamespaceTerminating(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]v1.NamespacePhase{
|
||||
namespace: v1.NamespaceTerminating,
|
||||
})
|
||||
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace when it is terminating")
|
||||
}
|
||||
|
||||
// verify update operations in the namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
|
||||
// verify delete operations in the namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
|
||||
// verify delete of namespace default can never proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), "", metav1.NamespaceDefault, v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error that this namespace can never be deleted")
|
||||
}
|
||||
|
||||
// verify delete of namespace other than default can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), "", "other", v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect an error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceForceLiveLookup verifies live lookups are done after deleting a namespace
|
||||
func TestAdmissionNamespaceForceLiveLookup(t *testing.T) {
|
||||
namespace := "test"
|
||||
getCalls := int64(0)
|
||||
phases := map[string]v1.NamespacePhase{namespace: v1.NamespaceActive}
|
||||
mockClient := newMockClientForTest(phases)
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
getCalls++
|
||||
return true, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}, Status: v1.NamespaceStatus{Phase: phases[namespace]}}, nil
|
||||
})
|
||||
|
||||
fakeClock := clock.NewFakeClock(time.Now())
|
||||
|
||||
handler, informerFactory, err := newHandlerForTestWithClock(mockClient, fakeClock)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
// verify create operations in the namespace is allowed
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error rejecting creates in an active namespace")
|
||||
}
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookups of the namespace, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// verify delete of namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), namespace, namespace, v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected namespace deletion to be allowed")
|
||||
}
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookups of the namespace, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// simulate the phase changing
|
||||
phases[namespace] = v1.NamespaceTerminating
|
||||
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace right after deleting it")
|
||||
}
|
||||
if getCalls != 1 {
|
||||
t.Errorf("Expected a live lookup of the namespace at t=0, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// Ensure the live lookup is still forced up to forceLiveLookupTTL
|
||||
fakeClock.Step(forceLiveLookupTTL)
|
||||
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace right after deleting it")
|
||||
}
|
||||
if getCalls != 1 {
|
||||
t.Errorf("Expected a live lookup of the namespace at t=forceLiveLookupTTL, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// Ensure the live lookup expires
|
||||
fakeClock.Step(time.Millisecond)
|
||||
|
||||
// verify create operations in the namespace don't force a live lookup after the timeout
|
||||
handler.Admit(admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookup of the namespace at t=forceLiveLookupTTL+1ms, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
}
|
||||
184
vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
Normal file
184
vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
Copyright 2014 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Factory is a function that returns an Interface for admission decisions.
|
||||
// The config parameter provides an io.Reader handler to the factory in
|
||||
// order to load specific configurations. If no configuration is provided
|
||||
// the parameter is nil.
|
||||
type Factory func(config io.Reader) (Interface, error)
|
||||
|
||||
type Plugins struct {
|
||||
lock sync.Mutex
|
||||
registry map[string]Factory
|
||||
}
|
||||
|
||||
// All registered admission options.
|
||||
var (
|
||||
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
|
||||
PluginEnabledFn = func(name string, config io.Reader) bool {
|
||||
return true
|
||||
}
|
||||
)
|
||||
|
||||
// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled
|
||||
type PluginEnabledFunc func(name string, config io.Reader) bool
|
||||
|
||||
// Registered enumerates the names of all registered plugins.
|
||||
func (ps *Plugins) Registered() []string {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
keys := []string{}
|
||||
for k := range ps.registry {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// Register registers a plugin Factory by name. This
|
||||
// is expected to happen during app startup.
|
||||
func (ps *Plugins) Register(name string, plugin Factory) {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
if ps.registry != nil {
|
||||
_, found := ps.registry[name]
|
||||
if found {
|
||||
glog.Fatalf("Admission plugin %q was registered twice", name)
|
||||
}
|
||||
} else {
|
||||
ps.registry = map[string]Factory{}
|
||||
}
|
||||
|
||||
glog.V(1).Infof("Registered admission plugin %q", name)
|
||||
ps.registry[name] = plugin
|
||||
}
|
||||
|
||||
// getPlugin creates an instance of the named plugin. It returns `false` if the
|
||||
// the name is not known. The error is returned only when the named provider was
|
||||
// known but failed to initialize. The config parameter specifies the io.Reader
|
||||
// handler of the configuration file for the cloud provider, or nil for no configuration.
|
||||
func (ps *Plugins) getPlugin(name string, config io.Reader) (Interface, bool, error) {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
f, found := ps.registry[name]
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
config1, config2, err := splitStream(config)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if !PluginEnabledFn(name, config1) {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
ret, err := f(config2)
|
||||
return ret, true, err
|
||||
}
|
||||
|
||||
// splitStream reads the stream bytes and constructs two copies of it.
|
||||
func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
|
||||
if config == nil || reflect.ValueOf(config).IsNil() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
configBytes, err := ioutil.ReadAll(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
|
||||
}
|
||||
|
||||
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
|
||||
// the given plugins.
|
||||
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer) (Interface, error) {
|
||||
plugins := []Interface{}
|
||||
for _, pluginName := range pluginNames {
|
||||
pluginConfig, err := configProvider.ConfigFor(pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plugin, err := ps.InitPlugin(pluginName, pluginConfig, pluginInitializer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plugin != nil {
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
}
|
||||
return chainAdmissionHandler(plugins), nil
|
||||
}
|
||||
|
||||
// InitPlugin creates an instance of the named interface.
|
||||
func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
|
||||
if name == "" {
|
||||
glog.Info("No admission plugin specified.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
plugin, found, err := ps.getPlugin(name, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't init admission plugin %q: %v", name, err)
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("Unknown admission plugin: %s", name)
|
||||
}
|
||||
|
||||
pluginInitializer.Initialize(plugin)
|
||||
// ensure that plugins have been properly initialized
|
||||
if err := Validate(plugin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
// Validate will call the Validate function in each plugin if they implement
|
||||
// the Validator interface.
|
||||
func Validate(plugin Interface) error {
|
||||
if validater, ok := plugin.(Validator); ok {
|
||||
err := validater.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PluginInitializers []PluginInitializer
|
||||
|
||||
func (pp PluginInitializers) Initialize(plugin Interface) {
|
||||
for _, p := range pp {
|
||||
p.Initialize(plugin)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue