├── 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 |
--------------------------------------------------------------------------------