Added initial nginx services 69/54269/6
authorEddie Arrage <eddie.arrage@huawei.com>
Wed, 21 Mar 2018 18:38:59 +0000 (18:38 +0000)
committerEddie Arrage <eddie.arrage@huawei.com>
Fri, 30 Mar 2018 01:22:37 +0000 (01:22 +0000)
- Proxy allows ingress traffic to be sent to another element in
service mesh
- Mirroring is also in the default configuration
- Default configuration is to proxy to a clover-server and mirror
to snort-ids
- A location_path (URI in HTTP requests) can be reconfigured to
restrict proxing; default to '/'
- A proxy_path can be reconfigured to specify an alternate destination
- A mirror path can be reconfigured to specify where traffic
will be spanned
- The default server_port (listen port) for the proxy is 9180 but can be
reconfigured
- The default server_name is http-proxy but can be reconfigured
- Reconfiguration is done over GRPC with jinja2 template for nginx
- Currently snort ids sends alerts to proxy with stub code in GRPC

- Refactored the code to have a nginx base with subservices
- Proxy, Load Balancer (lb), and Server can share code - mainly GRPC
server
- Nginx subservices have separate docker builds

- Improved build scripts for CI
- Render yaml manifests dynamically
- Improve nginx_client for runtime modifications (but not really
useful yet)

Change-Id: Icbff6890021bcc8a8da4690c9261205d6e1ca43a
Signed-off-by: Eddie Arrage <eddie.arrage@huawei.com>
20 files changed:
samples/services/nginx/docker/build_lb.sh [new file with mode: 0755]
samples/services/nginx/docker/build_proxy.sh [new file with mode: 0755]
samples/services/nginx/docker/build_server.sh [new file with mode: 0755]
samples/services/nginx/docker/grpc/build_proto.sh [new file with mode: 0755]
samples/services/nginx/docker/grpc/nginx.proto [new file with mode: 0644]
samples/services/nginx/docker/grpc/nginx_client.py [new file with mode: 0644]
samples/services/nginx/docker/grpc/nginx_grpc_server.py [new file with mode: 0644]
samples/services/nginx/docker/grpc/nginx_pb2.py [new file with mode: 0644]
samples/services/nginx/docker/grpc/nginx_pb2_grpc.py [new file with mode: 0644]
samples/services/nginx/docker/grpc/templates/lb.template [new file with mode: 0644]
samples/services/nginx/docker/grpc/templates/proxy.template [new file with mode: 0644]
samples/services/nginx/docker/grpc/templates/server.template [new file with mode: 0644]
samples/services/nginx/docker/process/grpc_process.sh [new file with mode: 0755]
samples/services/nginx/docker/process/nginx_process.sh [new file with mode: 0755]
samples/services/nginx/docker/process/start_process.sh [new file with mode: 0755]
samples/services/nginx/docker/subservices/lb/Dockerfile [new file with mode: 0644]
samples/services/nginx/docker/subservices/proxy/Dockerfile [new file with mode: 0644]
samples/services/nginx/docker/subservices/server/Dockerfile [new file with mode: 0644]
samples/services/nginx/yaml/manifest.template [new file with mode: 0644]
samples/services/nginx/yaml/render_yaml.py [new file with mode: 0644]

