├── e2e_tests ├── __init__.py ├── test-requirements.txt ├── README └── tests │ └── test_smoke.py ├── src ├── requirements.txt ├── templates │ └── hello.html └── exampleapp.py ├── sidecars └── ab │ ├── Dockerfile │ └── README.md ├── jobs ├── simplejob.yaml └── cronjob.yaml ├── Dockerfile ├── Jenkinsfile ├── deploy ├── redis.yml └── flask.yml ├── README.md ├── .gitignore └── LICENSE /e2e_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /e2e_tests/test-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-dependency 3 | kubernetes 4 | requests -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.12.2 2 | redis 3 | jaeger-client 4 | prometheus-client 5 | Flask-Opentracing 6 | requests 7 | -------------------------------------------------------------------------------- /sidecars/ab/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ENV TERM linux 4 | RUN apk --no-cache add apache2-utils 5 | RUN apk --no-cache add curl 6 | 7 | CMD ["/usr/bin/ab"] 8 | -------------------------------------------------------------------------------- /src/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | Hello from Flask 3 | 4 | {% if greeting %} 5 |

{{ greeting }}

6 | {% endif %} 7 | 8 | {% if name %} 9 |

Hello {{ name }}!

10 | {% else %} 11 |

Hello World!

12 | {% endif %} 13 | 14 | -------------------------------------------------------------------------------- /jobs/simplejob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: helloworld 5 | spec: 6 | template: 7 | metadata: 8 | name: helloworld 9 | spec: 10 | containers: 11 | - name: simple 12 | image: busybox 13 | command: ["/bin/sh", "echo", "-c", "'hello world'"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /jobs/cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob 3 | metadata: 4 | name: helloworld 5 | spec: 6 | schedule: "*/1 * * * *" 7 | jobTemplate: 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: simple 13 | image: busybox 14 | command: ["/bin/sh", "-c", "echo", "'hello world'"] 15 | restartPolicy: OnFailure 16 | -------------------------------------------------------------------------------- /sidecars/ab/README.md: -------------------------------------------------------------------------------- 1 | # ab testing container image 2 | 3 | ... for generating simple web-based load 4 | 5 | 6 | ## To build this image 7 | 8 | docker build -t quay.io/kubernetes-for-developers/ab:1.0.0 . 9 | docker push quay.io/kubernetes-for-developers/ab 10 | 11 | ## to run a shell with this image 12 | 13 | kubectl run -it --rm --restart=Never --image-pull-policy=Always \ 14 | --image=quay.io/kubernetes-for-developers/ab:1.0.0 quicktest -- sh 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | # load any public updates from Alpine packages 3 | RUN apk update 4 | # upgrade any existing packages that have been updated 5 | RUN apk upgrade 6 | # add/install python3 and related libraries 7 | # https://pkgs.alpinelinux.org/package/edge/main/x86/python3 8 | RUN apk add python3 9 | # make a directory for our application 10 | RUN mkdir -p /opt/exampleapp 11 | # move requirements file into the container 12 | COPY src /opt/exampleapp 13 | # install the library dependencies for this application 14 | RUN pip3 install -r /opt/exampleapp/requirements.txt 15 | ENTRYPOINT ["python3"] 16 | CMD ["/opt/exampleapp/exampleapp.py"] 17 | -------------------------------------------------------------------------------- /e2e_tests/README: -------------------------------------------------------------------------------- 1 | # setting up end-to-end testing 2 | 3 | These tests assume you have access to a kubernetes cluster previously 4 | arranged and available through kubectl commands. You can use the command 5 | 6 | kubectl version 7 | 8 | to verify you can see a remote system 9 | 10 | ## Install pytest and related dependencies 11 | 12 | virtualenv .venv 13 | source .venv/bin/activate 14 | pip3 install -r test-requirements.txt 15 | 16 | ## Run the tests 17 | 18 | pytest 19 | 20 | # show more detail of the test/fail and test names 21 | pytest -v 22 | 23 | # show all standard output captured from the tests during execution 24 | pytest -rapP 25 | 26 | # run the rests and general JUnit XML result file 27 | pytest --junitxml=results.xml 28 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /** 2 | * This pipeline will run a Docker image build 3 | */ 4 | 5 | def label = "worker-${UUID.randomUUID().toString()}" 6 | def imageBase = "quay.io/kubernetes-for-developers/flask" 7 | podTemplate(label: label, containers: [ 8 | containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.8.8', command: 'cat', ttyEnabled: true), 9 | containerTemplate(name: 'docker', image: 'docker:1.11', ttyEnabled: true, command: 'cat') 10 | ], 11 | volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')] 12 | ) { 13 | 14 | node(label) { 15 | def repository = checkout scm 16 | def gitCommit = repository.GIT_COMMIT 17 | def gitBranch = repository.GIT_BRANCH 18 | def shortCommit = "${gitCommit[0..10]}" 19 | def previousGitCommit = sh(script: "git rev-parse ${gitCommit}~", returnStdout: true) 20 | 21 | stage('create container image') { 22 | container('docker') { 23 | sh """ 24 | docker build -t ${imageBase}:${gitBranch}-${gitCommit} . 25 | """ 26 | } 27 | } 28 | stage('deploy') { 29 | container('kubectl') { 30 | sh("kubectl get pods") 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /deploy/redis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # EXPORT SERVICE INTERFACE 3 | kind: Service 4 | apiVersion: v1 5 | metadata: 6 | name: redis-service 7 | labels: 8 | app: redis 9 | role: master 10 | tier: backend 11 | spec: 12 | # type: NodePort 13 | ports: 14 | - port: 6379 15 | targetPort: 6379 16 | selector: 17 | app: redis 18 | role: master 19 | tier: backend 20 | --- 21 | apiVersion: v1 22 | kind: PersistentVolumeClaim 23 | metadata: 24 | name: redis-pv-claim 25 | labels: 26 | app: redis-master 27 | spec: 28 | accessModes: 29 | - ReadWriteOnce 30 | resources: 31 | requests: 32 | storage: 1Gi 33 | --- 34 | apiVersion: apps/v1beta1 35 | kind: Deployment 36 | metadata: 37 | name: redis-master 38 | spec: 39 | replicas: 1 40 | template: 41 | metadata: 42 | labels: 43 | app: redis 44 | role: master 45 | tier: backend 46 | spec: 47 | containers: 48 | - name: redis-master 49 | image: redis:4 50 | ports: 51 | - containerPort: 6379 52 | volumeMounts: 53 | - name: redis-persistent-storage 54 | mountPath: /data 55 | volumes: 56 | - name: redis-persistent-storage 57 | persistentVolumeClaim: 58 | claimName: redis-pv-claim -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kfd-flask 2 | 3 | A sample (and simple) Python/flask application for Kubernetes for Developers 4 | 5 | ## to rebuild all the images 6 | 7 | (this may need to be done to resolve any underlying image issues, such 8 | as security updates or vulnerabilities found and resolved in Alpine) 9 | 10 | The container images associated with this repo are available for review at 11 | https://quay.io/repository/kubernetes-for-developers/flask?tab=tags 12 | 13 | git checkout 0.1.1 14 | docker build -t quay.io/kubernetes-for-developers/flask:0.1.1 . 15 | git checkout 0.2.0 16 | docker build -t quay.io/kubernetes-for-developers/flask:0.2.0 . 17 | git checkout 0.3.0 18 | docker build -t quay.io/kubernetes-for-developers/flask:0.3.0 . 19 | git checkout 0.4.0 20 | docker build -t quay.io/kubernetes-for-developers/flask:0.4.0 . 21 | git checkout 0.5.0 22 | docker build -t quay.io/kubernetes-for-developers/flask:0.5.0 . 23 | git checkout 0.6.0 24 | docker build -t quay.io/kubernetes-for-developers/flask:0.6.0 . 25 | git checkout 0.7.0 26 | docker build -t quay.io/kubernetes-for-developers/flask:0.7.0 . 27 | git checkout master 28 | docker build -t quay.io/kubernetes-for-developers/flask:latest . 29 | docker push quay.io/kubernetes-for-developers/flask 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .pytest_cache 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .vscode 104 | results.xml 105 | -------------------------------------------------------------------------------- /deploy/flask.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # EXPORT SERVICE INTERFACE 3 | kind: Service 4 | apiVersion: v1 5 | metadata: 6 | name: flask-service 7 | annotations: 8 | prometheus.io/scrape: "true" 9 | spec: 10 | type: NodePort 11 | ports: 12 | - port: 5000 13 | selector: 14 | app: flask 15 | --- 16 | # CONFIGURATION FOR THE FLASK APP 17 | kind: ConfigMap 18 | apiVersion: v1 19 | metadata: 20 | name: flask-config 21 | data: 22 | # debuging enabled or not 23 | CONFIG_FILE: "/etc/flask-config/feature.flags" 24 | feature.flags: | 25 | [features] 26 | greeting=hello 27 | debug=true 28 | db=redis-service 29 | --- 30 | apiVersion: apps/v1beta1 31 | kind: Deployment 32 | metadata: 33 | name: flask 34 | labels: 35 | run: flask 36 | spec: 37 | template: 38 | metadata: 39 | labels: 40 | app: flask 41 | spec: 42 | containers: 43 | - name: jaeger-agent 44 | image: jaegertracing/jaeger-agent 45 | ports: 46 | - containerPort: 5775 47 | protocol: UDP 48 | - containerPort: 5778 49 | - containerPort: 6831 50 | protocol: UDP 51 | - containerPort: 6832 52 | protocol: UDP 53 | command: 54 | - "/go/bin/agent-linux" 55 | - "--collector.host-port=jaeger-collector:14267" 56 | - name: flask 57 | image: quay.io/kubernetes-for-developers/flask:0.7.0 58 | imagePullPolicy: Always 59 | resources: 60 | limits: 61 | memory: "40Mi" 62 | cpu: "500m" 63 | requests: 64 | memory: "40Mi" 65 | cpu: "500m" 66 | ports: 67 | - containerPort: 5000 68 | envFrom: 69 | - configMapRef: 70 | name: flask-config 71 | volumeMounts: 72 | - name: config 73 | mountPath: /etc/flask-config 74 | readOnly: true 75 | - name: cache-volume 76 | mountPath: /opt/cache 77 | livenessProbe: 78 | httpGet: 79 | path: /alive 80 | port: 5000 81 | initialDelaySeconds: 1 82 | periodSeconds: 15 83 | readinessProbe: 84 | httpGet: 85 | path: /ready 86 | port: 5000 87 | initialDelaySeconds: 5 88 | periodSeconds: 15 89 | volumes: 90 | - name: config 91 | configMap: 92 | name: flask-config 93 | - name: cache-volume 94 | emptyDir: {} 95 | initContainers: 96 | - name: init-myservice 97 | image: busybox 98 | command: ['sh', '-c', 'until nslookup redis-service; do echo waiting for redis-service; sleep 2; done;'] 99 | -------------------------------------------------------------------------------- /src/exampleapp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import time 5 | 6 | from configparser import SafeConfigParser 7 | from pathlib import Path 8 | 9 | from flask import Flask 10 | from flask import render_template, make_response, request, abort 11 | 12 | import opentracing 13 | from jaeger_client import Config 14 | from flask_opentracing import FlaskTracer 15 | 16 | from prometheus_client import Summary, Counter, Histogram 17 | from prometheus_client import generate_latest, CONTENT_TYPE_LATEST 18 | 19 | import requests 20 | import redis 21 | import signal 22 | import sys 23 | 24 | FLASK_REQUEST_LATENCY = Histogram('flask_request_latency_seconds', 'Flask Request Latency', 25 | ['method', 'endpoint']) 26 | FLASK_REQUEST_COUNT = Counter('flask_request_count', 'Flask Request Count', 27 | ['method', 'endpoint', 'http_status']) 28 | 29 | # defaults to reporting via UDP, port 6831, to localhost 30 | def initialize_tracer(): 31 | config = Config( 32 | config={ 33 | 'sampler': { 34 | 'type': 'const', 35 | 'param': 1 36 | }, 37 | 'logging': True 38 | }, 39 | service_name='flask-service' 40 | ) 41 | return config.initialize_tracer() # also sets opentracing.tracer 42 | 43 | def before_request(): 44 | request.start_time = time.time() 45 | 46 | def after_request(response): 47 | request_latency = time.time() - request.start_time 48 | FLASK_REQUEST_LATENCY.labels(request.method, request.path).observe(request_latency) 49 | FLASK_REQUEST_COUNT.labels(request.method, request.path, response.status_code).inc() 50 | return response 51 | 52 | def sigterm_handler(_signo, _stack_frame): 53 | sys.exit(0) 54 | 55 | signal.signal(signal.SIGTERM, sigterm_handler) 56 | 57 | # initialize the configuration parser with all the existing environment variables 58 | parser = SafeConfigParser(os.environ) 59 | # default location of ./feature.flags is used if the environment variable isn’t set 60 | config_file = Path(os.environ.get('CONFIG_FILE', './feature.flags')) 61 | # verify file exists before attempting to read and extend the configuration 62 | if config_file.is_file(): 63 | parser.read(os.environ.get('CONFIG_FILE')) 64 | 65 | redis_store = None 66 | app = Flask(__name__) 67 | flask_tracer = FlaskTracer(initialize_tracer, True, app, ["url_rule"]) 68 | 69 | @app.route('/') 70 | def index(): 71 | return "Index Page" 72 | 73 | @app.route('/activeconfig') 74 | def activeconfig(): 75 | output = "" 76 | for each_section in parser.sections(): 77 | output += "SECTION: "+each_section+"\n" 78 | for (each_key, each_val) in parser.items(each_section): 79 | output += each_key+" : "+each_val+"\n" 80 | return output 81 | 82 | @app.route('/hello') 83 | @app.route('/hello/') 84 | def hello(name=None): 85 | return render_template('hello.html', 86 | greeting=parser.get('features', 'greeting', fallback="Howdy"), 87 | name=name) 88 | 89 | @app.route('/alive') 90 | def alive(): 91 | return "Yes" 92 | 93 | @app.route('/ready') 94 | def ready(): 95 | parent_span = flask_tracer.get_span() 96 | with opentracing.tracer.start_span('redis-ping', child_of=parent_span) as span: 97 | result = redis_store.ping() 98 | span.set_tag("redis-ping", result) 99 | if result: 100 | return "Yes" 101 | else: 102 | abort(500) 103 | 104 | @app.route('/metrics') 105 | def metrics(): 106 | return make_response(generate_latest()) 107 | 108 | @app.route('/remote') 109 | def pull_requests(): 110 | parent_span = flask_tracer.get_span() 111 | github_url = "https://api.github.com/repos/opentracing/opentracing-python/pulls" 112 | with opentracing.tracer.start_span('github-api', child_of=parent_span) as span: 113 | span.set_tag("http.url",github_url) 114 | r = requests.get(github_url) 115 | span.set_tag("http.status_code", r.status_code) 116 | 117 | with opentracing.tracer.start_span('parse-json', child_of=parent_span) as span: 118 | json = r.json() 119 | span.set_tag("pull_requests", len(json)) 120 | pull_request_titles = map(lambda item: item['title'], json) 121 | 122 | return 'PRs: ' + ', '.join(pull_request_titles) 123 | 124 | if __name__ == '__main__': 125 | debug_enable = parser.getboolean('features', 'debug', fallback=False) 126 | redis_host = parser.get('features', 'db', fallback="localhost") 127 | redis_store = redis.StrictRedis(host=redis_host, port=6379, db=0) 128 | app.before_request(before_request) 129 | app.after_request(after_request) 130 | app.run(debug=debug_enable, host='0.0.0.0') 131 | -------------------------------------------------------------------------------- /e2e_tests/tests/test_smoke.py: -------------------------------------------------------------------------------- 1 | import kubernetes 2 | import pytest 3 | import time 4 | import subprocess 5 | import requests 6 | 7 | # in a larger example, this section could easily be in conftest.py 8 | @pytest.fixture 9 | def kube_v1_client(): 10 | kubernetes.config.load_kube_config() 11 | v1 = kubernetes.client.CoreV1Api() 12 | return v1 13 | 14 | @pytest.fixture(scope="module") 15 | def kubectl_proxy(): 16 | # establish proxy for kubectl communications 17 | # https://docs.python.org/3/library/subprocess.html#subprocess-replacements 18 | proxy = subprocess.Popen("kubectl proxy &", stdout=subprocess.PIPE, shell=True) 19 | yield 20 | # terminate the proxy 21 | proxy.kill() 22 | 23 | @pytest.mark.dependency() 24 | def test_kubernetes_components_healthy(kube_v1_client): 25 | # iterates through the core kuberneters components to verify the cluster is reporting healthy 26 | ret = kube_v1_client.list_component_status() 27 | for item in ret.items: 28 | assert item.conditions[0].type == "Healthy" 29 | print("%s: %s" % (item.metadata.name, item.conditions[0].type)) 30 | 31 | @pytest.mark.dependency(depends=["test_kubernetes_components_healthy"]) 32 | def test_deployment(): 33 | # https://docs.python.org/3/library/subprocess.html#subprocess.run 34 | # using check=True will throw an exception if a non-zero exit code is returned, saving us the need to assert 35 | # using timeout=10 will throw an exception if the process doesn't return within 10 seconds 36 | # Enables the deployment 37 | process_result = subprocess.run('kubectl apply -f ../deploy/', check=True, shell=True, timeout=10) 38 | 39 | @pytest.mark.dependency(depends=["test_deployment"]) 40 | def test_list_pods(kube_v1_client): 41 | ret = kube_v1_client.list_pod_for_all_namespaces(watch=False) 42 | for i in ret.items: 43 | print("%s\t%s\t%s" % 44 | (i.status.pod_ip, i.metadata.namespace, i.metadata.name)) 45 | 46 | @pytest.mark.dependency(depends=["test_deployment"]) 47 | def test_deployment_ready(kube_v1_client): 48 | TOTAL_TIMEOUT_SECONDS = 300 49 | DELAY_BETWEEN_REQUESTS_SECONDS = 5 50 | REQUEST_TIMEOUT_SECONDS=2 51 | apps_client = kubernetes.client.AppsV1beta2Api() 52 | now = time.time() 53 | while (time.time() < now+TOTAL_TIMEOUT_SECONDS): 54 | api_response = apps_client.list_namespaced_deployment("default", 55 | include_uninitialized=True, 56 | timeout_seconds=REQUEST_TIMEOUT_SECONDS) 57 | print("name\tavail\tready") 58 | for i in api_response.items: 59 | print("%s\t%s\t%s" % 60 | (i.metadata.name, i.status.available_replicas, i.status.ready_replicas)) 61 | if i.metadata.name == 'flask': 62 | if i.status and i.status.ready_replicas: 63 | return 64 | time.sleep(DELAY_BETWEEN_REQUESTS_SECONDS) 65 | assert False 66 | 67 | @pytest.mark.dependency(depends=["test_deployment_ready"]) 68 | def test_pods_running(kube_v1_client): 69 | TOTAL_TIMEOUT_SECONDS = 300 70 | DELAY_BETWEEN_REQUESTS_SECONDS = 5 71 | now = time.time() 72 | while (time.time() < now+TOTAL_TIMEOUT_SECONDS): 73 | pod_list = kube_v1_client.list_namespaced_pod("default") 74 | print("name\tphase\tcondition\tstatus") 75 | for pod in pod_list.items: 76 | for condition in pod.status.conditions: 77 | print("%s\t%s\t%s\t%s" % (pod.metadata.name, pod.status.phase, condition.type, condition.status)) 78 | if condition.type == 'Ready' and condition.status == 'True': 79 | return 80 | time.sleep(DELAY_BETWEEN_REQUESTS_SECONDS) 81 | assert False 82 | 83 | @pytest.mark.dependency(depends=["test_deployment_ready"]) 84 | def test_service_response(kube_v1_client, kubectl_proxy): 85 | NAMESPACE="default" 86 | SERVICE_NAME="flask-service" 87 | URI = "http://localhost:8001/api/v1/namespaces/%s/services/%s/proxy/" % (NAMESPACE, SERVICE_NAME) 88 | print("requesting %s" % (URI)) 89 | r = requests.get(URI) 90 | assert r.status_code == 200 91 | 92 | @pytest.mark.dependency(depends=["test_deployment_ready"]) 93 | def test_python_client_service_response(kube_v1_client): 94 | from pprint import pprint 95 | from kubernetes.client.rest import ApiException 96 | 97 | NAMESPACE="default" 98 | SERVICE_NAME="flask-service" 99 | 100 | try: 101 | api_response = kube_v1_client.proxy_get_namespaced_service(SERVICE_NAME, NAMESPACE) 102 | pprint(api_response) 103 | api_response = kube_v1_client.proxy_get_namespaced_service_with_path(SERVICE_NAME, NAMESPACE, "/metrics") 104 | pprint(api_response) 105 | except ApiException as e: 106 | print("Exception when calling CoreV1Api->proxy_get_namespaced_service: %s\n" % e) 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------