vendor: k8s.io/kubernetes/pkg/api/validation@v1.5.3 (#12267)
This commit is contained in:
parent
cf3d234f5c
commit
914e86d087
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Exponent Labs LLC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,66 @@
|
||||||
|
[![GoDoc](https://godoc.org/github.com/exponent-io/jsonpath?status.svg)](https://godoc.org/github.com/exponent-io/jsonpath)
|
||||||
|
[![Build Status](https://travis-ci.org/exponent-io/jsonpath.svg?branch=master)](https://travis-ci.org/exponent-io/jsonpath)
|
||||||
|
|
||||||
|
# jsonpath
|
||||||
|
|
||||||
|
This package extends the [json.Decoder](https://golang.org/pkg/encoding/json/#Decoder) to support navigating a stream of JSON tokens. You should be able to use this extended Decoder places where a json.Decoder would have been used.
|
||||||
|
|
||||||
|
This Decoder has the following enhancements...
|
||||||
|
* The [Scan](https://godoc.org/github.com/exponent-io/jsonpath/#Decoder.Scan) method supports scanning a JSON stream while extracting particular values along the way using [PathActions](https://godoc.org/github.com/exponent-io/jsonpath#PathActions).
|
||||||
|
* The [SeekTo](https://godoc.org/github.com/exponent-io/jsonpath#Decoder.SeekTo) method supports seeking forward in a JSON token stream to a particular path.
|
||||||
|
* The [Path](https://godoc.org/github.com/exponent-io/jsonpath#Decoder.Path) method returns the path of the most recently parsed token.
|
||||||
|
* The [Token](https://godoc.org/github.com/exponent-io/jsonpath#Decoder.Token) method has been modified to distinguish between strings that are object keys and strings that are values. Object key strings are returned as the [KeyString](https://godoc.org/github.com/exponent-io/jsonpath#KeyString) type rather than a native string.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/exponent-io/jsonpath
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
#### SeekTo
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/exponent-io/jsonpath"
|
||||||
|
|
||||||
|
var j = []byte(`[
|
||||||
|
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
|
||||||
|
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
|
||||||
|
]`)
|
||||||
|
|
||||||
|
w := json.NewDecoder(bytes.NewReader(j))
|
||||||
|
var v interface{}
|
||||||
|
|
||||||
|
w.SeekTo(1, "Point", "G")
|
||||||
|
w.Decode(&v) // v is 218
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scan with PathActions
|
||||||
|
|
||||||
|
```go
|
||||||
|
var j = []byte(`{"colors":[
|
||||||
|
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10, "A": 58}},
|
||||||
|
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255, "A": 231}}
|
||||||
|
]}`)
|
||||||
|
|
||||||
|
var actions PathActions
|
||||||
|
|
||||||
|
// Extract the value at Point.A
|
||||||
|
actions.Add(func(d *Decoder) error {
|
||||||
|
var alpha int
|
||||||
|
err := d.Decode(&alpha)
|
||||||
|
fmt.Printf("Alpha: %v\n", alpha)
|
||||||
|
return err
|
||||||
|
}, "Point", "A")
|
||||||
|
|
||||||
|
w := NewDecoder(bytes.NewReader(j))
|
||||||
|
w.SeekTo("colors", 0)
|
||||||
|
|
||||||
|
var ok = true
|
||||||
|
var err error
|
||||||
|
for ok {
|
||||||
|
ok, err = w.Scan(&actions)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,210 @@
|
||||||
|
package jsonpath
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyString is returned from Decoder.Token to represent each key in a JSON object value.
|
||||||
|
type KeyString string
|
||||||
|
|
||||||
|
// Decoder extends the Go runtime's encoding/json.Decoder to support navigating in a stream of JSON tokens.
|
||||||
|
type Decoder struct {
|
||||||
|
json.Decoder
|
||||||
|
|
||||||
|
path JsonPath
|
||||||
|
context jsonContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder creates a new instance of the extended JSON Decoder.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{Decoder: *json.NewDecoder(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeekTo causes the Decoder to move forward to a given path in the JSON structure.
|
||||||
|
//
|
||||||
|
// The path argument must consist of strings or integers. Each string specifies an JSON object key, and
|
||||||
|
// each integer specifies an index into a JSON array.
|
||||||
|
//
|
||||||
|
// Consider the JSON structure
|
||||||
|
//
|
||||||
|
// { "a": [0,"s",12e4,{"b":0,"v":35} ] }
|
||||||
|
//
|
||||||
|
// SeekTo("a",3,"v") will move to the value referenced by the "a" key in the current object,
|
||||||
|
// followed by a move to the 4th value (index 3) in the array, followed by a move to the value at key "v".
|
||||||
|
// In this example, a subsequent call to the decoder's Decode() would unmarshal the value 35.
|
||||||
|
//
|
||||||
|
// SeekTo returns a boolean value indicating whether a match was found.
|
||||||
|
//
|
||||||
|
// Decoder is intended to be used with a stream of tokens. As a result it navigates forward only.
|
||||||
|
func (d *Decoder) SeekTo(path ...interface{}) (bool, error) {
|
||||||
|
|
||||||
|
if len(path) == 0 {
|
||||||
|
return len(d.path) == 0, nil
|
||||||
|
}
|
||||||
|
last := len(path) - 1
|
||||||
|
if i, ok := path[last].(int); ok {
|
||||||
|
path[last] = i - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if d.path.Equal(path) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
_, err := d.Token()
|
||||||
|
if err == io.EOF {
|
||||||
|
return false, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v. This is
|
||||||
|
// equivalent to encoding/json.Decode().
|
||||||
|
func (d *Decoder) Decode(v interface{}) error {
|
||||||
|
switch d.context {
|
||||||
|
case objValue:
|
||||||
|
d.context = objKey
|
||||||
|
break
|
||||||
|
case arrValue:
|
||||||
|
d.path.incTop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return d.Decoder.Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns a slice of string and/or int values representing the path from the root of the JSON object to the
|
||||||
|
// position of the most-recently parsed token.
|
||||||
|
func (d *Decoder) Path() JsonPath {
|
||||||
|
p := make(JsonPath, len(d.path))
|
||||||
|
copy(p, d.path)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token is equivalent to the Token() method on json.Decoder. The primary difference is that it distinguishes
|
||||||
|
// between strings that are keys and and strings that are values. String tokens that are object keys are returned as a
|
||||||
|
// KeyString rather than as a native string.
|
||||||
|
func (d *Decoder) Token() (json.Token, error) {
|
||||||
|
t, err := d.Decoder.Token()
|
||||||
|
if err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t == nil {
|
||||||
|
switch d.context {
|
||||||
|
case objValue:
|
||||||
|
d.context = objKey
|
||||||
|
break
|
||||||
|
case arrValue:
|
||||||
|
d.path.incTop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := t.(type) {
|
||||||
|
case json.Delim:
|
||||||
|
switch t {
|
||||||
|
case json.Delim('{'):
|
||||||
|
if d.context == arrValue {
|
||||||
|
d.path.incTop()
|
||||||
|
}
|
||||||
|
d.path.push("")
|
||||||
|
d.context = objKey
|
||||||
|
break
|
||||||
|
case json.Delim('}'):
|
||||||
|
d.path.pop()
|
||||||
|
d.context = d.path.inferContext()
|
||||||
|
break
|
||||||
|
case json.Delim('['):
|
||||||
|
if d.context == arrValue {
|
||||||
|
d.path.incTop()
|
||||||
|
}
|
||||||
|
d.path.push(-1)
|
||||||
|
d.context = arrValue
|
||||||
|
break
|
||||||
|
case json.Delim(']'):
|
||||||
|
d.path.pop()
|
||||||
|
d.context = d.path.inferContext()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case float64, json.Number, bool:
|
||||||
|
switch d.context {
|
||||||
|
case objValue:
|
||||||
|
d.context = objKey
|
||||||
|
break
|
||||||
|
case arrValue:
|
||||||
|
d.path.incTop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case string:
|
||||||
|
switch d.context {
|
||||||
|
case objKey:
|
||||||
|
d.path.nameTop(t)
|
||||||
|
d.context = objValue
|
||||||
|
return KeyString(t), err
|
||||||
|
case objValue:
|
||||||
|
d.context = objKey
|
||||||
|
case arrValue:
|
||||||
|
d.path.incTop()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan moves forward over the JSON stream consuming all the tokens at the current level (current object, current array)
|
||||||
|
// invoking each matching PathAction along the way.
|
||||||
|
//
|
||||||
|
// Scan returns true if there are more contiguous values to scan (for example in an array).
|
||||||
|
func (d *Decoder) Scan(ext *PathActions) (bool, error) {
|
||||||
|
|
||||||
|
rootPath := d.Path()
|
||||||
|
|
||||||
|
// If this is an array path, increment the root path in our local copy.
|
||||||
|
if rootPath.inferContext() == arrValue {
|
||||||
|
rootPath.incTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// advance the token position
|
||||||
|
_, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
match:
|
||||||
|
var relPath JsonPath
|
||||||
|
|
||||||
|
// capture the new JSON path
|
||||||
|
path := d.Path()
|
||||||
|
|
||||||
|
if len(path) > len(rootPath) {
|
||||||
|
// capture the path relative to where the scan started
|
||||||
|
relPath = path[len(rootPath):]
|
||||||
|
} else {
|
||||||
|
// if the path is not longer than the root, then we are done with this scan
|
||||||
|
// return boolean flag indicating if there are more items to scan at the same level
|
||||||
|
return d.Decoder.More(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match the relative path against the path actions
|
||||||
|
if node := ext.node.match(relPath); node != nil {
|
||||||
|
if node.action != nil {
|
||||||
|
// we have a match so execute the action
|
||||||
|
err = node.action(d)
|
||||||
|
if err != nil {
|
||||||
|
return d.Decoder.More(), err
|
||||||
|
}
|
||||||
|
// The action may have advanced the decoder. If we are in an array, advancing it further would
|
||||||
|
// skip tokens. So, if we are scanning an array, jump to the top without advancing the token.
|
||||||
|
if d.path.inferContext() == arrValue && d.Decoder.More() {
|
||||||
|
goto match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Extends the Go runtime's json.Decoder enabling navigation of a stream of json tokens.
|
||||||
|
package jsonpath
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type jsonContext int
|
||||||
|
|
||||||
|
const (
|
||||||
|
none jsonContext = iota
|
||||||
|
objKey
|
||||||
|
objValue
|
||||||
|
arrValue
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnyIndex can be used in a pattern to match any array index.
|
||||||
|
const AnyIndex = -2
|
||||||
|
|
||||||
|
// JsonPath is a slice of strings and/or integers. Each string specifies an JSON object key, and
|
||||||
|
// each integer specifies an index into a JSON array.
|
||||||
|
type JsonPath []interface{}
|
||||||
|
|
||||||
|
func (p *JsonPath) push(n interface{}) { *p = append(*p, n) }
|
||||||
|
func (p *JsonPath) pop() { *p = (*p)[:len(*p)-1] }
|
||||||
|
|
||||||
|
// increment the index at the top of the stack (must be an array index)
|
||||||
|
func (p *JsonPath) incTop() { (*p)[len(*p)-1] = (*p)[len(*p)-1].(int) + 1 }
|
||||||
|
|
||||||
|
// name the key at the top of the stack (must be an object key)
|
||||||
|
func (p *JsonPath) nameTop(n string) { (*p)[len(*p)-1] = n }
|
||||||
|
|
||||||
|
// infer the context from the item at the top of the stack
|
||||||
|
func (p *JsonPath) inferContext() jsonContext {
|
||||||
|
if len(*p) == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
t := (*p)[len(*p)-1]
|
||||||
|
switch t.(type) {
|
||||||
|
case string:
|
||||||
|
return objKey
|
||||||
|
case int:
|
||||||
|
return arrValue
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Invalid stack type %T", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two JsonPath types.
|
||||||
|
func (p *JsonPath) Equal(o JsonPath) bool {
|
||||||
|
if len(*p) != len(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range *p {
|
||||||
|
if v != o[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *JsonPath) HasPrefix(o JsonPath) bool {
|
||||||
|
for i, v := range o {
|
||||||
|
if v != (*p)[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package jsonpath
|
||||||
|
|
||||||
|
// pathNode is used to construct a trie of paths to be matched
|
||||||
|
type pathNode struct {
|
||||||
|
matchOn interface{} // string, or integer
|
||||||
|
childNodes []pathNode
|
||||||
|
action DecodeAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// match climbs the trie to find a node that matches the given JSON path.
|
||||||
|
func (n *pathNode) match(path JsonPath) *pathNode {
|
||||||
|
var node *pathNode = n
|
||||||
|
for _, ps := range path {
|
||||||
|
found := false
|
||||||
|
for i, n := range node.childNodes {
|
||||||
|
if n.matchOn == ps {
|
||||||
|
node = &node.childNodes[i]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
} else if _, ok := ps.(int); ok && n.matchOn == AnyIndex {
|
||||||
|
node = &node.childNodes[i]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathActions represents a collection of DecodeAction functions that should be called at certain path positions
|
||||||
|
// when scanning the JSON stream. PathActions can be created once and used many times in one or more JSON streams.
|
||||||
|
type PathActions struct {
|
||||||
|
node pathNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeAction handlers are called by the Decoder when scanning objects. See PathActions.Add for more detail.
|
||||||
|
type DecodeAction func(d *Decoder) error
|
||||||
|
|
||||||
|
// Add specifies an action to call on the Decoder when the specified path is encountered.
|
||||||
|
func (je *PathActions) Add(action DecodeAction, path ...interface{}) {
|
||||||
|
|
||||||
|
var node *pathNode = &je.node
|
||||||
|
for _, ps := range path {
|
||||||
|
found := false
|
||||||
|
for i, n := range node.childNodes {
|
||||||
|
if n.matchOn == ps {
|
||||||
|
node = &node.childNodes[i]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
node.childNodes = append(node.childNodes, pathNode{matchOn: ps})
|
||||||
|
node = &node.childNodes[len(node.childNodes)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.action = action
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
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 features
|
||||||
|
|
||||||
|
import (
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Every feature gate should add method here following this template:
|
||||||
|
//
|
||||||
|
// // owner: @username
|
||||||
|
// // alpha: v1.4
|
||||||
|
// MyFeature() bool
|
||||||
|
|
||||||
|
// owner: timstclair
|
||||||
|
// alpha: v1.5
|
||||||
|
//
|
||||||
|
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
|
||||||
|
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
|
||||||
|
StreamingProxyRedirects utilfeature.Feature = "StreamingProxyRedirects"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilfeature.DefaultFeatureGate.Add(defaultKubernetesFeatureGates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
|
||||||
|
// To add a new feature, define a key for it above and add it here. The features will be
|
||||||
|
// available throughout Kubernetes binaries.
|
||||||
|
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
|
||||||
|
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 feature
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Feature string
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagName = "feature-gates"
|
||||||
|
|
||||||
|
// allAlphaGate is a global toggle for alpha features. Per-feature key
|
||||||
|
// values override the default set by allAlphaGate. Examples:
|
||||||
|
// AllAlpha=false,NewFeature=true will result in newFeature=true
|
||||||
|
// AllAlpha=true,NewFeature=false will result in newFeature=false
|
||||||
|
allAlphaGate Feature = "AllAlpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The generic features.
|
||||||
|
defaultFeatures = map[Feature]FeatureSpec{
|
||||||
|
allAlphaGate: {Default: false, PreRelease: Alpha},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for a few gates.
|
||||||
|
specialFeatures = map[Feature]func(f *featureGate, val bool){
|
||||||
|
allAlphaGate: setUnsetAlphaGates,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFeatureGate is a shared global FeatureGate.
|
||||||
|
DefaultFeatureGate = &featureGate{
|
||||||
|
known: defaultFeatures,
|
||||||
|
special: specialFeatures,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeatureSpec struct {
|
||||||
|
Default bool
|
||||||
|
PreRelease prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
type prerelease string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Values for PreRelease.
|
||||||
|
Alpha = prerelease("ALPHA")
|
||||||
|
Beta = prerelease("BETA")
|
||||||
|
GA = prerelease("")
|
||||||
|
)
|
||||||
|
|
||||||
|
// FeatureGate parses and stores flag gates for known features from
|
||||||
|
// a string like feature1=true,feature2=false,...
|
||||||
|
type FeatureGate interface {
|
||||||
|
AddFlag(fs *pflag.FlagSet)
|
||||||
|
Set(value string) error
|
||||||
|
Add(features map[Feature]FeatureSpec)
|
||||||
|
KnownFeatures() []string
|
||||||
|
|
||||||
|
// Every feature gate should add method here following this template:
|
||||||
|
//
|
||||||
|
// // owner: @username
|
||||||
|
// // alpha: v1.4
|
||||||
|
// MyFeature() bool
|
||||||
|
|
||||||
|
// owner: @timstclair
|
||||||
|
// beta: v1.4
|
||||||
|
AppArmor() bool
|
||||||
|
|
||||||
|
// owner: @girishkalele
|
||||||
|
// alpha: v1.4
|
||||||
|
ExternalTrafficLocalOnly() bool
|
||||||
|
|
||||||
|
// owner: @saad-ali
|
||||||
|
// alpha: v1.3
|
||||||
|
DynamicVolumeProvisioning() bool
|
||||||
|
|
||||||
|
// owner: @mtaufen
|
||||||
|
// alpha: v1.4
|
||||||
|
DynamicKubeletConfig() bool
|
||||||
|
|
||||||
|
// owner: timstclair
|
||||||
|
// alpha: v1.5
|
||||||
|
StreamingProxyRedirects() bool
|
||||||
|
|
||||||
|
// owner: @pweil-
|
||||||
|
// alpha: v1.5
|
||||||
|
ExperimentalHostUserNamespaceDefaulting() bool
|
||||||
|
|
||||||
|
// owner: @davidopp
|
||||||
|
// alpha: v1.6
|
||||||
|
// TODO: remove when alpha support for affinity is removed
|
||||||
|
AffinityInAnnotations() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
|
||||||
|
type featureGate struct {
|
||||||
|
known map[Feature]FeatureSpec
|
||||||
|
special map[Feature]func(*featureGate, bool)
|
||||||
|
enabled map[Feature]bool
|
||||||
|
|
||||||
|
// is set to true when AddFlag is called. Note: initialization is not go-routine safe, lookup is
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUnsetAlphaGates(f *featureGate, val bool) {
|
||||||
|
for k, v := range f.known {
|
||||||
|
if v.PreRelease == Alpha {
|
||||||
|
if _, found := f.enabled[k]; !found {
|
||||||
|
f.enabled[k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set, String, and Type implement pflag.Value
|
||||||
|
var _ pflag.Value = &featureGate{}
|
||||||
|
|
||||||
|
// Set Parses a string of the form // "key1=value1,key2=value2,..." into a
|
||||||
|
// map[string]bool of known keys or returns an error.
|
||||||
|
func (f *featureGate) Set(value string) error {
|
||||||
|
f.enabled = make(map[Feature]bool)
|
||||||
|
for _, s := range strings.Split(value, ",") {
|
||||||
|
if len(s) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
arr := strings.SplitN(s, "=", 2)
|
||||||
|
k := Feature(strings.TrimSpace(arr[0]))
|
||||||
|
_, ok := f.known[Feature(k)]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unrecognized key: %s", k)
|
||||||
|
}
|
||||||
|
if len(arr) != 2 {
|
||||||
|
return fmt.Errorf("missing bool value for %s", k)
|
||||||
|
}
|
||||||
|
v := strings.TrimSpace(arr[1])
|
||||||
|
boolValue, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value of %s: %s, err: %v", k, v, err)
|
||||||
|
}
|
||||||
|
f.enabled[k] = boolValue
|
||||||
|
|
||||||
|
// Handle "special" features like "all alpha gates"
|
||||||
|
if fn, found := f.special[k]; found {
|
||||||
|
fn(f, boolValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("feature gates: %v", f.enabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) String() string {
|
||||||
|
pairs := []string{}
|
||||||
|
for k, v := range f.enabled {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
return strings.Join(pairs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) Type() string {
|
||||||
|
return "mapStringBool"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
|
||||||
|
if f.closed {
|
||||||
|
return fmt.Errorf("cannot add a feature gate after adding it to the flag set")
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, spec := range features {
|
||||||
|
if existingSpec, found := f.known[name]; found {
|
||||||
|
if existingSpec == spec {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.known[name] = spec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) Enabled(key Feature) bool {
|
||||||
|
defaultValue := f.known[key].Default
|
||||||
|
if f.enabled != nil {
|
||||||
|
if v, ok := f.enabled[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||||
|
func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
||||||
|
f.closed = true
|
||||||
|
|
||||||
|
known := f.KnownFeatures()
|
||||||
|
fs.Var(f, flagName, ""+
|
||||||
|
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
||||||
|
"Options are:\n"+strings.Join(known, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string describing the FeatureGate's known features.
|
||||||
|
func (f *featureGate) KnownFeatures() []string {
|
||||||
|
var known []string
|
||||||
|
for k, v := range f.known {
|
||||||
|
pre := ""
|
||||||
|
if v.PreRelease != GA {
|
||||||
|
pre = fmt.Sprintf("%s - ", v.PreRelease)
|
||||||
|
}
|
||||||
|
known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.Default))
|
||||||
|
}
|
||||||
|
sort.Strings(known)
|
||||||
|
return known
|
||||||
|
}
|
|
@ -187,7 +187,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2014 The Kubernetes Authors.
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["util.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/types:go_default_library",
|
||||||
|
"//pkg/util/hash:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["util_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/types:go_default_library",
|
||||||
|
"//vendor:github.com/davecgh/go-spew/spew",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
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 endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"hash"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/types"
|
||||||
|
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO: to be deleted after v1.3 is released
|
||||||
|
// Its value is the json representation of map[string(IP)][HostRecord]
|
||||||
|
// example: '{"10.245.1.6":{"HostName":"my-webserver"}}'
|
||||||
|
PodHostnamesAnnotation = "endpoints.beta.kubernetes.io/hostnames-map"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: to be deleted after v1.3 is released
|
||||||
|
type HostRecord struct {
|
||||||
|
HostName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepackSubsets takes a slice of EndpointSubset objects, expands it to the full
|
||||||
|
// representation, and then repacks that into the canonical layout. This
|
||||||
|
// ensures that code which operates on these objects can rely on the common
|
||||||
|
// form for things like comparison. The result is a newly allocated slice.
|
||||||
|
func RepackSubsets(subsets []api.EndpointSubset) []api.EndpointSubset {
|
||||||
|
// First map each unique port definition to the sets of hosts that
|
||||||
|
// offer it.
|
||||||
|
allAddrs := map[addressKey]*api.EndpointAddress{}
|
||||||
|
portToAddrReadyMap := map[api.EndpointPort]addressSet{}
|
||||||
|
for i := range subsets {
|
||||||
|
for _, port := range subsets[i].Ports {
|
||||||
|
for k := range subsets[i].Addresses {
|
||||||
|
mapAddressByPort(&subsets[i].Addresses[k], port, true, allAddrs, portToAddrReadyMap)
|
||||||
|
}
|
||||||
|
for k := range subsets[i].NotReadyAddresses {
|
||||||
|
mapAddressByPort(&subsets[i].NotReadyAddresses[k], port, false, allAddrs, portToAddrReadyMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, map the sets of hosts to the sets of ports they offer.
|
||||||
|
// Go does not allow maps or slices as keys to maps, so we have
|
||||||
|
// to synthesize an artificial key and do a sort of 2-part
|
||||||
|
// associative entity.
|
||||||
|
type keyString string
|
||||||
|
keyToAddrReadyMap := map[keyString]addressSet{}
|
||||||
|
addrReadyMapKeyToPorts := map[keyString][]api.EndpointPort{}
|
||||||
|
for port, addrs := range portToAddrReadyMap {
|
||||||
|
key := keyString(hashAddresses(addrs))
|
||||||
|
keyToAddrReadyMap[key] = addrs
|
||||||
|
addrReadyMapKeyToPorts[key] = append(addrReadyMapKeyToPorts[key], port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, build the N-to-M association the API wants.
|
||||||
|
final := []api.EndpointSubset{}
|
||||||
|
for key, ports := range addrReadyMapKeyToPorts {
|
||||||
|
var readyAddrs, notReadyAddrs []api.EndpointAddress
|
||||||
|
for addr, ready := range keyToAddrReadyMap[key] {
|
||||||
|
if ready {
|
||||||
|
readyAddrs = append(readyAddrs, *addr)
|
||||||
|
} else {
|
||||||
|
notReadyAddrs = append(notReadyAddrs, *addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final = append(final, api.EndpointSubset{Addresses: readyAddrs, NotReadyAddresses: notReadyAddrs, Ports: ports})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, sort it.
|
||||||
|
return SortSubsets(final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sets of hosts must be de-duped, using IP+UID as the key.
|
||||||
|
type addressKey struct {
|
||||||
|
ip string
|
||||||
|
uid types.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapAddressByPort adds an address into a map by its ports, registering the address with a unique pointer, and preserving
|
||||||
|
// any existing ready state.
|
||||||
|
func mapAddressByPort(addr *api.EndpointAddress, port api.EndpointPort, ready bool, allAddrs map[addressKey]*api.EndpointAddress, portToAddrReadyMap map[api.EndpointPort]addressSet) *api.EndpointAddress {
|
||||||
|
// use addressKey to distinguish between two endpoints that are identical addresses
|
||||||
|
// but may have come from different hosts, for attribution. For instance, Mesos
|
||||||
|
// assigns pods the node IP, but the pods are distinct.
|
||||||
|
key := addressKey{ip: addr.IP}
|
||||||
|
if addr.TargetRef != nil {
|
||||||
|
key.uid = addr.TargetRef.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate the address. The full EndpointAddress structure is preserved for use when
|
||||||
|
// we rebuild the subsets so that the final TargetRef has all of the necessary data.
|
||||||
|
existingAddress := allAddrs[key]
|
||||||
|
if existingAddress == nil {
|
||||||
|
// Make a copy so we don't write to the
|
||||||
|
// input args of this function.
|
||||||
|
existingAddress = &api.EndpointAddress{}
|
||||||
|
*existingAddress = *addr
|
||||||
|
allAddrs[key] = existingAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember that this port maps to this address.
|
||||||
|
if _, found := portToAddrReadyMap[port]; !found {
|
||||||
|
portToAddrReadyMap[port] = addressSet{}
|
||||||
|
}
|
||||||
|
// if we have not yet recorded this port for this address, or if the previous
|
||||||
|
// state was ready, write the current ready state. not ready always trumps
|
||||||
|
// ready.
|
||||||
|
if wasReady, found := portToAddrReadyMap[port][existingAddress]; !found || wasReady {
|
||||||
|
portToAddrReadyMap[port][existingAddress] = ready
|
||||||
|
}
|
||||||
|
return existingAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
type addressSet map[*api.EndpointAddress]bool
|
||||||
|
|
||||||
|
type addrReady struct {
|
||||||
|
addr *api.EndpointAddress
|
||||||
|
ready bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAddresses(addrs addressSet) string {
|
||||||
|
// Flatten the list of addresses into a string so it can be used as a
|
||||||
|
// map key. Unfortunately, DeepHashObject is implemented in terms of
|
||||||
|
// spew, and spew does not handle non-primitive map keys well. So
|
||||||
|
// first we collapse it into a slice, sort the slice, then hash that.
|
||||||
|
slice := make([]addrReady, 0, len(addrs))
|
||||||
|
for k, ready := range addrs {
|
||||||
|
slice = append(slice, addrReady{k, ready})
|
||||||
|
}
|
||||||
|
sort.Sort(addrsReady(slice))
|
||||||
|
hasher := md5.New()
|
||||||
|
hashutil.DeepHashObject(hasher, slice)
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil)[0:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func lessAddrReady(a, b addrReady) bool {
|
||||||
|
// ready is not significant to hashing since we can't have duplicate addresses
|
||||||
|
return LessEndpointAddress(a.addr, b.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrsReady []addrReady
|
||||||
|
|
||||||
|
func (sl addrsReady) Len() int { return len(sl) }
|
||||||
|
func (sl addrsReady) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
|
||||||
|
func (sl addrsReady) Less(i, j int) bool {
|
||||||
|
return lessAddrReady(sl[i], sl[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
func LessEndpointAddress(a, b *api.EndpointAddress) bool {
|
||||||
|
ipComparison := bytes.Compare([]byte(a.IP), []byte(b.IP))
|
||||||
|
if ipComparison != 0 {
|
||||||
|
return ipComparison < 0
|
||||||
|
}
|
||||||
|
if b.TargetRef == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a.TargetRef == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return a.TargetRef.UID < b.TargetRef.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrPtrsByIpAndUID []*api.EndpointAddress
|
||||||
|
|
||||||
|
func (sl addrPtrsByIpAndUID) Len() int { return len(sl) }
|
||||||
|
func (sl addrPtrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
|
||||||
|
func (sl addrPtrsByIpAndUID) Less(i, j int) bool {
|
||||||
|
return LessEndpointAddress(sl[i], sl[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortSubsets sorts an array of EndpointSubset objects in place. For ease of
|
||||||
|
// use it returns the input slice.
|
||||||
|
func SortSubsets(subsets []api.EndpointSubset) []api.EndpointSubset {
|
||||||
|
for i := range subsets {
|
||||||
|
ss := &subsets[i]
|
||||||
|
sort.Sort(addrsByIpAndUID(ss.Addresses))
|
||||||
|
sort.Sort(addrsByIpAndUID(ss.NotReadyAddresses))
|
||||||
|
sort.Sort(portsByHash(ss.Ports))
|
||||||
|
}
|
||||||
|
sort.Sort(subsetsByHash(subsets))
|
||||||
|
return subsets
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashObject(hasher hash.Hash, obj interface{}) []byte {
|
||||||
|
hashutil.DeepHashObject(hasher, obj)
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type subsetsByHash []api.EndpointSubset
|
||||||
|
|
||||||
|
func (sl subsetsByHash) Len() int { return len(sl) }
|
||||||
|
func (sl subsetsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
|
||||||
|
func (sl subsetsByHash) Less(i, j int) bool {
|
||||||
|
hasher := md5.New()
|
||||||
|
h1 := hashObject(hasher, sl[i])
|
||||||
|
h2 := hashObject(hasher, sl[j])
|
||||||
|
return bytes.Compare(h1, h2) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrsByIpAndUID []api.EndpointAddress
|
||||||
|
|
||||||
|
func (sl addrsByIpAndUID) Len() int { return len(sl) }
|
||||||
|
func (sl addrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
|
||||||
|
func (sl addrsByIpAndUID) Less(i, j int) bool {
|
||||||
|
return LessEndpointAddress(&sl[i], &sl[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
type portsByHash []api.EndpointPort
|
||||||
|
|
||||||
|
func (sl portsByHash) Len() int { return len(sl) }
|
||||||
|
func (sl portsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }
|
||||||
|
func (sl portsByHash) Less(i, j int) bool {
|
||||||
|
hasher := md5.New()
|
||||||
|
h1 := hashObject(hasher, sl[i])
|
||||||
|
h2 := hashObject(hasher, sl[j])
|
||||||
|
return bytes.Compare(h1, h2) < 0
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["util.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/util/intstr:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["util_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/util/intstr:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
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 pod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO: to be de!eted after v1.3 is released. PodSpec has a dedicated Hostname field.
|
||||||
|
// The annotation value is a string specifying the hostname to be used for the pod e.g 'my-webserver-1'
|
||||||
|
PodHostnameAnnotation = "pod.beta.kubernetes.io/hostname"
|
||||||
|
|
||||||
|
// TODO: to be de!eted after v1.3 is released. PodSpec has a dedicated Subdomain field.
|
||||||
|
// The annotation value is a string specifying the subdomain e.g. "my-web-service"
|
||||||
|
// If specified, on the pod itself, "<hostname>.my-web-service.<namespace>.svc.<cluster domain>" would resolve to
|
||||||
|
// the pod's IP.
|
||||||
|
// If there is a headless service named "my-web-service" in the same namespace as the pod, then,
|
||||||
|
// <hostname>.my-web-service.<namespace>.svc.<cluster domain>" would be resolved by the cluster DNS Server.
|
||||||
|
PodSubdomainAnnotation = "pod.beta.kubernetes.io/subdomain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindPort locates the container port for the given pod and portName. If the
|
||||||
|
// targetPort is a number, use that. If the targetPort is a string, look that
|
||||||
|
// string up in all named ports in all containers in the target pod. If no
|
||||||
|
// match is found, fail.
|
||||||
|
func FindPort(pod *api.Pod, svcPort *api.ServicePort) (int, error) {
|
||||||
|
portName := svcPort.TargetPort
|
||||||
|
switch portName.Type {
|
||||||
|
case intstr.String:
|
||||||
|
name := portName.StrVal
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
for _, port := range container.Ports {
|
||||||
|
if port.Name == name && port.Protocol == svcPort.Protocol {
|
||||||
|
return int(port.ContainerPort), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case intstr.Int:
|
||||||
|
return portName.IntValue(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("no suitable port for manifest: %s", pod.UID)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"annotations.go",
|
||||||
|
"util.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/util/net/sets:go_default_library",
|
||||||
|
"//vendor:github.com/golang/glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["util_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/util/net/sets:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers
|
||||||
|
//
|
||||||
|
// It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to
|
||||||
|
// allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow
|
||||||
|
// access only from the CIDRs currently allocated to MIT & the USPS.
|
||||||
|
//
|
||||||
|
// Not all cloud providers support this annotation, though AWS & GCE do.
|
||||||
|
AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
|
||||||
|
|
||||||
|
// AnnotationValueExternalTrafficLocal Value of annotation to specify local endpoints behaviour
|
||||||
|
AnnotationValueExternalTrafficLocal = "OnlyLocal"
|
||||||
|
// AnnotationValueExternalTrafficGlobal Value of annotation to specify global (legacy) behaviour
|
||||||
|
AnnotationValueExternalTrafficGlobal = "Global"
|
||||||
|
|
||||||
|
// TODO: The alpha annotations have been deprecated, remove them when we move this feature to GA.
|
||||||
|
|
||||||
|
// AlphaAnnotationHealthCheckNodePort Annotation specifying the healthcheck nodePort for the service
|
||||||
|
// If not specified, annotation is created by the service api backend with the allocated nodePort
|
||||||
|
// Will use user-specified nodePort value if specified by the client
|
||||||
|
AlphaAnnotationHealthCheckNodePort = "service.alpha.kubernetes.io/healthcheck-nodeport"
|
||||||
|
|
||||||
|
// AlphaAnnotationExternalTraffic An annotation that denotes if this Service desires to route external traffic to local
|
||||||
|
// endpoints only. This preserves Source IP and avoids a second hop.
|
||||||
|
AlphaAnnotationExternalTraffic = "service.alpha.kubernetes.io/external-traffic"
|
||||||
|
|
||||||
|
// BetaAnnotationHealthCheckNodePort is the beta version of AlphaAnnotationHealthCheckNodePort.
|
||||||
|
BetaAnnotationHealthCheckNodePort = "service.beta.kubernetes.io/healthcheck-nodeport"
|
||||||
|
|
||||||
|
// BetaAnnotationExternalTraffic is the beta version of AlphaAnnotationExternalTraffic.
|
||||||
|
BetaAnnotationExternalTraffic = "service.beta.kubernetes.io/external-traffic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NeedsHealthCheck Check service for health check annotations
|
||||||
|
func NeedsHealthCheck(service *api.Service) bool {
|
||||||
|
// First check the alpha annotation and then the beta. This is so existing
|
||||||
|
// Services continue to work till the user decides to transition to beta.
|
||||||
|
// If they transition to beta, there's no way to go back to alpha without
|
||||||
|
// rolling back the cluster.
|
||||||
|
for _, annotation := range []string{AlphaAnnotationExternalTraffic, BetaAnnotationExternalTraffic} {
|
||||||
|
if l, ok := service.Annotations[annotation]; ok {
|
||||||
|
if l == AnnotationValueExternalTrafficLocal {
|
||||||
|
return true
|
||||||
|
} else if l == AnnotationValueExternalTrafficGlobal {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
glog.Errorf("Invalid value for annotation %v: %v", annotation, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServiceHealthCheckNodePort Return health check node port annotation for service, if one exists
|
||||||
|
func GetServiceHealthCheckNodePort(service *api.Service) int32 {
|
||||||
|
if !NeedsHealthCheck(service) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// First check the alpha annotation and then the beta. This is so existing
|
||||||
|
// Services continue to work till the user decides to transition to beta.
|
||||||
|
// If they transition to beta, there's no way to go back to alpha without
|
||||||
|
// rolling back the cluster.
|
||||||
|
for _, annotation := range []string{AlphaAnnotationHealthCheckNodePort, BetaAnnotationHealthCheckNodePort} {
|
||||||
|
if l, ok := service.Annotations[annotation]; ok {
|
||||||
|
p, err := strconv.Atoi(l)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to parse annotation %v: %v", annotation, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return int32(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServiceHealthCheckPathPort Return the path and nodePort programmed into the Cloud LB Health Check
|
||||||
|
func GetServiceHealthCheckPathPort(service *api.Service) (string, int32) {
|
||||||
|
if !NeedsHealthCheck(service) {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
port := GetServiceHealthCheckNodePort(service)
|
||||||
|
if port == 0 {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
return "/healthz", port
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
netsets "k8s.io/kubernetes/pkg/util/net/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultLoadBalancerSourceRanges = "0.0.0.0/0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsAllowAll checks whether the netsets.IPNet allows traffic from 0.0.0.0/0
|
||||||
|
func IsAllowAll(ipnets netsets.IPNet) bool {
|
||||||
|
for _, s := range ipnets.StringSlice() {
|
||||||
|
if s == "0.0.0.0/0" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoadBalancerSourceRanges first try to parse and verify LoadBalancerSourceRanges field from a service.
|
||||||
|
// If the field is not specified, turn to parse and verify the AnnotationLoadBalancerSourceRangesKey annotation from a service,
|
||||||
|
// extracting the source ranges to allow, and if not present returns a default (allow-all) value.
|
||||||
|
func GetLoadBalancerSourceRanges(service *api.Service) (netsets.IPNet, error) {
|
||||||
|
var ipnets netsets.IPNet
|
||||||
|
var err error
|
||||||
|
// if SourceRange field is specified, ignore sourceRange annotation
|
||||||
|
if len(service.Spec.LoadBalancerSourceRanges) > 0 {
|
||||||
|
specs := service.Spec.LoadBalancerSourceRanges
|
||||||
|
ipnets, err = netsets.ParseIPNets(specs...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("service.Spec.LoadBalancerSourceRanges: %v is not valid. Expecting a list of IP ranges. For example, 10.0.0.0/24. Error msg: %v", specs, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val := service.Annotations[AnnotationLoadBalancerSourceRangesKey]
|
||||||
|
val = strings.TrimSpace(val)
|
||||||
|
if val == "" {
|
||||||
|
val = defaultLoadBalancerSourceRanges
|
||||||
|
}
|
||||||
|
specs := strings.Split(val, ",")
|
||||||
|
ipnets, err = netsets.ParseIPNets(specs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %s is not valid. Expecting a comma-separated list of source IP ranges. For example, 10.0.0.0/24,192.168.2.0/24", AnnotationLoadBalancerSourceRangesKey, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ipnets, nil
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["validation.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/unversioned:go_default_library",
|
||||||
|
"//pkg/util/validation:go_default_library",
|
||||||
|
"//pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["validation_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//pkg/util/validation/field:go_default_library"],
|
||||||
|
)
|
74
vendor/k8s.io/kubernetes/pkg/api/unversioned/validation/validation.go
generated
vendored
Normal file
74
vendor/k8s.io/kubernetes/pkg/api/unversioned/validation/validation.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateLabelSelector(ps *unversioned.LabelSelector, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if ps == nil {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
|
||||||
|
for i, expr := range ps.MatchExpressions {
|
||||||
|
allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateLabelSelectorRequirement(sr unversioned.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
switch sr.Operator {
|
||||||
|
case unversioned.LabelSelectorOpIn, unversioned.LabelSelectorOpNotIn:
|
||||||
|
if len(sr.Values) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
|
||||||
|
}
|
||||||
|
case unversioned.LabelSelectorOpExists, unversioned.LabelSelectorOpDoesNotExist:
|
||||||
|
if len(sr.Values) > 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLabelName validates that the label name is correctly defined.
|
||||||
|
func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
for _, msg := range validation.IsQualifiedName(labelName) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLabels validates that a set of labels are correctly defined.
|
||||||
|
func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
for k, v := range labels {
|
||||||
|
allErrs = append(allErrs, ValidateLabelName(k, fldPath)...)
|
||||||
|
for _, msg := range validation.IsValidLabelValue(v) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, v, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["group_version.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["group_version_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [],
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: This GetVersion/GetGroup arrangement is temporary and will be replaced
|
||||||
|
// with a GroupAndVersion type.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func GetVersion(groupVersion string) string {
|
||||||
|
s := strings.Split(groupVersion, "/")
|
||||||
|
if len(s) != 2 {
|
||||||
|
// e.g. return "v1" for groupVersion="v1"
|
||||||
|
return s[len(s)-1]
|
||||||
|
}
|
||||||
|
return s[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup(groupVersion string) string {
|
||||||
|
s := strings.Split(groupVersion, "/")
|
||||||
|
if len(s) == 1 {
|
||||||
|
// e.g. return "" for groupVersion="v1"
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupVersion returns the "group/version". It returns "version" is if group
|
||||||
|
// is empty. It returns "group/" if version is empty.
|
||||||
|
func GetGroupVersion(group, version string) string {
|
||||||
|
if len(group) == 0 {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
return group + "/" + version
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"events.go",
|
||||||
|
"schema.go",
|
||||||
|
"validation.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/endpoints:go_default_library",
|
||||||
|
"//pkg/api/meta:go_default_library",
|
||||||
|
"//pkg/api/pod:go_default_library",
|
||||||
|
"//pkg/api/resource:go_default_library",
|
||||||
|
"//pkg/api/service:go_default_library",
|
||||||
|
"//pkg/api/unversioned:go_default_library",
|
||||||
|
"//pkg/api/unversioned/validation:go_default_library",
|
||||||
|
"//pkg/api/util:go_default_library",
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//pkg/apimachinery/registered:go_default_library",
|
||||||
|
"//pkg/capabilities:go_default_library",
|
||||||
|
"//pkg/labels:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/security/apparmor:go_default_library",
|
||||||
|
"//pkg/util/config:go_default_library",
|
||||||
|
"//pkg/util/errors:go_default_library",
|
||||||
|
"//pkg/util/intstr:go_default_library",
|
||||||
|
"//pkg/util/sets:go_default_library",
|
||||||
|
"//pkg/util/validation:go_default_library",
|
||||||
|
"//pkg/util/validation/field:go_default_library",
|
||||||
|
"//pkg/util/yaml:go_default_library",
|
||||||
|
"//vendor:github.com/emicklei/go-restful/swagger",
|
||||||
|
"//vendor:github.com/exponent-io/jsonpath",
|
||||||
|
"//vendor:github.com/golang/glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"events_test.go",
|
||||||
|
"schema_test.go",
|
||||||
|
"validation_test.go",
|
||||||
|
],
|
||||||
|
data = [
|
||||||
|
"testdata/v1/invalidPod.yaml",
|
||||||
|
"testdata/v1/invalidPod1.json",
|
||||||
|
"testdata/v1/invalidPod2.json",
|
||||||
|
"testdata/v1/invalidPod3.json",
|
||||||
|
"testdata/v1/invalidPod4.yaml",
|
||||||
|
"testdata/v1/validPod.yaml",
|
||||||
|
"//api/swagger-spec",
|
||||||
|
],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/resource:go_default_library",
|
||||||
|
"//pkg/api/service:go_default_library",
|
||||||
|
"//pkg/api/testapi:go_default_library",
|
||||||
|
"//pkg/api/testing:go_default_library",
|
||||||
|
"//pkg/api/unversioned:go_default_library",
|
||||||
|
"//pkg/apimachinery/registered:go_default_library",
|
||||||
|
"//pkg/apis/extensions:go_default_library",
|
||||||
|
"//pkg/capabilities:go_default_library",
|
||||||
|
"//pkg/runtime:go_default_library",
|
||||||
|
"//pkg/security/apparmor:go_default_library",
|
||||||
|
"//pkg/util/intstr:go_default_library",
|
||||||
|
"//pkg/util/sets:go_default_library",
|
||||||
|
"//pkg/util/validation/field:go_default_library",
|
||||||
|
"//pkg/util/yaml:go_default_library",
|
||||||
|
"//vendor:github.com/ghodss/yaml",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
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 validation has functions for validating the correctness of api
|
||||||
|
// objects and explaining what is wrong with them when they aren't valid.
|
||||||
|
package validation // import "k8s.io/kubernetes/pkg/api/validation"
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
apiutil "k8s.io/kubernetes/pkg/api/util"
|
||||||
|
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateEvent makes sure that the event makes sense.
|
||||||
|
func ValidateEvent(event *api.Event) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
// Make sure event.Namespace and the involvedObject.Namespace agree
|
||||||
|
if len(event.InvolvedObject.Namespace) == 0 {
|
||||||
|
// event.Namespace must also be empty (or "default", for compatibility with old clients)
|
||||||
|
if event.Namespace != api.NamespaceNone && event.Namespace != api.NamespaceDefault {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// event namespace must match
|
||||||
|
if event.Namespace != event.InvolvedObject.Namespace {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For kinds we recognize, make sure involvedObject.Namespace is set for namespaced kinds
|
||||||
|
if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil {
|
||||||
|
if namespaced && len(event.InvolvedObject.Namespace) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind)))
|
||||||
|
}
|
||||||
|
if !namespaced && len(event.InvolvedObject.Namespace) > 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for kind %s", event.InvolvedObject.Kind)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the kind in groupVersion is scoped at the root of the api hierarchy
|
||||||
|
func isNamespacedKind(kind, groupVersion string) (bool, error) {
|
||||||
|
group := apiutil.GetGroup(groupVersion)
|
||||||
|
g, err := registered.Group(group)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
restMapping, err := g.RESTMapper.RESTMapping(unversioned.GroupKind{Group: group, Kind: kind}, apiutil.GetVersion(groupVersion))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
scopeName := restMapping.Scope.Name()
|
||||||
|
if scopeName == meta.RESTScopeNameNamespace {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -0,0 +1,435 @@
|
||||||
|
/*
|
||||||
|
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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful/swagger"
|
||||||
|
ejson "github.com/exponent-io/jsonpath"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
apiutil "k8s.io/kubernetes/pkg/api/util"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
||||||
|
"k8s.io/kubernetes/pkg/util/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InvalidTypeError struct {
|
||||||
|
ExpectedKind reflect.Kind
|
||||||
|
ObservedKind reflect.Kind
|
||||||
|
FieldName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InvalidTypeError) Error() string {
|
||||||
|
return fmt.Sprintf("expected type %s, for field %s, got %s", i.ExpectedKind.String(), i.FieldName, i.ObservedKind.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName string) error {
|
||||||
|
return &InvalidTypeError{expected, observed, fieldName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeNotFoundError is returned when specified type
|
||||||
|
// can not found in schema
|
||||||
|
type TypeNotFoundError string
|
||||||
|
|
||||||
|
func (tnfe TypeNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("couldn't find type: %s", string(tnfe))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema is an interface that knows how to validate an API object serialized to a byte array.
|
||||||
|
type Schema interface {
|
||||||
|
ValidateBytes(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullSchema struct{}
|
||||||
|
|
||||||
|
func (NullSchema) ValidateBytes(data []byte) error { return nil }
|
||||||
|
|
||||||
|
type NoDoubleKeySchema struct{}
|
||||||
|
|
||||||
|
func (NoDoubleKeySchema) ValidateBytes(data []byte) error {
|
||||||
|
var list []error = nil
|
||||||
|
if err := validateNoDuplicateKeys(data, "metadata", "labels"); err != nil {
|
||||||
|
list = append(list, err)
|
||||||
|
}
|
||||||
|
if err := validateNoDuplicateKeys(data, "metadata", "annotations"); err != nil {
|
||||||
|
list = append(list, err)
|
||||||
|
}
|
||||||
|
return utilerrors.NewAggregate(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNoDuplicateKeys(data []byte, path ...string) error {
|
||||||
|
r := ejson.NewDecoder(bytes.NewReader(data))
|
||||||
|
// This is Go being unfriendly. The 'path ...string' comes in as a
|
||||||
|
// []string, and SeekTo takes ...interface{}, so we can't just pass
|
||||||
|
// the path straight in, we have to copy it. *sigh*
|
||||||
|
ifacePath := []interface{}{}
|
||||||
|
for ix := range path {
|
||||||
|
ifacePath = append(ifacePath, path[ix])
|
||||||
|
}
|
||||||
|
found, err := r.SeekTo(ifacePath...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for {
|
||||||
|
tok, err := r.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch t := tok.(type) {
|
||||||
|
case json.Delim:
|
||||||
|
if t.String() == "}" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case ejson.KeyString:
|
||||||
|
if seen[string(t)] {
|
||||||
|
return fmt.Errorf("duplicate key: %s", string(t))
|
||||||
|
} else {
|
||||||
|
seen[string(t)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConjunctiveSchema []Schema
|
||||||
|
|
||||||
|
func (c ConjunctiveSchema) ValidateBytes(data []byte) error {
|
||||||
|
var list []error = nil
|
||||||
|
schemas := []Schema(c)
|
||||||
|
for ix := range schemas {
|
||||||
|
if err := schemas[ix].ValidateBytes(data); err != nil {
|
||||||
|
list = append(list, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return utilerrors.NewAggregate(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwaggerSchema struct {
|
||||||
|
api swagger.ApiDeclaration
|
||||||
|
delegate Schema // For delegating to other api groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSwaggerSchemaFromBytes(data []byte, factory Schema) (Schema, error) {
|
||||||
|
schema := &SwaggerSchema{}
|
||||||
|
err := json.Unmarshal(data, &schema.api)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
schema.delegate = factory
|
||||||
|
return schema, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateList unpacks a list and validate every item in the list.
|
||||||
|
// It return nil if every item is ok.
|
||||||
|
// Otherwise it return an error list contain errors of every item.
|
||||||
|
func (s *SwaggerSchema) validateList(obj map[string]interface{}) []error {
|
||||||
|
items, exists := obj["items"]
|
||||||
|
if !exists {
|
||||||
|
return []error{fmt.Errorf("no items field in %#v", obj)}
|
||||||
|
}
|
||||||
|
return s.validateItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwaggerSchema) validateItems(items interface{}) []error {
|
||||||
|
allErrs := []error{}
|
||||||
|
itemList, ok := items.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return append(allErrs, fmt.Errorf("items isn't a slice"))
|
||||||
|
}
|
||||||
|
for i, item := range itemList {
|
||||||
|
fields, ok := item.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d] isn't a map[string]interface{}", i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groupVersion := fields["apiVersion"]
|
||||||
|
if groupVersion == nil {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion not set", i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemVersion, ok := groupVersion.(string)
|
||||||
|
if !ok {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion isn't string type", i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(itemVersion) == 0 {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].apiVersion is empty", i))
|
||||||
|
}
|
||||||
|
kind := fields["kind"]
|
||||||
|
if kind == nil {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].kind not set", i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemKind, ok := kind.(string)
|
||||||
|
if !ok {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].kind isn't string type", i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(itemKind) == 0 {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("items[%d].kind is empty", i))
|
||||||
|
}
|
||||||
|
version := apiutil.GetVersion(itemVersion)
|
||||||
|
errs := s.ValidateObject(item, "", version+"."+itemKind)
|
||||||
|
if len(errs) >= 1 {
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwaggerSchema) ValidateBytes(data []byte) error {
|
||||||
|
var obj interface{}
|
||||||
|
out, err := yaml.ToJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = out
|
||||||
|
if err := json.Unmarshal(data, &obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fields, ok := obj.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("error in unmarshaling data %s", string(data))
|
||||||
|
}
|
||||||
|
groupVersion := fields["apiVersion"]
|
||||||
|
if groupVersion == nil {
|
||||||
|
return fmt.Errorf("apiVersion not set")
|
||||||
|
}
|
||||||
|
if _, ok := groupVersion.(string); !ok {
|
||||||
|
return fmt.Errorf("apiVersion isn't string type")
|
||||||
|
}
|
||||||
|
kind := fields["kind"]
|
||||||
|
if kind == nil {
|
||||||
|
return fmt.Errorf("kind not set")
|
||||||
|
}
|
||||||
|
if _, ok := kind.(string); !ok {
|
||||||
|
return fmt.Errorf("kind isn't string type")
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(kind.(string), "List") {
|
||||||
|
return utilerrors.NewAggregate(s.validateList(fields))
|
||||||
|
}
|
||||||
|
version := apiutil.GetVersion(groupVersion.(string))
|
||||||
|
allErrs := s.ValidateObject(obj, "", version+"."+kind.(string))
|
||||||
|
if len(allErrs) == 1 {
|
||||||
|
return allErrs[0]
|
||||||
|
}
|
||||||
|
return utilerrors.NewAggregate(allErrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwaggerSchema) ValidateObject(obj interface{}, fieldName, typeName string) []error {
|
||||||
|
allErrs := []error{}
|
||||||
|
models := s.api.Models
|
||||||
|
model, ok := models.At(typeName)
|
||||||
|
|
||||||
|
// Verify the api version matches. This is required for nested types with differing api versions because
|
||||||
|
// s.api only has schema for 1 api version (the parent object type's version).
|
||||||
|
// e.g. an extensions/v1beta1 Template embedding a /v1 Service requires the schema for the extensions/v1beta1
|
||||||
|
// api to delegate to the schema for the /v1 api.
|
||||||
|
// Only do this for !ok objects so that cross ApiVersion vendored types take precedence.
|
||||||
|
if !ok && s.delegate != nil {
|
||||||
|
fields, mapOk := obj.(map[string]interface{})
|
||||||
|
if !mapOk {
|
||||||
|
return append(allErrs, fmt.Errorf("field %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, obj))
|
||||||
|
}
|
||||||
|
if delegated, err := s.delegateIfDifferentApiVersion(runtime.Unstructured{Object: fields}); delegated {
|
||||||
|
if err != nil {
|
||||||
|
allErrs = append(allErrs, err)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return append(allErrs, TypeNotFoundError(typeName))
|
||||||
|
}
|
||||||
|
properties := model.Properties
|
||||||
|
if len(properties.List) == 0 {
|
||||||
|
// The object does not have any sub-fields.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fields, ok := obj.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return append(allErrs, fmt.Errorf("field %s: expected object of type map[string]interface{}, but the actual type is %T", fieldName, obj))
|
||||||
|
}
|
||||||
|
if len(fieldName) > 0 {
|
||||||
|
fieldName = fieldName + "."
|
||||||
|
}
|
||||||
|
// handle required fields
|
||||||
|
for _, requiredKey := range model.Required {
|
||||||
|
if _, ok := fields[requiredKey]; !ok {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("field %s: is required", requiredKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key, value := range fields {
|
||||||
|
details, ok := properties.At(key)
|
||||||
|
|
||||||
|
// Special case for runtime.RawExtension and runtime.Objects because they always fail to validate
|
||||||
|
// This is because the actual values will be of some sub-type (e.g. Deployment) not the expected
|
||||||
|
// super-type (RawExtension)
|
||||||
|
if s.isGenericArray(details) {
|
||||||
|
errs := s.validateItems(value)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("found invalid field %s for %s", key, typeName))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if details.Type == nil && details.Ref == nil {
|
||||||
|
allErrs = append(allErrs, fmt.Errorf("could not find the type of %s from object: %v", key, details))
|
||||||
|
}
|
||||||
|
var fieldType string
|
||||||
|
if details.Type != nil {
|
||||||
|
fieldType = *details.Type
|
||||||
|
} else {
|
||||||
|
fieldType = *details.Ref
|
||||||
|
}
|
||||||
|
if value == nil {
|
||||||
|
glog.V(2).Infof("Skipping nil field: %s", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errs := s.validateField(value, fieldName+key, fieldType, &details)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// delegateIfDifferentApiVersion delegates the validation of an object if its ApiGroup does not match the
|
||||||
|
// current SwaggerSchema.
|
||||||
|
// First return value is true if the validation was delegated (by a different ApiGroup SwaggerSchema)
|
||||||
|
// Second return value is the result of the delegated validation if performed.
|
||||||
|
func (s *SwaggerSchema) delegateIfDifferentApiVersion(obj runtime.Unstructured) (bool, error) {
|
||||||
|
// Never delegate objects in the same ApiVersion or we will get infinite recursion
|
||||||
|
if !s.isDifferentApiVersion(obj) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the object back into bytes so that we can pass it to the ValidateBytes function
|
||||||
|
m, err := json.Marshal(obj.Object)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate validation of this object to the correct SwaggerSchema for its ApiGroup
|
||||||
|
return true, s.delegate.ValidateBytes(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDifferentApiVersion Returns true if obj lives in a different ApiVersion than the SwaggerSchema does.
|
||||||
|
// The SwaggerSchema will not be able to process objects in different ApiVersions unless they are vendored.
|
||||||
|
func (s *SwaggerSchema) isDifferentApiVersion(obj runtime.Unstructured) bool {
|
||||||
|
groupVersion := obj.GetAPIVersion()
|
||||||
|
return len(groupVersion) > 0 && s.api.ApiVersion != groupVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// isGenericArray Returns true if p is an array of generic Objects - either RawExtension or Object.
|
||||||
|
func (s *SwaggerSchema) isGenericArray(p swagger.ModelProperty) bool {
|
||||||
|
return p.DataTypeFields.Type != nil &&
|
||||||
|
*p.DataTypeFields.Type == "array" &&
|
||||||
|
p.Items != nil &&
|
||||||
|
p.Items.Ref != nil &&
|
||||||
|
(*p.Items.Ref == "runtime.RawExtension" || *p.Items.Ref == "runtime.Object")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This matches type name in the swagger spec, such as "v1.Binding".
|
||||||
|
var versionRegexp = regexp.MustCompile(`^(v.+|unversioned)\..*`)
|
||||||
|
|
||||||
|
func (s *SwaggerSchema) validateField(value interface{}, fieldName, fieldType string, fieldDetails *swagger.ModelProperty) []error {
|
||||||
|
allErrs := []error{}
|
||||||
|
if reflect.TypeOf(value) == nil {
|
||||||
|
return append(allErrs, fmt.Errorf("unexpected nil value for field %v", fieldName))
|
||||||
|
}
|
||||||
|
// TODO: caesarxuchao: because we have multiple group/versions and objects
|
||||||
|
// may reference objects in other group, the commented out way of checking
|
||||||
|
// if a filedType is a type defined by us is outdated. We use a hacky way
|
||||||
|
// for now.
|
||||||
|
// TODO: the type name in the swagger spec is something like "v1.Binding",
|
||||||
|
// and the "v1" is generated from the package name, not the groupVersion of
|
||||||
|
// the type. We need to fix go-restful to embed the group name in the type
|
||||||
|
// name, otherwise we couldn't handle identically named types in different
|
||||||
|
// groups correctly.
|
||||||
|
if versionRegexp.MatchString(fieldType) {
|
||||||
|
// if strings.HasPrefix(fieldType, apiVersion) {
|
||||||
|
return s.ValidateObject(value, fieldName, fieldType)
|
||||||
|
}
|
||||||
|
switch fieldType {
|
||||||
|
case "string":
|
||||||
|
// Be loose about what we accept for 'string' since we use IntOrString in a couple of places
|
||||||
|
_, isString := value.(string)
|
||||||
|
_, isNumber := value.(float64)
|
||||||
|
_, isInteger := value.(int)
|
||||||
|
if !isString && !isNumber && !isInteger {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.String, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
case "array":
|
||||||
|
arr, ok := value.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
var arrType string
|
||||||
|
if fieldDetails.Items.Ref == nil && fieldDetails.Items.Type == nil {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
if fieldDetails.Items.Ref != nil {
|
||||||
|
arrType = *fieldDetails.Items.Ref
|
||||||
|
} else {
|
||||||
|
arrType = *fieldDetails.Items.Type
|
||||||
|
}
|
||||||
|
for ix := range arr {
|
||||||
|
errs := s.validateField(arr[ix], fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "uint64":
|
||||||
|
case "int64":
|
||||||
|
case "integer":
|
||||||
|
_, isNumber := value.(float64)
|
||||||
|
_, isInteger := value.(int)
|
||||||
|
if !isNumber && !isInteger {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.Int, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
case "float64":
|
||||||
|
if _, ok := value.(float64); !ok {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.Float64, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
case "boolean":
|
||||||
|
if _, ok := value.(bool); !ok {
|
||||||
|
return append(allErrs, NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName))
|
||||||
|
}
|
||||||
|
// API servers before release 1.3 produce swagger spec with `type: "any"` as the fallback type, while newer servers produce spec with `type: "object"`.
|
||||||
|
// We have both here so that kubectl can work with both old and new api servers.
|
||||||
|
case "object":
|
||||||
|
case "any":
|
||||||
|
default:
|
||||||
|
return append(allErrs, fmt.Errorf("unexpected type: %v", fieldType))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"capabilities.go",
|
||||||
|
"doc.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
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 capabilities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Capabilities defines the set of capabilities available within the system.
|
||||||
|
// For now these are global. Eventually they may be per-user
|
||||||
|
type Capabilities struct {
|
||||||
|
AllowPrivileged bool
|
||||||
|
|
||||||
|
// Pod sources from which to allow privileged capabilities like host networking, sharing the host
|
||||||
|
// IPC namespace, and sharing the host PID namespace.
|
||||||
|
PrivilegedSources PrivilegedSources
|
||||||
|
|
||||||
|
// PerConnectionBandwidthLimitBytesPerSec limits the throughput of each connection (currently only used for proxy, exec, attach)
|
||||||
|
PerConnectionBandwidthLimitBytesPerSec int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivilegedSources defines the pod sources allowed to make privileged requests for certain types
|
||||||
|
// of capabilities like host networking, sharing the host IPC namespace, and sharing the host PID namespace.
|
||||||
|
type PrivilegedSources struct {
|
||||||
|
// List of pod sources for which using host network is allowed.
|
||||||
|
HostNetworkSources []string
|
||||||
|
|
||||||
|
// List of pod sources for which using host pid namespace is allowed.
|
||||||
|
HostPIDSources []string
|
||||||
|
|
||||||
|
// List of pod sources for which using host ipc is allowed.
|
||||||
|
HostIPCSources []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Clean these up into a singleton
|
||||||
|
var once sync.Once
|
||||||
|
var lock sync.Mutex
|
||||||
|
var capabilities *Capabilities
|
||||||
|
|
||||||
|
// Initialize the capability set. This can only be done once per binary, subsequent calls are ignored.
|
||||||
|
func Initialize(c Capabilities) {
|
||||||
|
// Only do this once
|
||||||
|
once.Do(func() {
|
||||||
|
capabilities = &c
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the capability set. It wraps Initialize for improving usability.
|
||||||
|
func Setup(allowPrivileged bool, privilegedSources PrivilegedSources, perConnectionBytesPerSec int64) {
|
||||||
|
Initialize(Capabilities{
|
||||||
|
AllowPrivileged: allowPrivileged,
|
||||||
|
PrivilegedSources: privilegedSources,
|
||||||
|
PerConnectionBandwidthLimitBytesPerSec: perConnectionBytesPerSec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetForTests sets capabilities for tests. Convenience method for testing. This should only be called from tests.
|
||||||
|
func SetForTests(c Capabilities) {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
capabilities = &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a read-only copy of the system capabilities.
|
||||||
|
func Get() Capabilities {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
// This check prevents clobbering of capabilities that might've been set via SetForTests
|
||||||
|
if capabilities == nil {
|
||||||
|
Initialize(Capabilities{
|
||||||
|
AllowPrivileged: false,
|
||||||
|
PrivilegedSources: PrivilegedSources{
|
||||||
|
HostNetworkSources: []string{},
|
||||||
|
HostPIDSources: []string{},
|
||||||
|
HostIPCSources: []string{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return *capabilities
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
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 capabilities manages system level capabilities
|
||||||
|
package capabilities // import "k8s.io/kubernetes/pkg/capabilities"
|
|
@ -0,0 +1,31 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["kube_features.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//vendor:k8s.io/apiserver/pkg/features",
|
||||||
|
"//vendor:k8s.io/apiserver/pkg/util/feature",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
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 features
|
||||||
|
|
||||||
|
import (
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Every feature gate should add method here following this template:
|
||||||
|
//
|
||||||
|
// // owner: @username
|
||||||
|
// // alpha: v1.4
|
||||||
|
// MyFeature() bool
|
||||||
|
|
||||||
|
// owner: @timstclair
|
||||||
|
// beta: v1.4
|
||||||
|
AppArmor utilfeature.Feature = "AppArmor"
|
||||||
|
|
||||||
|
// owner: @girishkalele
|
||||||
|
// alpha: v1.4
|
||||||
|
ExternalTrafficLocalOnly utilfeature.Feature = "AllowExtTrafficLocalEndpoints"
|
||||||
|
|
||||||
|
// owner: @saad-ali
|
||||||
|
// alpha: v1.3
|
||||||
|
DynamicVolumeProvisioning utilfeature.Feature = "DynamicVolumeProvisioning"
|
||||||
|
|
||||||
|
// owner: @mtaufen
|
||||||
|
// alpha: v1.4
|
||||||
|
DynamicKubeletConfig utilfeature.Feature = "DynamicKubeletConfig"
|
||||||
|
|
||||||
|
// owner: timstclair
|
||||||
|
// alpha: v1.5
|
||||||
|
//
|
||||||
|
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
|
||||||
|
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
|
||||||
|
StreamingProxyRedirects utilfeature.Feature = genericfeatures.StreamingProxyRedirects
|
||||||
|
|
||||||
|
// owner: @pweil-
|
||||||
|
// alpha: v1.5
|
||||||
|
//
|
||||||
|
// Default userns=host for containers that are using other host namespaces, host mounts, the pod
|
||||||
|
// contains a privileged container, or specific non-namespaced capabilities (MKNOD, SYS_MODULE,
|
||||||
|
// SYS_TIME). This should only be enabled if user namespace remapping is enabled in the docker daemon.
|
||||||
|
ExperimentalHostUserNamespaceDefaultingGate utilfeature.Feature = "ExperimentalHostUserNamespaceDefaulting"
|
||||||
|
|
||||||
|
// owner: @vishh
|
||||||
|
// alpha: v1.5
|
||||||
|
//
|
||||||
|
// Ensures guaranteed scheduling of pods marked with a special pod annotation `scheduler.alpha.kubernetes.io/critical-pod`
|
||||||
|
// and also prevents them from being evicted from a node.
|
||||||
|
// Note: This feature is not supported for `BestEffort` pods.
|
||||||
|
ExperimentalCriticalPodAnnotation utilfeature.Feature = "ExperimentalCriticalPodAnnotation"
|
||||||
|
|
||||||
|
// owner: @davidopp
|
||||||
|
// alpha: v1.6
|
||||||
|
//
|
||||||
|
// Determines if affinity defined in annotations should be processed
|
||||||
|
// TODO: remove when alpha support for affinity is removed
|
||||||
|
AffinityInAnnotations utilfeature.Feature = "AffinityInAnnotations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilfeature.DefaultFeatureGate.Add(defaultKubernetesFeatureGates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
|
||||||
|
// To add a new feature, define a key for it above and add it here. The features will be
|
||||||
|
// available throughout Kubernetes binaries.
|
||||||
|
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
|
||||||
|
ExternalTrafficLocalOnly: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
|
AppArmor: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
|
DynamicKubeletConfig: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
DynamicVolumeProvisioning: {Default: true, PreRelease: utilfeature.Alpha},
|
||||||
|
ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: utilfeature.Beta},
|
||||||
|
ExperimentalCriticalPodAnnotation: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
AffinityInAnnotations: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
|
||||||
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
|
// unintentionally on either side:
|
||||||
|
StreamingProxyRedirects: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"helpers.go",
|
||||||
|
"validate.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
|
"//pkg/util:go_default_library",
|
||||||
|
"//vendor:k8s.io/apiserver/pkg/util/feature",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["validate_test.go"],
|
||||||
|
data = [
|
||||||
|
"testdata/profiles",
|
||||||
|
],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//vendor:github.com/stretchr/testify/assert",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 apparmor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Move these values into the API package.
|
||||||
|
const (
|
||||||
|
// The prefix to an annotation key specifying a container profile.
|
||||||
|
ContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
|
||||||
|
// The annotation key specifying the default AppArmor profile.
|
||||||
|
DefaultProfileAnnotationKey = "apparmor.security.beta.kubernetes.io/defaultProfileName"
|
||||||
|
// The annotation key specifying the allowed AppArmor profiles.
|
||||||
|
AllowedProfilesAnnotationKey = "apparmor.security.beta.kubernetes.io/allowedProfileNames"
|
||||||
|
|
||||||
|
// The profile specifying the runtime default.
|
||||||
|
ProfileRuntimeDefault = "runtime/default"
|
||||||
|
// The prefix for specifying profiles loaded on the node.
|
||||||
|
ProfileNamePrefix = "localhost/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checks whether app armor is required for pod to be run.
|
||||||
|
func isRequired(pod *v1.Pod) bool {
|
||||||
|
for key := range pod.Annotations {
|
||||||
|
if strings.HasPrefix(key, ContainerAnnotationKeyPrefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the name of the profile to use with the container.
|
||||||
|
func GetProfileName(pod *v1.Pod, containerName string) string {
|
||||||
|
return GetProfileNameFromPodAnnotations(pod.Annotations, containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileNameFromPodAnnotations gets the name of the profile to use with container from
|
||||||
|
// pod annotations
|
||||||
|
func GetProfileNameFromPodAnnotations(annotations map[string]string, containerName string) string {
|
||||||
|
return annotations[ContainerAnnotationKeyPrefix+containerName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the name of the profile to use with the container.
|
||||||
|
func SetProfileName(pod *v1.Pod, containerName, profileName string) error {
|
||||||
|
if pod.Annotations == nil {
|
||||||
|
pod.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
pod.Annotations[ContainerAnnotationKeyPrefix+containerName] = profileName
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the name of the profile to use with the container.
|
||||||
|
func SetProfileNameFromPodAnnotations(annotations map[string]string, containerName, profileName string) error {
|
||||||
|
if annotations == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
annotations[ContainerAnnotationKeyPrefix+containerName] = profileName
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 apparmor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Whether AppArmor should be disabled by default.
|
||||||
|
// Set to true if the wrong build tags are set (see validate_disabled.go).
|
||||||
|
var isDisabledBuild bool
|
||||||
|
|
||||||
|
// Interface for validating that a pod with with an AppArmor profile can be run by a Node.
|
||||||
|
type Validator interface {
|
||||||
|
Validate(pod *v1.Pod) error
|
||||||
|
ValidateHost() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidator(runtime string) Validator {
|
||||||
|
if err := validateHost(runtime); err != nil {
|
||||||
|
return &validator{validateHostErr: err}
|
||||||
|
}
|
||||||
|
appArmorFS, err := getAppArmorFS()
|
||||||
|
if err != nil {
|
||||||
|
return &validator{
|
||||||
|
validateHostErr: fmt.Errorf("error finding AppArmor FS: %v", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &validator{
|
||||||
|
appArmorFS: appArmorFS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type validator struct {
|
||||||
|
validateHostErr error
|
||||||
|
appArmorFS string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *validator) Validate(pod *v1.Pod) error {
|
||||||
|
if !isRequired(pod) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.ValidateHost() != nil {
|
||||||
|
return v.validateHostErr
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedProfiles, err := v.getLoadedProfiles()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read loaded profiles: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range pod.Spec.InitContainers {
|
||||||
|
if err := validateProfile(GetProfileName(pod, container.Name), loadedProfiles); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
if err := validateProfile(GetProfileName(pod, container.Name), loadedProfiles); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *validator) ValidateHost() error {
|
||||||
|
return v.validateHostErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the host and runtime is capable of enforcing AppArmor profiles.
|
||||||
|
func validateHost(runtime string) error {
|
||||||
|
// Check feature-gates
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) {
|
||||||
|
return errors.New("AppArmor disabled by feature-gate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check build support.
|
||||||
|
if isDisabledBuild {
|
||||||
|
return errors.New("Binary not compiled for linux")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check kernel support.
|
||||||
|
if !IsAppArmorEnabled() {
|
||||||
|
return errors.New("AppArmor is not enabled on the host")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check runtime support. Currently only Docker is supported.
|
||||||
|
if runtime != "docker" {
|
||||||
|
return fmt.Errorf("AppArmor is only enabled for 'docker' runtime. Found: %q.", runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the profile is valid and loaded.
|
||||||
|
func validateProfile(profile string, loadedProfiles map[string]bool) error {
|
||||||
|
if err := ValidateProfileFormat(profile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(profile, ProfileNamePrefix) {
|
||||||
|
profileName := strings.TrimPrefix(profile, ProfileNamePrefix)
|
||||||
|
if !loadedProfiles[profileName] {
|
||||||
|
return fmt.Errorf("profile %q is not loaded", profileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateProfileFormat(profile string) error {
|
||||||
|
if profile == "" || profile == ProfileRuntimeDefault {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(profile, ProfileNamePrefix) {
|
||||||
|
return fmt.Errorf("invalid AppArmor profile name: %q", profile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *validator) getLoadedProfiles() (map[string]bool, error) {
|
||||||
|
profilesPath := path.Join(v.appArmorFS, "profiles")
|
||||||
|
profilesFile, err := os.Open(profilesPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open %s: %v", profilesPath, err)
|
||||||
|
}
|
||||||
|
defer profilesFile.Close()
|
||||||
|
|
||||||
|
profiles := map[string]bool{}
|
||||||
|
scanner := bufio.NewScanner(profilesFile)
|
||||||
|
for scanner.Scan() {
|
||||||
|
profileName := parseProfileName(scanner.Text())
|
||||||
|
if profileName == "" {
|
||||||
|
// Unknown line format; skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
profiles[profileName] = true
|
||||||
|
}
|
||||||
|
return profiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The profiles file is formatted with one profile per line, matching a form:
|
||||||
|
// namespace://profile-name (mode)
|
||||||
|
// profile-name (mode)
|
||||||
|
// Where mode is {enforce, complain, kill}. The "namespace://" is only included for namespaced
|
||||||
|
// profiles. For the purposes of Kubernetes, we consider the namespace part of the profile name.
|
||||||
|
func parseProfileName(profileLine string) string {
|
||||||
|
modeIndex := strings.IndexRune(profileLine, '(')
|
||||||
|
if modeIndex < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(profileLine[:modeIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAppArmorFS() (string, error) {
|
||||||
|
mountsFile, err := os.Open("/proc/mounts")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not open /proc/mounts: %v", err)
|
||||||
|
}
|
||||||
|
defer mountsFile.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(mountsFile)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if len(fields) < 3 {
|
||||||
|
// Unknown line format; skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fields[2] == "securityfs" {
|
||||||
|
appArmorFS := path.Join(fields[1], "apparmor")
|
||||||
|
if ok, err := util.FileExists(appArmorFS); !ok {
|
||||||
|
msg := fmt.Sprintf("path %s does not exist", appArmorFS)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%s: %v", msg, err)
|
||||||
|
} else {
|
||||||
|
return "", errors.New(msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return appArmorFS, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return "", fmt.Errorf("error scanning mounts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("securityfs not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAppArmorEnabled returns true if apparmor is enabled for the host.
|
||||||
|
// This function is forked from
|
||||||
|
// https://github.com/opencontainers/runc/blob/1a81e9ab1f138c091fe5c86d0883f87716088527/libcontainer/apparmor/apparmor.go
|
||||||
|
// to avoid the libapparmor dependency.
|
||||||
|
func IsAppArmorEnabled() bool {
|
||||||
|
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
|
||||||
|
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
|
||||||
|
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
|
||||||
|
return err == nil && len(buf) > 1 && buf[0] == 'Y'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
24
vendor/k8s.io/kubernetes/pkg/security/apparmor/validate_disabled.go
generated
vendored
Normal file
24
vendor/k8s.io/kubernetes/pkg/security/apparmor/validate_disabled.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 apparmor
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// If Kubernetes was not built for linux, apparmor is always disabled.
|
||||||
|
isDisabledBuild = true
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"config.go",
|
||||||
|
"configuration_map.go",
|
||||||
|
"doc.go",
|
||||||
|
"feature_gate.go",
|
||||||
|
"namedcertkey_flag.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/util/wait:go_default_library",
|
||||||
|
"//vendor:github.com/golang/glog",
|
||||||
|
"//vendor:github.com/spf13/pflag",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"config_test.go",
|
||||||
|
"feature_gate_test.go",
|
||||||
|
"namedcertkey_flag_test.go",
|
||||||
|
],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//vendor:github.com/spf13/pflag"],
|
||||||
|
)
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Merger interface {
|
||||||
|
// Invoked when a change from a source is received. May also function as an incremental
|
||||||
|
// merger if you wish to consume changes incrementally. Must be reentrant when more than
|
||||||
|
// one source is defined.
|
||||||
|
Merge(source string, update interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeFunc implements the Merger interface
|
||||||
|
type MergeFunc func(source string, update interface{}) error
|
||||||
|
|
||||||
|
func (f MergeFunc) Merge(source string, update interface{}) error {
|
||||||
|
return f(source, update)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mux is a class for merging configuration from multiple sources. Changes are
|
||||||
|
// pushed via channels and sent to the merge function.
|
||||||
|
type Mux struct {
|
||||||
|
// Invoked when an update is sent to a source.
|
||||||
|
merger Merger
|
||||||
|
|
||||||
|
// Sources and their lock.
|
||||||
|
sourceLock sync.RWMutex
|
||||||
|
// Maps source names to channels
|
||||||
|
sources map[string]chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMux creates a new mux that can merge changes from multiple sources.
|
||||||
|
func NewMux(merger Merger) *Mux {
|
||||||
|
mux := &Mux{
|
||||||
|
sources: make(map[string]chan interface{}),
|
||||||
|
merger: merger,
|
||||||
|
}
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel returns a channel where a configuration source
|
||||||
|
// can send updates of new configurations. Multiple calls with the same
|
||||||
|
// source will return the same channel. This allows change and state based sources
|
||||||
|
// to use the same channel. Different source names however will be treated as a
|
||||||
|
// union.
|
||||||
|
func (m *Mux) Channel(source string) chan interface{} {
|
||||||
|
if len(source) == 0 {
|
||||||
|
panic("Channel given an empty name")
|
||||||
|
}
|
||||||
|
m.sourceLock.Lock()
|
||||||
|
defer m.sourceLock.Unlock()
|
||||||
|
channel, exists := m.sources[source]
|
||||||
|
if exists {
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
newChannel := make(chan interface{})
|
||||||
|
m.sources[source] = newChannel
|
||||||
|
go wait.Until(func() { m.listen(source, newChannel) }, 0, wait.NeverStop)
|
||||||
|
return newChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mux) listen(source string, listenChannel <-chan interface{}) {
|
||||||
|
for update := range listenChannel {
|
||||||
|
m.merger.Merge(source, update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessor is an interface for retrieving the current merge state.
|
||||||
|
type Accessor interface {
|
||||||
|
// MergedState returns a representation of the current merge state.
|
||||||
|
// Must be reentrant when more than one source is defined.
|
||||||
|
MergedState() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessorFunc implements the Accessor interface.
|
||||||
|
type AccessorFunc func() interface{}
|
||||||
|
|
||||||
|
func (f AccessorFunc) MergedState() interface{} {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Listener interface {
|
||||||
|
// OnUpdate is invoked when a change is made to an object.
|
||||||
|
OnUpdate(instance interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenerFunc receives a representation of the change or object.
|
||||||
|
type ListenerFunc func(instance interface{})
|
||||||
|
|
||||||
|
func (f ListenerFunc) OnUpdate(instance interface{}) {
|
||||||
|
f(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Broadcaster struct {
|
||||||
|
// Listeners for changes and their lock.
|
||||||
|
listenerLock sync.RWMutex
|
||||||
|
listeners []Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBroadcaster registers a set of listeners that support the Listener interface
|
||||||
|
// and notifies them all on changes.
|
||||||
|
func NewBroadcaster() *Broadcaster {
|
||||||
|
return &Broadcaster{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add registers listener to receive updates of changes.
|
||||||
|
func (b *Broadcaster) Add(listener Listener) {
|
||||||
|
b.listenerLock.Lock()
|
||||||
|
defer b.listenerLock.Unlock()
|
||||||
|
b.listeners = append(b.listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify notifies all listeners.
|
||||||
|
func (b *Broadcaster) Notify(instance interface{}) {
|
||||||
|
b.listenerLock.RLock()
|
||||||
|
listeners := b.listeners
|
||||||
|
b.listenerLock.RUnlock()
|
||||||
|
for _, listener := range listeners {
|
||||||
|
listener.OnUpdate(instance)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigurationMap map[string]string
|
||||||
|
|
||||||
|
func (m *ConfigurationMap) String() string {
|
||||||
|
pairs := []string{}
|
||||||
|
for k, v := range *m {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
return strings.Join(pairs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConfigurationMap) Set(value string) error {
|
||||||
|
for _, s := range strings.Split(value, ",") {
|
||||||
|
if len(s) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
arr := strings.SplitN(s, "=", 2)
|
||||||
|
if len(arr) == 2 {
|
||||||
|
(*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1])
|
||||||
|
} else {
|
||||||
|
(*m)[strings.TrimSpace(arr[0])] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConfigurationMap) Type() string {
|
||||||
|
return "mapStringString"
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
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 config provides utility objects for decoupling sources of configuration and the
|
||||||
|
// actual configuration state. Consumers must implement the Merger interface to unify
|
||||||
|
// the sources of change into an object.
|
||||||
|
package config // import "k8s.io/kubernetes/pkg/util/config"
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagName = "feature-gates"
|
||||||
|
|
||||||
|
// All known feature keys
|
||||||
|
// To add a new feature, define a key for it below and add
|
||||||
|
// a featureSpec entry to knownFeatures.
|
||||||
|
|
||||||
|
// allAlphaGate is a global toggle for alpha features. Per-feature key
|
||||||
|
// values override the default set by allAlphaGate. Examples:
|
||||||
|
// AllAlpha=false,NewFeature=true will result in newFeature=true
|
||||||
|
// AllAlpha=true,NewFeature=false will result in newFeature=false
|
||||||
|
allAlphaGate = "AllAlpha"
|
||||||
|
externalTrafficLocalOnly = "AllowExtTrafficLocalEndpoints"
|
||||||
|
appArmor = "AppArmor"
|
||||||
|
dynamicKubeletConfig = "DynamicKubeletConfig"
|
||||||
|
dynamicVolumeProvisioning = "DynamicVolumeProvisioning"
|
||||||
|
streamingProxyRedirects = "StreamingProxyRedirects"
|
||||||
|
|
||||||
|
// experimentalHostUserNamespaceDefaulting Default userns=host for containers
|
||||||
|
// that are using other host namespaces, host mounts, the pod contains a privileged container,
|
||||||
|
// or specific non-namespaced capabilities
|
||||||
|
// (MKNOD, SYS_MODULE, SYS_TIME). This should only be enabled if user namespace remapping is enabled
|
||||||
|
// in the docker daemon.
|
||||||
|
experimentalHostUserNamespaceDefaultingGate = "ExperimentalHostUserNamespaceDefaulting"
|
||||||
|
// Ensures guaranteed scheduling of pods marked with a special pod annotation `scheduler.alpha.kubernetes.io/critical-pod`
|
||||||
|
// and also prevents them from being evicted from a node.
|
||||||
|
experimentalCriticalPodAnnotation = "ExperimentalCriticalPodAnnotation"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Default values for recorded features. Every new feature gate should be
|
||||||
|
// represented here.
|
||||||
|
knownFeatures = map[string]featureSpec{
|
||||||
|
allAlphaGate: {false, alpha},
|
||||||
|
externalTrafficLocalOnly: {true, beta},
|
||||||
|
appArmor: {true, beta},
|
||||||
|
dynamicKubeletConfig: {false, alpha},
|
||||||
|
dynamicVolumeProvisioning: {true, alpha},
|
||||||
|
streamingProxyRedirects: {false, alpha},
|
||||||
|
experimentalHostUserNamespaceDefaultingGate: {false, alpha},
|
||||||
|
experimentalCriticalPodAnnotation: {false, alpha},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for a few gates.
|
||||||
|
specialFeatures = map[string]func(f *featureGate, val bool){
|
||||||
|
allAlphaGate: setUnsetAlphaGates,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFeatureGate is a shared global FeatureGate.
|
||||||
|
DefaultFeatureGate = &featureGate{
|
||||||
|
known: knownFeatures,
|
||||||
|
special: specialFeatures,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type featureSpec struct {
|
||||||
|
enabled bool
|
||||||
|
prerelease prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
type prerelease string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Values for prerelease.
|
||||||
|
alpha = prerelease("ALPHA")
|
||||||
|
beta = prerelease("BETA")
|
||||||
|
ga = prerelease("")
|
||||||
|
)
|
||||||
|
|
||||||
|
// FeatureGate parses and stores flag gates for known features from
|
||||||
|
// a string like feature1=true,feature2=false,...
|
||||||
|
type FeatureGate interface {
|
||||||
|
AddFlag(fs *pflag.FlagSet)
|
||||||
|
Set(value string) error
|
||||||
|
KnownFeatures() []string
|
||||||
|
|
||||||
|
// Every feature gate should add method here following this template:
|
||||||
|
//
|
||||||
|
// // owner: @username
|
||||||
|
// // alpha: v1.4
|
||||||
|
// MyFeature() bool
|
||||||
|
|
||||||
|
// owner: @timstclair
|
||||||
|
// beta: v1.4
|
||||||
|
AppArmor() bool
|
||||||
|
|
||||||
|
// owner: @girishkalele
|
||||||
|
// alpha: v1.4
|
||||||
|
ExternalTrafficLocalOnly() bool
|
||||||
|
|
||||||
|
// owner: @saad-ali
|
||||||
|
// alpha: v1.3
|
||||||
|
DynamicVolumeProvisioning() bool
|
||||||
|
|
||||||
|
// owner: @mtaufen
|
||||||
|
// alpha: v1.4
|
||||||
|
DynamicKubeletConfig() bool
|
||||||
|
|
||||||
|
// owner: timstclair
|
||||||
|
// alpha: v1.5
|
||||||
|
StreamingProxyRedirects() bool
|
||||||
|
|
||||||
|
// owner: @pweil-
|
||||||
|
// alpha: v1.5
|
||||||
|
ExperimentalHostUserNamespaceDefaulting() bool
|
||||||
|
|
||||||
|
// owner: @vishh
|
||||||
|
// alpha: v1.4
|
||||||
|
ExperimentalCriticalPodAnnotation() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
|
||||||
|
type featureGate struct {
|
||||||
|
known map[string]featureSpec
|
||||||
|
special map[string]func(*featureGate, bool)
|
||||||
|
enabled map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUnsetAlphaGates(f *featureGate, val bool) {
|
||||||
|
for k, v := range f.known {
|
||||||
|
if v.prerelease == alpha {
|
||||||
|
if _, found := f.enabled[k]; !found {
|
||||||
|
f.enabled[k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set, String, and Type implement pflag.Value
|
||||||
|
|
||||||
|
// Set Parses a string of the form // "key1=value1,key2=value2,..." into a
|
||||||
|
// map[string]bool of known keys or returns an error.
|
||||||
|
func (f *featureGate) Set(value string) error {
|
||||||
|
f.enabled = make(map[string]bool)
|
||||||
|
for _, s := range strings.Split(value, ",") {
|
||||||
|
if len(s) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
arr := strings.SplitN(s, "=", 2)
|
||||||
|
k := strings.TrimSpace(arr[0])
|
||||||
|
_, ok := f.known[k]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unrecognized key: %s", k)
|
||||||
|
}
|
||||||
|
if len(arr) != 2 {
|
||||||
|
return fmt.Errorf("missing bool value for %s", k)
|
||||||
|
}
|
||||||
|
v := strings.TrimSpace(arr[1])
|
||||||
|
boolValue, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value of %s: %s, err: %v", k, v, err)
|
||||||
|
}
|
||||||
|
f.enabled[k] = boolValue
|
||||||
|
|
||||||
|
// Handle "special" features like "all alpha gates"
|
||||||
|
if fn, found := f.special[k]; found {
|
||||||
|
fn(f, boolValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("feature gates: %v", f.enabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) String() string {
|
||||||
|
pairs := []string{}
|
||||||
|
for k, v := range f.enabled {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
return strings.Join(pairs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) Type() string {
|
||||||
|
return "mapStringBool"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalTrafficLocalOnly returns value for AllowExtTrafficLocalEndpoints
|
||||||
|
func (f *featureGate) ExternalTrafficLocalOnly() bool {
|
||||||
|
return f.lookup(externalTrafficLocalOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppArmor returns the value for the AppArmor feature gate.
|
||||||
|
func (f *featureGate) AppArmor() bool {
|
||||||
|
return f.lookup(appArmor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicKubeletConfig returns value for dynamicKubeletConfig
|
||||||
|
func (f *featureGate) DynamicKubeletConfig() bool {
|
||||||
|
return f.lookup(dynamicKubeletConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicVolumeProvisioning returns value for dynamicVolumeProvisioning
|
||||||
|
func (f *featureGate) DynamicVolumeProvisioning() bool {
|
||||||
|
return f.lookup(dynamicVolumeProvisioning)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamingProxyRedirects controls whether the apiserver should intercept (and follow)
|
||||||
|
// redirects from the backend (Kubelet) for streaming requests (exec/attach/port-forward).
|
||||||
|
func (f *featureGate) StreamingProxyRedirects() bool {
|
||||||
|
return f.lookup(streamingProxyRedirects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExperimentalHostUserNamespaceDefaulting returns value for experimentalHostUserNamespaceDefaulting
|
||||||
|
func (f *featureGate) ExperimentalHostUserNamespaceDefaulting() bool {
|
||||||
|
return f.lookup(experimentalHostUserNamespaceDefaultingGate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExperimentalCriticalPodAnnotation returns true if experimentalCriticalPodAnnotation feature is enabled.
|
||||||
|
func (f *featureGate) ExperimentalCriticalPodAnnotation() bool {
|
||||||
|
return f.lookup(experimentalCriticalPodAnnotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *featureGate) lookup(key string) bool {
|
||||||
|
defaultValue := f.known[key].enabled
|
||||||
|
if f.enabled != nil {
|
||||||
|
if v, ok := f.enabled[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||||
|
func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
||||||
|
known := f.KnownFeatures()
|
||||||
|
fs.Var(f, flagName, ""+
|
||||||
|
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
||||||
|
"Options are:\n"+strings.Join(known, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string describing the FeatureGate's known features.
|
||||||
|
func (f *featureGate) KnownFeatures() []string {
|
||||||
|
var known []string
|
||||||
|
for k, v := range f.known {
|
||||||
|
pre := ""
|
||||||
|
if v.prerelease != ga {
|
||||||
|
pre = fmt.Sprintf("%s - ", v.prerelease)
|
||||||
|
}
|
||||||
|
known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.enabled))
|
||||||
|
}
|
||||||
|
sort.Strings(known)
|
||||||
|
return known
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NamedCertKey is a flag value parsing "certfile,keyfile" and "certfile,keyfile:name,name,name".
|
||||||
|
type NamedCertKey struct {
|
||||||
|
Names []string
|
||||||
|
CertFile, KeyFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ flag.Value = &NamedCertKey{}
|
||||||
|
|
||||||
|
func (nkc *NamedCertKey) String() string {
|
||||||
|
s := nkc.CertFile + "," + nkc.KeyFile
|
||||||
|
if len(nkc.Names) > 0 {
|
||||||
|
s = s + ":" + strings.Join(nkc.Names, ",")
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nkc *NamedCertKey) Set(value string) error {
|
||||||
|
cs := strings.SplitN(value, ":", 2)
|
||||||
|
var keycert string
|
||||||
|
if len(cs) == 2 {
|
||||||
|
var names string
|
||||||
|
keycert, names = strings.TrimSpace(cs[0]), strings.TrimSpace(cs[1])
|
||||||
|
if names == "" {
|
||||||
|
return errors.New("empty names list is not allowed")
|
||||||
|
}
|
||||||
|
nkc.Names = nil
|
||||||
|
for _, name := range strings.Split(names, ",") {
|
||||||
|
nkc.Names = append(nkc.Names, strings.TrimSpace(name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nkc.Names = nil
|
||||||
|
keycert = strings.TrimSpace(cs[0])
|
||||||
|
}
|
||||||
|
cs = strings.Split(keycert, ",")
|
||||||
|
if len(cs) != 2 {
|
||||||
|
return errors.New("expected comma separated certificate and key file paths")
|
||||||
|
}
|
||||||
|
nkc.CertFile = strings.TrimSpace(cs[0])
|
||||||
|
nkc.KeyFile = strings.TrimSpace(cs[1])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NamedCertKey) Type() string {
|
||||||
|
return "namedCertKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedCertKeyArray is a flag value parsing NamedCertKeys, each passed with its own
|
||||||
|
// flag instance (in contrast to comma separated slices).
|
||||||
|
type NamedCertKeyArray struct {
|
||||||
|
value *[]NamedCertKey
|
||||||
|
changed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ flag.Value = &NamedCertKey{}
|
||||||
|
|
||||||
|
// NewNamedKeyCertArray creates a new NamedCertKeyArray with the internal value
|
||||||
|
// pointing to p.
|
||||||
|
func NewNamedCertKeyArray(p *[]NamedCertKey) *NamedCertKeyArray {
|
||||||
|
return &NamedCertKeyArray{
|
||||||
|
value: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NamedCertKeyArray) Set(val string) error {
|
||||||
|
nkc := NamedCertKey{}
|
||||||
|
err := nkc.Set(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !a.changed {
|
||||||
|
*a.value = []NamedCertKey{nkc}
|
||||||
|
a.changed = true
|
||||||
|
} else {
|
||||||
|
*a.value = append(*a.value, nkc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NamedCertKeyArray) Type() string {
|
||||||
|
return "namedCertKey"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NamedCertKeyArray) String() string {
|
||||||
|
nkcs := make([]string, 0, len(*a.value))
|
||||||
|
for i := range *a.value {
|
||||||
|
nkcs = append(nkcs, (*a.value)[i].String())
|
||||||
|
}
|
||||||
|
return "[" + strings.Join(nkcs, ";") + "]"
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["hash.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//vendor:github.com/davecgh/go-spew/spew"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["hash_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//vendor:github.com/davecgh/go-spew/spew"],
|
||||||
|
)
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
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 hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepHashObject writes specified object to hash using the spew library
|
||||||
|
// which follows pointers and prints actual values of the nested objects
|
||||||
|
// ensuring the hash does not change when a pointer changes.
|
||||||
|
func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
|
||||||
|
hasher.Reset()
|
||||||
|
printer := spew.ConfigState{
|
||||||
|
Indent: " ",
|
||||||
|
SortKeys: true,
|
||||||
|
DisableMethods: true,
|
||||||
|
SpewKeys: true,
|
||||||
|
}
|
||||||
|
printer.Fprintf(hasher, "%#v", objectToWrite)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_binary",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
"cgo_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"ipnet.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["ipnet_test.go"],
|
||||||
|
library = "go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [],
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This package contains hand-coded set implementations that should be similar
|
||||||
|
// to the autogenerated ones in pkg/util/sets.
|
||||||
|
// We can't simply use net.IPNet as a map-key in Go (because it contains a
|
||||||
|
// []byte).
|
||||||
|
// We could use the same workaround we use here (a string representation as the
|
||||||
|
// key) to autogenerate sets. If we do that, or decide on an alternate
|
||||||
|
// approach, we should replace the implementations in this package with the
|
||||||
|
// autogenerated versions.
|
||||||
|
// It is expected that callers will alias this import as "netsets" i.e. import
|
||||||
|
// netsets "k8s.io/kubernetes/pkg/util/net/sets"
|
||||||
|
|
||||||
|
package sets
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 sets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPNet map[string]*net.IPNet
|
||||||
|
|
||||||
|
func ParseIPNets(specs ...string) (IPNet, error) {
|
||||||
|
ipnetset := make(IPNet)
|
||||||
|
for _, spec := range specs {
|
||||||
|
spec = strings.TrimSpace(spec)
|
||||||
|
_, ipnet, err := net.ParseCIDR(spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k := ipnet.String() // In case of normalization
|
||||||
|
ipnetset[k] = ipnet
|
||||||
|
}
|
||||||
|
return ipnetset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert adds items to the set.
|
||||||
|
func (s IPNet) Insert(items ...*net.IPNet) {
|
||||||
|
for _, item := range items {
|
||||||
|
s[item.String()] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes all items from the set.
|
||||||
|
func (s IPNet) Delete(items ...*net.IPNet) {
|
||||||
|
for _, item := range items {
|
||||||
|
delete(s, item.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns true if and only if item is contained in the set.
|
||||||
|
func (s IPNet) Has(item *net.IPNet) bool {
|
||||||
|
_, contained := s[item.String()]
|
||||||
|
return contained
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAll returns true if and only if all items are contained in the set.
|
||||||
|
func (s IPNet) HasAll(items ...*net.IPNet) bool {
|
||||||
|
for _, item := range items {
|
||||||
|
if !s.Has(item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference returns a set of objects that are not in s2
|
||||||
|
// For example:
|
||||||
|
// s1 = {a1, a2, a3}
|
||||||
|
// s2 = {a1, a2, a4, a5}
|
||||||
|
// s1.Difference(s2) = {a3}
|
||||||
|
// s2.Difference(s1) = {a4, a5}
|
||||||
|
func (s IPNet) Difference(s2 IPNet) IPNet {
|
||||||
|
result := make(IPNet)
|
||||||
|
for k, i := range s {
|
||||||
|
_, found := s2[k]
|
||||||
|
if found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[k] = i
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns a []string with the String representation of each element in the set.
|
||||||
|
// Order is undefined.
|
||||||
|
func (s IPNet) StringSlice() []string {
|
||||||
|
a := make([]string, 0, len(s))
|
||||||
|
for k := range s {
|
||||||
|
a = append(a, k)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuperset returns true if and only if s1 is a superset of s2.
|
||||||
|
func (s1 IPNet) IsSuperset(s2 IPNet) bool {
|
||||||
|
for k := range s2 {
|
||||||
|
_, found := s1[k]
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if and only if s1 is equal (as a set) to s2.
|
||||||
|
// Two sets are equal if their membership is identical.
|
||||||
|
// (In practice, this means same elements, order doesn't matter)
|
||||||
|
func (s1 IPNet) Equal(s2 IPNet) bool {
|
||||||
|
return len(s1) == len(s2) && s1.IsSuperset(s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the size of the set.
|
||||||
|
func (s IPNet) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
|
@ -1354,6 +1354,12 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"versionExact": "1.0.0"
|
"versionExact": "1.0.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "xPPXsop1MasrinvYYmMBeuwDu9c=",
|
||||||
|
"path": "github.com/exponent-io/jsonpath",
|
||||||
|
"revision": "d6023ce2651d8eafb5c75bb0c7167536102ec9f5",
|
||||||
|
"revisionTime": "2015-10-13T19:33:12Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "40Ns85VYa4smQPcewZ7SOdfLnKU=",
|
"checksumSHA1": "40Ns85VYa4smQPcewZ7SOdfLnKU=",
|
||||||
"path": "github.com/fatih/structs",
|
"path": "github.com/fatih/structs",
|
||||||
|
@ -3281,6 +3287,18 @@
|
||||||
"revision": "f005bb24262365e93726b94d89e6cd8a26a7455e",
|
"revision": "f005bb24262365e93726b94d89e6cd8a26a7455e",
|
||||||
"revisionTime": "2017-02-20T05:26:33Z"
|
"revisionTime": "2017-02-20T05:26:33Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "7Moak3A23GAQIGxMBNwdHng8ZUk=",
|
||||||
|
"path": "k8s.io/apiserver/pkg/features",
|
||||||
|
"revision": "f5134760969bc1bb7457ac717f329538208174e6",
|
||||||
|
"revisionTime": "2017-02-23T16:14:39Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "eD47gRCYqFRXXw4CdIqIbGnJid8=",
|
||||||
|
"path": "k8s.io/apiserver/pkg/util/feature",
|
||||||
|
"revision": "f5134760969bc1bb7457ac717f329538208174e6",
|
||||||
|
"revisionTime": "2017-02-23T16:14:39Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "7AJnegJy/B9YU9VnaSjbmwo7bxA=",
|
"checksumSHA1": "7AJnegJy/B9YU9VnaSjbmwo7bxA=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api",
|
"path": "k8s.io/kubernetes/pkg/api",
|
||||||
|
@ -3289,6 +3307,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "yTgKpHz2CFFN/lMP9Evm87rvYkM=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/endpoints",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "4X0Ov/3JS85n222SqSb9qmp3OTs=",
|
"checksumSHA1": "4X0Ov/3JS85n222SqSb9qmp3OTs=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api/errors",
|
"path": "k8s.io/kubernetes/pkg/api/errors",
|
||||||
|
@ -3321,6 +3347,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "YU331KDG4VxRBuD4zFoE9LNj3t4=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/pod",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "glESePhq3u+sf13SpTzJ9ict/T4=",
|
"checksumSHA1": "glESePhq3u+sf13SpTzJ9ict/T4=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api/resource",
|
"path": "k8s.io/kubernetes/pkg/api/resource",
|
||||||
|
@ -3329,6 +3363,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "dYhk2yrh+k3G3APFuYx5vF8Rmw0=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/service",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "kgSrbsYhDwcpuGpFFiQClIq5I0w=",
|
"checksumSHA1": "kgSrbsYhDwcpuGpFFiQClIq5I0w=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api/unversioned",
|
"path": "k8s.io/kubernetes/pkg/api/unversioned",
|
||||||
|
@ -3337,6 +3379,22 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "S3bCRWM0uMVXSas9JvKQdqfH76s=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/unversioned/validation",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "Oe4S5Bk0mR5lU5mh6WsJfS5tNMU=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/util",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "W5U8zCxHpibrVPlVv3YtNAG6mhI=",
|
"checksumSHA1": "W5U8zCxHpibrVPlVv3YtNAG6mhI=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api/v1",
|
"path": "k8s.io/kubernetes/pkg/api/v1",
|
||||||
|
@ -3345,6 +3403,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "h9jLBKF7NtXnJ+GRyG6NZJMNyZA=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/api/validation",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "96gUSyImHFoeCmgTSNeupC3La5s=",
|
"checksumSHA1": "96gUSyImHFoeCmgTSNeupC3La5s=",
|
||||||
"path": "k8s.io/kubernetes/pkg/api/validation/path",
|
"path": "k8s.io/kubernetes/pkg/api/validation/path",
|
||||||
|
@ -3633,6 +3699,12 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "DwUkQeGOyctoYMqg/QbdEuTzLWE=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/capabilities",
|
||||||
|
"revision": "cff3c99613fda9d4b1dc0d959cb528326ee4416c",
|
||||||
|
"revisionTime": "2017-02-26T16:10:02Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "QNBGekG5aiFIkuaTHX0DTgKLyiE=",
|
"checksumSHA1": "QNBGekG5aiFIkuaTHX0DTgKLyiE=",
|
||||||
"path": "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5",
|
"path": "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5",
|
||||||
|
@ -3822,6 +3894,12 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "tJOdN7PyF09mHrpWplVYO609sI8=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/features",
|
||||||
|
"revision": "cff3c99613fda9d4b1dc0d959cb528326ee4416c",
|
||||||
|
"revisionTime": "2017-02-26T16:10:02Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "n22dqZqoIzhpTiW7WsteC+Zd4ww=",
|
"checksumSHA1": "n22dqZqoIzhpTiW7WsteC+Zd4ww=",
|
||||||
"path": "k8s.io/kubernetes/pkg/fields",
|
"path": "k8s.io/kubernetes/pkg/fields",
|
||||||
|
@ -3902,6 +3980,12 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "JS1foKD2+ytR+8ioRzb894EGHBw=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/security/apparmor",
|
||||||
|
"revision": "cff3c99613fda9d4b1dc0d959cb528326ee4416c",
|
||||||
|
"revisionTime": "2017-02-26T16:10:02Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "JoovLtllozmF5E6V55//mtxkS3U=",
|
"checksumSHA1": "JoovLtllozmF5E6V55//mtxkS3U=",
|
||||||
"path": "k8s.io/kubernetes/pkg/selection",
|
"path": "k8s.io/kubernetes/pkg/selection",
|
||||||
|
@ -3942,6 +4026,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "9lYagUSPgmV/iiHV/ONEXeqFblc=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/util/config",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "MfaCGqPJXEj29bpJ/cDAA1K9FE4=",
|
"checksumSHA1": "MfaCGqPJXEj29bpJ/cDAA1K9FE4=",
|
||||||
"path": "k8s.io/kubernetes/pkg/util/errors",
|
"path": "k8s.io/kubernetes/pkg/util/errors",
|
||||||
|
@ -3966,6 +4058,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "rvkgO7Yk+ISdyFyabs3nu2e4tIs=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/util/hash",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "a0hqN01LyyiMrSTNTWC4QbMvkJQ=",
|
"checksumSHA1": "a0hqN01LyyiMrSTNTWC4QbMvkJQ=",
|
||||||
"path": "k8s.io/kubernetes/pkg/util/homedir",
|
"path": "k8s.io/kubernetes/pkg/util/homedir",
|
||||||
|
@ -4022,6 +4122,14 @@
|
||||||
"version": "v1.5.3",
|
"version": "v1.5.3",
|
||||||
"versionExact": "v1.5.3"
|
"versionExact": "v1.5.3"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "D/GHEAK824gP9AuEI3ct2dRQ1I0=",
|
||||||
|
"path": "k8s.io/kubernetes/pkg/util/net/sets",
|
||||||
|
"revision": "029c3a408176b55c30846f0faedf56aae5992e9b",
|
||||||
|
"revisionTime": "2017-02-15T06:33:32Z",
|
||||||
|
"version": "v1.5.3",
|
||||||
|
"versionExact": "v1.5.3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "7cnboByPrgJAdMHsdDYmMV6Hy70=",
|
"checksumSHA1": "7cnboByPrgJAdMHsdDYmMV6Hy70=",
|
||||||
"path": "k8s.io/kubernetes/pkg/util/parsers",
|
"path": "k8s.io/kubernetes/pkg/util/parsers",
|
||||||
|
|
Loading…
Reference in New Issue