diff --git a/samples/services/nginx/docker/build_lb.sh b/samples/services/nginx/docker/build_lb.sh
new file mode 100755 (executable)
index 0000000..46efec0
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-ns-nginx-lb"}
+
+docker build -f subservices/lb/Dockerfile -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/samples/services/nginx/docker/build_proxy.sh b/samples/services/nginx/docker/build_proxy.sh
new file mode 100755 (executable)
index 0000000..6b6937c
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-ns-nginx-proxy"}
+
+docker build -f subservices/proxy/Dockerfile  -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/samples/services/nginx/docker/build_server.sh b/samples/services/nginx/docker/build_server.sh
new file mode 100755 (executable)
index 0000000..a884213
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+IMAGE_PATH=${IMAGE_PATH:-"localhost:5000"}
+IMAGE_NAME=${IMAGE_NAME:-"clover-ns-nginx-server"}
+
+docker build -f subservices/server/Dockerfile -t $IMAGE_NAME .
+docker tag $IMAGE_NAME $IMAGE_PATH/$IMAGE_NAME
+docker push $IMAGE_PATH/$IMAGE_NAME
diff --git a/samples/services/nginx/docker/grpc/build_proto.sh b/samples/services/nginx/docker/grpc/build_proto.sh
new file mode 100755 (executable)
index 0000000..a98080a
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. nginx.proto
diff --git a/samples/services/nginx/docker/grpc/nginx.proto b/samples/services/nginx/docker/grpc/nginx.proto
new file mode 100644 (file)
index 0000000..3779a82
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+syntax = "proto3";
+
+package nginx;
+
+// The controller service definition.
+service Controller {
+
+  rpc ModifyProxy (ConfigProxy) returns (NginxReply) {}
+  rpc ModifyServer (ConfigServer) returns (NginxReply) {}
+  rpc ModifyLB (ConfigLB) returns (NginxReply) {}
+  rpc ProcessAlerts (AlertMessage) returns (NginxReply) {}
+}
+
+message AlertMessage {
+  string event_id = 1;
+  string redis_key = 2;
+}
+
+message ConfigProxy {
+  string server_port = 1;
+  string server_name = 2;
+  string location_path = 3;
+  string proxy_path = 4;
+  string mirror_path = 5;
+}
+
+message ConfigServer {
+  string server_port = 1;
+  string server_name = 2;
+  string site_root = 3;
+  string site_index = 4;
+}
+
+message ConfigLB {
+  string server_port = 1;
+  string server_name = 2;
+  string slb_list = 3;
+  string slb_group = 4;
+  string lb_path = 5;
+}
+
+message NginxReply {
+  string message = 1;
+}
diff --git a/samples/services/nginx/docker/grpc/nginx_client.py b/samples/services/nginx/docker/grpc/nginx_client.py
new file mode 100644 (file)
index 0000000..dfefb08
--- /dev/null
@@ -0,0 +1,87 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+from __future__ import print_function
+from kubernetes import client, config
+
+import grpc
+import argparse
+import pickle
+
+import nginx_pb2
+import nginx_pb2_grpc
+
+
+def run(args):
+    # get pod ip for grpc
+    pod_ip = get_podip(args['service_name'])
+    if pod_ip == '':
+        return "Cant find service with name: {}".format(args['service_name'])
+    nginx_grpc = pod_ip + ':50054'
+    channel = grpc.insecure_channel(nginx_grpc)
+    stub = nginx_pb2_grpc.ControllerStub(channel)
+
+    # modify config
+    if args['service_type'] == 'lb':
+        modify_lb(stub)
+    elif args['service_type'] == 'proxy':
+        modify_proxy(stub)
+    elif args['service_type'] == 'server':
+        modify_server(stub)
+    else:
+        return "Invalid service type: {}".format(args['service_type'])
+    return "Modification complete"
+
+
+def get_podip(pod_name):
+    config.load_kube_config()
+    v1 = client.CoreV1Api()
+    ret = v1.list_pod_for_all_namespaces(watch=False)
+    ip = ''
+    for i in ret.items:
+        if i.metadata.name.lower().find(pod_name.lower()) != -1:
+            print(i.status.pod_ip)
+            ip = i.status.pod_ip
+    return str(ip)
+
+
+def modify_proxy(stub):
+    response = stub.ModifyProxy(nginx_pb2.ConfigProxy(
+            server_port='9180', server_name='http-proxy',
+            location_path='/', proxy_path='http://clover-server:9180',
+            mirror_path='http://snort-ids:80'))
+    print(response.message)
+
+
+def modify_server(stub):
+    response = stub.ModifyServer(nginx_pb2.ConfigServer(
+            server_port='9180', server_name='clover-server',
+            site_root='/var/www/html', site_index='index.nginx-debian.html'))
+    print(response.message)
+
+
+def modify_lb(stub):
+    slb_list = pickle.dumps(
+                    ['clover-server1', 'clover-server2', 'clover-server3'])
+    response = stub.ModifyLB(nginx_pb2.ConfigLB(
+            server_port='9188', server_name='http-lb',
+            slb_list=slb_list,
+            slb_group='cloverlb', lb_path='/'))
+    print(response.message)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--service_type', required=True,
+            help='The service to reconfigure')
+    parser.add_argument(
+            '--service_name', required=True,
+            help='The service to reconfigure')
+
+    args = parser.parse_args()
+    print(run(vars(args)))
diff --git a/samples/services/nginx/docker/grpc/nginx_grpc_server.py b/samples/services/nginx/docker/grpc/nginx_grpc_server.py
new file mode 100644 (file)
index 0000000..6f2de0f
--- /dev/null
@@ -0,0 +1,142 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+
+from concurrent import futures
+import time
+import sys
+import grpc
+import subprocess
+import pickle
+import logging
+import nginx_pb2
+import nginx_pb2_grpc
+
+from jinja2 import Template
+
+_ONE_DAY_IN_SECONDS = 60 * 60 * 24
+GRPC_PORT = '[::]:50054'
+
+
+class Controller(nginx_pb2_grpc.ControllerServicer):
+
+    def __init__(self, service_type):
+        logging.basicConfig(filename='nginx.log', level=logging.DEBUG)
+        self.service_type = service_type
+        self.out_file = '/etc/nginx/nginx.conf'
+        # self.out_file = 'testfile'
+        if service_type == "proxy":
+            # self.template_file = 'templates/proxy.template'
+            self.template_file = '/grpc/templates/proxy.template'
+            self.ModifyProxy(nginx_pb2.ConfigProxy(
+                server_port='9180', server_name='http-proxy',
+                location_path='/', proxy_path='http://clover-server:9180',
+                mirror_path='http://snort-ids:80'), "")
+        if service_type == "server":
+            # self.template_file = 'templates/server.template'
+            self.template_file = '/grpc/templates/server.template'
+            self.ModifyServer(nginx_pb2.ConfigServer(
+                server_port='9180', server_name='clover-server',
+                site_root='/var/www/html',
+                site_index='index.nginx-debian.html'), "")
+        if service_type == "lb":
+            # self.template_file = 'templates/lb.template'
+            self.template_file = '/grpc/templates/lb.template'
+            slb_list = pickle.dumps(
+                    ['clover-server1', 'clover-server2', 'clover-server3'])
+            self.ModifyLB(nginx_pb2.ConfigLB(
+                server_port='9180', server_name='http-lb',
+                slb_list=slb_list,
+                slb_group='cloverlb', lb_path='/'), "")
+
+    def ModifyProxy(self, r, context):
+        try:
+            with open(self.template_file) as f:
+                tmpl = Template(f.read())
+            output = tmpl.render(
+                server_port=r.server_port,
+                server_name=r.server_name,
+                location_path=r.location_path,
+                proxy_path=r.proxy_path,
+                mirror_path=r.mirror_path
+            )
+            with open(self.out_file, "wb") as fh:
+                fh.write(output)
+            msg = "Modified nginx config"
+            self.RestartNginx()
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to modify nginx config"
+        return nginx_pb2.NginxReply(message=msg)
+
+    def ModifyServer(self, r, context):
+        try:
+            with open(self.template_file) as f:
+                tmpl = Template(f.read())
+            output = tmpl.render(
+                server_port=r.server_port,
+                server_name=r.server_name,
+                site_root=r.site_root,
+                site_index=r.site_index
+            )
+            with open(self.out_file, "wb") as fh:
+                fh.write(output)
+            msg = "Modified nginx config"
+            self.RestartNginx()
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to modify nginx config"
+        return nginx_pb2.NginxReply(message=msg)
+
+    def ModifyLB(self, r, context):
+        try:
+            with open(self.template_file) as f:
+                tmpl = Template(f.read())
+            output = tmpl.render(
+                server_port=r.server_port,
+                server_name=r.server_name,
+                slb_list=pickle.loads(r.slb_list),
+                slb_group=r.slb_group,
+                lb_path=r.lb_path
+            )
+            with open(self.out_file, "wb") as fh:
+                fh.write(output)
+            msg = "Modified nginx config"
+            self.RestartNginx()
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to modify nginx config"
+        return nginx_pb2.NginxReply(message=msg)
+
+    def RestartNginx(self):
+        subprocess.Popen(
+                  ["service nginx restart"], shell=True)
+
+    def ProcessAlerts(self, request, context):
+        try:
+            msg = "Processed alert"
+        except Exception as e:
+            logging.debug(e)
+            msg = "Failed to process alert"
+        return nginx_pb2.NginxReply(message=msg)
+
+
+def serve(service_type):
+    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+    nginx_pb2_grpc.add_ControllerServicer_to_server(
+                    Controller(service_type), server)
+    server.add_insecure_port(GRPC_PORT)
+    server.start()
+    try:
+        while True:
+            time.sleep(_ONE_DAY_IN_SECONDS)
+    except KeyboardInterrupt:
+        server.stop(0)
+
+
+if __name__ == '__main__':
+    serve(sys.argv[1])
diff --git a/samples/services/nginx/docker/grpc/nginx_pb2.py b/samples/services/nginx/docker/grpc/nginx_pb2.py
new file mode 100644 (file)
index 0000000..3600b6d
--- /dev/null
@@ -0,0 +1,360 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: nginx.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='nginx.proto',
+  package='nginx',
+  syntax='proto3',
+  serialized_pb=_b('\n\x0bnginx.proto\x12\x05nginx\"3\n\x0c\x41lertMessage\x12\x10\n\x08\x65vent_id\x18\x01 \x01(\t\x12\x11\n\tredis_key\x18\x02 \x01(\t\"w\n\x0b\x43onfigProxy\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x15\n\rlocation_path\x18\x03 \x01(\t\x12\x12\n\nproxy_path\x18\x04 \x01(\t\x12\x13\n\x0bmirror_path\x18\x05 \x01(\t\"_\n\x0c\x43onfigServer\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x11\n\tsite_root\x18\x03 \x01(\t\x12\x12\n\nsite_index\x18\x04 \x01(\t\"j\n\x08\x43onfigLB\x12\x13\n\x0bserver_port\x18\x01 \x01(\t\x12\x13\n\x0bserver_name\x18\x02 \x01(\t\x12\x10\n\x08slb_list\x18\x03 \x01(\t\x12\x11\n\tslb_group\x18\x04 \x01(\t\x12\x0f\n\x07lb_path\x18\x05 \x01(\t\"\x1d\n\nNginxReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xeb\x01\n\nController\x12\x36\n\x0bModifyProxy\x12\x12.nginx.ConfigProxy\x1a\x11.nginx.NginxReply\"\x00\x12\x38\n\x0cModifyServer\x12\x13.nginx.ConfigServer\x1a\x11.nginx.NginxReply\"\x00\x12\x30\n\x08ModifyLB\x12\x0f.nginx.ConfigLB\x1a\x11.nginx.NginxReply\"\x00\x12\x39\n\rProcessAlerts\x12\x13.nginx.AlertMessage\x1a\x11.nginx.NginxReply\"\x00\x62\x06proto3')
+)
+
+
+
+
+_ALERTMESSAGE = _descriptor.Descriptor(
+  name='AlertMessage',
+  full_name='nginx.AlertMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='event_id', full_name='nginx.AlertMessage.event_id', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='redis_key', full_name='nginx.AlertMessage.redis_key', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=22,
+  serialized_end=73,
+)
+
+
+_CONFIGPROXY = _descriptor.Descriptor(
+  name='ConfigProxy',
+  full_name='nginx.ConfigProxy',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigProxy.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigProxy.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='location_path', full_name='nginx.ConfigProxy.location_path', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='proxy_path', full_name='nginx.ConfigProxy.proxy_path', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='mirror_path', full_name='nginx.ConfigProxy.mirror_path', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=75,
+  serialized_end=194,
+)
+
+
+_CONFIGSERVER = _descriptor.Descriptor(
+  name='ConfigServer',
+  full_name='nginx.ConfigServer',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigServer.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigServer.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='site_root', full_name='nginx.ConfigServer.site_root', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='site_index', full_name='nginx.ConfigServer.site_index', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=196,
+  serialized_end=291,
+)
+
+
+_CONFIGLB = _descriptor.Descriptor(
+  name='ConfigLB',
+  full_name='nginx.ConfigLB',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='server_port', full_name='nginx.ConfigLB.server_port', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='server_name', full_name='nginx.ConfigLB.server_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='slb_list', full_name='nginx.ConfigLB.slb_list', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='slb_group', full_name='nginx.ConfigLB.slb_group', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='lb_path', full_name='nginx.ConfigLB.lb_path', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=293,
+  serialized_end=399,
+)
+
+
+_NGINXREPLY = _descriptor.Descriptor(
+  name='NginxReply',
+  full_name='nginx.NginxReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='message', full_name='nginx.NginxReply.message', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=401,
+  serialized_end=430,
+)
+
+DESCRIPTOR.message_types_by_name['AlertMessage'] = _ALERTMESSAGE
+DESCRIPTOR.message_types_by_name['ConfigProxy'] = _CONFIGPROXY
+DESCRIPTOR.message_types_by_name['ConfigServer'] = _CONFIGSERVER
+DESCRIPTOR.message_types_by_name['ConfigLB'] = _CONFIGLB
+DESCRIPTOR.message_types_by_name['NginxReply'] = _NGINXREPLY
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+AlertMessage = _reflection.GeneratedProtocolMessageType('AlertMessage', (_message.Message,), dict(
+  DESCRIPTOR = _ALERTMESSAGE,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.AlertMessage)
+  ))
+_sym_db.RegisterMessage(AlertMessage)
+
+ConfigProxy = _reflection.GeneratedProtocolMessageType('ConfigProxy', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGPROXY,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigProxy)
+  ))
+_sym_db.RegisterMessage(ConfigProxy)
+
+ConfigServer = _reflection.GeneratedProtocolMessageType('ConfigServer', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGSERVER,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigServer)
+  ))
+_sym_db.RegisterMessage(ConfigServer)
+
+ConfigLB = _reflection.GeneratedProtocolMessageType('ConfigLB', (_message.Message,), dict(
+  DESCRIPTOR = _CONFIGLB,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.ConfigLB)
+  ))
+_sym_db.RegisterMessage(ConfigLB)
+
+NginxReply = _reflection.GeneratedProtocolMessageType('NginxReply', (_message.Message,), dict(
+  DESCRIPTOR = _NGINXREPLY,
+  __module__ = 'nginx_pb2'
+  # @@protoc_insertion_point(class_scope:nginx.NginxReply)
+  ))
+_sym_db.RegisterMessage(NginxReply)
+
+
+
+_CONTROLLER = _descriptor.ServiceDescriptor(
+  name='Controller',
+  full_name='nginx.Controller',
+  file=DESCRIPTOR,
+  index=0,
+  options=None,
+  serialized_start=433,
+  serialized_end=668,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='ModifyProxy',
+    full_name='nginx.Controller.ModifyProxy',
+    index=0,
+    containing_service=None,
+    input_type=_CONFIGPROXY,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyServer',
+    full_name='nginx.Controller.ModifyServer',
+    index=1,
+    containing_service=None,
+    input_type=_CONFIGSERVER,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyLB',
+    full_name='nginx.Controller.ModifyLB',
+    index=2,
+    containing_service=None,
+    input_type=_CONFIGLB,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ProcessAlerts',
+    full_name='nginx.Controller.ProcessAlerts',
+    index=3,
+    containing_service=None,
+    input_type=_ALERTMESSAGE,
+    output_type=_NGINXREPLY,
+    options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_CONTROLLER)
+
+DESCRIPTOR.services_by_name['Controller'] = _CONTROLLER
+
+# @@protoc_insertion_point(module_scope)
diff --git a/samples/services/nginx/docker/grpc/nginx_pb2_grpc.py b/samples/services/nginx/docker/grpc/nginx_pb2_grpc.py
new file mode 100644 (file)
index 0000000..2b36fc0
--- /dev/null
@@ -0,0 +1,97 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+import nginx_pb2 as nginx__pb2
+
+
+class ControllerStub(object):
+  """The controller service definition.
+  """
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.ModifyProxy = channel.unary_unary(
+        '/nginx.Controller/ModifyProxy',
+        request_serializer=nginx__pb2.ConfigProxy.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ModifyServer = channel.unary_unary(
+        '/nginx.Controller/ModifyServer',
+        request_serializer=nginx__pb2.ConfigServer.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ModifyLB = channel.unary_unary(
+        '/nginx.Controller/ModifyLB',
+        request_serializer=nginx__pb2.ConfigLB.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+    self.ProcessAlerts = channel.unary_unary(
+        '/nginx.Controller/ProcessAlerts',
+        request_serializer=nginx__pb2.AlertMessage.SerializeToString,
+        response_deserializer=nginx__pb2.NginxReply.FromString,
+        )
+
+
+class ControllerServicer(object):
+  """The controller service definition.
+  """
+
+  def ModifyProxy(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ModifyServer(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ModifyLB(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def ProcessAlerts(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_ControllerServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'ModifyProxy': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyProxy,
+          request_deserializer=nginx__pb2.ConfigProxy.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ModifyServer': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyServer,
+          request_deserializer=nginx__pb2.ConfigServer.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ModifyLB': grpc.unary_unary_rpc_method_handler(
+          servicer.ModifyLB,
+          request_deserializer=nginx__pb2.ConfigLB.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+      'ProcessAlerts': grpc.unary_unary_rpc_method_handler(
+          servicer.ProcessAlerts,
+          request_deserializer=nginx__pb2.AlertMessage.FromString,
+          response_serializer=nginx__pb2.NginxReply.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'nginx.Controller', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/samples/services/nginx/docker/grpc/templates/lb.template b/samples/services/nginx/docker/grpc/templates/lb.template
new file mode 100644 (file)
index 0000000..4866408
--- /dev/null
@@ -0,0 +1,82 @@
+user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+
+events {
+       worker_connections 768;
+       # multi_accept on;
+}
+
+http {
+
+       ##
+       # Basic Settings
+       ##
+
+       sendfile on;
+       tcp_nopush on;
+       tcp_nodelay on;
+       keepalive_timeout 65;
+       types_hash_max_size 2048;
+       # server_tokens off;
+
+
+       include /etc/nginx/mime.types;
+       default_type application/octet-stream;
+
+       ##
+       # SSL Settings
+       ##
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+       ssl_prefer_server_ciphers on;
+
+       ##
+       # Logging Settings
+       ##
+
+       access_log /var/log/nginx/access.log;
+       error_log /var/log/nginx/error.log;
+
+       ##
+       # Gzip Settings
+       ##
+
+       gzip on;
+       gzip_disable "msie6";
+
+       # gzip_vary on;
+       # gzip_proxied any;
+       # gzip_comp_level 6;
+       # gzip_buffers 16 8k;
+       # gzip_http_version 1.1;
+       # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+       ##
+       # Virtual Host Configs
+       ##
+
+       include /etc/nginx/conf.d/*.conf;
+       #include /etc/nginx/sites-enabled/*;
+
+    upstream {{ slb_group }} {
+        {%- for item in slb_list %}
+            server {{ item }};
+        {%- endfor %}
+    }
+
+    server {
+        listen {{ server_port }};
+        server_name {{ server_name }};
+
+        location {{ lb_path }} {
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "upgrade";
+            proxy_pass http://{{ slb_group }};
+        }
+
+    }
+
+}
+
diff --git a/samples/services/nginx/docker/grpc/templates/proxy.template b/samples/services/nginx/docker/grpc/templates/proxy.template
new file mode 100644 (file)
index 0000000..72d611e
--- /dev/null
@@ -0,0 +1,85 @@
+user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+
+events {
+       worker_connections 768;
+       # multi_accept on;
+}
+
+http {
+
+       ##
+       # Basic Settings
+       ##
+
+       sendfile on;
+       tcp_nopush on;
+       tcp_nodelay on;
+       keepalive_timeout 65;
+       types_hash_max_size 2048;
+       # server_tokens off;
+
+
+       include /etc/nginx/mime.types;
+       default_type application/octet-stream;
+
+       ##
+       # SSL Settings
+       ##
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+       ssl_prefer_server_ciphers on;
+
+       ##
+       # Logging Settings
+       ##
+
+       access_log /var/log/nginx/access.log;
+       error_log /var/log/nginx/error.log;
+
+       ##
+       # Gzip Settings
+       ##
+
+       gzip on;
+       gzip_disable "msie6";
+
+       # gzip_vary on;
+       # gzip_proxied any;
+       # gzip_comp_level 6;
+       # gzip_buffers 16 8k;
+       # gzip_http_version 1.1;
+       # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+       ##
+       # Virtual Host Configs
+       ##
+
+       include /etc/nginx/conf.d/*.conf;
+       #include /etc/nginx/sites-enabled/*;
+
+    server {
+    listen {{ server_port }};
+    server_name {{ server_name }};
+
+        location {{ location_path }} {
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "upgrade";
+            proxy_pass {{ proxy_path }};
+
+            post_action @post_ids;
+        }
+
+        location @post_ids {
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "upgrade";
+            proxy_pass {{ mirror_path }};
+        }
+
+    }
+
+}
+
diff --git a/samples/services/nginx/docker/grpc/templates/server.template b/samples/services/nginx/docker/grpc/templates/server.template
new file mode 100644 (file)
index 0000000..b5f8f1f
--- /dev/null
@@ -0,0 +1,71 @@
+user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+
+events {
+       worker_connections 768;
+       # multi_accept on;
+}
+
+http {
+
+       ##
+       # Basic Settings
+       ##
+
+       sendfile on;
+       tcp_nopush on;
+       tcp_nodelay on;
+       keepalive_timeout 65;
+       types_hash_max_size 2048;
+       # server_tokens off;
+
+
+       include /etc/nginx/mime.types;
+       default_type application/octet-stream;
+
+       ##
+       # SSL Settings
+       ##
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+       ssl_prefer_server_ciphers on;
+
+       ##
+       # Logging Settings
+       ##
+
+       access_log /var/log/nginx/access.log;
+       error_log /var/log/nginx/error.log;
+
+       ##
+       # Gzip Settings
+       ##
+
+       gzip on;
+       gzip_disable "msie6";
+
+       # gzip_vary on;
+       # gzip_proxied any;
+       # gzip_comp_level 6;
+       # gzip_buffers 16 8k;
+       # gzip_http_version 1.1;
+       # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+       ##
+       # Virtual Host Configs
+       ##
+
+       include /etc/nginx/conf.d/*.conf;
+       #include /etc/nginx/sites-enabled/*;
+
+    server {
+        listen {{ server_port }};
+        server_name {{ server_name }};
+
+        root {{ site_root }};
+        index {{ site_index }};
+    }
+
+}
+
diff --git a/samples/services/nginx/docker/process/grpc_process.sh b/samples/services/nginx/docker/process/grpc_process.sh
new file mode 100755 (executable)
index 0000000..cc8aa60
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+python grpc/nginx_grpc_server.py $1
+
diff --git a/samples/services/nginx/docker/process/nginx_process.sh b/samples/services/nginx/docker/process/nginx_process.sh
new file mode 100755 (executable)
index 0000000..00edc66
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+service nginx restart
+
diff --git a/samples/services/nginx/docker/process/start_process.sh b/samples/services/nginx/docker/process/start_process.sh
new file mode 100755 (executable)
index 0000000..b84d2d6
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+#
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+# Start the nginx process
+./process/nginx_process.sh
+
+# Start the grpc server
+./process/grpc_process.sh $1 -D
diff --git a/samples/services/nginx/docker/subservices/lb/Dockerfile b/samples/services/nginx/docker/subservices/lb/Dockerfile
new file mode 100644 (file)
index 0000000..125da0b
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM ubuntu:16.04
+LABEL maintainer="Eddie Arrage" maintainer_email="eddie.arrage@huawei.com"
+LABEL version="0.1" description="Clover - Nginx HTTP LB"
+
+RUN \
+    apt-get update && apt-get install -y \
+# Some debug tool in container
+    wget \
+    libdnet \
+    net-tools \
+# Nginx as proxy
+    nginx \
+    python-pip \
+&& \
+# Install required python packages
+    python -m pip install grpcio redis jinja2
+
+COPY /process /process
+COPY /grpc /grpc
+CMD ./process/start_process.sh lb
+
diff --git a/samples/services/nginx/docker/subservices/proxy/Dockerfile b/samples/services/nginx/docker/subservices/proxy/Dockerfile
new file mode 100644 (file)
index 0000000..0f061a5
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM ubuntu:16.04
+LABEL maintainer="Eddie Arrage" maintainer_email="eddie.arrage@huawei.com"
+LABEL version="0.1" description="Clover - Nginx HTTP Proxy"
+
+RUN \
+    apt-get update && apt-get install -y \
+# Some debug tools in container
+    wget \
+    libdnet \
+    net-tools \
+# Nginx as proxy
+    nginx \
+    python-pip \
+&& \
+# Install required python packages
+    python -m pip install grpcio redis jinja2
+
+COPY /process /process
+COPY /grpc /grpc
+CMD ./process/start_process.sh proxy
diff --git a/samples/services/nginx/docker/subservices/server/Dockerfile b/samples/services/nginx/docker/subservices/server/Dockerfile
new file mode 100644 (file)
index 0000000..8bf9449
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+FROM ubuntu:16.04
+LABEL maintainer="Eddie Arrage" maintainer_email="eddie.arrage@huawei.com"
+LABEL version="0.1" description="Clover - Nginx HTTP Server"
+
+RUN \
+    apt-get update && apt-get install -y \
+# Some debug tools in container
+    wget \
+    libdnet \
+    net-tools \
+# Nginx as proxy
+    nginx \
+    python-pip \
+&& \
+# Install required python packages
+    python -m pip install grpcio redis jinja2
+
+COPY /process /process
+COPY /grpc /grpc
+CMD ./process/start_process.sh server
+
diff --git a/samples/services/nginx/yaml/manifest.template b/samples/services/nginx/yaml/manifest.template
new file mode 100644 (file)
index 0000000..ebd5392
--- /dev/null
@@ -0,0 +1,35 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+  template:
+    metadata:
+      labels:
+        app: {{ deploy_name }}
+    spec:
+      containers:
+        - name: {{ deploy_name }}
+          image: {{ image_path }}/{{ image_name }}:{{ image_tag }}
+          ports:
+           - containerPort: {{ grpc_port }}
+           - containerPort: {{ server_port }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ deploy_name }}
+  labels:
+    app: {{ deploy_name }}
+spec:
+  ports:
+  - port: {{ grpc_port }}
+    name: grpc
+  - port: {{ server_port }}
+    name: http
+  selector:
+    app: {{ deploy_name }}
+---
diff --git a/samples/services/nginx/yaml/render_yaml.py b/samples/services/nginx/yaml/render_yaml.py
new file mode 100644 (file)
index 0000000..527ba8d
--- /dev/null
@@ -0,0 +1,64 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import argparse
+
+from jinja2 import Template
+
+
+def render_yaml(args):
+    template_file = 'manifest.template'
+    server_port = '9180'
+    grpc_port = '50054'
+    if args['service_type'] == 'lb':
+        out_file = 'lb.yaml'
+        deploy_name = 'http-lb'
+    elif args['service_type'] == 'proxy':
+        out_file = 'proxy.yaml'
+        deploy_name = 'proxy-access-control'
+    elif args['service_type'] == 'server':
+        out_file = 'server.yaml'
+        deploy_name = 'clover-server'
+    else:
+        return "Invalid service type: {}".format(args['service_type'])
+
+    try:
+        with open(template_file) as f:
+            tmpl = Template(f.read())
+        output = tmpl.render(
+            image_path=args['image_path'],
+            image_name=args['image_name'],
+            image_tag=args['image_tag'],
+            deploy_name=deploy_name,
+            server_port=server_port,
+            grpc_port=grpc_port
+        )
+        with open(out_file, "wb") as fh:
+            fh.write(output)
+        return "Generated manifest for {}".format(args['service_type'])
+    except Exception as e:
+        print(e)
+        return "Unable to generate manifest for {}".format(
+                                        args['service_type'])
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--service_type', required=True,
+            help='The service to generate k8s manifest for')
+    parser.add_argument(
+            '--image_name', required=True,
+            help='The image name to use')
+    parser.add_argument(
+            '--image_path', default='localhost:5000',
+            help='The path to the images to use')
+    parser.add_argument(
+            '--image_tag', default='latest',
+            help='The image tag to use')
+    args = parser.parse_args()
+    print(render_yaml(vars(args)))