├── Makefile ├── README.md ├── example ├── config.yaml └── pod.yaml ├── src ├── command │ ├── pods.go │ └── root.go ├── kube-lint │ └── main.go ├── pods │ ├── pods.go │ └── pods_test.go └── rules │ └── rules.go └── vendor └── manifest /Makefile: -------------------------------------------------------------------------------- 1 | BIN=$(GOPATH)/bin 2 | GB=$(BIN)/gb 3 | 4 | .PHONY: deps 5 | deps: 6 | go get github.com/constabulary/gb/... 7 | $(GB) vendor restore 8 | 9 | .PHONY: build 10 | build: 11 | $(GB) test 12 | $(GB) build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kube-lint 2 | A linter for Kubernetes resources with a customizable rule set. 3 | 4 | ## Introduction 5 | `kube-lint` hopes to make it easy to validate that your Kubernetes configuration files and your running resources 6 | adhere to a standard that you define. You define a list of rules that you would like to validate against your resources 7 | and `kube-lint` will evaluate those rules against them. 8 | 9 | In many organizations you will want to have a standard for what is considered "correct" enough to be deployed into 10 | your Kubernetes clusters. You may have conventions for labels or restrictions on certain types of services being created. 11 | You can use `kube-lint` during your CI/CD pipeline to gate resources being created that do not adhere to your standards. 12 | Additionally you can use kube-lint to audit against a running set of resources in your cluster. 13 | 14 | ***CONSIDER THIS A PROTOTYPE. PLEASE PROVIDE FEEDBACK IN THE [ISSUES](https://github.com/viglesiasce/kube-lint/issues)*** 15 | 16 | ***Only Pod linting is currently implemented*** 17 | 18 | ## Installation 19 | ``` 20 | make deps 21 | make build 22 | ``` 23 | 24 | ## Rule configuration 25 | The rule configuration file is a YAML formatted list of [KubernetesRules](https://github.com/viglesiasce/kube-lint/blob/master/pkg/rules/rules.go#L44). An example config file is 26 | available at `example/config.yaml` in this repository. 27 | 28 | A KubernetesRule has the following format: 29 | ``` 30 | name: app-label 31 | description: Includes a label with key "app" 32 | kind: Pod 33 | field: .metadata.labels.app 34 | operator: set 35 | valueType: string 36 | tags: 37 | - operations 38 | - security 39 | ``` 40 | 41 | `name` is an identifier for this rule. 42 | 43 | `description` provides details about what the rule is checking for. 44 | 45 | `kind` is the type of resource this check should be done against. 46 | 47 | `field` is a [jsonpath](https://kubernetes.io/docs/user-guide/jsonpath/) used to get the value you want to evaluate against. 48 | 49 | `operator` is the check that youd like to do against your expected vs actual values (ie equal, matches, lessthan). 50 | For `string` type the available operators are `equal`, `notequal`, `set`, `unset`, `matches`. For `bool` type the available 51 | operators are `equal`, `notequal`, `set`, `unset`. For `float64` type, the available operators are `equal`, `notequal`, 52 | `set`, `unset`, `greaterthan`, `lessthan`. 53 | 54 | 55 | `valueType` is the type of the value that needs to be evaluated. `string` is the default. `bool` and `float64` are also implemented. 56 | 57 | `tags` is a list of strings that can be used to decide whether to run this rule or not via the CLI. 58 | 59 | ## Running kube-lint 60 | 61 | ### Basic operation 62 | Once installed you can run kube-lint from this directory as follows: 63 | ``` 64 | kube-lint pods --config example/config.yaml 65 | ``` 66 | 67 | To change the rules edit `example/config.yaml`. You rulebender you. 68 | 69 | ### Filtering rules by tag 70 | You can evaluate a subset of rules by filtering down to only those that include certain tags. For example: 71 | ``` 72 | kube-lint pods --config example/config.yaml --tags security,operations 73 | ``` 74 | ### Filtering resources by namespace 75 | You can also filter which resources are evaluated by passing the `--namespace` flag as follows: 76 | ``` 77 | kube-lint pods --config example/config.yaml --namespace kube-system 78 | ``` 79 | 80 | ## TODO if this seems like a reasonable approach to pursue 81 | - Replace `panic` everywhere with proper error handling 82 | - Add tests. Lots of tests. 83 | - Add docstrings to all exported functions/types/methods 84 | - Make -f be able to load a directories of yaml files (like kubectl) 85 | - Decide on how to deal with unset parameters 86 | - Choose a logging framework and use it 87 | - Add more resources (services/deployments/etc.) 88 | - Use ${HOME}/.kube-lint for config params 89 | - Develop standardized baseline of rules that are useful 90 | - Vendor dependencies using glide 91 | 92 | ## Contributing 93 | Add an issue to talk about what youd like to see changed. Lets talk about it then come up with a plan of action. 94 | -------------------------------------------------------------------------------- /example/config.yaml: -------------------------------------------------------------------------------- 1 | - name: unpriveleged 2 | description: Does not use security context priveleged 3 | kind: Pod 4 | field: .spec.containers[*].securityContext.privileged 5 | operator: unset 6 | tags: 7 | - security 8 | - name: no-host-network 9 | description: hostNetwork namespace 10 | kind: Pod 11 | field: .spec.hostNetwork 12 | operator: unset 13 | tags: 14 | - security 15 | - name: app-label 16 | description: Includes a label with key "app" 17 | kind: Pod 18 | field: .metadata.labels.app 19 | operator: set 20 | valueType: string 21 | tags: 22 | - operations 23 | - security 24 | - name: redis-port 25 | description: Uses the redis containerPort 26 | kind: Pod 27 | field: .spec.containers[*].ports[*].containerPort 28 | operator: equal 29 | valueType: float64 30 | value: 6379 31 | tags: 32 | - operations 33 | - security 34 | - name: name-regex 35 | description: Includes a name with cd- 36 | kind: Pod 37 | field: .metadata.name 38 | operator: matches 39 | value: "cd-.*" 40 | valueType: string 41 | tags: 42 | - operations 43 | - name: limit-set 44 | description: Has resource requests set 45 | kind: Pod 46 | field: .spec.containers[0].resources.requests.cpu 47 | operator: set 48 | valueType: string 49 | tags: 50 | - performance 51 | -------------------------------------------------------------------------------- /example/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: redis-django 5 | labels: 6 | app: web 7 | spec: 8 | containers: 9 | - name: key-value-store 10 | image: redis 11 | ports: 12 | - containerPort: 6379 13 | resources: 14 | requests: 15 | cpu: 100m 16 | memory: 100Mi 17 | limits: 18 | cpu: 100m 19 | memory: 100Mi -------------------------------------------------------------------------------- /src/command/pods.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Vic Iglesias 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package command 16 | 17 | import ( 18 | "io/ioutil" 19 | "os/user" 20 | 21 | v1 "k8s.io/api/core/v1" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/ghodss/yaml" 26 | "github.com/spf13/cobra" 27 | "pods" 28 | "rules" 29 | ) 30 | 31 | var filename string 32 | var configFile string 33 | var kubeconfig string 34 | var namespace string 35 | var tags []string 36 | var showAll bool 37 | 38 | // podsCmd represents the pods command 39 | var podsCmd = &cobra.Command{ 40 | Use: "pods", 41 | Short: "Evaluate rules for pods", 42 | Long: `Evaluate all rules marked as kind "Pod"`, 43 | Run: func(cmd *cobra.Command, args []string) { 44 | // load config 45 | if configFile == "" { 46 | fmt.Println("[ERROR] Pass your linter config file using --config or -c") 47 | os.Exit(1) 48 | } 49 | var config rules.LinterConfig 50 | configFile, err := ioutil.ReadFile(configFile) 51 | if err != nil { 52 | panic("Unable to read config file") 53 | } 54 | err = yaml.Unmarshal(configFile, &config) 55 | if err != nil { 56 | panic("Unable to unmarshal config file") 57 | } 58 | 59 | inputPods := []v1.Pod{} 60 | if filename != "" { 61 | fmt.Println("Getting pods for", filename) 62 | inputPods = pods.NewLocalFilesystem(filename).GetPods() 63 | } else { 64 | // kubeconfig has a default value so will always be populated 65 | fmt.Println("Getting pods for", kubeconfig) 66 | inputPods = pods.NewKubeServer(kubeconfig).GetPods(namespace) 67 | } 68 | 69 | if len(inputPods) == 0 { 70 | fmt.Println("NO PODS FOUND") 71 | os.Exit(0) 72 | } 73 | pods.EvaluateRules(config, inputPods, tags, showAll) 74 | }, 75 | } 76 | 77 | func init() { 78 | user, err := user.Current() 79 | if err != nil { 80 | fmt.Println("Unable to determine current user") 81 | os.Exit(1) 82 | } 83 | defaultKubeConfig := fmt.Sprintf("%s/%s", user.HomeDir, ".kube/config") 84 | RootCmd.AddCommand(podsCmd) 85 | podsCmd.PersistentFlags().StringVarP(&filename, "filename", "f", "", "Filename or directory of manifest(s)") 86 | podsCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "Filename of config file with linter rules") 87 | podsCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", defaultKubeConfig, "Path to the kubeconfig file to use for requests") 88 | podsCmd.PersistentFlags().StringVar(&namespace, "namespace", "", "Namespace to use for requests") 89 | podsCmd.PersistentFlags().StringSliceVarP(&tags, "tags", "t", []string{}, "Tags used to filter rules (all by default)") 90 | podsCmd.PersistentFlags().BoolVar(&showAll, "show-all", false, "Show passing rules and failing rules") 91 | } 92 | -------------------------------------------------------------------------------- /src/command/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Vic Iglesias 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package command 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/spf13/cobra" 22 | "github.com/spf13/viper" 23 | ) 24 | 25 | var cfgFile string 26 | 27 | //RootCmd represents the base command when called without any subcommands 28 | var RootCmd = &cobra.Command{ 29 | Use: "kube-lint", 30 | Short: "A brief description of your application", 31 | Long: `A longer description that spans multiple lines and likely contains 32 | examples and usage of using your application. For example: 33 | 34 | Cobra is a CLI library for Go that empowers applications. 35 | This application is a tool to generate the needed files 36 | to quickly create a Cobra application.`, 37 | // Uncomment the following line if your bare application 38 | // has an action associated with it: 39 | // Run: func(cmd *cobra.Command, args []string) { }, 40 | } 41 | 42 | // Execute adds all child commands to the root command sets flags appropriately. 43 | // This is called by main.main(). It only needs to happen once to the rootCmd. 44 | func Execute() { 45 | if err := RootCmd.Execute(); err != nil { 46 | fmt.Println(err) 47 | os.Exit(-1) 48 | } 49 | } 50 | 51 | func init() { 52 | cobra.OnInitialize(initConfig) 53 | 54 | // Here you will define your flags and configuration settings. 55 | // Cobra supports Persistent Flags, which, if defined here, 56 | // will be global for your application. 57 | 58 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kube-lint.yaml)") 59 | // Cobra also supports local flags, which will only run 60 | // when this action is called directly. 61 | RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 62 | } 63 | 64 | // initConfig reads in config file and ENV variables if set. 65 | func initConfig() { 66 | if cfgFile != "" { // enable ability to specify config file via flag 67 | viper.SetConfigFile(cfgFile) 68 | } 69 | 70 | viper.SetConfigName(".kube-lint") // name of config file (without extension) 71 | viper.AddConfigPath("$HOME") // adding home directory as first search path 72 | viper.AutomaticEnv() // read in environment variables that match 73 | 74 | // If a config file is found, read it in. 75 | if err := viper.ReadInConfig(); err == nil { 76 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/kube-lint/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "command" 19 | ) 20 | 21 | func main() { 22 | 23 | command.Execute() 24 | } 25 | -------------------------------------------------------------------------------- /src/pods/pods.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Vic Iglesias 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pods 16 | 17 | import ( 18 | "encoding/json" 19 | "github.com/fatih/color" 20 | "github.com/ghodss/yaml" 21 | "github.com/olekukonko/tablewriter" 22 | "io/ioutil" 23 | v1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/client-go/kubernetes" 26 | "k8s.io/client-go/tools/clientcmd" 27 | "os" 28 | "rules" 29 | ) 30 | 31 | type PodList interface { 32 | GetPods() []v1.Pod 33 | } 34 | 35 | type KubeServer struct { 36 | kubeconfig string 37 | } 38 | 39 | func NewKubeServer(kubeconfig string) KubeServer { 40 | return KubeServer{kubeconfig: kubeconfig} 41 | } 42 | 43 | func (ks KubeServer) GetPods(namespace string) []v1.Pod { 44 | k8sClientConfig, err := clientcmd.BuildConfigFromFlags("", ks.kubeconfig) 45 | if err != nil { 46 | panic(err.Error()) 47 | } 48 | clientset, err := kubernetes.NewForConfig(k8sClientConfig) 49 | if err != nil { 50 | panic(err.Error()) 51 | } 52 | 53 | pods, err := clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{}) 54 | if err != nil { 55 | panic(err.Error()) 56 | } 57 | return pods.Items 58 | } 59 | 60 | type LocalFilesystem struct { 61 | path string 62 | } 63 | 64 | func NewLocalFilesystem(path string) LocalFilesystem { 65 | return LocalFilesystem{path: path} 66 | } 67 | 68 | func (lfs LocalFilesystem) GetPods() []v1.Pod { 69 | resourceFile, err := ioutil.ReadFile(lfs.path) 70 | 71 | if err != nil { 72 | panic("Unable to read pod file") 73 | } 74 | 75 | resourceJSONBytes, err := yaml.YAMLToJSON(resourceFile) 76 | if err != nil { 77 | panic("Unable to convert resource to JSON") 78 | } 79 | 80 | if err != nil { 81 | panic("Unable to Unmarshal json") 82 | } 83 | pod := v1.Pod{} 84 | err = json.Unmarshal(resourceJSONBytes, &pod) 85 | pods := []v1.Pod{pod} 86 | return pods 87 | } 88 | 89 | func CreateTable() *tablewriter.Table { 90 | table := tablewriter.NewWriter(os.Stdout) 91 | // "PROFILE", "OPERATOR", "EXPECTED", "ACTUAL", 92 | table.SetHeader([]string{"POD", "DESCRIPTION", "RESULT"}) 93 | table.SetHeaderLine(false) 94 | table.SetBorder(false) 95 | table.SetCenterSeparator("") 96 | table.SetColumnSeparator("") 97 | table.SetRowSeparator("") 98 | return table 99 | } 100 | 101 | func EvaluateRules(config rules.LinterConfig, pods []v1.Pod, tags []string, showAll bool) { 102 | table := CreateTable() 103 | activeRules := rules.LinterConfig{} 104 | if len(tags) == 0 { 105 | // Evaluate all rules by default 106 | activeRules = config 107 | } else { 108 | // Filter active rules by tag 109 | // Currently multiple tags are OR'd 110 | // label break taken from: https://www.goinggo.net/2013/11/label-breaks-in-go.html 111 | for _, rule := range config { 112 | TagLoop: 113 | for _, ruleTag := range rule.Tags { 114 | for _, tag := range tags { 115 | if ruleTag == tag { 116 | activeRules = append(activeRules, rule) 117 | break TagLoop 118 | } 119 | } 120 | } 121 | } 122 | } 123 | for _, pod := range pods { 124 | for _, rule := range activeRules { 125 | // TODO need to be able to provide a list of resources to test against 126 | if rule.Kind != "Pod" && rule.Kind != "" { 127 | continue 128 | } 129 | k8sRule := rules.NewKubernetesRule(rule.Operator, rule.Field, rule.Value, rule.ValueType) 130 | resourceJSON, err := json.Marshal(pod) 131 | if err != nil { 132 | panic(err) 133 | } 134 | result := k8sRule.Evaluate(resourceJSON) 135 | if err != nil { 136 | panic(err) 137 | } 138 | var colorizedResult string 139 | if result.Passed { 140 | if showAll { 141 | colorizedResult = color.GreenString("passed") 142 | table.Append([]string{pod.Name, rule.Description, colorizedResult}) 143 | } 144 | } else { 145 | colorizedResult = color.RedString("failed") 146 | table.Append([]string{pod.Name, rule.Description, colorizedResult}) 147 | } 148 | } 149 | } 150 | table.Render() 151 | } 152 | -------------------------------------------------------------------------------- /src/pods/pods_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Vic Iglesias 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pods 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | 21 | "encoding/json" 22 | 23 | "io/ioutil" 24 | 25 | "rules" 26 | 27 | "github.com/ghodss/yaml" 28 | "github.com/olekukonko/tablewriter" 29 | v1 "k8s.io/api/core/v1" 30 | ) 31 | 32 | func TestGetPodsFromServer(t *testing.T) { 33 | type args struct { 34 | kubeconfig string 35 | } 36 | tests := []struct { 37 | name string 38 | args args 39 | want []v1.Pod 40 | }{} 41 | for _, tt := range tests { 42 | if got := NewKubeServer(tt.args.kubeconfig).GetPods(""); !reflect.DeepEqual(got, tt.want) { 43 | t.Errorf("%q. GetPodsFromServer() = %v, want %v", tt.name, got, tt.want) 44 | } 45 | } 46 | } 47 | 48 | func TestGetPodsFromFile(t *testing.T) { 49 | type args struct { 50 | filename string 51 | } 52 | examplePodFile := "../../example/pod.yaml" 53 | examplePod := v1.Pod{} 54 | podBytes, err := ioutil.ReadFile(examplePodFile) 55 | if err != nil { 56 | t.Errorf("Unable to read example file") 57 | } 58 | podJSONBytes, err := yaml.YAMLToJSON(podBytes) 59 | if err != nil { 60 | panic(err) 61 | } 62 | err = json.Unmarshal(podJSONBytes, &examplePod) 63 | if err != nil { 64 | panic(err) 65 | } 66 | tests := []struct { 67 | name string 68 | args args 69 | want []v1.Pod 70 | }{ 71 | {"Example file", args{filename: examplePodFile}, []v1.Pod{examplePod}}, 72 | } 73 | for _, tt := range tests { 74 | if got := NewLocalFilesystem(tt.args.filename).GetPods(); !reflect.DeepEqual(got, tt.want) { 75 | t.Errorf("%q. GetPodsFromFile() = %v, want %v", tt.name, got, tt.want) 76 | } 77 | } 78 | } 79 | 80 | func TestCreateTable(t *testing.T) { 81 | tests := []struct { 82 | name string 83 | want *tablewriter.Table 84 | }{ 85 | // TODO: Add test cases. 86 | } 87 | for _, tt := range tests { 88 | if got := CreateTable(); !reflect.DeepEqual(got, tt.want) { 89 | t.Errorf("%q. CreateTable() = %v, want %v", tt.name, got, tt.want) 90 | } 91 | } 92 | } 93 | 94 | func TestEvaluateRules(t *testing.T) { 95 | type args struct { 96 | config rules.LinterConfig 97 | pods []v1.Pod 98 | tags []string 99 | showAll bool 100 | } 101 | tests := []struct { 102 | name string 103 | args args 104 | }{ 105 | // TODO: Add test cases. 106 | } 107 | for _, tt := range tests { 108 | EvaluateRules(tt.args.config, tt.args.pods, tt.args.tags, tt.args.showAll) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/rules/rules.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Vic Iglesias 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package rules 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "regexp" 22 | "strconv" 23 | "strings" 24 | "k8s.io/client-go/util/jsonpath" 25 | ) 26 | 27 | type Rule interface { 28 | Evaluate(resource []byte) Result 29 | GetName() string 30 | } 31 | 32 | type Result struct { 33 | Passed bool 34 | Expected string 35 | Actual string 36 | } 37 | 38 | type Operator interface { 39 | Evaluate(resource []byte, field string) 40 | } 41 | 42 | // KubernetesRule represents a single policy for the linting of a resource 43 | type KubernetesRule struct { 44 | Name string `json:"name"` 45 | Description string `json:"description"` 46 | Kind string `json:"kind"` 47 | Operator string `json:"operator"` 48 | Field string `json:"field"` 49 | Value interface{} `json:"value"` 50 | ValueType string `json:"valueType"` 51 | Tags []string `json:"tags"` 52 | } 53 | 54 | type LinterConfig []KubernetesRule 55 | 56 | // Evaluate rule 57 | func (kr KubernetesRule) Evaluate(resource []byte) Result { 58 | j := jsonpath.New(kr.Name) 59 | j.AllowMissingKeys(true) 60 | // insert brackets for the user so that the YAML gets easier to write 61 | err := j.Parse("{" + kr.Field + "}") 62 | if err != nil { 63 | panic(fmt.Errorf("in %s, parse %s error %v", kr.Name, resource, err)) 64 | } 65 | buf := new(bytes.Buffer) 66 | var data interface{} 67 | err = json.Unmarshal(resource, &data) 68 | if err != nil { 69 | panic(err) 70 | } 71 | err = j.Execute(buf, data) 72 | if err != nil { 73 | panic(fmt.Errorf("in %s, execute error %v", kr.Name, err)) 74 | } 75 | 76 | var value string 77 | var passed bool 78 | var out interface{} 79 | var actual string 80 | switch kr.ValueType { 81 | case "float64": 82 | var parsedValue float64 83 | // TODO what should you do if it is not found? 84 | if buf.String() != "" { 85 | floats := strings.Fields(buf.String()) 86 | for _, float := range floats { 87 | parsedValue64, err := strconv.ParseFloat(float, 64) 88 | if err != nil { 89 | panic(err) 90 | } 91 | parsedValue = parsedValue64 92 | out, passed = kr.evaluateAsFloat(parsedValue) 93 | if passed { 94 | break 95 | } 96 | } 97 | actual = buf.String() 98 | value = strconv.FormatFloat(kr.Value.(float64), 'f', 4, 64) 99 | } 100 | case "bool": 101 | parsedValue := false 102 | // TODO what should you do if it is not found? 103 | if buf.String() != "" { 104 | bools := strings.Fields(buf.String()) 105 | for _, boolean := range bools { 106 | parsedValue, err = strconv.ParseBool(boolean) 107 | if err != nil { 108 | panic(err) 109 | } 110 | out, passed = kr.evaluateAsBool(parsedValue) 111 | if passed { 112 | break 113 | } 114 | 115 | } 116 | actual = strconv.FormatBool(out.(bool)) 117 | value = strconv.FormatBool(kr.Value.(bool)) 118 | } 119 | 120 | // String is the default type 121 | default: 122 | // TODO need to figure out what to do with the evaluation of multiple 123 | // fields (ie .spec.containers[*].name ) 124 | out, passed = kr.evaluateAsString(buf.String()) 125 | actual = out.(string) 126 | if kr.Value != nil { 127 | value = kr.Value.(string) 128 | } else { 129 | value = "" 130 | } 131 | } 132 | return Result{Passed: passed, Expected: value, Actual: actual} 133 | } 134 | 135 | func (kr KubernetesRule) evaluateAsBool(value bool) (bool, bool) { 136 | var passed bool 137 | switch kr.Operator { 138 | case "null": 139 | passed = true 140 | case "equal": 141 | passed = value == kr.Value.(bool) 142 | case "notequal": 143 | passed = value != kr.Value.(bool) 144 | case "set": 145 | passed = value == true 146 | case "unset": 147 | passed = value == false 148 | default: 149 | panic("Operator not implemented for boolean type: " + kr.Operator) 150 | } 151 | return value, passed 152 | } 153 | 154 | func (kr KubernetesRule) evaluateAsString(value string) (string, bool) { 155 | var passed bool 156 | switch kr.Operator { 157 | case "null": 158 | passed = true 159 | case "equal": 160 | passed = value == kr.Value.(string) 161 | case "notequal": 162 | passed = value != kr.Value.(string) 163 | case "set": 164 | passed = value != "" 165 | case "unset": 166 | passed = value == "" 167 | case "matches": 168 | regex := regexp.MustCompile(kr.Value.(string)) 169 | passed = regex.MatchString(value) 170 | default: 171 | panic("Operator not implemented for string type: " + kr.Operator) 172 | } 173 | return value, passed 174 | } 175 | 176 | func (kr KubernetesRule) evaluateAsFloat(value float64) (float64, bool) { 177 | var passed bool 178 | switch kr.Operator { 179 | case "null": 180 | passed = true 181 | case "equal": 182 | passed = value == kr.Value.(float64) 183 | case "notequal": 184 | passed = value != kr.Value.(float64) 185 | case "greaterthan": 186 | passed = value > kr.Value.(float64) 187 | case "lessthan": 188 | passed = value < kr.Value.(float64) 189 | case "set": 190 | passed = value != 0 191 | case "unset": 192 | passed = value == 0 193 | default: 194 | panic("Operator not implemented for string type: " + kr.Operator) 195 | } 196 | return value, passed 197 | } 198 | 199 | // GetName returns the name of a KubernetesRule 200 | func (kr KubernetesRule) GetName() string { 201 | return kr.Name 202 | } 203 | 204 | // NewKubernetesRule returns a KubernetesRule object 205 | func NewKubernetesRule(operator string, field string, value interface{}, valueType string) Rule { 206 | return KubernetesRule{Operator: operator, Field: field, Value: value, ValueType: valueType} 207 | } 208 | -------------------------------------------------------------------------------- /vendor/manifest: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "dependencies": [ 4 | { 5 | "importpath": "cloud.google.com/go/compute/metadata", 6 | "repository": "https://code.googlesource.com/gocloud", 7 | "revision": "e0c17b1c932efccf9d30ca4beca8bcf215ea7762", 8 | "branch": "master", 9 | "path": "/compute/metadata" 10 | }, 11 | { 12 | "importpath": "cloud.google.com/go/internal", 13 | "repository": "https://code.googlesource.com/gocloud", 14 | "revision": "e0c17b1c932efccf9d30ca4beca8bcf215ea7762", 15 | "branch": "master", 16 | "path": "/internal" 17 | }, 18 | { 19 | "importpath": "github.com/Azure/go-autorest/autorest", 20 | "repository": "https://github.com/Azure/go-autorest", 21 | "revision": "a88c19ef2016e095f0b6c3b451074b4663f53bed", 22 | "branch": "master", 23 | "path": "/autorest" 24 | }, 25 | { 26 | "importpath": "github.com/Azure/go-autorest/logger", 27 | "repository": "https://github.com/Azure/go-autorest", 28 | "revision": "a88c19ef2016e095f0b6c3b451074b4663f53bed", 29 | "branch": "master", 30 | "path": "/logger" 31 | }, 32 | { 33 | "importpath": "github.com/Azure/go-autorest/version", 34 | "repository": "https://github.com/Azure/go-autorest", 35 | "revision": "a88c19ef2016e095f0b6c3b451074b4663f53bed", 36 | "branch": "master", 37 | "path": "/version" 38 | }, 39 | { 40 | "importpath": "github.com/cpuguy83/go-md2man/md2man", 41 | "repository": "https://github.com/cpuguy83/go-md2man", 42 | "revision": "691ee98543af2f262f35fbb54bdd42f00b9b9cc5", 43 | "branch": "master", 44 | "path": "/md2man" 45 | }, 46 | { 47 | "importpath": "github.com/davecgh/go-spew/spew", 48 | "repository": "https://github.com/davecgh/go-spew", 49 | "revision": "d8f796af33cc11cb798c1aaeb27a4ebc5099927d", 50 | "branch": "master", 51 | "path": "/spew" 52 | }, 53 | { 54 | "importpath": "github.com/dgrijalva/jwt-go", 55 | "repository": "https://github.com/dgrijalva/jwt-go", 56 | "revision": "0b96aaa707760d6ab28d9b9d1913ff5993328bae", 57 | "branch": "master" 58 | }, 59 | { 60 | "importpath": "github.com/docker/spdystream/spdy", 61 | "repository": "https://github.com/docker/spdystream", 62 | "revision": "bc6354cbbc295e925e4c611ffe90c1f287ee54db", 63 | "branch": "master", 64 | "path": "/spdy" 65 | }, 66 | { 67 | "importpath": "github.com/fatih/color", 68 | "repository": "https://github.com/fatih/color", 69 | "revision": "2d684516a8861da43017284349b7e303e809ac21", 70 | "branch": "master" 71 | }, 72 | { 73 | "importpath": "github.com/fsnotify/fsnotify", 74 | "repository": "https://github.com/fsnotify/fsnotify", 75 | "revision": "ccc981bf80385c528a65fbfdd49bf2d8da22aa23", 76 | "branch": "master" 77 | }, 78 | { 79 | "importpath": "github.com/ghodss/yaml", 80 | "repository": "https://github.com/ghodss/yaml", 81 | "revision": "c7ce16629ff4cd059ed96ed06419dd3856fd3577", 82 | "branch": "master" 83 | }, 84 | { 85 | "importpath": "github.com/gogo/protobuf/proto", 86 | "repository": "https://github.com/gogo/protobuf", 87 | "revision": "e14cafb6a2c249986e51c4b65c3206bf18578715", 88 | "branch": "master", 89 | "path": "/proto" 90 | }, 91 | { 92 | "importpath": "github.com/gogo/protobuf/sortkeys", 93 | "repository": "https://github.com/gogo/protobuf", 94 | "revision": "e14cafb6a2c249986e51c4b65c3206bf18578715", 95 | "branch": "master", 96 | "path": "/sortkeys" 97 | }, 98 | { 99 | "importpath": "github.com/golang/glog", 100 | "repository": "https://github.com/golang/glog", 101 | "revision": "23def4e6c14b4da8ac2ed8007337bc5eb5007998", 102 | "branch": "master" 103 | }, 104 | { 105 | "importpath": "github.com/golang/groupcache/lru", 106 | "repository": "https://github.com/golang/groupcache", 107 | "revision": "24b0969c4cb722950103eed87108c8d291a8df00", 108 | "branch": "master", 109 | "path": "/lru" 110 | }, 111 | { 112 | "importpath": "github.com/golang/protobuf/proto", 113 | "repository": "https://github.com/golang/protobuf", 114 | "revision": "7716a980bceefe9a0213654823d9ea62d23b87a2", 115 | "branch": "master", 116 | "path": "/proto" 117 | }, 118 | { 119 | "importpath": "github.com/golang/protobuf/ptypes", 120 | "repository": "https://github.com/golang/protobuf", 121 | "revision": "7716a980bceefe9a0213654823d9ea62d23b87a2", 122 | "branch": "master", 123 | "path": "/ptypes" 124 | }, 125 | { 126 | "importpath": "github.com/google/btree", 127 | "repository": "https://github.com/google/btree", 128 | "revision": "4030bb1f1f0c35b30ca7009e9ebd06849dd45306", 129 | "branch": "master" 130 | }, 131 | { 132 | "importpath": "github.com/google/gofuzz", 133 | "repository": "https://github.com/google/gofuzz", 134 | "revision": "24818f796faf91cd76ec7bddd72458fbced7a6c1", 135 | "branch": "master" 136 | }, 137 | { 138 | "importpath": "github.com/googleapis/gax-go", 139 | "repository": "https://github.com/googleapis/gax-go", 140 | "revision": "1ef592c90f479e3ab30c6c2312e20e13881b7ea6", 141 | "branch": "master" 142 | }, 143 | { 144 | "importpath": "github.com/googleapis/gnostic/OpenAPIv2", 145 | "repository": "https://github.com/googleapis/gnostic", 146 | "revision": "48a0ecefe2e4190c7a2d63b477875854a2f993b3", 147 | "branch": "master", 148 | "path": "/OpenAPIv2" 149 | }, 150 | { 151 | "importpath": "github.com/googleapis/gnostic/compiler", 152 | "repository": "https://github.com/googleapis/gnostic", 153 | "revision": "48a0ecefe2e4190c7a2d63b477875854a2f993b3", 154 | "branch": "master", 155 | "path": "/compiler" 156 | }, 157 | { 158 | "importpath": "github.com/googleapis/gnostic/extensions", 159 | "repository": "https://github.com/googleapis/gnostic", 160 | "revision": "48a0ecefe2e4190c7a2d63b477875854a2f993b3", 161 | "branch": "master", 162 | "path": "/extensions" 163 | }, 164 | { 165 | "importpath": "github.com/gophercloud/gophercloud", 166 | "repository": "https://github.com/gophercloud/gophercloud", 167 | "revision": "08691de1bc189905e3271adff2b51bc7cd7578ba", 168 | "branch": "master" 169 | }, 170 | { 171 | "importpath": "github.com/gregjones/httpcache", 172 | "repository": "https://github.com/gregjones/httpcache", 173 | "revision": "9cad4c3443a7200dd6400aef47183728de563a38", 174 | "branch": "master" 175 | }, 176 | { 177 | "importpath": "github.com/hashicorp/golang-lru/simplelru", 178 | "repository": "https://github.com/hashicorp/golang-lru", 179 | "revision": "20f1fb78b0740ba8c3cb143a61e86ba5c8669768", 180 | "branch": "master", 181 | "path": "/simplelru" 182 | }, 183 | { 184 | "importpath": "github.com/hashicorp/hcl", 185 | "repository": "https://github.com/hashicorp/hcl", 186 | "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", 187 | "branch": "master" 188 | }, 189 | { 190 | "importpath": "github.com/imdario/mergo", 191 | "repository": "https://github.com/imdario/mergo", 192 | "revision": "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4", 193 | "branch": "master" 194 | }, 195 | { 196 | "importpath": "github.com/json-iterator/go", 197 | "repository": "https://github.com/json-iterator/go", 198 | "revision": "2433035e513208b0f7bd5b50d0aecd889b2c1ff8", 199 | "branch": "master" 200 | }, 201 | { 202 | "importpath": "github.com/kubernetes/client-go", 203 | "repository": "https://github.com/kubernetes/client-go", 204 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 205 | "branch": "master" 206 | }, 207 | { 208 | "importpath": "github.com/kubernetes/client-go/tools/clientcmd", 209 | "repository": "https://github.com/kubernetes/client-go", 210 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 211 | "branch": "master", 212 | "path": "/tools/clientcmd" 213 | }, 214 | { 215 | "importpath": "github.com/magiconair/properties", 216 | "repository": "https://github.com/magiconair/properties", 217 | "revision": "c2353362d570a7bfa228149c62842019201cfb71", 218 | "branch": "master" 219 | }, 220 | { 221 | "importpath": "github.com/mattn/go-colorable", 222 | "repository": "https://github.com/mattn/go-colorable", 223 | "revision": "efa589957cd060542a26d2dd7832fd6a6c6c3ade", 224 | "branch": "master" 225 | }, 226 | { 227 | "importpath": "github.com/mattn/go-isatty", 228 | "repository": "https://github.com/mattn/go-isatty", 229 | "revision": "3fb116b820352b7f0c281308a4d6250c22d94e27", 230 | "branch": "master" 231 | }, 232 | { 233 | "importpath": "github.com/mattn/go-runewidth", 234 | "repository": "https://github.com/mattn/go-runewidth", 235 | "revision": "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb", 236 | "branch": "master" 237 | }, 238 | { 239 | "importpath": "github.com/mitchellh/go-homedir", 240 | "repository": "https://github.com/mitchellh/go-homedir", 241 | "revision": "ae18d6b8b3205b561c79e8e5f69bff09736185f4", 242 | "branch": "master" 243 | }, 244 | { 245 | "importpath": "github.com/mitchellh/mapstructure", 246 | "repository": "https://github.com/mitchellh/mapstructure", 247 | "revision": "fa473d140ef3c6adf42d6b391fe76707f1f243c8", 248 | "branch": "master" 249 | }, 250 | { 251 | "importpath": "github.com/modern-go/concurrent", 252 | "repository": "https://github.com/modern-go/concurrent", 253 | "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", 254 | "branch": "master" 255 | }, 256 | { 257 | "importpath": "github.com/modern-go/reflect2", 258 | "repository": "https://github.com/modern-go/reflect2", 259 | "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", 260 | "branch": "master" 261 | }, 262 | { 263 | "importpath": "github.com/olekukonko/tablewriter", 264 | "repository": "https://github.com/olekukonko/tablewriter", 265 | "revision": "be2c049b30ccd4d3fd795d6bf7dce74e42eeedaa", 266 | "branch": "master" 267 | }, 268 | { 269 | "importpath": "github.com/pelletier/go-toml", 270 | "repository": "https://github.com/pelletier/go-toml", 271 | "revision": "e33f6544292e42cce6432aad20ac1eb020e2a1d8", 272 | "branch": "master" 273 | }, 274 | { 275 | "importpath": "github.com/peterbourgon/diskv", 276 | "repository": "https://github.com/peterbourgon/diskv", 277 | "revision": "0646ccaebea1ed1539efcab30cae44019090093f", 278 | "branch": "master" 279 | }, 280 | { 281 | "importpath": "github.com/pmezard/go-difflib/difflib", 282 | "repository": "https://github.com/pmezard/go-difflib", 283 | "revision": "792786c7400a136282c1664665ae0a8db921c6c2", 284 | "branch": "master", 285 | "path": "/difflib" 286 | }, 287 | { 288 | "importpath": "github.com/russross/blackfriday", 289 | "repository": "https://github.com/russross/blackfriday", 290 | "revision": "05f3235734ad95d0016f6a23902f06461fcf567a", 291 | "branch": "master" 292 | }, 293 | { 294 | "importpath": "github.com/spf13/afero", 295 | "repository": "https://github.com/spf13/afero", 296 | "revision": "d40851caa0d747393da1ffb28f7f9d8b4eeffebd", 297 | "branch": "master" 298 | }, 299 | { 300 | "importpath": "github.com/spf13/cast", 301 | "repository": "https://github.com/spf13/cast", 302 | "revision": "8965335b8c7107321228e3e3702cab9832751bac", 303 | "branch": "master" 304 | }, 305 | { 306 | "importpath": "github.com/spf13/cobra", 307 | "repository": "https://github.com/spf13/cobra", 308 | "revision": "8d114be902bc9f08717804830a55c48378108a28", 309 | "branch": "master" 310 | }, 311 | { 312 | "importpath": "github.com/spf13/jwalterweatherman", 313 | "repository": "https://github.com/spf13/jwalterweatherman", 314 | "revision": "4a4406e478ca629068e7768fc33f3f044173c0a6", 315 | "branch": "master" 316 | }, 317 | { 318 | "importpath": "github.com/spf13/pflag", 319 | "repository": "https://github.com/spf13/pflag", 320 | "revision": "298182f68c66c05229eb03ac171abe6e309ee79a", 321 | "branch": "master" 322 | }, 323 | { 324 | "importpath": "github.com/spf13/viper", 325 | "repository": "https://github.com/spf13/viper", 326 | "revision": "3171ef9a229903ce60a9513ec3899b63c003e91c", 327 | "branch": "master" 328 | }, 329 | { 330 | "importpath": "golang.org/x/crypto/ssh/terminal", 331 | "repository": "https://go.googlesource.com/crypto", 332 | "revision": "0e37d006457bf46f9e6692014ba72ef82c33022c", 333 | "branch": "master", 334 | "path": "/ssh/terminal" 335 | }, 336 | { 337 | "importpath": "golang.org/x/net/context", 338 | "repository": "https://go.googlesource.com/net", 339 | "revision": "2f5d2388922f370f4355f327fcf4cfe9f5583908", 340 | "branch": "master", 341 | "path": "/context" 342 | }, 343 | { 344 | "importpath": "golang.org/x/net/http/httpguts", 345 | "repository": "https://go.googlesource.com/net", 346 | "revision": "2f5d2388922f370f4355f327fcf4cfe9f5583908", 347 | "branch": "master", 348 | "path": "/http/httpguts" 349 | }, 350 | { 351 | "importpath": "golang.org/x/net/http2", 352 | "repository": "https://go.googlesource.com/net", 353 | "revision": "2f5d2388922f370f4355f327fcf4cfe9f5583908", 354 | "branch": "master", 355 | "path": "/http2" 356 | }, 357 | { 358 | "importpath": "golang.org/x/net/idna", 359 | "repository": "https://go.googlesource.com/net", 360 | "revision": "2f5d2388922f370f4355f327fcf4cfe9f5583908", 361 | "branch": "master", 362 | "path": "/idna" 363 | }, 364 | { 365 | "importpath": "golang.org/x/oauth2", 366 | "repository": "https://go.googlesource.com/oauth2", 367 | "revision": "d2e6202438beef2727060aa7cabdd924d92ebfd9", 368 | "branch": "master" 369 | }, 370 | { 371 | "importpath": "golang.org/x/sys/unix", 372 | "repository": "https://go.googlesource.com/sys", 373 | "revision": "d641721ec2dead6fe5ca284096fe4b1fcd49e427", 374 | "branch": "master", 375 | "path": "/unix" 376 | }, 377 | { 378 | "importpath": "golang.org/x/text/secure/bidirule", 379 | "repository": "https://go.googlesource.com/text", 380 | "revision": "905a57155faa8230500121607930ebb9dd8e139c", 381 | "branch": "master", 382 | "path": "/secure/bidirule" 383 | }, 384 | { 385 | "importpath": "golang.org/x/text/transform", 386 | "repository": "https://go.googlesource.com/text", 387 | "revision": "905a57155faa8230500121607930ebb9dd8e139c", 388 | "branch": "master", 389 | "path": "/transform" 390 | }, 391 | { 392 | "importpath": "golang.org/x/text/unicode/bidi", 393 | "repository": "https://go.googlesource.com/text", 394 | "revision": "905a57155faa8230500121607930ebb9dd8e139c", 395 | "branch": "master", 396 | "path": "/unicode/bidi" 397 | }, 398 | { 399 | "importpath": "golang.org/x/text/unicode/norm", 400 | "repository": "https://go.googlesource.com/text", 401 | "revision": "905a57155faa8230500121607930ebb9dd8e139c", 402 | "branch": "master", 403 | "path": "/unicode/norm" 404 | }, 405 | { 406 | "importpath": "golang.org/x/time/rate", 407 | "repository": "https://go.googlesource.com/time", 408 | "revision": "fbb02b2291d28baffd63558aa44b4b56f178d650", 409 | "branch": "master", 410 | "path": "/rate" 411 | }, 412 | { 413 | "importpath": "gopkg.in/inf.v0", 414 | "repository": "https://gopkg.in/inf.v0", 415 | "revision": "d2d2541c53f18d2a059457998ce2876cc8e67cbf", 416 | "branch": "master" 417 | }, 418 | { 419 | "importpath": "gopkg.in/yaml.v2", 420 | "repository": "https://gopkg.in/yaml.v2", 421 | "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", 422 | "branch": "master" 423 | }, 424 | { 425 | "importpath": "k8s.io/api/admissionregistration/v1alpha1", 426 | "repository": "https://github.com/kubernetes/api", 427 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 428 | "branch": "master", 429 | "path": "/admissionregistration/v1alpha1" 430 | }, 431 | { 432 | "importpath": "k8s.io/api/admissionregistration/v1beta1", 433 | "repository": "https://github.com/kubernetes/api", 434 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 435 | "branch": "master", 436 | "path": "/admissionregistration/v1beta1" 437 | }, 438 | { 439 | "importpath": "k8s.io/api/apps/v1", 440 | "repository": "https://github.com/kubernetes/api", 441 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 442 | "branch": "master", 443 | "path": "/apps/v1" 444 | }, 445 | { 446 | "importpath": "k8s.io/api/apps/v1beta1", 447 | "repository": "https://github.com/kubernetes/api", 448 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 449 | "branch": "master", 450 | "path": "/apps/v1beta1" 451 | }, 452 | { 453 | "importpath": "k8s.io/api/apps/v1beta2", 454 | "repository": "https://github.com/kubernetes/api", 455 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 456 | "branch": "master", 457 | "path": "/apps/v1beta2" 458 | }, 459 | { 460 | "importpath": "k8s.io/api/auditregistration/v1alpha1", 461 | "repository": "https://github.com/kubernetes/api", 462 | "revision": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4", 463 | "branch": "master", 464 | "path": "/auditregistration/v1alpha1" 465 | }, 466 | { 467 | "importpath": "k8s.io/api/authentication/v1", 468 | "repository": "https://github.com/kubernetes/api", 469 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 470 | "branch": "master", 471 | "path": "/authentication/v1" 472 | }, 473 | { 474 | "importpath": "k8s.io/api/authentication/v1beta1", 475 | "repository": "https://github.com/kubernetes/api", 476 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 477 | "branch": "master", 478 | "path": "/authentication/v1beta1" 479 | }, 480 | { 481 | "importpath": "k8s.io/api/authorization/v1", 482 | "repository": "https://github.com/kubernetes/api", 483 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 484 | "branch": "master", 485 | "path": "/authorization/v1" 486 | }, 487 | { 488 | "importpath": "k8s.io/api/authorization/v1beta1", 489 | "repository": "https://github.com/kubernetes/api", 490 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 491 | "branch": "master", 492 | "path": "/authorization/v1beta1" 493 | }, 494 | { 495 | "importpath": "k8s.io/api/autoscaling/v1", 496 | "repository": "https://github.com/kubernetes/api", 497 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 498 | "branch": "master", 499 | "path": "/autoscaling/v1" 500 | }, 501 | { 502 | "importpath": "k8s.io/api/autoscaling/v2beta1", 503 | "repository": "https://github.com/kubernetes/api", 504 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 505 | "branch": "master", 506 | "path": "/autoscaling/v2beta1" 507 | }, 508 | { 509 | "importpath": "k8s.io/api/autoscaling/v2beta2", 510 | "repository": "https://github.com/kubernetes/api", 511 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 512 | "branch": "master", 513 | "path": "/autoscaling/v2beta2" 514 | }, 515 | { 516 | "importpath": "k8s.io/api/batch/v1", 517 | "repository": "https://github.com/kubernetes/api", 518 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 519 | "branch": "master", 520 | "path": "/batch/v1" 521 | }, 522 | { 523 | "importpath": "k8s.io/api/batch/v1beta1", 524 | "repository": "https://github.com/kubernetes/api", 525 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 526 | "branch": "master", 527 | "path": "/batch/v1beta1" 528 | }, 529 | { 530 | "importpath": "k8s.io/api/batch/v2alpha1", 531 | "repository": "https://github.com/kubernetes/api", 532 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 533 | "branch": "master", 534 | "path": "/batch/v2alpha1" 535 | }, 536 | { 537 | "importpath": "k8s.io/api/certificates/v1beta1", 538 | "repository": "https://github.com/kubernetes/api", 539 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 540 | "branch": "master", 541 | "path": "/certificates/v1beta1" 542 | }, 543 | { 544 | "importpath": "k8s.io/api/coordination/v1beta1", 545 | "repository": "https://github.com/kubernetes/api", 546 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 547 | "branch": "master", 548 | "path": "/coordination/v1beta1" 549 | }, 550 | { 551 | "importpath": "k8s.io/api/core/v1", 552 | "repository": "https://github.com/kubernetes/api", 553 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 554 | "branch": "master", 555 | "path": "/core/v1" 556 | }, 557 | { 558 | "importpath": "k8s.io/api/events/v1beta1", 559 | "repository": "https://github.com/kubernetes/api", 560 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 561 | "branch": "master", 562 | "path": "/events/v1beta1" 563 | }, 564 | { 565 | "importpath": "k8s.io/api/extensions/v1beta1", 566 | "repository": "https://github.com/kubernetes/api", 567 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 568 | "branch": "master", 569 | "path": "/extensions/v1beta1" 570 | }, 571 | { 572 | "importpath": "k8s.io/api/networking/v1", 573 | "repository": "https://github.com/kubernetes/api", 574 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 575 | "branch": "master", 576 | "path": "/networking/v1" 577 | }, 578 | { 579 | "importpath": "k8s.io/api/policy/v1beta1", 580 | "repository": "https://github.com/kubernetes/api", 581 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 582 | "branch": "master", 583 | "path": "/policy/v1beta1" 584 | }, 585 | { 586 | "importpath": "k8s.io/api/rbac/v1", 587 | "repository": "https://github.com/kubernetes/api", 588 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 589 | "branch": "master", 590 | "path": "/rbac/v1" 591 | }, 592 | { 593 | "importpath": "k8s.io/api/rbac/v1alpha1", 594 | "repository": "https://github.com/kubernetes/api", 595 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 596 | "branch": "master", 597 | "path": "/rbac/v1alpha1" 598 | }, 599 | { 600 | "importpath": "k8s.io/api/rbac/v1beta1", 601 | "repository": "https://github.com/kubernetes/api", 602 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 603 | "branch": "master", 604 | "path": "/rbac/v1beta1" 605 | }, 606 | { 607 | "importpath": "k8s.io/api/scheduling/v1alpha1", 608 | "repository": "https://github.com/kubernetes/api", 609 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 610 | "branch": "master", 611 | "path": "/scheduling/v1alpha1" 612 | }, 613 | { 614 | "importpath": "k8s.io/api/scheduling/v1beta1", 615 | "repository": "https://github.com/kubernetes/api", 616 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 617 | "branch": "master", 618 | "path": "/scheduling/v1beta1" 619 | }, 620 | { 621 | "importpath": "k8s.io/api/settings/v1alpha1", 622 | "repository": "https://github.com/kubernetes/api", 623 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 624 | "branch": "master", 625 | "path": "/settings/v1alpha1" 626 | }, 627 | { 628 | "importpath": "k8s.io/api/storage/v1", 629 | "repository": "https://github.com/kubernetes/api", 630 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 631 | "branch": "master", 632 | "path": "/storage/v1" 633 | }, 634 | { 635 | "importpath": "k8s.io/api/storage/v1alpha1", 636 | "repository": "https://github.com/kubernetes/api", 637 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 638 | "branch": "master", 639 | "path": "/storage/v1alpha1" 640 | }, 641 | { 642 | "importpath": "k8s.io/api/storage/v1beta1", 643 | "repository": "https://github.com/kubernetes/api", 644 | "revision": "f456898a08e4bbc5891694118f3819f324de12ff", 645 | "branch": "master", 646 | "path": "/storage/v1beta1" 647 | }, 648 | { 649 | "importpath": "k8s.io/apimachinery/pkg/api/errors", 650 | "repository": "https://github.com/kubernetes/apimachinery", 651 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 652 | "branch": "master", 653 | "path": "/pkg/api/errors" 654 | }, 655 | { 656 | "importpath": "k8s.io/apimachinery/pkg/api/meta", 657 | "repository": "https://github.com/kubernetes/apimachinery", 658 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 659 | "branch": "master", 660 | "path": "/pkg/api/meta" 661 | }, 662 | { 663 | "importpath": "k8s.io/apimachinery/pkg/api/resource", 664 | "repository": "https://github.com/kubernetes/apimachinery", 665 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 666 | "branch": "master", 667 | "path": "/pkg/api/resource" 668 | }, 669 | { 670 | "importpath": "k8s.io/apimachinery/pkg/apis/meta/v1", 671 | "repository": "https://github.com/kubernetes/apimachinery", 672 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 673 | "branch": "master", 674 | "path": "/pkg/apis/meta/v1" 675 | }, 676 | { 677 | "importpath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", 678 | "repository": "https://github.com/kubernetes/apimachinery", 679 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 680 | "branch": "master", 681 | "path": "/pkg/apis/meta/v1/unstructured" 682 | }, 683 | { 684 | "importpath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1", 685 | "repository": "https://github.com/kubernetes/apimachinery", 686 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 687 | "branch": "master", 688 | "path": "/pkg/apis/meta/v1beta1" 689 | }, 690 | { 691 | "importpath": "k8s.io/apimachinery/pkg/conversion", 692 | "repository": "https://github.com/kubernetes/apimachinery", 693 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 694 | "branch": "master", 695 | "path": "/pkg/conversion" 696 | }, 697 | { 698 | "importpath": "k8s.io/apimachinery/pkg/fields", 699 | "repository": "https://github.com/kubernetes/apimachinery", 700 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 701 | "branch": "master", 702 | "path": "/pkg/fields" 703 | }, 704 | { 705 | "importpath": "k8s.io/apimachinery/pkg/labels", 706 | "repository": "https://github.com/kubernetes/apimachinery", 707 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 708 | "branch": "master", 709 | "path": "/pkg/labels" 710 | }, 711 | { 712 | "importpath": "k8s.io/apimachinery/pkg/runtime", 713 | "repository": "https://github.com/kubernetes/apimachinery", 714 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 715 | "branch": "master", 716 | "path": "/pkg/runtime" 717 | }, 718 | { 719 | "importpath": "k8s.io/apimachinery/pkg/selection", 720 | "repository": "https://github.com/kubernetes/apimachinery", 721 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 722 | "branch": "master", 723 | "path": "/pkg/selection" 724 | }, 725 | { 726 | "importpath": "k8s.io/apimachinery/pkg/types", 727 | "repository": "https://github.com/kubernetes/apimachinery", 728 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 729 | "branch": "master", 730 | "path": "/pkg/types" 731 | }, 732 | { 733 | "importpath": "k8s.io/apimachinery/pkg/util/clock", 734 | "repository": "https://github.com/kubernetes/apimachinery", 735 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 736 | "branch": "master", 737 | "path": "/pkg/util/clock" 738 | }, 739 | { 740 | "importpath": "k8s.io/apimachinery/pkg/util/errors", 741 | "repository": "https://github.com/kubernetes/apimachinery", 742 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 743 | "branch": "master", 744 | "path": "/pkg/util/errors" 745 | }, 746 | { 747 | "importpath": "k8s.io/apimachinery/pkg/util/framer", 748 | "repository": "https://github.com/kubernetes/apimachinery", 749 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 750 | "branch": "master", 751 | "path": "/pkg/util/framer" 752 | }, 753 | { 754 | "importpath": "k8s.io/apimachinery/pkg/util/intstr", 755 | "repository": "https://github.com/kubernetes/apimachinery", 756 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 757 | "branch": "master", 758 | "path": "/pkg/util/intstr" 759 | }, 760 | { 761 | "importpath": "k8s.io/apimachinery/pkg/util/json", 762 | "repository": "https://github.com/kubernetes/apimachinery", 763 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 764 | "branch": "master", 765 | "path": "/pkg/util/json" 766 | }, 767 | { 768 | "importpath": "k8s.io/apimachinery/pkg/util/mergepatch", 769 | "repository": "https://github.com/kubernetes/apimachinery", 770 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 771 | "branch": "master", 772 | "path": "/pkg/util/mergepatch" 773 | }, 774 | { 775 | "importpath": "k8s.io/apimachinery/pkg/util/naming", 776 | "repository": "https://github.com/kubernetes/apimachinery", 777 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 778 | "branch": "master", 779 | "path": "/pkg/util/naming" 780 | }, 781 | { 782 | "importpath": "k8s.io/apimachinery/pkg/util/net", 783 | "repository": "https://github.com/kubernetes/apimachinery", 784 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 785 | "branch": "master", 786 | "path": "/pkg/util/net" 787 | }, 788 | { 789 | "importpath": "k8s.io/apimachinery/pkg/util/runtime", 790 | "repository": "https://github.com/kubernetes/apimachinery", 791 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 792 | "branch": "master", 793 | "path": "/pkg/util/runtime" 794 | }, 795 | { 796 | "importpath": "k8s.io/apimachinery/pkg/util/sets", 797 | "repository": "https://github.com/kubernetes/apimachinery", 798 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 799 | "branch": "master", 800 | "path": "/pkg/util/sets" 801 | }, 802 | { 803 | "importpath": "k8s.io/apimachinery/pkg/util/strategicpatch", 804 | "repository": "https://github.com/kubernetes/apimachinery", 805 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 806 | "branch": "master", 807 | "path": "/pkg/util/strategicpatch" 808 | }, 809 | { 810 | "importpath": "k8s.io/apimachinery/pkg/util/validation", 811 | "repository": "https://github.com/kubernetes/apimachinery", 812 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 813 | "branch": "master", 814 | "path": "/pkg/util/validation" 815 | }, 816 | { 817 | "importpath": "k8s.io/apimachinery/pkg/util/yaml", 818 | "repository": "https://github.com/kubernetes/apimachinery", 819 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 820 | "branch": "master", 821 | "path": "/pkg/util/yaml" 822 | }, 823 | { 824 | "importpath": "k8s.io/apimachinery/pkg/version", 825 | "repository": "https://github.com/kubernetes/apimachinery", 826 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 827 | "branch": "master", 828 | "path": "/pkg/version" 829 | }, 830 | { 831 | "importpath": "k8s.io/apimachinery/pkg/watch", 832 | "repository": "https://github.com/kubernetes/apimachinery", 833 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 834 | "branch": "master", 835 | "path": "/pkg/watch" 836 | }, 837 | { 838 | "importpath": "k8s.io/apimachinery/third_party/forked/golang/json", 839 | "repository": "https://github.com/kubernetes/apimachinery", 840 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 841 | "branch": "master", 842 | "path": "/third_party/forked/golang/json" 843 | }, 844 | { 845 | "importpath": "k8s.io/apimachinery/third_party/forked/golang/reflect", 846 | "repository": "https://github.com/kubernetes/apimachinery", 847 | "revision": "99c5fa21f87204c2875a7f7ba977895d79b35c44", 848 | "branch": "master", 849 | "path": "/third_party/forked/golang/reflect" 850 | }, 851 | { 852 | "importpath": "k8s.io/client-go/discovery", 853 | "repository": "https://github.com/kubernetes/client-go", 854 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 855 | "branch": "master", 856 | "path": "/discovery" 857 | }, 858 | { 859 | "importpath": "k8s.io/client-go/kubernetes", 860 | "repository": "https://github.com/kubernetes/client-go", 861 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 862 | "branch": "master", 863 | "path": "/kubernetes" 864 | }, 865 | { 866 | "importpath": "k8s.io/client-go/pkg/apis/clientauthentication", 867 | "repository": "https://github.com/kubernetes/client-go", 868 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 869 | "branch": "master", 870 | "path": "/pkg/apis/clientauthentication" 871 | }, 872 | { 873 | "importpath": "k8s.io/client-go/pkg/version", 874 | "repository": "https://github.com/kubernetes/client-go", 875 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 876 | "branch": "master", 877 | "path": "/pkg/version" 878 | }, 879 | { 880 | "importpath": "k8s.io/client-go/plugin/pkg/client/auth/exec", 881 | "repository": "https://github.com/kubernetes/client-go", 882 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 883 | "branch": "master", 884 | "path": "/plugin/pkg/client/auth/exec" 885 | }, 886 | { 887 | "importpath": "k8s.io/client-go/rest", 888 | "repository": "https://github.com/kubernetes/client-go", 889 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 890 | "branch": "master", 891 | "path": "/rest" 892 | }, 893 | { 894 | "importpath": "k8s.io/client-go/testing", 895 | "repository": "https://github.com/kubernetes/client-go", 896 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 897 | "branch": "master", 898 | "path": "/testing" 899 | }, 900 | { 901 | "importpath": "k8s.io/client-go/third_party/forked/golang/template", 902 | "repository": "https://github.com/kubernetes/client-go", 903 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 904 | "branch": "master", 905 | "path": "/third_party/forked/golang/template" 906 | }, 907 | { 908 | "importpath": "k8s.io/client-go/tools/auth", 909 | "repository": "https://github.com/kubernetes/client-go", 910 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 911 | "branch": "master", 912 | "path": "/tools/auth" 913 | }, 914 | { 915 | "importpath": "k8s.io/client-go/tools/clientcmd", 916 | "repository": "https://github.com/kubernetes/client-go", 917 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 918 | "branch": "master", 919 | "path": "/tools/clientcmd" 920 | }, 921 | { 922 | "importpath": "k8s.io/client-go/tools/clientcmd/api", 923 | "repository": "https://github.com/kubernetes/client-go", 924 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 925 | "branch": "master", 926 | "path": "/tools/clientcmd/api" 927 | }, 928 | { 929 | "importpath": "k8s.io/client-go/tools/metrics", 930 | "repository": "https://github.com/kubernetes/client-go", 931 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 932 | "branch": "master", 933 | "path": "/tools/metrics" 934 | }, 935 | { 936 | "importpath": "k8s.io/client-go/tools/reference", 937 | "repository": "https://github.com/kubernetes/client-go", 938 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 939 | "branch": "master", 940 | "path": "/tools/reference" 941 | }, 942 | { 943 | "importpath": "k8s.io/client-go/transport", 944 | "repository": "https://github.com/kubernetes/client-go", 945 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 946 | "branch": "master", 947 | "path": "/transport" 948 | }, 949 | { 950 | "importpath": "k8s.io/client-go/util/cert", 951 | "repository": "https://github.com/kubernetes/client-go", 952 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 953 | "branch": "master", 954 | "path": "/util/cert" 955 | }, 956 | { 957 | "importpath": "k8s.io/client-go/util/connrotation", 958 | "repository": "https://github.com/kubernetes/client-go", 959 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 960 | "branch": "master", 961 | "path": "/util/connrotation" 962 | }, 963 | { 964 | "importpath": "k8s.io/client-go/util/flowcontrol", 965 | "repository": "https://github.com/kubernetes/client-go", 966 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 967 | "branch": "master", 968 | "path": "/util/flowcontrol" 969 | }, 970 | { 971 | "importpath": "k8s.io/client-go/util/homedir", 972 | "repository": "https://github.com/kubernetes/client-go", 973 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 974 | "branch": "master", 975 | "path": "/util/homedir" 976 | }, 977 | { 978 | "importpath": "k8s.io/client-go/util/integer", 979 | "repository": "https://github.com/kubernetes/client-go", 980 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 981 | "branch": "master", 982 | "path": "/util/integer" 983 | }, 984 | { 985 | "importpath": "k8s.io/client-go/util/jsonpath", 986 | "repository": "https://github.com/kubernetes/client-go", 987 | "revision": "ca74aedde1ee2ea0ae6a39c3fb58409c1f04840f", 988 | "branch": "master", 989 | "path": "/util/jsonpath" 990 | }, 991 | { 992 | "importpath": "k8s.io/klog", 993 | "repository": "https://github.com/kubernetes/klog", 994 | "revision": "4767aaa894b9d76b3511781272db07d4ef45175e", 995 | "branch": "master" 996 | }, 997 | { 998 | "importpath": "k8s.io/kube-openapi/pkg/util/proto", 999 | "repository": "https://github.com/kubernetes/kube-openapi", 1000 | "revision": "e3762e86a74c878ffed47484592986685639c2cd", 1001 | "branch": "master", 1002 | "path": "/pkg/util/proto" 1003 | } 1004 | ] 1005 | } 1006 | --------------------------------------------------------------------------------