Merge remote-tracking branch 'upstream/master'

pull/71/head^2
Robert Leenders 10 years ago
commit e4fb3cc0e4

@ -0,0 +1,14 @@
sudo: true
language: go
go:
- 1.4
install:
- sudo pip install jinja2
- sudo pip install pyyaml
- sudo pip install jsonschema
script:
- make test

@ -2,26 +2,28 @@
[![Go Report Card](http://goreportcard.com/badge/kubernetes/deployment-manager)](http://goreportcard.com/report/kubernetes/deployment-manager) [![Go Report Card](http://goreportcard.com/badge/kubernetes/deployment-manager)](http://goreportcard.com/report/kubernetes/deployment-manager)
Deployment Manager (DM) provides parameterized templates for Kubernetes clusters. Deployment Manager (DM) provides parameterized templates for Kubernetes resources,
such as:
You can use it deploy ready-to-use types, such as: * [Replicated Service](templates/replicatedservice/v1)
* [Replicated Service](types/replicatedservice/v1) * [Redis](templates/redis/v1)
* [Redis](types/redis/v1)
Types live in ordinary Github repositories. This repository is a DM type registry. Templates live in ordinary Github repositories called template registries. This
Github repository contains a template registry, as well as the DM source code.
You can also use DM to deploy simple templates that use types, such as: You can use DM to deploy simple configurations that use templates, such as:
* [Guestbook](examples/guestbook/guestbook.yaml) * [Guestbook](examples/guestbook/guestbook.yaml)
* [Deployment Manager](examples/bootstrap/bootstrap.yaml) * [Deployment Manager](examples/bootstrap/bootstrap.yaml)
A template is just a `YAML` file that supplies parameters. (Yes, you're reading A configuration is just a `YAML` file that supplies parameters. (Yes,
that second example correctly. It uses DM to deploy itself. you're reading that second example correctly. It uses DM to deploy itself. See
See [examples/bootstrap/README.md](examples/bootstrap/README.md) for more information.) [examples/bootstrap/README.md](examples/bootstrap/README.md) for more information.)
DM runs server side, in your Kubernetes cluster, so it can tell you what types DM runs server side, in your Kubernetes cluster, so it can tell you what types
you've instantiated there, what instances you've created of a given type, and even you've instantiated there, including both primitive types and templates, what
how the instances are organized. So, you can ask questions like: instances you've created of a given type, and even how the instances are organized.
So, you can ask questions like:
* What Redis instances are running in this cluster? * What Redis instances are running in this cluster?
* What Redis master and slave services are part of this Redis instance? * What Redis master and slave services are part of this Redis instance?
@ -30,10 +32,12 @@ how the instances are organized. So, you can ask questions like:
Because DM stores its state in the cluster, not on your workstation, you can ask Because DM stores its state in the cluster, not on your workstation, you can ask
those questions from any client at any time. those questions from any client at any time.
For more information about types, including both primitive types and templates,
see the [design document](../design/design.md#types).
Please hang out with us in Please hang out with us in
[the Slack chat room](https://kubernetes.slack.com/messages/sig-configuration/) [the Slack chat room](https://kubernetes.slack.com/messages/sig-configuration/)
and/or and/or [the Google Group](https://groups.google.com/forum/#!forum/kubernetes-sig-config)
[the Google Group](https://groups.google.com/forum/#!forum/kubernetes-sig-config)
for the Kubernetes configuration SIG. Your feedback and contributions are welcome. for the Kubernetes configuration SIG. Your feedback and contributions are welcome.
## Installing Deployment Manager ## Installing Deployment Manager
@ -43,11 +47,10 @@ Follow these 3 steps to install DM:
1. Make sure your Kubernetes cluster is up and running, and that you can run 1. Make sure your Kubernetes cluster is up and running, and that you can run
`kubectl` commands against it. `kubectl` commands against it.
1. Clone this repository into the src folder of your GOPATH, if you haven't already. 1. Clone this repository into the src folder of your GOPATH, if you haven't already.
1. Use `kubectl` to install DM into your cluster: See the [Kubernetes developer documentation](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md)
for information on how to setup Go and use the repository.
``` 1. Use `kubectl` to install DM into your cluster `kubectl create -f
kubectl create -f install.yaml install.yaml`
```
That's it. You can now use `kubectl` to see DM running in your cluster: That's it. You can now use `kubectl` to see DM running in your cluster:
@ -59,38 +62,29 @@ If you see expandybird-service, manager-service, resourcifier-service, and
expandybird-rc, manager-rc and resourcifier-rc with pods that are READY, then DM expandybird-rc, manager-rc and resourcifier-rc with pods that are READY, then DM
is up and running! is up and running!
The easiest way to interact with Deployment Manager is through `kubectl` proxy: ## Using Deployment Manager
### Setting up the client
``` The easiest way to interact with Deployment Manager is through the `dm` tool
kubectl proxy --port=8001 & hitting a `kubectl` proxy. To set that up:
```
This command starts a proxy that lets you interact with the Kubernetes api 1. Build the tool by running `make` in the deployment-manager repository.
server through port 8001 on localhost. `dm` uses 1. Run `kubectl proxy --port=8001 &` to start a proxy that lets you interact
with the Kubernetes API server through port 8001 on localhost. `dm` uses
`http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager` `http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager`
as the default service address for DM. as the default service address for DM.
## Using Deployment Manager ### Using the client
You can use `dm` to deploy a type from the command line. This command deploys a
redis cluster with two workers from the type definition in this repository:
``` The DM client, `dm`, can deploy configurations from the command line. It can also
dm deploy redis/v1 pull templates from a template registry, generate configurations from them using
``` parameters supplied on the command line, and deploy the resulting configurations.
When you deploy a type, you can optionally supply values for input parameters,
like this:
```
dm --properties workers=3 deploy redis/v1
```
When you deploy a type, `dm` generates a template from the type and input #### Deploying a configuration
parameters, and then deploys it.
You can also deploy an existing template, or read one from `stdin`. This command `dm` can deploy a configuration from a file, or read one from `stdin`. This
deploys the canonical Guestbook example from the examples directory: command deploys the canonical Guestbook example from the examples directory:
``` ```
dm deploy examples/guestbook/guestbook.yaml dm deploy examples/guestbook/guestbook.yaml
@ -108,68 +102,75 @@ to see the guestbook in action.
For more information about this example, see [examples/guestbook/README.md](examples/guestbook/README.md) For more information about this example, see [examples/guestbook/README.md](examples/guestbook/README.md)
## Additional commands #### Deploying a template directly
The command line tool makes it easy to configure a cluster from a set of predefined You can also deploy a template directly, without a configuration. This command
types. Here's a list of available commands: deploys a redis cluster with two workers from the redis template in this repository:
``` ```
expand Expands the supplied template(s) dm deploy redis:v1
deploy Deploys the supplied type or template(s) ```
list Lists the deployments in the cluster
get Retrieves the supplied deployment
delete Deletes the supplied deployment
update Updates a deployment using the supplied template(s)
deployed-types Lists the types deployed in the cluster
deployed-instances Lists the instances of the supplied type deployed in the cluster
types Lists the types in the current registry
describe Describes the supplied type in the current registry
You can optionally supply values for template parameters on the command line,
like this:
```
dm --properties workers=3 deploy redis:v1
``` ```
## Uninstalling Deployment Manager When you deploy a template directly, without a configuration, `dm` generates a
configuration from the template and any supplied parameters, and then deploys the
generated configuration.
For more information about deploying templates from a template registry or adding
types to a template registry, see [the template registry documentation](docs/templates/registry.md).
You can uninstall Deployment Manager using the same configuration file: ### Additional commands
`dm` makes it easy to configure a cluster from a set of predefined templates.
Here's a list of available `dm` commands:
``` ```
kubectl delete -f install.yaml expand Expands the supplied configuration(s)
deploy Deploys the named template or the supplied configuration(s)
list Lists the deployments in the cluster
get Retrieves the named deployment
delete Deletes the named deployment
update Updates a deployment using the supplied configuration(s)
deployed-types Lists the types deployed in the cluster
deployed-instances Lists the instances of the named type deployed in the cluster
templates Lists the templates in a given template registry
describe Describes the named template in a given template registry
``` ```
## Creating a type registry ## Uninstalling Deployment Manager
All you need to create a type registry is a Github repository with top level file
named `registry.yaml`, and a top level folder named `types` that contains type definitions.
A type definition is just a folder that contains one or more versions, like `/v1`, You can uninstall Deployment Manager using the same configuration:
`/v2`, etc.
A version is just a folder that contains a type definition. As you can see from the ```
examples above, a type definition is just a Python or [Jinja](http://jinja.pocoo.org/) kubectl delete -f install.yaml
file plus an optional schema. ```
## Building the container images ## Building the Container Images
This project runs Deployment Manager on Kubernetes as three replicated services. This project runs Deployment Manager on Kubernetes as three replicated services.
By default, install.yaml uses prebuilt images stored in Google Container Registry By default, install.yaml uses prebuilt images stored in Google Container Registry
to install them. However, you can build your own container images and push them to install them. However, you can build your own container images and push them
to your own project in the Google Container Registry: to your own project in the Google Container Registry:
1. Set the environment variable PROJECT to the name of a project known to gcloud. 1. Set the environment variable `PROJECT` to the name of a project known to
1. Run the following command: GCloud.
1. Run `make push`
```
make push
```
## Design of Deployment Manager ## Design of Deployment Manager
There is a more detailed [design document](docs/design/design.md) available. There is a more detailed [design document](docs/design/design.md) available.
## Status of the project ## Status of the Project
This project is still under active development, so you might run into issues. If This project is still under active development, so you might run into issues. If
you do, please don't be shy about letting us know, or better yet, contribute a you do, please don't be shy about letting us know, or better yet, contribute a
fix or feature. We use the same [development process](CONTRIBUTING.md) as the main fix or feature. We use the same [development process](CONTRIBUTING.md) as the main
Kubernetes repository. Kubernetes repository.
## Relationship to Google Cloud Platform ## Relationship to Google Cloud Platform
@ -177,5 +178,3 @@ DM uses the same concepts and languages as
[Google Cloud Deployment Manager](https://cloud.google.com/deployment-manager/overview), [Google Cloud Deployment Manager](https://cloud.google.com/deployment-manager/overview),
but creates resources in Kubernetes clusters, not in Google Cloud Platform projects. but creates resources in Kubernetes clusters, not in Google Cloud Platform projects.

@ -36,29 +36,28 @@ import (
) )
var ( var (
// TODO(jackgr): Implement reading a template from stdin stdin = flag.Bool("stdin", false, "Reads a configuration from the standard input")
//stdin = flag.Bool("stdin", false, "Reads a template from the standard input") properties = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)")
properties = flag.String("properties", "", "Properties to use when deploying a type (e.g., --properties k1=v1,k2=v2)") template_registry = flag.String("registry", "kubernetes/deployment-manager/templates", "Github based template registry (owner/repo[/path])")
type_registry = flag.String("registry", "kubernetes/deployment-manager", "Github based type registry [owner/repo]") service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager", "URL for deployment manager")
service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/default/services/manager-service:manager", "URL for deployment manager") binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
) )
var commands = []string{ var commands = []string{
"expand \t\t\t Expands the supplied template(s)", "expand \t\t\t Expands the supplied configuration(s)",
"deploy \t\t\t Deploys the supplied type or template(s)", "deploy \t\t\t Deploys the named template or the supplied configuration(s)",
"list \t\t\t Lists the deployments in the cluster", "list \t\t\t Lists the deployments in the cluster",
"get \t\t\t Retrieves the supplied deployment", "get \t\t\t Retrieves the supplied deployment",
"delete \t\t\t Deletes the supplied deployment", "delete \t\t\t Deletes the supplied deployment",
"update \t\t\t Updates a deployment using the supplied template(s)", "update \t\t\t Updates a deployment using the supplied configuration(s)",
"deployed-types \t\t Lists the types deployed in the cluster", "deployed-types \t\t Lists the types deployed in the cluster",
"deployed-instances \t Lists the instances of the supplied type deployed in the cluster", "deployed-instances \t Lists the instances of the named type deployed in the cluster",
"types \t\t\t Lists the types in the current registry", "templates \t\t Lists the templates in a given template registry",
"describe \t\t Describes the supplied type in the current registry", "describe \t\t Describes the named template in a given template registry",
} }
var usage = func() { var usage = func() {
message := "Usage: %s [<flags>] <command> (<type-name> | <deployment-name> | (<template> [<import1>...<importN>]))\n" message := "Usage: %s [<flags>] <command> (<template-name> | <deployment-name> | (<configuration> [<import1>...<importN>]))\n"
fmt.Fprintf(os.Stderr, message, os.Args[0]) fmt.Fprintf(os.Stderr, message, os.Args[0])
fmt.Fprintln(os.Stderr, "Commands:") fmt.Fprintln(os.Stderr, "Commands:")
for _, command := range commands { for _, command := range commands {
@ -73,12 +72,17 @@ var usage = func() {
} }
func getGitRegistry() *registry.GithubRegistry { func getGitRegistry() *registry.GithubRegistry {
s := strings.Split(*type_registry, "/") s := strings.Split(*template_registry, "/")
if len(s) != 2 { if len(s) < 2 {
log.Fatalf("invalid type registry: %s", type_registry) log.Fatalf("invalid template registry: %s", *template_registry)
} }
return registry.NewGithubRegistry(s[0], s[1]) var path = ""
if len(s) > 2 {
path = strings.Join(s[2:], "/")
}
return registry.NewGithubRegistry(s[0], s[1], path)
} }
func main() { func main() {
@ -89,27 +93,32 @@ func main() {
usage() usage()
} }
if *stdin {
fmt.Printf("reading from stdin is not yet implemented")
os.Exit(0)
}
command := args[0] command := args[0]
switch command { switch command {
case "types": case "templates":
git := getGitRegistry() git := getGitRegistry()
types, err := git.List() templates, err := git.List()
if err != nil { if err != nil {
log.Fatalf("Cannot list %v err") log.Fatalf("Cannot list %v", err)
} }
fmt.Printf("Types:") fmt.Printf("Templates:\n")
for _, t := range types { for _, t := range templates {
fmt.Printf("%s:%s", t.Name, t.Version) fmt.Printf("%s:%s\n", t.Name, t.Version)
downloadURL, err := git.GetURL(t) downloadURL, err := git.GetURL(t)
if err != nil { if err != nil {
log.Printf("Failed to get download URL for type %s:%s", t.Name, t.Version) log.Printf("Failed to get download URL for template %s:%s", t.Name, t.Version)
} }
fmt.Printf("\tdownload URL: %s", downloadURL) fmt.Printf("\tdownload URL: %s\n", downloadURL)
} }
case "describe": case "describe":
fmt.Printf("this feature is not yet implemented") fmt.Printf("the describe feature is not yet implemented")
case "expand": case "expand":
backend := expander.NewExpander(*binary) backend := expander.NewExpander(*binary)
template := loadTemplate(args) template := loadTemplate(args)
@ -121,7 +130,7 @@ func main() {
fmt.Println(output) fmt.Println(output)
case "deploy": case "deploy":
template := loadTemplate(args) template := loadTemplate(args)
action := fmt.Sprintf("deploy template named %s", template.Name) action := fmt.Sprintf("deploy configuration named %s", template.Name)
callService("deployments", "POST", action, marshalTemplate(template)) callService("deployments", "POST", action, marshalTemplate(template))
case "list": case "list":
callService("deployments", "GET", "list deployments", nil) callService("deployments", "GET", "list deployments", nil)
@ -149,8 +158,7 @@ func main() {
action := fmt.Sprintf("delete deployment named %s", template.Name) action := fmt.Sprintf("delete deployment named %s", template.Name)
callService(path, "PUT", action, marshalTemplate(template)) callService(path, "PUT", action, marshalTemplate(template))
case "deployed-types": case "deployed-types":
action := fmt.Sprintf("list types in registry %s", *type_registry) callService("types", "GET", "list deployed types", nil)
callService("types", "GET", action, nil)
case "deployed-instances": case "deployed-instances":
if len(args) < 2 { if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No type name supplied") fmt.Fprintln(os.Stderr, "No type name supplied")
@ -158,7 +166,7 @@ func main() {
} }
path := fmt.Sprintf("types/%s/instances", url.QueryEscape(args[1])) path := fmt.Sprintf("types/%s/instances", url.QueryEscape(args[1]))
action := fmt.Sprintf("list instances of type %s in registry %s", args[1], *type_registry) action := fmt.Sprintf("list deployed instances of type %s", args[1])
callService(path, "GET", action, nil) callService(path, "GET", action, nil)
default: default:
usage() usage()
@ -193,7 +201,7 @@ func loadTemplate(args []string) *expander.Template {
var template *expander.Template var template *expander.Template
var err error var err error
if len(args) < 2 { if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No type name or template file(s) supplied") fmt.Fprintln(os.Stderr, "No template name or configuration(s) supplied")
usage() usage()
} }
@ -208,7 +216,7 @@ func loadTemplate(args []string) *expander.Template {
} }
if err != nil { if err != nil {
log.Fatalf("cannot create template from supplied arguments: %s\n", err) log.Fatalf("cannot create configuration from supplied arguments: %s\n", err)
} }
return template return template
@ -275,7 +283,7 @@ func buildTemplateFromType(name string, t registry.Type) *expander.Template {
func marshalTemplate(template *expander.Template) io.ReadCloser { func marshalTemplate(template *expander.Template) io.ReadCloser {
j, err := json.Marshal(template) j, err := json.Marshal(template)
if err != nil { if err != nil {
log.Fatalf("cannot deploy template %s: %s\n", template.Name, err) log.Fatalf("cannot deploy configuration %s: %s\n", template.Name, err)
} }
return ioutil.NopCloser(bytes.NewReader(j)) return ioutil.NopCloser(bytes.NewReader(j))

@ -1,25 +1,24 @@
# Deployment Manager Design # Deployment Manager Design
## Overview ## Overview
Deployment Manager is a service that runs in a Kubernetes cluster. It
provides a declarative configuration language to describe Kubernetes Deployment Manager (DM) is a service that runs in a Kubernetes cluster,
resources, and a mechanism for deploying, updating, and deleting configurations. supported by a command line interface. It provides a declarative `YAML`-based
This document describes the configuration language, object model, and language for configuring Kubernetes resources, and a mechanism for deploying,
architecture of the service in detail. updating, and deleting configurations. This document describes the configuration
language, the API model, and the service architecture in detail.
## Configuration Language ## Configuration Language
The configuration language in Deployment Manager consists of two parts: a
YAML-based language for describing resources, and a templating mechanism for
creating abstract parameterizable types.
A configuration consists of a list of resources in YAML. Resources have three DM uses a `YAML`-based configuration language with a templating mechanism. A
properties: configuration is a `YAML` file that describes a list of resources. A resource has
three properties:
* name: the name to use when managing the resource * `name`: the name to use when managing the resource
* type: the type of the resource being managed * `type`: the type of the resource being configured
* properties: the configuration properties of the resource * `properties`: the configuration properties of the resource
An example snippet of a configuration looks like: Here's a snippet from a typical configuration file:
``` ```
resources: resources:
@ -37,51 +36,63 @@ resources:
... ...
``` ```
### References It describes two resources:
Resources can reference values from other resources. The version of Deployment
Manager running in the Google Cloud Platform uses references to understand
dependencies between resources and properly order the operations it performs on
a configuration. (This version of DM doesn't yet order operations to satisfy
dependencies, but it will soon.)
A reference follows this syntax: **$(ref.NAME.PATH)**, where _NAME_ is the name * A replication controller named `my-rc`, and
of the resource being referenced, and _PATH_ is a JSON path to the value in the * A service named `my-service`
resource object.
For example: ## Types
``` Resource types are either primitives or templates.
$(ref.my-service.metadata.name)
```
In this case, _my-service_ is the name of the resource, and _metadata.name_ is ### Primitives
the JSON path to the value being referenced.
### Configurable Resources Primitives are types implemented by the Kubernetes runtime, such as:
Configurable resources are the primitive resources that can be configured in * `Pod`
Deployment Manager, including: * `ReplicationController`
* `Service`
* `Namespace`
* `Secret`
* Pod DM processes primitive resources by passing their properties directly to
* ReplicationController `kubectl` to create, update, or delete the corresponding objects in the cluster.
* Service
Deployment Manager processes configurable resources by passing their (Note that DM runs `kubectl` server side, in a container.)
configuration properties directly to kubectl to create, update, or delete them
in the cluster.
### Templates ### Templates
Templates are abstract types that can be created using Python or Templates are abstract types created using Python or
[Jinja](http://jinja.pocoo.org/). A template takes a set of properties as input, [Jinja](http://jinja.pocoo.org/). A template takes a set of properties as input,
and must output a valid YAML configuration string. Properties are bound to and must output a valid `YAML` configuration. Properties are bound to values when
values when a template is instantiated in a configuration. a template is instantiated by a configuration.
Templates are expanded before primitive resources are processed. The
configuration produced by expanding a template may contain primitive resources
and/or additional template invocations. All template invocations are expanded
recursively until the resulting configuration is a list of primitive resources.
(Note, however, that DM preserves the template hierarchy and any dependencies
between resources in a layout that can be used to reason programmatically about
the structure of the resulting collection of resources created in the cluster,
as described in greater detail below.)
Here's an example of a template written in [Jinja](http://jinja.pocoo.org/):
```
resources:
- name: {{ env['name'] }}-service
type: Service
properties:
prop1: {{ properties['prop1'] }}
...
```
Templates are expanded in a pre-processing step before configurable resources As you can see, it's just a `YAML` file that contains expansion directives. For
are processed. They can output configurations containing configurable resources, more information about the kinds of things you can do in a Jinja based template,
or additional nested templates. Nested templates are processed recursively. see [the Jina documentation](http://jinja.pocoo.org/docs/).
An example of a template in python is: Here's an example of a template written in Python:
``` ```
import yaml import yaml
@ -99,39 +110,33 @@ def GenerateConfig(context):
return yaml.dump({'resources': resources}) return yaml.dump({'resources': resources})
``` ```
and in Jinja: Of course, you can do a lot more in Python than in Jinja, but basic things, such
as simple parameter substitution, may be easier to implement and easier to read in
``` Jinja than in Python.
resources:
- name: {{ env['name'] }}-service
type: Service
properties:
prop1: {{ properties['prop1'] }}
...
```
Templates provide access to multiple sets of data, which can be used for Templates provide access to multiple sets of data that can be used to
parameterizing or further customizing configurations: parameterize or further customize configurations:
* env: a map of key/value pairs from the environment, including pairs * `env`: a map of key/value pairs from the environment, including pairs
defined by Deployment Manager, such as _deployment_, _name_, and _type_ defined by Deployment Manager, such as `deployment`, `name`, and `type`
* properties: a map of the key/value pairs passed in the properties section when * `properties`: a map of the key/value pairs passed in the properties section
instantiating the template of the template invocation
* imports: a map of import file names to file contents of all imports * `imports`: a map of import file names to file contents for all imports
originally specified for the configuration originally specified for the configuration
In Python, this data is available from the _context_ object passed into the In Jinja, these variables are available in the global scope. In Python, they are
_GenerateConfig_ method. available as properties of the `context` object passed into the `GenerateConfig`
method.
### Template Schemas ### Template schemas
A schema can be optionally provided for a template. The schema describes
the template in more detail, including:
* info: more information about the template, including long description and A template can optionally be accompanied by a schema that describes it in more
title detail, including:
* imports: any sub-imports used by this template (may be relative path or URL)
* required: properties which are required when instantiating the template * `info`: more information about the template, including long description and title
* properties: JSON Schema descriptions of each property the template accepts * `imports`: any files imported by this template (may be relative paths or URLs)
* `required`: properties that must have values when the template is expanded
* `properties`: A `JSON Schema` description of each property the template accepts
Here's an example of a template schema: Here's an example of a template schema:
@ -153,21 +158,23 @@ properties:
default: prop-value default: prop-value
``` ```
Schemas are used by Deployment Manager to validate properties during When a schema is provided for a template, DM uses it to validate properties
template instantiation, and to provide default values. passed to the template by its invocation, and to provide default values for
properties that were not given values.
Schemas must be supplied to DM along with the templates they describe.
Schemas must be imported with the templates they describe, when passing ### Supplying templates
configuration to Deployment Manager.
### Instantiating Templates Templates can be supplied to DM in two different ways:
Templates can be used in two different ways: either passed to the API as an * They can be passed to DM along with configurations that import them, or
imported file, or used from a public HTTP endpoint. * They can be retrieved by DM from public HTTP endpoints for configurations that
reference them.
#### Imported Templates #### Template imports
Templates can be imported as part of the target configuration, and used Configurations can import templates using path declarations. For example:
directly, for example:
``` ```
imports: imports:
@ -180,50 +187,85 @@ resources:
prop1: prop-value prop1: prop-value
``` ```
The _imports_ list is not understood by the Deployment Manager service. The `imports` list is not understood by the Deployment Manager service.
It's a directive used by client-side tools to specify what additional files It's a directive used by client-side tools to specify what additional files
should be included when passing a configuration to the API. should be included when passing the configuration to the API.
Using the Deployment Manager API, these templates can be included in the If you are calling the Deployment Manager service directly, you must embed the
imports section of the _configuration_. imported templates in the configuration passed to the API.
#### External Templates #### Template references
Templates can also be used from a public HTTP endpoint. For example: Configurations can also reference templates using URLs for public HTTP endpoints.
DM will attempt to resolve template references during expansion. For example:
``` ```
resources: resources:
- name: example - name: my-template
type: https://raw.githubusercontent.com/example/example.py type: https://raw.githubusercontent.com/my-template/my-template.py
properties: properties:
prop1: prop-value prop1: prop-value
``` ```
The service will process external templates as follows: When resolving template references, DM assumes that templates are stored in
directories, which may also contain schemas, examples and other supporting files.
It therefore processes template references as follows:
1. Attempt to fetch the template, and treat it as an import.
1. Attempt to fetch the schema for the template from
`<base path>/<template name>.schema`
1. Attempt to fetch files imported by the schema from `<base path>/<import path>`
Referring to the previous example,
* the base path is `https://raw.githubusercontent.com/my-template`,
* the template name is `my-template`, and
* the schema name is `my-template.schema`
If we include a configuration that uses the template as an example, then the
directory that contains `my-template` might look like this:
```
example.yaml
my-template.py
my-template.py.schema
helper.py
```
### Value references
Resources can reference values from other resources. The version of Deployment
Manager running in the Google Cloud Platform uses references to understand
dependencies between resources and properly order the operations it performs on
a configuration.
(Note that this version of DM doesn't yet order operations to satisfy
dependencies, but it will soon.)
1. Fetch the external template as an import A reference follows this syntax: `$(ref.NAME.PATH)`, where `NAME` is the name
1. Attempt to fetch the schema for the template, using of the resource being referenced, and `PATH` is a `JSON` path to the value in the
_<full template path>.schema_ as the schema path resource object.
1. Repeat for any sub-imports found in the schema file
When fetching schema files and sub-imports, the base path of the external For example:
template is used for relative paths.
```
$(ref.my-service.metadata.name)
```
In this case, `my-service` is the name of the resource, and `metadata.name` is
the `JSON` path to the value being referenced.
## API Model ## API Model
Deployment Manager exposes a set of RESTful collections over HTTP/JSON. DM exposes a set of RESTful collections over HTTP/JSON.
### Deployments ### Deployments
Deployments are the primary resource in the Deployment Manager service. The Deployments are the primary resources managed by the Deployment Manager service.
inputs to a deployment are: The inputs to a deployment are:
* name * `name`: the name by which the deployment can be referenced once created
* configuration * `configuration`: the configuration file, plus any imported files (templates,
schemas, helper files used by the templates, etc.).
When creating a deployment, users pass their configuration,
as well as any import files (templates, datafiles, etc.), all encoded in `YAML`,
in as the _configuration_.
Creating, updating or deleting a deployment creates a new manifest for the Creating, updating or deleting a deployment creates a new manifest for the
deployment. When deleting a deployment, the deployment is first updated to deployment. When deleting a deployment, the deployment is first updated to
@ -240,10 +282,9 @@ http://manager-service/deployments
A manifest is created for a deployment every time it is changed. It contains A manifest is created for a deployment every time it is changed. It contains
three key components: three key components:
* inputConfig: the original input configuration for the manifest * `inputConfig`: the original input configuration for the manifest
* expandedConfig: the expanded configuration to be used when processing resources * `expandedConfig`: the expanded configuration describing only primitive resources
* for the manifest * `layout`: the hierarchical structure of the configuration
* layout: the hierarchical structure of the manifest
Manifests are available at the HTTP endpoint: Manifests are available at the HTTP endpoint:
@ -251,31 +292,31 @@ Manifests are available at the HTTP endpoint:
http://manager-service/deployments/<deployment>/manifests http://manager-service/deployments/<deployment>/manifests
``` ```
#### Expanded Configuration #### Expanded configuration
Given a new _inputConfig_, Deployment Manager expands all template Given a new `inputConfig`, DM expands all template invocations recursively,
instantiations recursively until there is a flat set of configurable resources. until the result is a flat set of primitive resources. This final set is stored
This final set is stored as the _expandedConfig_ and is used during resource as the `expandedConfig` and is used to instantiate the primitive resources.
processing.
#### Layout #### Layout
Users can use templates to build a rich, deep hierarchical architecture in their Using templates, callers can build rich, deeply hierarchical architectures in
configuration. Expansion flattens this hierarchy and removes the template their configurations. Expansion flattens these hierarchies to simplify the process
relationships from the configuration to create a format optimized for the process of instantiating the primitive resources. However, the structural information
of instantiating the resources. However, the structural information contained in contained in the original configuration has many potential uses, so rather than
the original configuration has many uses, so rather than discard it, Deployment discard it, DM preserves it in the form of a `layout`.
Manager preserves it in the form of a _layout_.
The _layout_ looks very much like an input configuration. It is a YAML list of The `layout` looks a lot like the original configuration. It is a `YAML` file
resources, where each resource contains the following information: that describes a list of resources. Each resource contains the `name`, `type`
and `properties` from the original configuration, plus a list of nested resources
discovered during expansion. The resulting structure looks like this:
* name: name of the resource * name: name of the resource
* type: type of the resource * type: type of the resource
* properties: properties of the resource, set only for templates * properties: properties of the resource, set only for templates
* resources: sub-resources from expansion, set only for templates * resources: sub-resources from expansion, set only for templates
An example layout is: Here's an example of a layout:
``` ```
resources: resources:
@ -290,26 +331,35 @@ resources:
type: Service type: Service
``` ```
The layout can be used for visualizing the architecture of resources, including In this example, the top level resource is a replicated service named `rs`,
their hierarchical structure and reference relationships. defined by the template named `replicatedservice.py`. Expansion produced the
two nested resources: a replication controller named `rs-rc`, and a service
named `rs-service`.
Using the layout, callers can discover that `rs-rc` and `rs-service` are part
of the replicated service named `rs`. More importantly, if `rs` was created by
the expansion of a larger configuration, such as one that described a complete
application, callers could discover that `rs-rc` and `rs-service` were part of
the application, and perhaps even that they were part of a RabbitMQ cluster in
the application's mid-tier.
### Types ### Types
The types API provides information about existing types being used the cluster. The types API provides information about types used in the cluster.
It can be used to list all known types that are in use in existing deployments: It can be used to list all known types used by active deployments:
``` ```
http://manager-service/types http://manager-service/types
``` ```
It can be used to list all active instances of a specific type in the cluster: Or to list all active instances of a specific type in the cluster:
``` ```
http://manager-service/types/<type>/instances http://manager-service/types/<type>/instances
``` ```
Passing _all_ as the type shows all instances of all types in the cluster. Type Passing `all` as the type name shows all instances of all types in the
instances include the following information: cluster. The following information is reported for type instances:
* name: name of resource * name: name of resource
* type: type of resource * type: type of resource
@ -318,58 +368,67 @@ instances include the following information:
* path: JSON path to the entry for the resource in the manifest layout * path: JSON path to the entry for the resource in the manifest layout
## Architecture ## Architecture
The Deployment Manager service is built to run as a service within a Kubernetes
cluster. It has three major components to manage deployments. The following The Deployment Manager service is manages deployments within a Kubernetes
diagram illustrates the relationships between the components, which are described cluster. It has three major components. The following diagram illustrates the
in more detail below. components and the relationships between them.
![Architecture Diagram](architecture.png "Architecture Diagram") ![Architecture Diagram](architecture.png "Architecture Diagram")
Currently there are two caveats in the design of the service: Currently, there are two caveats in the service implementation:
* Synchronous API: the API currently blocks on all processing for * Synchronous API: the API currently blocks on all processing for
a deployment request. In the future, this design will change to an a deployment request. In the future, this design will change to an
asynchronous operation-based mode. asynchronous operation-based mode.
* Non-persistence: the service currently stores all metadata in memory, * In-memory state: the service currently stores all state in memory,
so it will lose all knowledge of deployments and their metadata on restart. so it will lose all knowledge of deployments and related objects on restart.
In the future, the service will persist all deployment metadata. In the future, the service will persist all state in the cluster.
### Manager ### Manager
The **manager** service acts as both the API server and the workflow engine for The `manager` service acts as both the API server and the workflow engine for
processing deployments. It uses the following process: processing deployments. It handles a `POST` to the `/deployments` collection as
follows:
1. Create a new deployment with a manifest containing _inputConfig_ from the 1. Create a new deployment with a manifest containing `inputConfig` from the
user request user request
1. Call out to he **expandybird** service to expand the _inputConfig_ 1. Call out to the `expandybird` service to expand the `inputConfig`
1. Store the resulting _expandedConfig_ and _layout_ 1. Store the resulting `expandedConfig` and `layout`
1. Call out to the **resourcifier** service to perform processing on resources 1. Call out to the `resourcifier` service to instantiate the primitive resources
from the _expandedConfig_ described by the `expandedConfig`
1. Respond with success or error messages to the original API request 1. Respond with success or error messages to the original API request
The manager is responsible for saving the metadata associated with `GET`, `PUT` and `DELETE` operations are processed in a similar manner, except
that:
* No expansion is performed for `GET` or `DELETE`
* The primitive resources are updated for `PUT` and deleted for `DELETE`
The manager is responsible for saving the information associated with
deployments, manifests, type instances, and other resources in the Deployment deployments, manifests, type instances, and other resources in the Deployment
Manager model. Manager model.
### Expandybird ### Expandybird
The **expandybird** service takes in input configurations, performs all template The `expandybird` service takes in a configuration, performs all necesary
expansions, and returns the resulting flat configuration and layout. It is completely template expansions, and returns the resulting flat configuration and layout.
stateless. It is completely stateless.
Because templates are written in Python or Jinja, the actual expansion process Because templates are written in Python or Jinja, the actual expansion process
is performed in a sub-process that runs a Python interpreter. A new sub-process is performed in a sub-process that runs a Python interpreter. A new sub-process
is created for every request to expandybird. is created for every request to `expandybird`.
Currently, expansion is not sandboxed, but templates should be reproducable, Currently, expansion is not sandboxed, but templates should be reproducable,
hermetically sealed entities. Future designs may therefore, introduce a sandbox to hermetically sealed entities. Future designs may therefore introduce a sandbox to
limit external interaction, such as network or disk access, during expansion. limit external interaction, such as network or disk access, during expansion.
### Resourcifier ### Resourcifier
The **resourcifier** service takes in flat expanded configurations containing The `resourcifier` service takes in a flat expanded configuration describing
only configurable resources, and makes the respective kubectl calls to process only primitive resources, and makes the necessary `kubectl` calls to process
each resource. It is totally stateless, and handles requests synchronously. them. It is totally stateless, and handles requests synchronously.
The `resourcifier` runs `kubectl` in a sub-process within its container. A new
sub-process is created for every request to `resourcifier`.
The resourcifier returns either success or error messages encountered during It returns either success or error messages encountered during resource processing.
resource processing.

@ -0,0 +1,187 @@
# Template Registries
DM lets configurations instantiate [templates](../design/design.md#templates)
using both [imports](../design/design.md#template-imports) and
[references](../design/design.md#template-references).
Because template references can use any public HTTP endpoint, they provide
a way to share templates. While you can store templates anywhere you want and
organize them any way you want, you may not be able to share them effectively
without some organizing principles. This document defines conventions for
template registries that store templates in Github and organize them by name
and by version to make sharing easier.
## Template Versions
Since templates referenced by configurations and by other templates may change
over time, we need a versioning scheme, so that template references can be reliably
resolved to specific template versions.
Every template must therefore carry a version based on the
[Semantic Versioning](http://semver.org/) specification. A template version
consists of a MAJOR version, a MINOR version and a PATCH version, and can
be represented as a three part string starting with the letter `v` and using
dot delimiters between the parts. For example `v1.1.0`.
Parts may be omitted from left to right, up to but not include the MAJOR
version. All omitted parts default to zero. So, for example:
* `v1.1` is equivalent to `v1.1.0`, and
* `v2` is equivalent to `v2.0.0`
As required by Semantic Versioning:
* The MAJOR version must be incremented for incompatible changes
* The MINOR version must be incremented functionality is added in a backwards-compatible
manner, and
* The PATCH version must be incremented for backwards-compatible bug fixes.
When resolving a template reference, DM will attempt to fetch the template with
the highest available PATCH version that has the same MAJOR and MINOR versions as
the referenced version.
## Template Validation
Every template version should include a configuration named `example.yaml`
that can be used to deploy an instance of the template. This file may be used,
along with any supporting files it requires, to validate the template.
## Template Organization
Technically, all you need to reference a template is a directory at a public
HTTP endpoint that contains a template file named either `<template-name>.py`
or `<template-name>.jinja`, depending on the implementation language, along
with any supporting files it might require, such as an optional schema file
named `<template-name>.py.schema` or `<template-name>.jinja.schema`, respectively,
helper files used by the implementation, files imported by the schema, and so on.
These constraints impose a basic level of organization on the template definition
by ensuring that the template and all of its supporting files at least live in the
same directory, and that the template and schema files follow well-defined naming
conventions.
They do not, however, provide any encapsulation. Without additional constraints,
there is nothing to prevent template publishers from putting multiple templates,
or multiple versions of the same template, in the same directory. While there
might be some benefits in allowing templates to share a directory, such as avoiding
the duplication of helper files, the cost of discovering and maintaining templates
would quickly outweigh them as the number of templates in the directory increased.
Every template version must therefore live in its own directory, and that
directory must contain one and only one top-level template file and supporting
files for one and only template version.
Since it may reduce management overhead to store many different templates,
and/or many versions of the same template, in a single repository, we need a way
to organize templates within a repository.
A template repository must therefore place all of the versions of a given
template in directories named for the template versions under a directory named
for the template.
For example:
```
templateA/
v1/
example.yaml
templateA.py
templateA.py.schema
v1.0.1/
example.yaml
templateA.py
templateA.py.schema
v1.1/
example.yaml
templateA.py
templateA.py.schema
helper.py
```
The template directories may be organized in any way that makes sense to the
repository maintainers.
For example, this flat list of template directories is valid:
```
templates/
templateA/
v1/
...
templateB/
v2/
...
```
This example, where template directories are organized by category, is also valid:
```
templates/
big-data/
templateA/
v1/
...
templateB/
v2/
...
signals
templateC/
v1/
...
templateD/
v1.1/
...
```
## Template Registries
Github is a convenient place to store and manage templates. A template registry
is a Github repository that conforms to the requirements detailed in this document.
For a working example of a template registry, please see the
[Kubernetes Template Registry](https://github.com/kubernetes/deployment-manager/tree/master/templates).
### Accessing a template registry
The Deployment Manager client, `dm`, can deploy templates directly from a registry
using the following command:
```
$ dm deploy <template-name>:<version>
```
To resolve the template reference, `dm` looks for a template version directory
with the given version in the template directory with the given template name.
By default, it uses the Kubernetes Template Registry. However, you can set a
different default using the `--registry` flag:
```
$ dm --registry my-org/my-repo/my-root-directory deploy <template-name>:<version>
```
Alternatively, you can qualify the template name with the path to the template
directory within the registry, like this:
```
$ dm deploy my-org/my-repo/my-root-directory/<template-name>:<version>
```
Specifying the path to the template directory this way doesn't change the default.
For templates that require properties, you can provide them on the command line:
```
$ dm --properties prop1=value1,prop2=value2 deploy <template-name>:<version>
```
### Changing a template registry
DM relies on Github to provide the tools and processes needed to add, modify or
delete the contents of a template registry. Conventions for changing a template
registry are defined by the registry maintainers, and should be published in the
top level README.md or a file it references, following usual Github practices.
The [Kubernetes Template Registry](https://github.com/kubernetes/deployment-manager/tree/master/templates)
follows the [git setup](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md#git-setup)
used by Kubernetes.

@ -1,6 +1,6 @@
resources: resources:
- name: expandybird - name: expandybird
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
service_port: 8081 service_port: 8081
target_port: 8080 target_port: 8080
@ -11,7 +11,7 @@ resources:
labels: labels:
app: dm app: dm
- name: resourcifier - name: resourcifier
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
service_port: 8082 service_port: 8082
target_port: 8080 target_port: 8080
@ -22,7 +22,7 @@ resources:
labels: labels:
app: dm app: dm
- name: manager - name: manager
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
service_port: 8080 service_port: 8080
target_port: 8080 target_port: 8080

@ -9,9 +9,9 @@ First, make sure DM is installed in your Kubernetes cluster and that the
Guestbook example is deployed by following the instructions in the top level Guestbook example is deployed by following the instructions in the top level
[README.md](../../README.md). [README.md](../../README.md).
## Understanding the Guestbook example template ## Understanding the Guestbook example
Let's take a closer look at the template used by the Guestbook example. Let's take a closer look at the configuration used by the Guestbook example.
### Replicated services ### Replicated services
@ -19,20 +19,22 @@ The typical design pattern for microservices in Kubernetes is to create a
replication controller and a service with the same selector, so that the service replication controller and a service with the same selector, so that the service
exposes ports from the pods managed by the replication controller. exposes ports from the pods managed by the replication controller.
We have created a parameterized type for this kind of replicated service called We have created a parameterized template for this kind of replicated service
[Replicated Service](../../types/replicatedservice/v1), and we use it three times in this called [Replicated Service](../../templates/replicatedservice/v1), and we use it
example. three times in the Guestbook example.
Note that the type is defined by a The template is defined by a
[python script](../../types/replicatedservice/v1/replicatedservice.py). It also has a [Python script](../../templates/replicatedservice/v1/replicatedservice.py). It
[schema](../../types/replicatedservice/v1/replicatedservice.py.schema). Schemas are also has a [schema](../../templates/replicatedservice/v1/replicatedservice.py.schema).
optional. If present in the type definition, they are used to validate uses of the Schemas are optional. If provided, they are used to validate template invocations
type that appear in DM templates. that appear in configurations.
For more information about types and templates, see the [design document](../../docs/design/design.md). For more information about templates and schemas, see the
[design document](../../docs/design/design.md#templates).
### The Guestbook application ### The Guestbook application
The Guestbook application consists of 2 microservices: a front end and a Redis cluster. The Guestbook application consists of 2 microservices: a front end and a Redis
cluster.
#### The front end #### The front end
@ -40,7 +42,7 @@ The front end is a replicated service with 3 replicas:
``` ```
- name: frontend - name: frontend
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
service_port: 80 service_port: 80
container_port: 80 container_port: 80
@ -49,13 +51,14 @@ The front end is a replicated service with 3 replicas:
image: gcr.io/google_containers/example-guestbook-php-redis:v3 image: gcr.io/google_containers/example-guestbook-php-redis:v3
``` ```
(Note that we use the URL for the type replicatedservice.py, not just the type name.) (Note that we use the URL for a specific version of the template replicatedservice.py,
not just the template name.)
#### The Redis cluster #### The Redis cluster
The Redis cluster consists of two replicated services: a master with a single replica The Redis cluster consists of two replicated services: a master with a single replica
and the slaves with 2 replicas. It's defined by [this composite type](../../types/redis/v1/redis.jinja), and the slaves with 2 replicas. It's defined by [this template](../../templates/redis/v1/redis.jinja),
which is a [Jinja](http://jinja.pocoo.org/) template with a [schema](../../types/redis/v1/redis.jinja.schema). which is a [Jinja](http://jinja.pocoo.org/) file with a [schema](../../templates/redis/v1/redis.jinja.schema).
``` ```
{% set REDIS_PORT = 6379 %} {% set REDIS_PORT = 6379 %}
@ -63,7 +66,7 @@ which is a [Jinja](http://jinja.pocoo.org/) template with a [schema](../../types
resources: resources:
- name: redis-master - name: redis-master
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
# This has to be overwritten since service names are hard coded in the code # This has to be overwritten since service names are hard coded in the code
service_name: redis-master service_name: redis-master
@ -75,7 +78,7 @@ resources:
image: redis image: redis
- name: redis-slave - name: redis-slave
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
# This has to be overwritten since service names are hard coded in the code # This has to be overwritten since service names are hard coded in the code
service_name: redis-slave service_name: redis-slave
@ -94,16 +97,17 @@ resources:
### Displaying types ### Displaying types
You can see the types you deployed to the cluster using the `deployed-types` command: You can see both the both primitive types and the templates you've deployed to the
cluster using the `deployed-types` command:
``` ```
dm deployed-types dm deployed-types
["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py"] ["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py"]
``` ```
This output shows 2 primitive types (Service and ReplicationController), and 2 This output shows 2 primitive types (Service and ReplicationController), and 2
composite types (redis.jinja and one imported from github (replicatedservice.py)). templates (redis.jinja and one imported from github named replicatedservice.py).
You can also see where a specific type is being used with the `deployed-instances` command: You can also see where a specific type is being used with the `deployed-instances` command:
@ -115,6 +119,7 @@ dm deployed-instances Service
This output describes the deployment and manifest, as well as the JSON paths to This output describes the deployment and manifest, as well as the JSON paths to
the instances of the type within the layout. the instances of the type within the layout.
For more information about deployments, manifests and layouts, see the [design document](../../docs/design/design.md). For more information about deployments, manifests and layouts, see the
[design document](../../docs/design/design.md#api-model).

@ -1,6 +1,6 @@
resources: resources:
- name: frontend - name: frontend
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
service_port: 80 service_port: 80
container_port: 80 container_port: 80
@ -8,5 +8,5 @@ resources:
replicas: 3 replicas: 3
image: gcr.io/google_containers/example-guestbook-php-redis:v3 image: gcr.io/google_containers/example-guestbook-php-redis:v3
- name: redis - name: redis
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/redis/v1/redis.jinja type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/redis/v1/redis.jinja
properties: null properties: null

@ -1,17 +1,22 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/expandybird # Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/expandybird
# MAINTAINER: Jack Greenfield <jackgr@google.com> # MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing. # If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean .PHONY : all build test push container clean
PREFIX := gcr.io/$(PROJECT) DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := expandybird IMAGE := expandybird
TAG := latest TAG := latest
DIR := . DIR := .
push: container push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container: expandybird container: expandybird
cp $(shell which expandybird) . cp $(shell which expandybird) .
@ -25,4 +30,4 @@ expandybird:
clean: clean:
-docker rmi $(PREFIX)/$(IMAGE):$(TAG) -docker rmi $(PREFIX)/$(IMAGE):$(TAG)
rm -f expandybird rm -f expandybird

@ -24,7 +24,7 @@ import (
const invalidFileName = "afilethatdoesnotexist" const invalidFileName = "afilethatdoesnotexist"
var importFileNames = []string{ var importFileNames = []string{
"../test/replicatedservice.py", "../../templates/replicatedservice/v1/replicatedservice.py",
} }
var outputFileName = "../test/ExpectedOutput.yaml" var outputFileName = "../test/ExpectedOutput.yaml"

@ -68,14 +68,14 @@ func NewExpansionHandler(backend expander.Expander) restful.RouteFunction {
output, err := backend.ExpandTemplate(template) output, err := backend.ExpandTemplate(template)
if err != nil { if err != nil {
message := fmt.Sprintf("error (%s) expanding template:\n%v\n", err, template) message := fmt.Sprintf("error expanding template: %s", err)
logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp) logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp)
return return
} }
response, err := expander.NewExpansionResponse(output) response, err := expander.NewExpansionResponse(output)
if err != nil { if err != nil {
message := fmt.Sprintf("error (%s) marshaling output:\n%v\n", err, output) message := fmt.Sprintf("error marshaling output: %s", err)
logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp) logAndReturnErrorFromHandler(http.StatusBadRequest, message, resp)
return return
} }

@ -65,7 +65,7 @@ const (
) )
var importFileNames = []string{ var importFileNames = []string{
"../test/replicatedservice.py", "../../templates/replicatedservice/v1/replicatedservice.py",
} }
type ServiceWrapperTestCase struct { type ServiceWrapperTestCase struct {

@ -20,9 +20,8 @@ config:
metadata: metadata:
labels: labels:
app: expandybird app: expandybird
name: expandybird-service
name: expandybird-service name: expandybird-service
namespace: default namespace: default
spec: spec:
ports: ports:
- name: expandybird - name: expandybird
@ -30,7 +29,6 @@ config:
targetPort: 8080 targetPort: 8080
selector: selector:
app: expandybird app: expandybird
name: expandybird
type: LoadBalancer type: LoadBalancer
type: Service type: Service
- name: expandybird-rc - name: expandybird-rc
@ -40,22 +38,20 @@ config:
metadata: metadata:
labels: labels:
app: expandybird app: expandybird
name: expandybird-rc
name: expandybird-rc name: expandybird-rc
namespace: default namespace: default
spec: spec:
replicas: 3 replicas: 3
selector: selector:
app: expandybird app: expandybird
name: expandybird
template: template:
metadata: metadata:
labels: labels:
app: expandybird app: expandybird
name: expandybird
spec: spec:
containers: containers:
- image: b.gcr.io/dm-k8s-testing/expandybird - env: []
image: b.gcr.io/dm-k8s-testing/expandybird
name: expandybird name: expandybird
ports: ports:
- containerPort: 8080 - containerPort: 8080
@ -78,4 +74,4 @@ layout:
type: Service type: Service
- name: expandybird-rc - name: expandybird-rc
type: ReplicationController type: ReplicationController
type: replicatedservice.py type: replicatedservice.py

@ -1,176 +0,0 @@
######################################################################
# Copyright 2015 The Kubernetes Authors 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.
######################################################################
"""Defines a ReplicatedService type by creating both a Service and an RC.
This module creates a typical abstraction for running a service in a
Kubernetes cluster, namely a replication controller and a service packaged
together into a single unit.
"""
import yaml
SERVICE_TYPE_COLLECTION = 'Service'
RC_TYPE_COLLECTION = 'ReplicationController'
def GenerateConfig(context):
"""Generates a Replication Controller and a matching Service.
Args:
context: Template context, which can contain the following properties:
container_name - Name to use for container. If omitted, name is
used.
namespace - Namespace to create the resources in. If omitted,
'default' is used.
protocol - Protocol to use for the service
service_port - Port to use for the service
target_port - Target port for the service
container_port - Container port to use
replicas - Number of replicas to create in RC
image - Docker image to use for replicas. Required.
labels - labels to apply.
external_service - If set to true, enable external Load Balancer
Returns:
A Container Manifest as a YAML string.
"""
# YAML config that we're going to create for both RC & Service
config = {'resources': []}
name = context.env['name']
container_name = context.properties.get('container_name', name)
namespace = context.properties.get('namespace', 'default')
# Define things that the Service cares about
service_name = name + '-service'
service_type = SERVICE_TYPE_COLLECTION
# Define things that the Replication Controller (rc) cares about
rc_name = name + '-rc'
rc_type = RC_TYPE_COLLECTION
service = {
'name': service_name,
'type': service_type,
'properties': {
'apiVersion': 'v1',
'kind': 'Service',
'namespace': namespace,
'metadata': {
'name': service_name,
'labels': GenerateLabels(context, service_name),
},
'spec': {
'ports': [GenerateServicePorts(context, container_name)],
'selector': GenerateLabels(context, name)
}
}
}
set_up_external_lb = context.properties.get('external_service', None)
if set_up_external_lb:
service['properties']['spec']['type'] = 'LoadBalancer'
config['resources'].append(service)
rc = {
'name': rc_name,
'type': rc_type,
'properties': {
'apiVersion': 'v1',
'kind': 'ReplicationController',
'namespace': namespace,
'metadata': {
'name': rc_name,
'labels': GenerateLabels(context, rc_name),
},
'spec': {
'replicas': context.properties['replicas'],
'selector': GenerateLabels(context, name),
'template': {
'metadata': {
'labels': GenerateLabels(context, name),
},
'spec': {
'containers': [
{
'name': container_name,
'image': context.properties['image'],
'ports': [
{
'name': container_name,
'containerPort': context.properties['container_port'],
}
]
}
]
}
}
}
}
}
config['resources'].append(rc)
return yaml.dump(config)
# Generates labels either from the context.properties['labels'] or generates
# a default label 'name':name
def GenerateLabels(context, name):
"""Generates labels from context.properties['labels'] or creates default.
We make a deep copy of the context.properties['labels'] section to avoid
linking in the yaml document, which I believe reduces readability of the
expanded template. If no labels are given, generate a default 'name':name.
Args:
context: Template context, which can contain the following properties:
labels - Labels to generate
Returns:
A dict containing labels in a name:value format
"""
tmp_labels = context.properties.get('labels', None)
ret_labels = {'name': name}
if isinstance(tmp_labels, dict):
for key, value in tmp_labels.iteritems():
ret_labels[key] = value
return ret_labels
def GenerateServicePorts(context, name):
"""Generates a ports section for a service.
Args:
context: Template context, which can contain the following properties:
service_port - Port to use for the service
target_port - Target port for the service
protocol - Protocol to use.
Returns:
A dict containing a port definition
"""
service_port = context.properties.get('service_port', None)
target_port = context.properties.get('target_port', None)
protocol = context.properties.get('protocol')
ports = {}
if name:
ports['name'] = name
if service_port:
ports['port'] = service_port
if target_port:
ports['targetPort'] = target_port
if protocol:
ports['protocol'] = protocol
return ports

@ -1,10 +1,11 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/manager # Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/manager
# MAINTAINER: Jack Greenfield <jackgr@google.com> # MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing. # If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean .project .PHONY : all build test push container clean .project
PREFIX := gcr.io/$(PROJECT) DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := manager IMAGE := manager
TAG := latest TAG := latest
@ -12,11 +13,15 @@ ROOT_DIR := $(abspath ./..)
DIR = $(ROOT_DIR) DIR = $(ROOT_DIR)
push: container push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container: container:
docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR) docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR)
clean: clean:
-docker rmi $(PREFIX)/$(IMAGE):$(TAG) -docker rmi $(PREFIX)/$(IMAGE):$(TAG)

@ -100,7 +100,7 @@ func (d *deployer) PutConfiguration(configuration *Configuration) error {
func (d *deployer) callServiceWithConfiguration(method, operation string, configuration *Configuration) error { func (d *deployer) callServiceWithConfiguration(method, operation string, configuration *Configuration) error {
callback := func(e error) error { callback := func(e error) error {
return fmt.Errorf("cannot %s configuration (%s)", operation, e) return fmt.Errorf("cannot %s configuration: %s", operation, e)
} }
y, err := yaml.Marshal(configuration) y, err := yaml.Marshal(configuration)
@ -129,14 +129,14 @@ func (d *deployer) callService(method, url string, reader io.Reader, callback fo
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode < http.StatusOK || body, err := ioutil.ReadAll(response.Body)
response.StatusCode >= http.StatusMultipleChoices { if err != nil {
err := fmt.Errorf("deployer service response:\n%v\n", response)
return nil, callback(err) return nil, callback(err)
} }
body, err := ioutil.ReadAll(response.Body) if response.StatusCode < http.StatusOK ||
if err != nil { response.StatusCode >= http.StatusMultipleChoices {
err := fmt.Errorf("resourcifier response:\n%s", body)
return nil, callback(err) return nil, callback(err)
} }

@ -54,7 +54,7 @@ func (e *expander) getBaseURL() string {
} }
func expanderError(t *Template, err error) error { func expanderError(t *Template, err error) error {
return fmt.Errorf("cannot expand template named %s (%s):\n%s\n", t.Name, err, t.Content) return fmt.Errorf("cannot expand template named %s (%s):\n%s", t.Name, err, t.Content)
} }
// ExpanderResponse gives back a layout, which has nested structure // ExpanderResponse gives back a layout, which has nested structure
@ -117,7 +117,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// 4. If type resolution resulted in new imports being available, return to 2. // 4. If type resolution resulted in new imports being available, return to 2.
config := &Configuration{} config := &Configuration{}
if err := yaml.Unmarshal([]byte(t.Content), config); err != nil { if err := yaml.Unmarshal([]byte(t.Content), config); err != nil {
e := fmt.Errorf("Unable to unmarshal configuration (%s): %s\n", err, t.Content) e := fmt.Errorf("Unable to unmarshal configuration (%s): %s", err, t.Content)
return nil, e return nil, e
} }
@ -127,7 +127,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// Start things off by attempting to resolve the templates in a first pass. // Start things off by attempting to resolve the templates in a first pass.
newImp, err := e.typeResolver.ResolveTypes(config, t.Imports) newImp, err := e.typeResolver.ResolveTypes(config, t.Imports)
if err != nil { if err != nil {
e := fmt.Errorf("type resolution failed:%s\n", err) e := fmt.Errorf("type resolution failed: %s", err)
return nil, expanderError(&t, e) return nil, expanderError(&t, e)
} }
@ -137,7 +137,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
// Now expand with everything imported. // Now expand with everything imported.
result, err := e.expandTemplate(&t) result, err := e.expandTemplate(&t)
if err != nil { if err != nil {
e := fmt.Errorf("template expansion:%s\n", err) e := fmt.Errorf("template expansion: %s", err)
return nil, expanderError(&t, e) return nil, expanderError(&t, e)
} }
@ -152,7 +152,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
newImp, err = e.typeResolver.ResolveTypes(result.Config, nil) newImp, err = e.typeResolver.ResolveTypes(result.Config, nil)
if err != nil { if err != nil {
e := fmt.Errorf("type resolution failed:%s\n", err) e := fmt.Errorf("type resolution failed: %s", err)
return nil, expanderError(&t, e) return nil, expanderError(&t, e)
} }
@ -167,7 +167,7 @@ func (e *expander) ExpandTemplate(t Template) (*ExpandedTemplate, error) {
content, err = yaml.Marshal(result.Config) content, err = yaml.Marshal(result.Config)
t.Content = string(content) t.Content = string(content)
if err != nil { if err != nil {
e := fmt.Errorf("Unable to unmarshal response from expander (%s): %s\n", e := fmt.Errorf("Unable to unmarshal response from expander (%s): %s",
err, result.Config) err, result.Config)
return nil, expanderError(&t, e) return nil, expanderError(&t, e)
} }
@ -183,31 +183,31 @@ func (e *expander) expandTemplate(t *Template) (*ExpandedTemplate, error) {
response, err := http.Post(e.getBaseURL(), "application/json", ioutil.NopCloser(bytes.NewReader(j))) response, err := http.Post(e.getBaseURL(), "application/json", ioutil.NopCloser(bytes.NewReader(j)))
if err != nil { if err != nil {
e := fmt.Errorf("http POST failed:%s\n", err) e := fmt.Errorf("http POST failed: %s", err)
return nil, e return nil, e
} }
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
err := fmt.Errorf("expander service response:%v", response)
return nil, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
e := fmt.Errorf("error reading response:%s\n", err) e := fmt.Errorf("error reading response: %s", err)
return nil, e return nil, e
} }
if response.StatusCode != http.StatusOK {
err := fmt.Errorf("expandybird response:\n%s", body)
return nil, err
}
er := &ExpansionResponse{} er := &ExpansionResponse{}
if err := json.Unmarshal(body, er); err != nil { if err := json.Unmarshal(body, er); err != nil {
e := fmt.Errorf("cannot unmarshal response body (%s):%s\n", err, body) e := fmt.Errorf("cannot unmarshal response body (%s):%s", err, body)
return nil, e return nil, e
} }
template, err := er.Unmarshal() template, err := er.Unmarshal()
if err != nil { if err != nil {
e := fmt.Errorf("cannot unmarshal response yaml (%s):%v\n", err, er) e := fmt.Errorf("cannot unmarshal response yaml (%s):%v", err, er)
return nil, e return nil, e
} }

@ -113,15 +113,16 @@ func (m *manager) CreateDeployment(t *Template) (*Deployment, error) {
return nil, err return nil, err
} }
// TODO: Mark this as failed instead of deleting.
if err := m.deployer.CreateConfiguration(et.Config); err != nil { if err := m.deployer.CreateConfiguration(et.Config); err != nil {
m.repository.DeleteDeployment(t.Name, true) // Deployment failed, mark as deleted
log.Printf("CreateConfiguration failed: %v", err)
m.repository.SetDeploymentStatus(t.Name, FailedStatus)
return nil, err return nil, err
} }
m.repository.SetDeploymentStatus(t.Name, DeployedStatus)
// Finally update the type instances for this deployment. // Finally update the type instances for this deployment.
m.addTypeInstances(t.Name, manifest.Name, manifest.Layout) m.addTypeInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name) return m.repository.GetValidDeployment(t.Name)
} }

@ -122,6 +122,7 @@ type repositoryStub struct {
TypeInstancesCleared bool TypeInstancesCleared bool
GetTypeInstancesCalled bool GetTypeInstancesCalled bool
ListTypesCalled bool ListTypesCalled bool
DeploymentStatuses []DeploymentStatus
} }
func (repository *repositoryStub) reset() { func (repository *repositoryStub) reset() {
@ -134,6 +135,7 @@ func (repository *repositoryStub) reset() {
repository.TypeInstancesCleared = false repository.TypeInstancesCleared = false
repository.GetTypeInstancesCalled = false repository.GetTypeInstancesCalled = false
repository.ListTypesCalled = false repository.ListTypesCalled = false
repository.DeploymentStatuses = make([]DeploymentStatus, 0)
} }
func newRepositoryStub() *repositoryStub { func newRepositoryStub() *repositoryStub {
@ -165,6 +167,11 @@ func (repository *repositoryStub) GetValidDeployment(d string) (*Deployment, err
return &deploymentWithConfiguration, nil return &deploymentWithConfiguration, nil
} }
func (repository *repositoryStub) SetDeploymentStatus(name string, status DeploymentStatus) error {
repository.DeploymentStatuses = append(repository.DeploymentStatuses, status)
return nil
}
func (repository *repositoryStub) CreateDeployment(d string) (*Deployment, error) { func (repository *repositoryStub) CreateDeployment(d string) (*Deployment, error) {
repository.Created = append(repository.Created, d) repository.Created = append(repository.Created, d)
return &deploymentWithConfiguration, nil return &deploymentWithConfiguration, nil
@ -294,6 +301,10 @@ func TestCreateDeployment(t *testing.T) {
testDeployer.Created[0], configuration) testDeployer.Created[0], configuration)
} }
if testRepository.DeploymentStatuses[0] != DeployedStatus {
t.Error("CreateDeployment success did not mark deployment as deployed")
}
if !testRepository.TypeInstancesCleared { if !testRepository.TypeInstancesCleared {
t.Error("Repository did not clear type instances during creation") t.Error("Repository did not clear type instances during creation")
} }
@ -314,9 +325,13 @@ func TestCreateDeploymentCreationFailure(t *testing.T) {
testRepository.Created[0], template.Name) testRepository.Created[0], template.Name)
} }
if testRepository.Deleted[0] != template.Name { if len(testRepository.Deleted) != 0 {
t.Errorf("Repository DeleteDeployment was called with %s but expected %s.", t.Errorf("DeleteDeployment was called with %s but not expected",
testRepository.Created[0], template.Name) testRepository.Created[0])
}
if testRepository.DeploymentStatuses[0] != FailedStatus {
t.Error("CreateDeployment failure did not mark deployment as failed")
} }
if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") { if !strings.HasPrefix(testRepository.ManifestAdd[template.Name].Name, "manifest-") {

@ -50,6 +50,7 @@ type Repository interface {
GetValidDeployment(name string) (*Deployment, error) GetValidDeployment(name string) (*Deployment, error)
CreateDeployment(name string) (*Deployment, error) CreateDeployment(name string) (*Deployment, error)
DeleteDeployment(name string, forget bool) (*Deployment, error) DeleteDeployment(name string, forget bool) (*Deployment, error)
SetDeploymentStatus(name string, status DeploymentStatus) error
// Manifests. // Manifests.
AddManifest(deploymentName string, manifest *Manifest) error AddManifest(deploymentName string, manifest *Manifest) error
@ -72,7 +73,7 @@ type Deployment struct {
DeployedAt time.Time `json:"deployedAt,omitempty"` DeployedAt time.Time `json:"deployedAt,omitempty"`
ModifiedAt time.Time `json:"modifiedAt,omitempty"` ModifiedAt time.Time `json:"modifiedAt,omitempty"`
DeletedAt time.Time `json:"deletedAt,omitempty"` DeletedAt time.Time `json:"deletedAt,omitempty"`
Status deploymentStatus `json:"status,omitempty"` Status DeploymentStatus `json:"status,omitempty"`
Current *Configuration `json:"current,omitEmpty"` Current *Configuration `json:"current,omitEmpty"`
Manifests map[string]*Manifest `json:"manifests,omitempty"` Manifests map[string]*Manifest `json:"manifests,omitempty"`
} }
@ -88,19 +89,19 @@ func NewManifest(deploymentName string, manifestName string) *Manifest {
return &Manifest{Deployment: deploymentName, Name: manifestName} return &Manifest{Deployment: deploymentName, Name: manifestName}
} }
// deploymentStatus is an enumeration type for the status of a deployment. // DeploymentStatus is an enumeration type for the status of a deployment.
type deploymentStatus string type DeploymentStatus string
// These constants implement the deploymentStatus enumeration type. // These constants implement the deploymentStatus enumeration type.
const ( const (
CreatedStatus deploymentStatus = "Created" CreatedStatus DeploymentStatus = "Created"
DeletedStatus deploymentStatus = "Deleted" DeletedStatus DeploymentStatus = "Deleted"
DeployedStatus deploymentStatus = "Deployed" DeployedStatus DeploymentStatus = "Deployed"
FailedStatus deploymentStatus = "Failed" FailedStatus DeploymentStatus = "Failed"
ModifiedStatus deploymentStatus = "Modified" ModifiedStatus DeploymentStatus = "Modified"
) )
func (s deploymentStatus) String() string { func (s DeploymentStatus) String() string {
return string(s) return string(s)
} }

@ -84,6 +84,24 @@ func (r *mapBasedRepository) GetValidDeployment(name string) (*manager.Deploymen
return d, nil return d, nil
} }
// SetDeploymentStatus sets the DeploymentStatus of the deployment and updates ModifiedAt
func (r *mapBasedRepository) SetDeploymentStatus(name string, status manager.DeploymentStatus) error {
return func() error {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return err
}
d.Status = status
d.ModifiedAt = time.Now()
r.deployments[name] = *d
return nil
}()
}
// CreateDeployment creates a new deployment and stores it in the repository. // CreateDeployment creates a new deployment and stores it in the repository.
func (r *mapBasedRepository) CreateDeployment(name string) (*manager.Deployment, error) { func (r *mapBasedRepository) CreateDeployment(name string) (*manager.Deployment, error) {
d, err := func() (*manager.Deployment, error) { d, err := func() (*manager.Deployment, error) {

@ -1,14 +0,0 @@
######################################################################
# Copyright 2015 The Kubernetes Authors 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.
######################################################################
root: .

@ -24,14 +24,16 @@ import (
type GithubRegistry struct { type GithubRegistry struct {
owner string owner string
repository string repository string
path string
client *github.Client client *github.Client
} }
// NewGithubRegistry creates a Registry that can be used to talk to github. // NewGithubRegistry creates a Registry that can be used to talk to github.
func NewGithubRegistry(owner string, repository string) *GithubRegistry { func NewGithubRegistry(owner, repository, path string) *GithubRegistry {
return &GithubRegistry{ return &GithubRegistry{
owner: owner, owner: owner,
repository: repository, repository: repository,
path: path,
client: github.NewClient(nil), client: github.NewClient(nil),
} }
} }
@ -39,19 +41,21 @@ func NewGithubRegistry(owner string, repository string) *GithubRegistry {
// List the types from the Registry. // List the types from the Registry.
func (g *GithubRegistry) List() ([]Type, error) { func (g *GithubRegistry) List() ([]Type, error) {
// First list all the types at the top level. // First list all the types at the top level.
types, err := g.getDirs(TypesDir) types, err := g.getDirs("")
if err != nil { if err != nil {
log.Printf("Failed to list types : %v", err) log.Printf("Failed to list templates: %v", err)
return nil, err return nil, err
} }
var retTypes []Type var retTypes []Type
for _, t := range types { for _, t := range types {
// Then we need to fetch the versions (directories for this type) // Then we need to fetch the versions (directories for this type)
versions, err := g.getDirs(TypesDir + "/" + t) versions, err := g.getDirs(t)
if err != nil { if err != nil {
log.Printf("Failed to fetch versions for type: %s", t) log.Printf("Failed to fetch versions for template: %s", t)
return nil, err return nil, err
} }
for _, v := range versions { for _, v := range versions {
retTypes = append(retTypes, Type{Name: t, Version: v}) retTypes = append(retTypes, Type{Name: t, Version: v})
} }
@ -60,34 +64,53 @@ func (g *GithubRegistry) List() ([]Type, error) {
return retTypes, nil return retTypes, nil
} }
// GetURL fetches the download URL for a given Type. // GetURL fetches the download URL for a given Type and checks for existence of a schema file.
func (g *GithubRegistry) GetURL(t Type) (string, error) { func (g *GithubRegistry) GetURL(t Type) (string, error) {
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, TypesDir+"/"+t.Name+"/"+t.Version, nil) path := g.path + "/" + t.Name + "/" + t.Version
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, path, nil)
if err != nil { if err != nil {
log.Printf("Failed to list types : %v", err) log.Printf("Failed to list versions at path: %s: %v", path, err)
return "", err return "", err
} }
var downloadURL, typeName, schemaName string
for _, f := range dc { for _, f := range dc {
if *f.Type == "file" { if *f.Type == "file" {
if *f.Name == t.Name+".jinja" || *f.Name == t.Name+".py" { if *f.Name == t.Name+".jinja" || *f.Name == t.Name+".py" {
return *f.DownloadURL, nil typeName = *f.Name
downloadURL = *f.DownloadURL
}
if *f.Name == t.Name+".jinja.schema" || *f.Name == t.Name+".py.schema" {
schemaName = *f.Name
} }
} }
} }
return "", fmt.Errorf("Can not find type %s:%s", t.Name, t.Version) if downloadURL == "" {
return "", fmt.Errorf("Can not find template %s:%s", t.Name, t.Version)
}
if schemaName == typeName+".schema" {
return downloadURL, nil
}
return "", fmt.Errorf("Can not find schema for %s:%s, expected to find %s", t.Name, t.Version, typeName+".schema")
} }
func (g *GithubRegistry) getDirs(dir string) ([]string, error) { func (g *GithubRegistry) getDirs(dir string) ([]string, error) {
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, dir, nil) var path = g.path
if dir != "" {
path = g.path + "/" + dir
}
_, dc, _, err := g.client.Repositories.GetContents(g.owner, g.repository, path, nil)
if err != nil { if err != nil {
log.Printf("Failed to call ListRefs : %v", err) log.Printf("Failed to get contents at path: %s: %v", path, err)
return nil, err return nil, err
} }
var dirs []string var dirs []string
for _, entry := range dc { for _, entry := range dc {
if *entry.Type == "dir" { if *entry.Type == "dir" {
dirs = append(dirs, *entry.Name) dirs = append(dirs, *entry.Name)
} }
} }
return dirs, nil return dirs, nil
} }

@ -13,33 +13,36 @@ limitations under the License.
package registry package registry
// Registry abstracts a types registry which holds types that can be // Registry abstracts a registry that holds templates, which can be
// used in a Deployment Manager configurations. A registry root must have // used in a Deployment Manager configurations. A registry root must be a
// a 'types' directory which contains all the available types. Each type // directory that contains all the available templates, one directory per
// then contains version directories which in turn contains all the files // template. Each template directory then contains version directories, each
// necessary for that version of the type. // of which in turn contains all the files necessary for that version of the
// For example a type registry holding two types: // template.
// redis v1 (implemented in jinja) // For example, a template registry containing two versions of redis
// replicatedservice v2 (implemented in python) // (implemented in jinja), and one version of replicatedservice (implemented
// would have a directory structure like so: // in python) would have a directory structure that looks something like this:
// /types/redis/v1 // /redis
// redis.jinja // /v1
// redis.jinja.schema // redis.jinja
// /types/replicatedservice/v2 // redis.jinja.schema
// replicatedservice.python // /v2
// replicatedservice.python.schema // redis.jinja
// redis.jinja.schema
const TypesDir string = "types" // /replicatedservice
// /v1
// replicatedservice.python
// replicatedservice.python.schema
type Type struct { type Type struct {
Name string Name string
Version string Version string
} }
// Registry abstracts type interactions. // Registry abstracts type interactions.
type Registry interface { type Registry interface {
// List all the types in the given registry // List all the templates at the given path
List() ([]Type, error) List() ([]Type, error)
// Get the download URL for a given type and version // Get the download URL for a given template and version
GetURL(t Type) (string, error) GetURL(t Type) (string, error)
} }

@ -1,10 +1,11 @@
# Makefile for the Docker image gcr.io/$(PROJECT)/resourcifier # Makefile for the Docker image $(DOCKER_REGISTRY)/$(PROJECT)/resourcifier
# MAINTAINER: Jack Greenfield <jackgr@google.com> # MAINTAINER: Jack Greenfield <jackgr@google.com>
# If you update this image please check the tag value before pushing. # If you update this image please check the tag value before pushing.
.PHONY : all build test push container clean .PHONY : all build test push container clean
PREFIX := gcr.io/$(PROJECT) DOCKER_REGISTRY := gcr.io
PREFIX := $(DOCKER_REGISTRY)/$(PROJECT)
IMAGE := resourcifier IMAGE := resourcifier
TAG := latest TAG := latest
@ -12,7 +13,11 @@ ROOT_DIR := $(abspath ./..)
DIR = $(ROOT_DIR) DIR = $(ROOT_DIR)
push: container push: container
ifeq ($(DOCKER_REGISTRY),gcr.io)
gcloud docker push $(PREFIX)/$(IMAGE):$(TAG) gcloud docker push $(PREFIX)/$(IMAGE):$(TAG)
else
docker push $(PREFIX)/$(IMAGE):$(TAG)
endif
container: container:
docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR) docker build -t $(PREFIX)/$(IMAGE):$(TAG) -f Dockerfile $(DIR)

@ -0,0 +1,29 @@
# Kubernetes Template Registry
Welcome to the Kubernetes Template Registry!
This registry holds Deployment Manager
[templates](https://github.com/kubernetes/deployment-manager/tree/master/docs/design/design.md#templates)
that you can use to deploy Kubernetes resources.
For more information about installing and using Deployment Manager, see its
[README.md](https://github.com/kubernetes/deployment-manager/tree/master/README.md).
## Organization
The Kubernetes Template Registry is organized as a flat list of template
directories. Templates are versioned. The versions of a template live in version
directories under its template directory.
For more information about Deployment Manager template registries, including
directory structure and template versions, see the
[template registry documentation](https://github.com/kubernetes/deployment-manager/tree/master/docs/templates/registry.md)
## Contributing
The Kubernetes Template Registry follows the
[git setup](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md#git-setup)
used by Kubernetes.
You will also need to have a Contributor License Agreement on file, as described
in [CONTRIBUTING.md](../CONTRIBUTING.md).

@ -1,9 +1,9 @@
{% set REDIS_PORT = 6379 %} {% set REDIS_PORT = 6379 %}
{% set WORKERS = properties['workers'] or 2 %} {% set WORKERS = properties['workers'] if properties and properties['workers'] else 2 %}
resources: resources:
- name: redis-master - name: redis-master
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
# This has to be overwritten since service names are hard coded in the code # This has to be overwritten since service names are hard coded in the code
service_name: redis-master service_name: redis-master
@ -15,7 +15,7 @@ resources:
image: redis image: redis
- name: redis-slave - name: redis-slave
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/types/replicatedservice/v1/replicatedservice.py type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
properties: properties:
# This has to be overwritten since service names are hard coded in the code # This has to be overwritten since service names are hard coded in the code
service_name: redis-slave service_name: redis-slave

@ -0,0 +1,6 @@
imports:
- path: redis.jinja
resources:
- name: redis
type: redis.jinja

@ -1,3 +1,16 @@
######################################################################
# Copyright 2015 The Kubernetes Authors 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.
######################################################################
"""Defines a ReplicatedService type by creating both a Service and an RC. """Defines a ReplicatedService type by creating both a Service and an RC.
This module creates a typical abstraction for running a service in a This module creates a typical abstraction for running a service in a
@ -41,9 +54,9 @@ def GenerateConfig(context):
'properties': { 'properties': {
'apiVersion': 'v1', 'apiVersion': 'v1',
'kind': 'Service', 'kind': 'Service',
'namespace': namespace,
'metadata': { 'metadata': {
'name': service_name, 'name': service_name,
'namespace': namespace,
'labels': GenerateLabels(context, service_name), 'labels': GenerateLabels(context, service_name),
}, },
'spec': { 'spec': {
@ -63,9 +76,9 @@ def GenerateConfig(context):
'properties': { 'properties': {
'apiVersion': 'v1', 'apiVersion': 'v1',
'kind': 'ReplicationController', 'kind': 'ReplicationController',
'namespace': namespace,
'metadata': { 'metadata': {
'name': rc_name, 'name': rc_name,
'namespace': namespace,
'labels': GenerateLabels(context, rc_name), 'labels': GenerateLabels(context, rc_name),
}, },
'spec': { 'spec': {
@ -101,11 +114,11 @@ def GenerateConfig(context):
def GenerateLabels(context, name): def GenerateLabels(context, name):
"""Generates labels either from the context.properties['labels'] or """Generates labels either from the context.properties['labels'] or
generates a default label 'name':name generates a default label 'app':name
We make a deep copy of the context.properties['labels'] section to avoid We make a deep copy of the context.properties['labels'] section to avoid
linking in the yaml document, which I believe reduces readability of the linking in the yaml document, which I believe reduces readability of the
expanded template. If no labels are given, generate a default 'name':name. expanded template. If no labels are given, generate a default 'app':name.
Args: Args:
context: Template context, which can contain the following properties: context: Template context, which can contain the following properties:
@ -115,7 +128,7 @@ def GenerateLabels(context, name):
A dict containing labels in a name:value format A dict containing labels in a name:value format
""" """
tmp_labels = context.properties.get('labels', None) tmp_labels = context.properties.get('labels', None)
ret_labels = {'name': name} ret_labels = {'app': name}
if isinstance(tmp_labels, dict): if isinstance(tmp_labels, dict):
for key, value in tmp_labels.iteritems(): for key, value in tmp_labels.iteritems():
ret_labels[key] = value ret_labels[key] = value
Loading…
Cancel
Save