[CORD-2968]

Service scaffold and virtualenv created during devel of A-CORD services

Change-Id: I8f6e73fb7c8a357784bdfd1463f669bc1ed4d280
diff --git a/scripts/service_scaffold.sh b/scripts/service_scaffold.sh
new file mode 100755
index 0000000..918cab2
--- /dev/null
+++ b/scripts/service_scaffold.sh
@@ -0,0 +1,706 @@
+#!/usr/bin/env bash
+
+# Copyright 2018-present Open Networking Foundation
+#
+# 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.
+
+# service_scaffold.sh
+# creates directories and scaffolding for a service
+
+set -e -o pipefail
+
+LICENSE_TEXT=$(cat <<EOF
+# Copyright $(date +%Y)-present Open Networking Foundation
+#
+# 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.
+EOF
+)
+
+TPL_LICENSE_TEXT=$(cat <<EOF
+{{/*
+Copyright $(date +%Y)-present Open Networking Foundation
+
+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.
+*/}}
+EOF
+)
+
+if [ -z "${SERVICE_NAME}" ];
+then
+  SERVICE_NAME=$(basename "${PWD}")
+  echo "SERVICE_NAME undefined, using parent directory name: ${SERVICE_NAME}"
+fi
+
+if [ -z "${SERVICE_VERSION}" ];
+then
+  SERVICE_VERSION="0.0.1"
+  echo "SERVICE_VERSION undefined, using default: ${SERVICE_VERSION}"
+fi
+
+echo "Creating service named: ${SERVICE_NAME}, version: ${SERVICE_VERSION}"
+
+echo "${SERVICE_VERSION}" > VERSION
+
+# create uppercase version of SERVICE_NAME in a naive fashion
+UC_SERVICE_NAME=$(echo "${SERVICE_NAME}" | cut -c1 | tr '[:lower:]' '[:upper:]')$(echo "${SERVICE_NAME}" | cut -c2-)
+
+echo "Uppercase service name: ${UC_SERVICE_NAME}"
+
+echo "Creating README.md"
+echo "# ${UC_SERVICE_NAME} Service" > README.md
+
+echo "Creating directories"
+mkdir -p xos/synchronizer/model_policies/
+mkdir -p xos/synchronizer/models/
+mkdir -p xos/synchronizer/pull_steps/
+mkdir -p xos/synchronizer/steps/
+mkdir -p xos/synchronizer/tests/
+mkdir -p "helm-charts/${SERVICE_NAME}/templates"
+
+echo "Creating empty model-deps"
+echo "{}" > xos/synchronizer/model-deps
+
+echo "Creating xproto scaffold"
+cat << EOF > "xos/synchronizer/models/${SERVICE_NAME}.xproto"
+option app_label = "${SERVICE_NAME}";
+option name = "${SERVICE_NAME}";
+
+message ${UC_SERVICE_NAME}Service (Service){
+  option verbose_name = "${UC_SERVICE_NAME} Service";
+}
+
+message ${UC_SERVICE_NAME}ServiceInstance (ServiceInstance){
+  option owner_class_name = "${UC_SERVICE_NAME}Service";
+  option verbose_name = "${UC_SERVICE_NAME} Service Instance";
+}
+EOF
+
+echo "Creating unittest.cfg"
+cat << EOF > "xos/unittest.cfg"
+[unittest]
+plugins=nose2.plugins.junitxml
+code-directories=synchronizer
+                 model_policies
+                 steps
+                 pull_steps
+                 event_steps
+EOF
+
+echo "Creating service config.yaml"
+cat << EOF > "xos/synchronizer/config.yaml"
+${LICENSE_TEXT}
+
+name: ${SERVICE_NAME}
+required_models:
+  - ${UC_SERVICE_NAME}Service
+  - ${UC_SERVICE_NAME}ServiceInstance
+dependency_graph: "/opt/xos/synchronizers/${SERVICE_NAME}/model-deps"
+model_policies_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/model_policies"
+models_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/models"
+pull_steps_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/pull_steps"
+steps_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/steps"
+sys_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/sys"
+EOF
+
+echo "Creating test_config.yaml"
+cat << EOF > "xos/synchronizer/test_config.yaml"
+${LICENSE_TEXT}
+
+name: ${SERVICE_NAME}-testconfig
+accessor:
+  username: xosadmin@opencord.org
+  password: "sample"
+  kind: "testframework"
+logging:
+  version: 1
+  handlers:
+    console:
+      class: logging.StreamHandler
+  loggers:
+    'multistructlog':
+      handlers:
+          - console
+EOF
+
+echo "Creating synchronizer.py"
+cat << EOF > "xos/synchronizer/${SERVICE_NAME}-synchronizer.py"
+#!/usr/bin/env python
+
+${LICENSE_TEXT}
+
+"""
+${SERVICE_NAME}-synchronizer.py
+This is the main entrypoint for the synchronizer. It loads the config file, and
+then starts the synchronizer.
+"""
+
+import importlib
+import os
+import sys
+from xosconfig import Config
+
+config_file = os.path.abspath(os.path.dirname(
+    os.path.realpath(__file__)) + '/config.yaml')
+
+base_config_file = os.path.abspath(os.path.dirname(
+    os.path.realpath(__file__)) + '/config.yaml')
+mounted_config_file = os.path.abspath(os.path.dirname(
+    os.path.realpath(__file__)) + '/mounted_config.yaml')
+
+if os.path.isfile(mounted_config_file):
+    Config.init(base_config_file, 'synchronizer-config-schema.yaml',
+                mounted_config_file)
+else:
+    Config.init(base_config_file, 'synchronizer-config-schema.yaml')
+
+synchronizer_path = os.path.join(os.path.dirname(
+    os.path.realpath(__file__)), "../../synchronizers/new_base")
+
+sys.path.append(synchronizer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
+EOF
+
+chmod +x "xos/synchronizer/${SERVICE_NAME}-synchronizer.py"
+
+echo "Creating synchronizer Dockerfile"
+cat << EOF > Dockerfile.synchronizer
+${LICENSE_TEXT}
+
+# xosproject/${SERVICE_NAME}
+
+FROM xosproject/xos-synchronizer-base:2.0.0
+
+COPY xos/synchronizer /opt/xos/synchronizers/${SERVICE_NAME}
+COPY VERSION /opt/xos/synchronizers/${SERVICE_NAME}/
+
+ENTRYPOINT []
+
+WORKDIR "/opt/xos/synchronizers/${SERVICE_NAME}"
+
+# Label image
+ARG org_label_schema_schema_version=1.0
+ARG org_label_schema_name=${SERVICE_NAME}
+ARG org_label_schema_version=unknown
+ARG org_label_schema_vcs_url=unknown
+ARG org_label_schema_vcs_ref=unknown
+ARG org_label_schema_build_date=unknown
+ARG org_opencord_vcs_commit_date=unknown
+ARG org_opencord_component_chameleon_version=unknown
+ARG org_opencord_component_chameleon_vcs_url=unknown
+ARG org_opencord_component_chameleon_vcs_ref=unknown
+ARG org_opencord_component_xos_version=unknown
+ARG org_opencord_component_xos_vcs_url=unknown
+ARG org_opencord_component_xos_vcs_ref=unknown
+
+LABEL org.label-schema.schema-version=\$org_label_schema_schema_version \\
+      org.label-schema.name=\$org_label_schema_name \\
+      org.label-schema.version=\$org_label_schema_version \\
+      org.label-schema.vcs-url=\$org_label_schema_vcs_url \\
+      org.label-schema.vcs-ref=\$org_label_schema_vcs_ref \\
+      org.label-schema.build-date=\$org_label_schema_build_date \\
+      org.opencord.vcs-commit-date=\$org_opencord_vcs_commit_date \\
+      org.opencord.component.chameleon.version=\$org_opencord_component_chameleon_version \\
+      org.opencord.component.chameleon.vcs-url=\$org_opencord_component_chameleon_vcs_url \\
+      org.opencord.component.chameleon.vcs-ref=\$org_opencord_component_chameleon_vcs_ref \\
+      org.opencord.component.xos.version=\$org_opencord_component_xos_version \\
+      org.opencord.component.xos.vcs-url=\$org_opencord_component_xos_vcs_url \\
+      org.opencord.component.xos.vcs-ref=\$org_opencord_component_xos_vcs_ref
+
+CMD ["/usr/bin/python", "/opt/xos/synchronizers/${SERVICE_NAME}/${SERVICE_NAME}-synchronizer.py"]
+EOF
+
+echo "Creating sync step"
+cat << EOF > "xos/synchronizer/steps/sync_${SERVICE_NAME}_serviceinstance.py"
+${LICENSE_TEXT}
+
+from synchronizers.new_base.syncstep import SyncStep
+from synchronizers.new_base.modelaccessor import ${UC_SERVICE_NAME}ServiceInstance
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+log = create_logger(Config().get('logging'))
+
+
+class Sync${UC_SERVICE_NAME}ServiceInstance(SyncStep):
+    """
+    Sync${UC_SERVICE_NAME}ServiceInstance
+    Implements sync step for syncing ${UC_SERVICE_NAME} Services
+    """
+
+    provides = [${UC_SERVICE_NAME}ServiceInstance]
+    observes = [${UC_SERVICE_NAME}ServiceInstance]
+    requested_interval = 0
+
+    def sync_record(self, model):
+        log.info("Synchronizing ${UC_SERVICE_NAME}ServiceInstance",
+                 object=str(model))
+        # TODO: Implement sync step
+
+        # Verify that the name is not empty, used in tests
+        if model.name != "":
+            model.save()
+        else:
+            raise Exception("Empty names aren't allowed")
+
+    def delete_record(self, model):
+        log.info("Deleting ${UC_SERVICE_NAME}ServiceInstance",
+                 object=str(model))
+        # TODO: Implement delete step
+EOF
+
+echo "Creating test for synchronizer of service instance"
+cat << EOF > "xos/synchronizer/steps/test_sync_${SERVICE_NAME}_serviceinstance.py"
+${LICENSE_TEXT}
+
+import os
+import sys
+import unittest
+from mock import Mock
+
+# Hack to load synchronizer framework
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+xos_dir = os.path.join(test_path, "../../..")
+
+if not os.path.exists(os.path.join(test_path, "new_base")):
+    xos_dir = os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+    services_dir = os.path.join(xos_dir, "../../xos_services")
+
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+    name = os.path.join(service_name, "xos", xproto_name)
+    if os.path.exists(os.path.join(services_dir, name)):
+        return name
+    else:
+        name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+        if os.path.exists(os.path.join(services_dir, name)):
+            return name
+    raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+
+class TestSync${UC_SERVICE_NAME}ServiceInstance(unittest.TestCase):
+
+    def setUp(self):
+
+        self.sys_path_save = sys.path
+        sys.path.append(xos_dir)
+        sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+        # Setting up the config module
+        from xosconfig import Config
+        config = os.path.join(test_path, "../test_config.yaml")
+        Config.clear()
+        Config.init(config, "synchronizer-config-schema.yaml")
+        # END Setting up the config module
+
+        from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+        build_mock_modelaccessor(xos_dir, services_dir, [
+            get_models_fn("${SERVICE_NAME}", "${SERVICE_NAME}.xproto"),
+        ])
+
+        from synchronizers.new_base.modelaccessor import model_accessor
+        from sync_${SERVICE_NAME}_serviceinstance import Sync${UC_SERVICE_NAME}ServiceInstance
+        # import all class names to globals
+        for (k, v) in model_accessor.all_model_classes.items():
+            globals()[k] = v
+
+        self.sync_step = Sync${UC_SERVICE_NAME}ServiceInstance
+
+        # create a mock instance instance
+        self.model = Mock()
+        self.model.name = "Example"
+
+    def tearDown(self):
+        self.model = None
+        sys.path = self.sys_path_save
+
+    # TODO - The following two tests are very simple, replace with more meaningful ones
+
+    def test_save(self):
+        # Tests that the model can be saved
+
+        self.model.name = "${SERVICE_NAME}_test"
+        self.sync_step().sync_record(self.model)
+        self.model.save.assert_called()
+
+    def test_sync_rejected(self):
+        # Tests that an empty name raises an exception
+
+        self.model.name = ""
+        with self.assertRaises(Exception):
+            self.sync_step().sync_record(self.model)
+
+
+if __name__ == '__main__':
+    unittest.main()
+EOF
+
+echo "Creating model policy for instance"
+cat << EOF > "xos/synchronizer/model_policies/model_policy_${SERVICE_NAME}_serviceinstance.py"
+${LICENSE_TEXT}
+
+from synchronizers.new_base.policy import Policy
+
+
+class ${UC_SERVICE_NAME}ServiceInstancePolicy(Policy):
+    """
+    ${UC_SERVICE_NAME}ServiceInstancePolicy
+    Implements model policy for ${UC_SERVICE_NAME}Instance
+    """
+
+    model_name = "${UC_SERVICE_NAME}ServiceInstance"
+
+    def handle_create(self, si):
+        self.logger.debug(
+            "MODEL_POLICY: enter handle_create for ${UC_SERVICE_NAME}ServiceInstance %s" %
+            si.id)
+        self.handle_update(si)
+        # TODO: Implement creation policy, if it differs from update policy
+
+    def handle_update(self, si):
+        self.logger.debug(
+            "MODEL_POLICY: enter handle_update for ${UC_SERVICE_NAME}ServiceInstance %s, valid=%s" %
+            (si.id, si.valid))
+
+        if (si.backend_code != 1):
+            raise Exception(
+                "MODEL_POLICY: ${UC_SERVICE_NAME}ServiceInstance %s has not been synced yet" %
+                si.id)
+
+        # TODO: Implement update policy
+
+    def handle_delete(self, si):
+        self.logger.debug(
+            "MODEL_POLICY: enter handle_delete for ${UC_SERVICE_NAME}ServiceInstance %s" %
+            si.id)
+        # TODO: Implement delete policy
+EOF
+
+echo "Creating test for model policy of service instance"
+cat << EOF > "xos/synchronizer/model_policies/test_model_policy_${SERVICE_NAME}_serviceinstance.py"
+${LICENSE_TEXT}
+
+import os
+import sys
+import unittest
+from mock import Mock
+
+# Hack to load synchronizer framework
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+xos_dir = os.path.join(test_path, "../../..")
+
+if not os.path.exists(os.path.join(test_path, "new_base")):
+    xos_dir = os.path.join(test_path, "../../../../../../orchestration/xos/xos")
+    services_dir = os.path.join(xos_dir, "../../xos_services")
+
+sys.path.append(xos_dir)
+sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+# END Hack to load synchronizer framework
+
+
+# generate model from xproto
+def get_models_fn(service_name, xproto_name):
+    name = os.path.join(service_name, "xos", xproto_name)
+    if os.path.exists(os.path.join(services_dir, name)):
+        return name
+    else:
+        name = os.path.join(service_name, "xos", "synchronizer", "models", xproto_name)
+        if os.path.exists(os.path.join(services_dir, name)):
+            return name
+    raise Exception("Unable to find service=%s xproto=%s" % (service_name, xproto_name))
+# END generate model from xproto
+
+
+class TestModelPolicy${UC_SERVICE_NAME}ServiceInstance(unittest.TestCase):
+
+    def setUp(self):
+
+        self.sys_path_save = sys.path
+        sys.path.append(xos_dir)
+        sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
+
+        # Setting up the config module
+        from xosconfig import Config
+        config = os.path.join(test_path, "../test_config.yaml")
+        Config.clear()
+        Config.init(config, "synchronizer-config-schema.yaml")
+        # END Setting up the config module
+
+        from synchronizers.new_base.mock_modelaccessor_build import build_mock_modelaccessor
+        build_mock_modelaccessor(xos_dir, services_dir, [
+            get_models_fn("${SERVICE_NAME}", "${SERVICE_NAME}.xproto"),
+        ])
+
+        from synchronizers.new_base.modelaccessor import model_accessor
+        from model_policy_${SERVICE_NAME}_serviceinstance import ${UC_SERVICE_NAME}ServiceInstancePolicy
+        # import all class names to globals
+        for (k, v) in model_accessor.all_model_classes.items():
+            globals()[k] = v
+
+        # Some of the functions we call have side-effects, reset the world.
+        model_accessor.reset_all_object_stores()
+
+        self.policy = ${UC_SERVICE_NAME}ServiceInstancePolicy()
+        self.si = Mock()
+
+    def tearDown(self):
+        sys.path = self.sys_path_save
+        self.si = None
+
+    def test_not_synced(self):
+        self.si.valid = "awaiting"
+        self.si.backend_code = 0
+
+        with self.assertRaises(Exception) as e:
+            self.policy.handle_update(self.si)
+
+        self.assertIn("has not been synced yet", e.exception.message)
+
+    def test_skip_update(self):
+        self.si.valid = "awaiting"
+        self.si.backend_code = 1
+
+        self.policy.handle_update(self.si)
+
+
+if __name__ == '__main__':
+    unittest.main()
+EOF
+
+
+echo "Creating helm chart for service"
+cat << EOF > "helm-charts/${SERVICE_NAME}/Chart.yaml"
+---
+${LICENSE_TEXT}
+
+name: ${SERVICE_NAME}
+version: ${SERVICE_VERSION}
+EOF
+
+cat << EOF > "helm-charts/${SERVICE_NAME}/values.yaml"
+---
+${LICENSE_TEXT}
+
+nameOverride: ""
+fullnameOverride: ""
+
+${SERVICE_NAME}_synchronizerImage: "xosproject/${SERVICE_NAME}-synchronizer:{{ .Chart.Version }}"
+
+imagePullPolicy: 'IfNotPresent'
+
+xosAdminUser: "admin@opencord.org"
+xosAdminPassword: "letmein"
+
+affinity: {}
+nodeSelector: {}
+replicaCount: 1
+resources: {}
+tolerations: []
+EOF
+
+cat << EOF > "helm-charts/${SERVICE_NAME}/templates/_helpers.tpl"
+{{/* vim: set filetype=mustache: */}}
+${TPL_LICENSE_TEXT}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "${SERVICE_NAME}.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "${SERVICE_NAME}.fullname" -}}
+{{- if .Values.fullnameOverride -}}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- \$name := default .Chart.Name .Values.nameOverride -}}
+{{- if contains \$name .Release.Name -}}
+{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- printf "%s-%s" .Release.Name \$name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "${SERVICE_NAME}.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "${SERVICE_NAME}.serviceConfig" -}}
+name: ${SERVICE_NAME}
+accessor:
+  username: {{ .Values.xosAdminUser | quote }}
+  password: {{ .Values.xosAdminPassword | quote }}
+  endpoint: xos-core:50051
+required_models:
+  - ${UC_SERVICE_NAME}Service
+  - ${UC_SERVICE_NAME}ServiceInstance
+dependency_graph: "/opt/xos/synchronizers/${SERVICE_NAME}/model-deps"
+model_policies_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/model_policies"
+models_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/models"
+steps_dir: "/opt/xos/synchronizers/${SERVICE_NAME}/steps"
+logging:
+  version: 1
+  handlers:
+    console:
+      class: logging.StreamHandler
+    file:
+      class: logging.handlers.RotatingFileHandler
+      filename: /var/log/xos.log
+      maxBytes: 10485760
+      backupCount: 5
+  loggers:
+    'multistructlog':
+      handlers:
+          - console
+          - file
+      level: DEBUG
+{{- end -}}
+EOF
+
+cat << EOF > "helm-charts/${SERVICE_NAME}/templates/_tosca.tpl"
+{{/* vim: set filetype=mustache: */}}
+${TPL_LICENSE_TEXT}
+{{- define "${SERVICE_NAME}.serviceTosca" -}}
+tosca_definitions_version: tosca_simple_yaml_1_0
+description: Set up ${SERVICE_NAME} service
+imports:
+  - custom_types/${SERVICE_NAME}service.yaml
+
+topology_template:
+  node_templates:
+    service#${SERVICE_NAME}:
+      type: tosca.nodes.${UC_SERVICE_NAME}Service
+      properties:
+        name: ${SERVICE_NAME}
+        kind: ${UC_SERVICE_NAME}
+{{- end -}}
+EOF
+
+cat << EOF > "helm-charts/${SERVICE_NAME}/templates/configmap.yaml"
+---
+${LICENSE_TEXT}
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: ${SERVICE_NAME}
+data:
+  serviceConfig: |
+{{ include "${SERVICE_NAME}.serviceConfig" . | indent 4 }}
+EOF
+
+cat << EOF > "helm-charts/${SERVICE_NAME}/templates/deployment.yaml"
+---
+${LICENSE_TEXT}
+
+apiVersion: apps/v1beta2
+kind: Deployment
+metadata:
+  name: {{ template "${SERVICE_NAME}.fullname" . }}
+  labels:
+    app: {{ template "${SERVICE_NAME}.name" . }}
+    chart: {{ template "${SERVICE_NAME}.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      app: {{ template "${SERVICE_NAME}.name" . }}
+      release: {{ .Release.Name }}
+  template:
+    metadata:
+      labels:
+        app: {{ template "${SERVICE_NAME}.name" . }}
+        release: {{ .Release.Name }}
+      annotations:
+        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+    spec:
+      containers:
+        - name: {{ .Chart.Name }}
+          image: {{ tpl .Values.${SERVICE_NAME}_synchronizerImage . | quote }}
+          imagePullPolicy: {{ .Values.imagePullPolicy }}
+          resources:
+{{ toYaml .Values.resources | indent 12 }}
+          volumeMounts:
+            - name: ${SERVICE_NAME}-config
+              mountPath: /opt/xos/synchronizers/${SERVICE_NAME}/config.yaml
+              subPath: config.yaml
+            - name: certchain-volume
+              mountPath: /usr/local/share/ca-certificates/local_certs.crt
+              subPath: config/ca_cert_chain.pem
+      volumes:
+        - name: ${SERVICE_NAME}-config
+          configMap:
+            name: ${SERVICE_NAME}
+            items:
+              - key: serviceConfig
+                path: config.yaml
+        - name: certchain-volume
+          configMap:
+            name: ca-certificates
+            items:
+              - key: chain
+                path: config/ca_cert_chain.pem
+    {{- with .Values.nodeSelector }}
+      nodeSelector:
+{{ toYaml . | indent 8 }}
+    {{- end }}
+    {{- with .Values.affinity }}
+      affinity:
+{{ toYaml . | indent 8 }}
+    {{- end }}
+    {{- with .Values.tolerations }}
+      tolerations:
+{{ toYaml . | indent 8 }}
+    {{- end }}
+EOF
+
+echo 'Done! Check the newly created service repo with service_lint.sh'