forked from kubeflow/kubeflow
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a version of echo-server to echo headers.
* This is intended to support debugging IAP; we want to see what headers are on resulting requests. * See kubeflow#574 * While creating this I ran into an issue with ksonnet not formatting the Ambassador mapping correctly unless we import it from a libsonnet file see ksonnet/ksonnet#670
- Loading branch information
Showing
7 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM python:2.7-slim | ||
|
||
ADD . /app | ||
WORKDIR /app | ||
|
||
RUN pip install -r requirements.txt | ||
|
||
ENTRYPOINT ["gunicorn", "-b", ":8080", "main:app"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Copyright 2017 The Kubernetes Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# Requirements: | ||
# https://github.com/mattrobenolt/jinja2-cli | ||
# pip install jinja2-clie | ||
IMG = gcr.io/kubeflow-images-staging/echo-server | ||
|
||
TAG := $(shell date +v%Y%m%d)-$(shell git describe --always --dirty)-$(shell git diff | shasum -a256 | cut -c -6) | ||
DIR := ${CURDIR} | ||
|
||
all: build | ||
|
||
# To build without the cache set the environment variable | ||
# export DOCKER_BUILD_OPTS=--no-cache | ||
build: | ||
docker build ${DOCKER_BUILD_OPTS} -t $(IMG):$(TAG) . | ||
docker tag ${DOCKER_BUILD_OPTS} $(IMG):$(TAG) $(IMG):latest | ||
@echo Built $(IMG):$(TAG) | ||
|
||
# Build but don't attach the latest tag. This allows manual testing/inspection of the image | ||
# first. | ||
push: build | ||
gcloud docker -- push $(IMG):$(TAG) | ||
@echo Pushed $(IMG) with :$(TAG) tags | ||
|
||
push-latest: push | ||
gcloud container images add-tag --quiet $(IMG):$(TAG) $(IMG):latest --verbosity=info | ||
echo created $(IMG):latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Copyright 2016 Google Inc. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Google Cloud Endpoints sample application. | ||
Demonstrates how to create a simple echo API as well as how to deal with | ||
various authentication methods. | ||
""" | ||
|
||
import base64 | ||
import json | ||
import logging | ||
|
||
from flask import Flask, jsonify, request | ||
from flask_cors import cross_origin | ||
from six.moves import http_client | ||
|
||
|
||
app = Flask(__name__) | ||
|
||
|
||
def _base64_decode(encoded_str): | ||
# Add paddings manually if necessary. | ||
num_missed_paddings = 4 - len(encoded_str) % 4 | ||
if num_missed_paddings != 4: | ||
encoded_str += b'=' * num_missed_paddings | ||
return base64.b64decode(encoded_str).decode('utf-8') | ||
|
||
|
||
@app.route('/echo', methods=['POST']) | ||
def echo(): | ||
"""Simple echo service.""" | ||
message = request.get_json().get('message', '') | ||
return jsonify({'message': message}) | ||
|
||
@app.route('/') | ||
@app.route('/headers') | ||
def headers(): | ||
return jsonify({'headers': request.headers.to_list()}) | ||
|
||
def auth_info(): | ||
"""Retrieves the authenication information from Google Cloud Endpoints.""" | ||
encoded_info = request.headers.get('X-Endpoint-API-UserInfo', None) | ||
|
||
if encoded_info: | ||
info_json = _base64_decode(encoded_info) | ||
user_info = json.loads(info_json) | ||
else: | ||
user_info = {'id': 'anonymous'} | ||
|
||
return jsonify(user_info) | ||
|
||
|
||
@app.route('/auth/info/googlejwt', methods=['GET']) | ||
def auth_info_google_jwt(): | ||
"""Auth info with Google signed JWT.""" | ||
return auth_info() | ||
|
||
|
||
@app.route('/auth/info/googleidtoken', methods=['GET']) | ||
def auth_info_google_id_token(): | ||
"""Auth info with Google ID token.""" | ||
return auth_info() | ||
|
||
|
||
@app.route('/auth/info/firebase', methods=['GET']) | ||
@cross_origin(send_wildcard=True) | ||
def auth_info_firebase(): | ||
"""Auth info with Firebase auth.""" | ||
return auth_info() | ||
|
||
|
||
@app.errorhandler(http_client.INTERNAL_SERVER_ERROR) | ||
def unexpected_error(e): | ||
"""Handle exceptions by returning swagger-compliant json.""" | ||
logging.exception('An error occured while processing the request.') | ||
response = jsonify({ | ||
'code': http_client.INTERNAL_SERVER_ERROR, | ||
'message': 'Exception: {}'.format(e)}) | ||
response.status_code = http_client.INTERNAL_SERVER_ERROR | ||
return response | ||
|
||
|
||
if __name__ == '__main__': | ||
# This is used when running locally. Gunicorn is used to run the | ||
# application on Google App Engine. See entrypoint in app.yaml. | ||
app.run(host='127.0.0.1', port=8080, debug=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Copyright 2016 Google Inc. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import base64 | ||
import json | ||
import os | ||
|
||
import pytest | ||
|
||
import main | ||
|
||
|
||
@pytest.fixture | ||
def client(monkeypatch): | ||
monkeypatch.chdir(os.path.dirname(main.__file__)) | ||
main.app.testing = True | ||
client = main.app.test_client() | ||
return client | ||
|
||
|
||
def test_echo(client): | ||
r = client.post( | ||
'/echo', | ||
data='{"message": "Hello"}', | ||
headers={ | ||
'Content-Type': 'application/json' | ||
}) | ||
|
||
assert r.status_code == 200 | ||
data = json.loads(r.data.decode('utf-8')) | ||
assert data['message'] == 'Hello' | ||
|
||
|
||
def test_auth_info(client): | ||
endpoints = [ | ||
'/auth/info/googlejwt', | ||
'/auth/info/googleidtoken', | ||
'/auth/info/firebase'] | ||
|
||
encoded_info = base64.b64encode(json.dumps({ | ||
'id': '123' | ||
}).encode('utf-8')) | ||
|
||
for endpoint in endpoints: | ||
r = client.get( | ||
endpoint, | ||
headers={ | ||
'Content-Type': 'application/json' | ||
}) | ||
|
||
assert r.status_code == 200 | ||
data = json.loads(r.data.decode('utf-8')) | ||
assert data['id'] == 'anonymous' | ||
|
||
r = client.get( | ||
endpoint, | ||
headers={ | ||
'Content-Type': 'application/json', | ||
'X-Endpoint-API-UserInfo': encoded_info | ||
}) | ||
|
||
assert r.status_code == 200 | ||
data = json.loads(r.data.decode('utf-8')) | ||
assert data['id'] == '123' | ||
|
||
|
||
def test_cors(client): | ||
r = client.options( | ||
'/auth/info/firebase', headers={'Origin': 'example.com'}) | ||
assert r.status_code == 200 | ||
assert r.headers['Access-Control-Allow-Origin'] == '*' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Flask==0.12.2 | ||
flask-cors==3.0.3 | ||
gunicorn==19.7.1 | ||
six==1.11.0 | ||
pyyaml==3.12 | ||
requests==2.18.4 | ||
google-auth==1.4.1 | ||
google-auth-oauthlib==0.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
{ | ||
service(namespace, name):: { | ||
apiVersion: "v1", | ||
kind: "Service", | ||
metadata: { | ||
labels: { | ||
app: name, | ||
}, | ||
name: name, | ||
namespace: namespace, | ||
annotations: { | ||
"getambassador.io/config": | ||
std.join("\n", [ | ||
"---", | ||
"apiVersion: ambassador/v0", | ||
"kind: Mapping", | ||
"name: " + name + "-mapping", | ||
"prefix: /" + name, | ||
"rewrite: /", | ||
"service: " + name + "." + namespace, | ||
]), | ||
}, //annotations | ||
}, | ||
spec: { | ||
ports: [ | ||
{ | ||
port: 80, | ||
targetPort: 8080, | ||
}, | ||
], | ||
selector: { | ||
app: name, | ||
}, | ||
type: "ClusterIP", | ||
}, | ||
}, | ||
|
||
deploy(namespace, name, image):: { | ||
apiVersion: "extensions/v1beta1", | ||
kind: "Deployment", | ||
metadata: { | ||
name: name, | ||
namespace: namespace, | ||
|
||
}, | ||
spec: { | ||
replicas: 1, | ||
template: { | ||
metadata: { | ||
labels: { | ||
app: name, | ||
}, | ||
}, | ||
spec: { | ||
containers: [ | ||
{ | ||
image: image, | ||
name: "app", | ||
ports: [ | ||
{ | ||
containerPort: 8080, | ||
}, | ||
], | ||
|
||
readinessProbe: { | ||
httpGet: { | ||
path: "/headers", | ||
port: 8080, | ||
}, | ||
initialDelaySeconds: 5, | ||
periodSeconds: 30, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// @apiVersion 0.1 | ||
// @name io.ksonnet.pkg.echo-server | ||
// @description Provides a simple server for testing connections; primarily IAP. | ||
// @shortDescription A simple echo server. | ||
// @param name string Name for the component | ||
// @optionalParam image string gcr.io/kubeflow-images-staging/echo-server:v20180628-e545118c-dirty-8a27d6 The image to use. | ||
|
||
local k = import "k.libsonnet"; | ||
|
||
// TODO(https://github.com/ksonnet/ksonnet/issues/670) If we don't import the service | ||
// from a libsonnet file the annotation doesn't end up being escaped/represented in a way that | ||
// Ambassador can understand. | ||
local echoParts = import "kubeflow/core/echo-server.libsonnet"; | ||
local namespace = env.namespace; | ||
|
||
std.prune(k.core.v1.list.new([echoParts.service(namespace, params.name), echoParts.deploy(namespace, params.name, params.image)])) |