| /* |
| 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" |
| "strings" |
| "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 |
| } |
| |
| func NewPlugins() *Plugins { |
| return &Plugins{} |
| } |
| |
| // 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, decorator Decorator) (Interface, error) { |
| handlers := []Interface{} |
| mutationPlugins := []string{} |
| validationPlugins := []string{} |
| 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 { |
| if decorator != nil { |
| handlers = append(handlers, decorator.Decorate(plugin, pluginName)) |
| } else { |
| handlers = append(handlers, plugin) |
| } |
| |
| if _, ok := plugin.(MutationInterface); ok { |
| mutationPlugins = append(mutationPlugins, pluginName) |
| } |
| if _, ok := plugin.(ValidationInterface); ok { |
| validationPlugins = append(validationPlugins, pluginName) |
| } |
| } |
| } |
| if len(mutationPlugins) != 0 { |
| glog.Infof("Loaded %d mutating admission controller(s) successfully in the following order: %s.", len(mutationPlugins), strings.Join(mutationPlugins, ",")) |
| } |
| if len(validationPlugins) != 0 { |
| glog.Infof("Loaded %d validating admission controller(s) successfully in the following order: %s.", len(validationPlugins), strings.Join(validationPlugins, ",")) |
| } |
| return chainAdmissionHandler(handlers), 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 := ValidateInitialization(plugin); err != nil { |
| return nil, fmt.Errorf("failed to initialize admission plugin %q: %v", name, err) |
| } |
| |
| return plugin, nil |
| } |
| |
| // ValidateInitialization will call the InitializationValidate function in each plugin if they implement |
| // the InitializationValidator interface. |
| func ValidateInitialization(plugin Interface) error { |
| if validater, ok := plugin.(InitializationValidator); ok { |
| err := validater.ValidateInitialization() |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| type PluginInitializers []PluginInitializer |
| |
| func (pp PluginInitializers) Initialize(plugin Interface) { |
| for _, p := range pp { |
| p.Initialize(plugin) |
| } |
| } |