├── go.mod ├── go.sum ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── version.go ├── gcr.go ├── kubectl.go ├── main.go └── LICENSE /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ahmetb/kubectl-extension-versions 2 | 3 | require github.com/pkg/errors v0.8.1 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 2 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= 4 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # output binary 2 | kubectl-extension-versions* 3 | kubectl-extension_versions* 4 | 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubectl extension-versions 2 | 3 | This is a kubectl plugin that lists you the installed versions of well-known 4 | Kubernetes extensions/operators (and their subcomponents, if any) on your 5 | cluster. 6 | 7 | For example: 8 | 9 | ```sh 10 | kubectl extension-versions 11 | - istio: 12 | - pilot: docker.io/istio/pilot:1.0.2 13 | - sidecar-injector: docker.io/istio/sidecar_injector:1.0.2 14 | - policy: docker.io/istio/mixer:1.0.2 15 | - prometheus: (not found) 16 | - knative: 17 | - build: gcr.io/knative-releases/github.com/knative/build/cmd/controller:v0.4.0 18 | - serving: gcr.io/knative-releases/github.com/knative/serving/cmd/controller:v0.4.0 19 | - eventing: gcr.io/knative-releases/github.com/knative/eventing/cmd/controller:v0.4.0 20 | ``` 21 | 22 | ## Installation 23 | 24 | > :warning::warning: These instructions don't work yet. Just `go build` this and 25 | > place the binary to your `$PATH` as `kubectl-extension_versions` (mind the 26 | > underscore) to get it to work. 27 | 28 | 1. Install [krew](https://github.com/GoogleContainerTools/krew) plugin manager 29 | for kubectl. 30 | 31 | 2. Install this plugin by running: 32 | 33 | kubectl krew install extension-versions 34 | 35 | 3. Run the plugin by calling it as: 36 | 37 | kubectl extension-versions 38 | 39 | ## Authors 40 | 41 | - Ahmet Alp Balkan [(@ahmetb)](https://twitter.com/ahmetb) 42 | 43 | --- 44 | 45 | This is not an official Google project. 46 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 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 | // https://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 | package main 15 | 16 | import ( 17 | "context" 18 | "log" 19 | 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | func versionInfoFromImage(image string) versionInfo { 24 | // can simplify gcr.io/[img]@sha256:[...] as versioned :tag? 25 | if isGCRHash(image) { 26 | i, err := resolveGCRHashToTag(image) 27 | if err != nil { 28 | log.Printf("WARN: failed to query tags for gcr image: %v", err) 29 | } else if i != "" { 30 | return versionInfo(i) 31 | } 32 | } 33 | return versionInfo(image) // fallback 34 | } 35 | 36 | func versionFromDeploymentImage(namespace, deploymentName, containerName string) versionFunc { 37 | return func(ctx context.Context) (versionInfo, error) { 38 | img, err := getPodImageByPrefix(ctx, namespace, deploymentName+"-", containerName) 39 | if err != nil { 40 | return "", errors.Wrap(err, "failed to determine container image") 41 | } 42 | return versionInfoFromImage(img), nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gcr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 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 | // https://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 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/pkg/errors" 22 | 23 | "regexp" 24 | ) 25 | 26 | var ( 27 | gcrSHA256Pattern = regexp.MustCompile(`^gcr.io\/(.*)@(sha256:[0-9a-f]{64})$`) 28 | ) 29 | 30 | func isGCRHash(image string) bool { return gcrSHA256Pattern.MatchString(image) } 31 | 32 | // resolveGCRHashToTag returns the image with IMAGE:TAG format if it can be 33 | // resolved. If no tags are available, an empty string is returned. If multiple 34 | // tags are available, first one that's not "latest" is returned. 35 | func resolveGCRHashToTag(image string) (string, error) { 36 | groups := gcrSHA256Pattern.FindStringSubmatch(image) 37 | if len(groups) != 3 { 38 | return "", errors.Errorf("image %s cannot be parsed into repo/sha (got %d groups)", image, len(groups)) 39 | } 40 | repo, hash := groups[1], groups[2] 41 | 42 | resp, err := http.Get(fmt.Sprintf("https://gcr.io/v2/%s/tags/list", repo)) 43 | if err != nil { 44 | return "", errors.Wrapf(err, "failed to query tags from GCR for image %s", image) 45 | } 46 | defer resp.Body.Close() 47 | var v struct { 48 | Manifest map[string]struct { 49 | Tags []string `json:"tag"` 50 | } `json:"manifest"` 51 | } 52 | if err := json.NewDecoder(resp.Body).Decode(&v); err != nil { 53 | return "", errors.Wrap(err, "failed to read and decode response body") 54 | } 55 | man, ok := v.Manifest[hash] 56 | if !ok { 57 | return "", errors.Wrapf(err, "hash %q not found in response manifest", hash) 58 | } 59 | if len(man.Tags) == 0 { 60 | return "", errors.Errorf("no tags found for gcr image %s", image) 61 | } 62 | // return the first tag that's not "latest" 63 | var tag string 64 | for _, t := range man.Tags { 65 | if t != "latest" { 66 | tag = t 67 | break 68 | } 69 | } 70 | if tag == "" { 71 | tag = man.Tags[0] 72 | } 73 | return fmt.Sprintf("gcr.io/%s:%s", repo, tag), nil 74 | } 75 | -------------------------------------------------------------------------------- /kubectl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 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 | // https://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 | package main 15 | 16 | import ( 17 | "bytes" 18 | "context" 19 | "encoding/json" 20 | "io" 21 | "os/exec" 22 | "strings" 23 | "sync" 24 | 25 | "github.com/pkg/errors" 26 | ) 27 | 28 | var ( 29 | namespaces []string 30 | nsLock sync.Once 31 | 32 | podList []pod 33 | podsLock sync.Once 34 | ) 35 | 36 | type pod struct { 37 | Metadata struct { 38 | Name string `json:"name"` 39 | Namespace string `json:"namespace"` 40 | } `json:"metadata"` 41 | Spec struct { 42 | Containers []struct { 43 | Name string `json:"name"` 44 | Image string `json:"image"` 45 | } `json:"containers"` 46 | } `json:"spec"` 47 | } 48 | 49 | func getNamespaces(ctx context.Context) ([]string, error) { 50 | var errOut error 51 | 52 | nsLock.Do(func() { 53 | j, err := execKubectl(ctx, "get", "namespaces", "-o=json") 54 | if err != nil { 55 | errOut = err 56 | return 57 | } 58 | var v struct { 59 | Items []struct { 60 | Metadata struct { 61 | Name string `json:"name"` 62 | } `json:"metadata"` 63 | } `json:"items"` 64 | } 65 | if err := json.Unmarshal(j, &v); err != nil { 66 | errOut = errors.Wrap(err, "decoding json failed") 67 | return 68 | } 69 | for _, vv := range v.Items { 70 | namespaces = append(namespaces, vv.Metadata.Name) 71 | } 72 | }) 73 | 74 | return namespaces, errOut 75 | } 76 | 77 | func hasNamespace(ctx context.Context, s string) (bool, error) { 78 | list, err := getNamespaces(ctx) 79 | if err != nil { 80 | return false, errors.Wrap(err, "failed to get namespaces") 81 | } 82 | for _, n := range list { 83 | if n == s { 84 | return true, nil 85 | } 86 | } 87 | return false, nil 88 | } 89 | 90 | func detectByNamespace(ns string) detectFunc { 91 | return func(ctx context.Context) (bool, error) { 92 | return hasNamespace(ctx, ns) 93 | } 94 | } 95 | 96 | func hasNamespaceWithPrefix(ctx context.Context, prefix string) (bool, error) { 97 | ns, err := getNamespaces(ctx) 98 | if err != nil { 99 | return false, errors.Wrap(err, "failed to get namespaces") 100 | } 101 | for _, n := range ns { 102 | if strings.HasPrefix(n, prefix) { 103 | return true, nil 104 | } 105 | } 106 | return false, nil 107 | } 108 | 109 | func detectByNamespacePrefix(nsPrefix string) detectFunc { 110 | return func(ctx context.Context) (bool, error) { 111 | return hasNamespaceWithPrefix(ctx, nsPrefix) 112 | } 113 | } 114 | 115 | func getPods(ctx context.Context) ([]pod, error) { 116 | var errOut error 117 | podsLock.Do(func() { 118 | j, err := execKubectl(ctx, "get", "pods", "--all-namespaces", "-o=json") 119 | if err != nil { 120 | errOut = err 121 | return 122 | } 123 | var v struct{ Items []pod } 124 | if err := json.Unmarshal(j, &v); err != nil { 125 | errOut = errors.Wrap(err, "decoding json failed") 126 | return 127 | } 128 | podList = v.Items 129 | }) 130 | return podList, errOut 131 | } 132 | 133 | func hasPodsByPrefix(ctx context.Context, namespace, podPrefix string) (bool, error) { 134 | pods, err := getPods(ctx) 135 | if err != nil { 136 | return false, errors.Wrap(err, "failed to get pods") 137 | } 138 | for _, p := range pods { 139 | if p.Metadata.Namespace == namespace && strings.HasPrefix(p.Metadata.Name, podPrefix) { 140 | return true, nil 141 | } 142 | } 143 | return false, nil 144 | } 145 | 146 | func detectByPod(namespace, podPrefix string) detectFunc { 147 | return func(ctx context.Context) (bool, error) { 148 | return hasPodsByPrefix(ctx, namespace, podPrefix) 149 | } 150 | } 151 | 152 | func getPodImageByPrefix(ctx context.Context, namespace, podPrefix, containerName string) (string, error) { 153 | pods, err := getPods(ctx) 154 | if err != nil { 155 | return "", errors.Wrap(err, "failed to get pods") 156 | } 157 | var p *pod 158 | for _, pp := range pods { 159 | if pp.Metadata.Namespace == namespace && strings.HasPrefix(pp.Metadata.Name, podPrefix) { 160 | p = &pp 161 | break 162 | } 163 | } 164 | if p == nil { 165 | return "", errors.Errorf("no pod found with \"%s/%s\" prefix", namespace, podPrefix) 166 | } 167 | if len(p.Spec.Containers) == 1 { 168 | return p.Spec.Containers[0].Image, nil 169 | } 170 | if containerName == "" { 171 | return "", errors.Errorf("pod %s has %d containers, could not disambiguate (containerName filter not given)", p.Metadata.Name, len(p.Spec.Containers)) 172 | } 173 | for _, c := range p.Spec.Containers { 174 | if c.Name == containerName { 175 | return c.Image, nil 176 | } 177 | } 178 | return "", errors.Errorf("could not find container name %q in pod %s/%s", containerName, p.Metadata.Namespace, p.Metadata.Name) 179 | } 180 | 181 | func execKubectl(ctx context.Context, args ...string) ([]byte, error) { 182 | var stdout, stderr, combined bytes.Buffer 183 | 184 | cmd := exec.CommandContext(ctx, "kubectl", args...) 185 | cmd.Stdout = io.MultiWriter(&stdout, &combined) 186 | cmd.Stderr = io.MultiWriter(&stderr, &combined) 187 | if err := cmd.Run(); err != nil { 188 | return nil, errors.Errorf("kubectl command failed (%s). output=%s", err, combined.String()) 189 | } 190 | return stdout.Bytes(), nil 191 | } 192 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 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 | // https://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 | package main 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "log" 20 | "sync" 21 | 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | type installStatus int 26 | 27 | const ( 28 | unknown installStatus = iota 29 | notFound 30 | installed 31 | failed 32 | ) 33 | 34 | type versionInfo string 35 | 36 | type detectFunc func(context.Context) (bool, error) 37 | type versionFunc func(context.Context) (versionInfo, error) 38 | 39 | type extension struct { 40 | name string 41 | detectFn detectFunc 42 | versionFn versionFunc 43 | subcomponents []*extension 44 | 45 | result detectResult 46 | } 47 | 48 | type detectResult struct { 49 | status installStatus 50 | version versionInfo 51 | error error 52 | } 53 | 54 | func main() { 55 | 56 | ctx, cancel := context.WithCancel(context.Background()) 57 | defer cancel() // TODO implement signal handling and call cancel() 58 | 59 | extensions := []*extension{ 60 | &extension{ 61 | name: "istio", 62 | detectFn: detectByNamespacePrefix("istio-system"), 63 | subcomponents: []*extension{ 64 | { 65 | name: "pilot", 66 | detectFn: detectByPod("istio-system", "istio-pilot-"), 67 | versionFn: versionFromDeploymentImage("istio-system", "istio-pilot", "discovery"), 68 | }, 69 | { 70 | name: "sidecar-injector", 71 | detectFn: detectByPod("istio-system", "istio-sidecar-injector-"), 72 | versionFn: versionFromDeploymentImage("istio-system", "istio-sidecar-injector", ""), 73 | }, 74 | { 75 | name: "policy", 76 | detectFn: detectByPod("istio-system", "istio-policy-"), 77 | versionFn: versionFromDeploymentImage("istio-system", "istio-policy", "mixer"), 78 | }, 79 | { 80 | name: "prometheus", 81 | detectFn: detectByPod("istio-system", "prometheus-"), 82 | versionFn: versionFromDeploymentImage("istio-system", "prometheus", "prometheus"), 83 | }, 84 | }, 85 | }, 86 | &extension{ 87 | name: "knative", 88 | detectFn: detectByNamespacePrefix("knative-"), 89 | subcomponents: []*extension{ 90 | &extension{ 91 | name: "serving", 92 | detectFn: detectByNamespace("knative-serving"), 93 | versionFn: versionFromDeploymentImage("knative-serving", "controller", ""), 94 | }, 95 | &extension{ 96 | name: "build", 97 | detectFn: detectByNamespace("knative-build"), 98 | versionFn: versionFromDeploymentImage("knative-build", "build-controller", ""), 99 | }, 100 | &extension{ 101 | name: "eventing", 102 | detectFn: detectByNamespace("knative-eventing"), 103 | versionFn: versionFromDeploymentImage("knative-eventing", "eventing-controller", ""), 104 | }, 105 | }, 106 | }, 107 | &extension{ 108 | name: "helm-tiller", 109 | detectFn: detectByPod("kube-system", "tiller-deploy-"), 110 | versionFn: versionFromDeploymentImage("kube-system", "tiller-deploy", "tiller"), 111 | }, 112 | } 113 | if err := processExtensions(ctx, extensions); err != nil { 114 | log.Printf("WARN: failed to detect some extensions: %v", err) 115 | } 116 | printStatuses("", extensions) 117 | } 118 | 119 | func processExtensions(ctx context.Context, extensions []*extension) error { 120 | var wg sync.WaitGroup 121 | var outErr error 122 | for _, ex := range extensions { 123 | wg.Add(1) 124 | go func(e *extension) { 125 | defer wg.Done() 126 | if err := processExtension(ctx, e); err != nil { 127 | outErr = errors.Wrapf(err, "failed to process %q", e.name) 128 | } 129 | }(ex) 130 | } 131 | wg.Wait() 132 | return outErr 133 | } 134 | 135 | func processExtension(ctx context.Context, e *extension) error { 136 | if e.detectFn == nil { 137 | return errors.Errorf("extension %q has no detection function", e.name) 138 | } 139 | installStatus, err := e.detectFn(ctx) 140 | if err != nil { 141 | e.result.status = failed 142 | e.result.error = err 143 | return err // TODO: we don't return the err, so wanna continue processing 144 | } 145 | if installStatus { 146 | e.result.status = installed 147 | } else { 148 | e.result.status = notFound 149 | } 150 | 151 | if e.result.status != installed { 152 | return nil 153 | } 154 | // process subcomponents if any 155 | if len(e.subcomponents) > 0 { 156 | if err := processExtensions(ctx, e.subcomponents); err != nil { 157 | return errors.Wrapf(err, "failed to process subcomponents of %q", e.name) 158 | } 159 | return nil 160 | } 161 | 162 | if e.versionFn == nil { 163 | return errors.Errorf("extension %q has no version function", e.name) 164 | } 165 | version, err := e.versionFn(ctx) 166 | e.result.error = err 167 | if err != nil { 168 | e.result.status = failed 169 | e.result.error = err 170 | return err 171 | } 172 | e.result.version = version 173 | return nil 174 | } 175 | 176 | func statusText(r detectResult) string { 177 | switch r.status { 178 | case installed: 179 | return fmt.Sprintf("%s", r.version) 180 | case notFound: 181 | return "" 182 | case unknown: 183 | return "???" 184 | case failed: 185 | return fmt.Sprintf(": %s", r.error) 186 | default: 187 | return "" 188 | } 189 | } 190 | 191 | func printStatuses(prefix string, extensions []*extension) { 192 | for _, e := range extensions { 193 | if len(e.subcomponents) == 0 { 194 | // leaf component 195 | fmt.Printf("%s- %s: %s", prefix, e.name, statusText(e.result)) 196 | } else { 197 | // non-leaf component (if installed, do not print install status) 198 | fmt.Printf("%s- %s:", prefix, e.name) 199 | if e.result.status != installed { 200 | fmt.Printf(" %s", statusText(e.result)) 201 | } 202 | } 203 | fmt.Println() 204 | 205 | if e.result.status == installed && len(e.subcomponents) > 0 { 206 | printStatuses(prefix+" ", e.subcomponents) 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------