GoogleCloudPlatform / GoogleCloudPlatform/deploymentmanager-samples
Error 'References on type must be of type String' when referencing template's output
- Dominant language
- Jinja
- Stars
- 951
- Forks
- 700
- PR merge metrics
- No merged PRs in 30d
Description
I'm trying to spawn a k8s-backed service from scratch using deployment manager. The deployment is divided into two templates, `hello-word.py` and `cluster.py`, with one root `all-in-one.yaml` config importing both. `hello-world.py` is dependent on `cluster.py`'s output. My original intention was to inject the depedencies through properties declared on `all-in-one.yaml`, but that didn't work. As per the suggestion in #103, I've decided to reference the needed outputs directly in the `hello-world.py` template, but that also failed. This is the error I'm getting, for each of the attempted refs:
```
The reference '' is invalid, reason:
References on type must be of type String
```
Am I doing something wrong ? More generally, how could I inject `cluster.py` outputs into `hello-world.py`'s scope?
Below is the code for my deployment. The snippet that actually references the output is at the beginning of `hello-world.py`'s `generate_config` function.
`issued command in deployment root folder`:
`gcloud deployment-manager deployments create all-in-one --config all-in-one-1/all-in-one.yaml`
`all-in-one.yaml`:
```
imports:
- path: templates/cluster.py
name: cluster.py
- path: templates/hello-world.py
name: hello-world.py
resources:
- name: example-cluster
type: cluster.py
properties:
description: "Example Cluster"
zones:
- europe-west1-b
- europe-west1-c
initialNodeCount: 1
- name: hello-world
type: hello-world.py
metadata:
dependsOn:
- example-cluster
properties:
name: "My service"
```
`templates/cluster.py`:
```
def generate_k8s_api_endpoint_output(type_suffix, endpoint, context):
return {
'name': 'clusterType' + type_suffix,
'value': context.env['name'] + '-type' + type_suffix
}
def generate_k8s_api_endpoint_resource(type_suffix, endpoint, context):
return {
'name': context.env['name'] + "-type" + type_suffix,
'type': 'deploymentmanager.v2beta.typeProvider',
'properties': {
'options': {
'validationOptions': {
'schemaValidation': 'IGNORE_WITH_WARNINGS',
},
'inputMappings': [
{
'fieldName': 'name',
'location': 'PATH',
'methodMatch': '^(GET|DELETE|PUT)$',
'value': '$.ifNull('
'$.resource.properties.metadata.name, '
'$.resource.name)'
},
{
'fieldName': 'metadata.name',
'location': 'BODY',
'methodMatch': '^(PUT|POST)$',
'value': '$.ifNull('
'$.resource.properties.metadata.name, '
'$.resource.name)'
},
{
'fieldName': 'Authorization',
'location': 'HEADER',
'value': '$.concat("Bearer ",'
'$.googleOauth2AccessToken())'
}
]
},
'descriptorUrl': ''.join([
'https://' + '$(ref.' + context.env['name'] + '.endpoint)' + '/swaggerapi/',
endpoint
])
},
}
def generate_config(context):
K8S_ENDPOINTS = {
'': 'api/v1',
'-apps': 'apis/apps/v1',
'-rbac': 'apis/rbac.authorization.k8s.io/v1',
'-v1beta1-extensions': 'apis/extensions/v1beta1'
}
outputs = []
resources = [{
'name': context.env['name'],
'type': 'container.v1.cluster',
'properties': {
'zone': context.properties['zones'][0],
'cluster': {
'description': context.properties['description'],
'locations': context.properties['zones'],
'nodePools': [{
'name': context.env['name'],
'initialNodeCount': 1,
'config': {
'machineType': 'n1-standard-1',
'oauthScopes': [
'https://www.googleapis.com/auth/' + scope
for scope in [
'compute',
'devstorage.read_only',
'logging.write',
'monitoring'
]
]
},
'management': {
'autoUpgrade': True,
'autoRepair': False
}
}]
}
}
}]
for type_suffix, endpoint in K8S_ENDPOINTS.iteritems():
resources.append(generate_k8s_api_endpoint_resource(
type_suffix,
endpoint,
context
))
outputs.append(generate_k8s_api_endpoint_output(
type_suffix,
endpoint,
context
))
return {
'resources': resources,
'outputs': outputs
}
```
`templates/cluster.py.schema`:
```
info:
title: Cluster Template
author: Bernardo Amorim
description: Cria um novo cluster k8s
version: 1.0
properties:
description:
type: string
description: Description of the cluster
initialNodeCount:
type: number
description: Number of nodes in cluster
zones:
type: array
items:
type: string
description: Zones into which cluster will be deployed
outputs:
clusterType:
type: string
description: Root cluster type
clusterType-rbac:
type: string
description: rbac cluster type
clusterType-v1beta1-extensions:
type: string
description: v1beta1 cluster type
clusterType-apps:
type: string
description: apps cluster type
```
`templates/hello-world.py`:
```
def value_or_default(dct, key, default):
return dct[key] if (key in dct) else default
def generate_hello_world_deployment_resource(
name_prefix,
cluster_type_apps,
deployment_collection,
context
):
namespace = value_or_default(context.properties, 'namespace', 'default')
replicas = value_or_default(context.properties, 'replicas', '2')
return {
'name': name_prefix,
'type': cluster_type_apps + ':' + deployment_collection,
'properties': {
'apiVersion': 'apps/v1',
'kind': 'Deployment',
'namespace': namespace,
'metadata': {
'labels': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
},
'spec': {
'replicas': replicas,
'selector': {
'matchLabels': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
},
'template': {
'metadata': {
'labels': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
},
'spec': {
'containers': [
{
'image': 'gcr.io/prefab-envoy-243718/min-app:four',
'name': 'hello-server',
'ports': [
{
'containerPort': '4000',
'protocol': 'TCP'
}
]
}
]
}
}
}
}
}
def generate_hello_world_service_resource(
name_prefix,
cluster_type,
service_collection,
context
):
namespace = value_or_default(context.properties, 'namespace', 'default')
return {
'name': name_prefix + '-svc',
'type': cluster_type + ':' + service_collection,
'properties': {
'apiVersion': 'v1',
'kind': 'Service',
'namespace': namespace,
'metadata': {
'name': name_prefix + '-svc',
'labels': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
},
'spec': {
'type': 'NodePort',
'ports': [
{
'port': '4000',
'targetPort': '4000'
}
],
'selector': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
}
}
}
def generate_hello_world_ingress_resource(
name_prefix,
cluster_type_beta,
ingress_collection,
context
):
namespace = value_or_default(context.properties, 'namespace', 'default')
return {
'name': name_prefix + '-ingress',
'type': cluster_type_beta + ':' + ingress_collection,
'properties': {
'apiVersion': 'extensions/v1beta1',
'kind': 'Ingress',
'namespace': namespace,
'metadata': {
'name': name_prefix,
'labels': {
'app': context.env['name'],
'deployment': context.env['deployment']
}
},
'spec': {
'backend': {
'serviceName': name_prefix + '-svc',
'servicePort': '4000'
}
}
}
}
pass
def generate_config(context):
NAME_PREFIX = context.env['deployment']
CLUSTER_TYPE = ''.join([
context.env['project'] + '/',
'$(ref.example-cluster.clusterType)'
])
CLUSTER_TYPE_APPS = ''.join([
context.env['project'] + '/',
'$(ref.example-cluster.clusterType-apps)'
])
CLUSTER_TYPE_BETA = ''.join([
context.env['project'] + '/',
'$(ref.example-cluster.clusterType-v1beta1-extensions)'
])
DEPLOYMENT_COLLECTION = '/apis/apps/v1/namespaces/{namespace}/deployments/deployments'
INGRESS_COLLECTION = '/apis/extensions/v1beta1/namespaces/{namespace}/ingresses'
SERVICE_COLLECTION = '/api/v1/namespaces/{namespace}/services'
resources = []
resources.append(
generate_hello_world_deployment_resource(
NAME_PREFIX,
CLUSTER_TYPE_APPS,
DEPLOYMENT_COLLECTION,
context
)
)
resources.append(
generate_hello_world_service_resource(
NAME_PREFIX,
CLUSTER_TYPE,
SERVICE_COLLECTION,
context
)
)
resources.append(
generate_hello_world_ingress_resource(
NAME_PREFIX,
CLUSTER_TYPE_BETA,
INGRESS_COLLECTION,
context
)
)
return {
'resources': resources
}
```
Contributor guide
Assessment
This issue has not been assessed yet.