Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
420906bb
Commit
420906bb
authored
Jan 12, 2017
by
Derek Carr
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add support for versioned configuration in admission
parent
a2a6b218
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
355 additions
and
20 deletions
+355
-20
BUILD
pkg/admission/BUILD
+15
-2
config.go
pkg/admission/config.go
+176
-0
config_test.go
pkg/admission/config_test.go
+148
-0
plugins.go
pkg/admission/plugins.go
+13
-18
admission.go
plugin/pkg/admission/imagepolicy/admission.go
+1
-0
admission.go
plugin/pkg/admission/initialresources/admission.go
+1
-0
admission.go
plugin/pkg/admission/podnodeselector/admission.go
+1
-0
No files found.
pkg/admission/BUILD
View file @
420906bb
...
...
@@ -13,6 +13,7 @@ go_library(
srcs = [
"attributes.go",
"chain.go",
"config.go",
"errors.go",
"handler.go",
"interfaces.go",
...
...
@@ -20,7 +21,12 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/errors:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/componentconfig/v1alpha1:go_default_library",
"//pkg/util/sets:go_default_library",
"//vendor:github.com/ghodss/yaml",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/runtime",
...
...
@@ -33,10 +39,17 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["chain_test.go"],
srcs = [
"chain_test.go",
"config_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = ["//vendor:k8s.io/apimachinery/pkg/runtime/schema"],
deps = [
"//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/componentconfig/install:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
],
)
filegroup(
...
...
pkg/admission/config.go
0 → 100644
View file @
420906bb
/*
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/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/componentconfig"
componentconfigv1alpha1
"k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1"
"k8s.io/kubernetes/pkg/util/sets"
runtime
"k8s.io/apimachinery/pkg/runtime"
)
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
)
(
*
componentconfig
.
AdmissionConfiguration
,
error
)
{
if
configFilePath
==
""
{
return
&
componentconfig
.
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
:=
api
.
Codecs
.
UniversalDecoder
()
decodedObj
,
err
:=
runtime
.
Decode
(
decoder
,
data
)
// we were able to decode the file successfully
if
err
==
nil
{
decodedConfig
,
ok
:=
decodedObj
.
(
*
componentconfig
.
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
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
:=
&
componentconfigv1alpha1
.
AdmissionConfiguration
{}
for
_
,
pluginName
:=
range
pluginNames
{
if
legacyPluginsWithUnversionedConfig
.
Has
(
pluginName
)
{
externalConfig
.
Plugins
=
append
(
externalConfig
.
Plugins
,
componentconfigv1alpha1
.
AdmissionPluginConfiguration
{
Name
:
pluginName
,
Path
:
configFilePath
})
}
}
api
.
Scheme
.
Default
(
externalConfig
)
internalConfig
:=
&
componentconfig
.
AdmissionConfiguration
{}
if
err
:=
api
.
Scheme
.
Convert
(
externalConfig
,
internalConfig
,
nil
);
err
!=
nil
{
return
internalConfig
,
err
}
return
internalConfig
,
nil
}
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
func
GetAdmissionPluginConfigurationFor
(
pluginCfg
componentconfig
.
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
GetAdmissionPluginConfiguration
(
cfg
*
componentconfig
.
AdmissionConfiguration
,
pluginName
string
)
(
io
.
Reader
,
error
)
{
// there is no config, so there is no potential config
if
cfg
==
nil
{
return
nil
,
nil
}
// look for matching plugin and get configuration
for
_
,
pluginCfg
:=
range
cfg
.
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
(
api
.
Codecs
.
LegacyCodec
(),
obj
)
if
err
!=
nil
{
return
nil
,
err
}
content
,
err
:=
yaml
.
JSONToYAML
(
json
)
if
err
!=
nil
{
return
nil
,
err
}
return
content
,
err
}
pkg/admission/config_test.go
0 → 100644
View file @
420906bb
/*
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/kubernetes/pkg/apis/componentconfig"
_
"k8s.io/kubernetes/pkg/apis/componentconfig/install"
)
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
*
componentconfig
.
AdmissionConfiguration
PluginNames
[]
string
}{
"v1Alpha1 configuration - path fixup"
:
{
ConfigBody
:
`{
"apiVersion": "componentconfig/v1alpha1",
"kind": "AdmissionConfiguration",
"plugins": [
{"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"},
{"name": "ResourceQuota"}
]}`
,
ExpectedAdmissionConfig
:
&
componentconfig
.
AdmissionConfiguration
{
Plugins
:
[]
componentconfig
.
AdmissionPluginConfiguration
{
{
Name
:
"ImagePolicyWebhook"
,
Path
:
imagePolicyWebhookFile
,
},
{
Name
:
"ResourceQuota"
,
},
},
},
PluginNames
:
[]
string
{},
},
"v1Alpha1 configuration - abspath"
:
{
ConfigBody
:
`{
"apiVersion": "componentconfig/v1alpha1",
"kind": "AdmissionConfiguration",
"plugins": [
{"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"},
{"name": "ResourceQuota"}
]}`
,
ExpectedAdmissionConfig
:
&
componentconfig
.
AdmissionConfiguration
{
Plugins
:
[]
componentconfig
.
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
:
&
componentconfig
.
AdmissionConfiguration
{
Plugins
:
[]
componentconfig
.
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
:
&
componentconfig
.
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
,
testCase
.
ExpectedAdmissionConfig
)
{
t
.
Errorf
(
"%s: Expected:
\n\t
%#v
\n
Got:
\n\t
%#v"
,
testName
,
testCase
.
ExpectedAdmissionConfig
,
config
)
}
}
}
pkg/admission/plugins.go
View file @
420906bb
...
...
@@ -21,7 +21,6 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"sort"
"sync"
...
...
@@ -115,9 +114,20 @@ func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
// the given plugins.
func
NewFromPlugins
(
pluginNames
[]
string
,
configFilePath
string
,
pluginInitializer
PluginInitializer
)
(
Interface
,
error
)
{
// load config file path into a componentconfig.AdmissionConfiguration
admissionCfg
,
err
:=
ReadAdmissionConfiguration
(
pluginNames
,
configFilePath
)
if
err
!=
nil
{
return
nil
,
err
}
plugins
:=
[]
Interface
{}
for
_
,
pluginName
:=
range
pluginNames
{
plugin
,
err
:=
InitPlugin
(
pluginName
,
configFilePath
,
pluginInitializer
)
pluginConfig
,
err
:=
GetAdmissionPluginConfiguration
(
admissionCfg
,
pluginName
)
if
err
!=
nil
{
return
nil
,
err
}
plugin
,
err
:=
InitPlugin
(
pluginName
,
pluginConfig
,
pluginInitializer
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -129,27 +139,12 @@ func NewFromPlugins(pluginNames []string, configFilePath string, pluginInitializ
}
// InitPlugin creates an instance of the named interface.
func
InitPlugin
(
name
string
,
configFilePath
string
,
pluginInitializer
PluginInitializer
)
(
Interface
,
error
)
{
var
(
config
*
os
.
File
err
error
)
func
InitPlugin
(
name
string
,
config
io
.
Reader
,
pluginInitializer
PluginInitializer
)
(
Interface
,
error
)
{
if
name
==
""
{
glog
.
Info
(
"No admission plugin specified."
)
return
nil
,
nil
}
if
configFilePath
!=
""
{
config
,
err
=
os
.
Open
(
configFilePath
)
if
err
!=
nil
{
glog
.
Fatalf
(
"Couldn't open admission plugin configuration %s: %#v"
,
configFilePath
,
err
)
}
defer
config
.
Close
()
}
plugin
,
found
,
err
:=
getPlugin
(
name
,
config
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Couldn't init admission plugin %q: %v"
,
name
,
err
)
...
...
plugin/pkg/admission/imagepolicy/admission.go
View file @
420906bb
...
...
@@ -214,6 +214,7 @@ func (a *imagePolicyWebhook) admitPod(attributes admission.Attributes, review *v
// For additional HTTP configuration, refer to the kubeconfig documentation
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
func
NewImagePolicyWebhook
(
configFile
io
.
Reader
)
(
admission
.
Interface
,
error
)
{
// TODO: move this to a versioned configuration file format
var
config
AdmissionConfig
d
:=
yaml
.
NewYAMLOrJSONDecoder
(
configFile
,
4096
)
err
:=
d
.
Decode
(
&
config
)
...
...
plugin/pkg/admission/initialresources/admission.go
View file @
420906bb
...
...
@@ -47,6 +47,7 @@ const (
// WARNING: this feature is experimental and will definitely change.
func
init
()
{
admission
.
RegisterPlugin
(
"InitialResources"
,
func
(
config
io
.
Reader
)
(
admission
.
Interface
,
error
)
{
// TODO: remove the usage of flags in favor of reading versioned configuration
s
,
err
:=
newDataSource
(
*
source
)
if
err
!=
nil
{
return
nil
,
err
...
...
plugin/pkg/admission/podnodeselector/admission.go
View file @
420906bb
...
...
@@ -41,6 +41,7 @@ var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-select
func
init
()
{
admission
.
RegisterPlugin
(
"PodNodeSelector"
,
func
(
config
io
.
Reader
)
(
admission
.
Interface
,
error
)
{
// TODO move this to a versioned configuration file format.
pluginConfig
:=
readConfig
(
config
)
plugin
:=
NewPodNodeSelector
(
pluginConfig
.
PodNodeSelectorPluginConfig
)
return
plugin
,
nil
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment