mirror of https://github.com/helm/helm
commit
5217a351ad
@ -1,12 +0,0 @@
|
||||
# Makefile for the Docker image gcr.io/$(PROJECT)/expandybird
|
||||
# MAINTAINER: Jack Greenfield <jackgr@google.com>
|
||||
# If you update this image please check the tag value before pushing.
|
||||
|
||||
.PHONY : all build test push container clean
|
||||
|
||||
test: client
|
||||
client --action=expand test/guestbook.yaml test/replicatedservice.py test/redis.jinja > /dev/null
|
||||
|
||||
client:
|
||||
go get -v ./...
|
||||
go install -v ./...
|
@ -1,249 +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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/kubernetes/deployment-manager/client/registry"
|
||||
"github.com/kubernetes/deployment-manager/expandybird/expander"
|
||||
"github.com/kubernetes/deployment-manager/manager/manager"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
action = flag.String("action", "deploy", "expand | deploy | list | get | delete | update | listtypes | listtypeinstances | types")
|
||||
name = flag.String("name", "", "Name of template or deployment")
|
||||
service = flag.String("service", "http://localhost:8080", "URL for deployment manager")
|
||||
type_registry = flag.String("type_registry", "kubernetes/deployment-manager", "Type registry [owner/repo], defaults to kubernetes/deployment-manager")
|
||||
binary = flag.String("binary", "../expandybird/expansion/expansion.py",
|
||||
"Path to template expansion binary")
|
||||
|
||||
properties = flag.String("properties", "", "Properties to use when deploying a type (e.g., --properties k1=v1,k2=v2)")
|
||||
)
|
||||
|
||||
var usage = func() {
|
||||
message := "usage: %s [<flags>] (name | (<template> [<import1>...<importN>]))\n"
|
||||
fmt.Fprintf(os.Stderr, message, os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func getGitRegistry() *registry.GithubRegistry {
|
||||
s := strings.Split(*type_registry, "/")
|
||||
if len(s) != 2 {
|
||||
log.Fatalf("invalid type registry: %s", type_registry)
|
||||
}
|
||||
|
||||
return registry.NewGithubRegistry(s[0], s[1])
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
name := getNameArgument()
|
||||
switch *action {
|
||||
case "types":
|
||||
git := getGitRegistry()
|
||||
types, err := git.List()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot list %v err")
|
||||
}
|
||||
log.Printf("Types:")
|
||||
for _, t := range types {
|
||||
log.Printf("%s:%s", t.Name, t.Version)
|
||||
downloadURL, err := git.GetURL(t)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get download URL for type %s:%s", t.Name, t.Version)
|
||||
}
|
||||
log.Printf("\tdownload URL: %s", downloadURL)
|
||||
}
|
||||
|
||||
case "expand":
|
||||
backend := expander.NewExpander(*binary)
|
||||
template := loadTemplate(name)
|
||||
output, err := backend.ExpandTemplate(template)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot expand %s: %s\n", name, err)
|
||||
}
|
||||
|
||||
fmt.Println(output)
|
||||
case "deploy":
|
||||
callService("deployments", "POST", name, readTemplate(name))
|
||||
case "list":
|
||||
callService("deployments", "GET", name, nil)
|
||||
case "get":
|
||||
path := fmt.Sprintf("deployments/%s", name)
|
||||
callService(path, "GET", name, nil)
|
||||
case "delete":
|
||||
path := fmt.Sprintf("deployments/%s", name)
|
||||
callService(path, "DELETE", name, nil)
|
||||
case "update":
|
||||
path := fmt.Sprintf("deployments/%s", name)
|
||||
callService(path, "PUT", name, readTemplate(name))
|
||||
case "listtypes":
|
||||
callService("types", "GET", name, nil)
|
||||
case "listtypeinstances":
|
||||
path := fmt.Sprintf("types/%s/instances", url.QueryEscape(name))
|
||||
callService(path, "GET", name, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func callService(path, method, name string, reader io.ReadCloser) {
|
||||
action := strings.ToLower(method)
|
||||
if action == "post" {
|
||||
action = "deploy"
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("%s/%s", *service, path)
|
||||
request, err := http.NewRequest(method, u, reader)
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot %s template named %s: %s\n", action, name, err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot %s template named %s: %s\n", action, name, err)
|
||||
}
|
||||
|
||||
if response.StatusCode < http.StatusOK ||
|
||||
response.StatusCode >= http.StatusMultipleChoices {
|
||||
message := fmt.Sprintf("status code: %d status: %s : %s", response.StatusCode, response.Status, body)
|
||||
log.Fatalf("cannot %s template named %s: %s\n", action, name, message)
|
||||
}
|
||||
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
func readTemplate(name string) io.ReadCloser {
|
||||
return marshalTemplate(loadTemplate(name))
|
||||
}
|
||||
|
||||
func loadTemplate(name string) *expander.Template {
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var template *expander.Template
|
||||
var err error
|
||||
if len(args) == 1 {
|
||||
if t := getRegistryType(args[0]); t != nil {
|
||||
template = buildTemplateFromType(name, *t)
|
||||
} else {
|
||||
template, err = expander.NewTemplateFromRootTemplate(args[0])
|
||||
}
|
||||
} else {
|
||||
template, err = expander.NewTemplateFromFileNames(args[0], args[1:])
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create template from supplied file names: %s\n", err)
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
template.Name = name
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
// TODO: needs better validation that this is actually a registry type.
|
||||
func getRegistryType(fullType string) *registry.Type {
|
||||
tList := strings.Split(fullType, ":")
|
||||
if len(tList) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ®istry.Type{
|
||||
Name: tList[0],
|
||||
Version: tList[1],
|
||||
}
|
||||
}
|
||||
|
||||
func buildTemplateFromType(name string, t registry.Type) *expander.Template {
|
||||
git := getGitRegistry()
|
||||
downloadURL, err := git.GetURL(t)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get download URL for type %s:%s", t.Name, t.Version)
|
||||
}
|
||||
|
||||
props := make(map[string]interface{})
|
||||
if *properties != "" {
|
||||
plist := strings.Split(*properties, ",")
|
||||
for _, p := range plist {
|
||||
ppair := strings.Split(p, "=")
|
||||
if len(ppair) != 2 {
|
||||
log.Fatalf("--properties must be in the form \"p1=v1,p2=v2,...\": %s", p)
|
||||
}
|
||||
|
||||
// support ints
|
||||
// TODO: needs to support other types.
|
||||
i, err := strconv.Atoi(ppair[1])
|
||||
if err != nil {
|
||||
props[ppair[0]] = ppair[1]
|
||||
} else {
|
||||
props[ppair[0]] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config := manager.Configuration{Resources: []*manager.Resource{&manager.Resource{
|
||||
Name: name,
|
||||
Type: downloadURL,
|
||||
Properties: props,
|
||||
}}}
|
||||
|
||||
y, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create configuration for deployment: %v\n", config)
|
||||
}
|
||||
|
||||
return &expander.Template{
|
||||
// Name will be set later.
|
||||
Content: string(y),
|
||||
// No imports, as this is a single type from repository.
|
||||
}
|
||||
}
|
||||
|
||||
func marshalTemplate(template *expander.Template) io.ReadCloser {
|
||||
j, err := json.Marshal(template)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot deploy template %s: %s\n", template.Name, err)
|
||||
}
|
||||
|
||||
return ioutil.NopCloser(bytes.NewReader(j))
|
||||
}
|
||||
|
||||
func getNameArgument() string {
|
||||
if *name == "" {
|
||||
*name = fmt.Sprintf("manifest-%d", time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
return *name
|
||||
}
|
@ -1,49 +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.
|
||||
######################################################################
|
||||
|
||||
imports:
|
||||
- path: replicatedservice.py
|
||||
resources:
|
||||
- name: expandybird
|
||||
type: replicatedservice.py
|
||||
properties:
|
||||
service_port: 8081
|
||||
target_port: 8080
|
||||
container_port: 8080
|
||||
external_service: false
|
||||
replicas: 2
|
||||
image: gcr.io/PROJECT/expandybird:latest
|
||||
labels:
|
||||
app: dm
|
||||
- name: resourcifier
|
||||
type: replicatedservice.py
|
||||
properties:
|
||||
service_port: 8082
|
||||
target_port: 8080
|
||||
container_port: 8080
|
||||
external_service: false
|
||||
replicas: 2
|
||||
image: gcr.io/PROJECT/resourcifier:latest
|
||||
labels:
|
||||
app: dm
|
||||
- name: manager
|
||||
type: replicatedservice.py
|
||||
properties:
|
||||
service_port: 8080
|
||||
target_port: 8080
|
||||
container_port: 8080
|
||||
external_service: true
|
||||
replicas: 1
|
||||
image: gcr.io/PROJECT/manager:latest
|
||||
labels:
|
||||
app: dm
|
@ -1,32 +0,0 @@
|
||||
{% set REDIS_PORT = 6379 %}
|
||||
{% set WORKERS = properties['workers'] or 2 %}
|
||||
|
||||
resources:
|
||||
- name: redis-master
|
||||
type: replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-master
|
||||
service_port: {{ REDIS_PORT }}
|
||||
target_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: 1
|
||||
container_name: master
|
||||
image: redis
|
||||
|
||||
- name: redis-slave
|
||||
type: replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-slave
|
||||
service_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: {{ WORKERS }}
|
||||
container_name: worker
|
||||
image: kubernetes/redis-slave:v2
|
||||
# An example of how to specify env variables.
|
||||
env:
|
||||
- name: GET_HOSTS_FROM
|
||||
value: env
|
||||
- name: REDIS_MASTER_SERVICE_HOST
|
||||
value: redis-master
|
@ -1,200 +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.
|
||||
service_name - Name to use for service. If omitted name-service 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.
|
||||
env - Environmental variables to apply (list of maps). Format
|
||||
should be:
|
||||
[{'name': ENV_VAR_NAME, 'value':'ENV_VALUE'},
|
||||
{'name': ENV_VAR_NAME_2, 'value':'ENV_VALUE_2'}]
|
||||
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 = context.properties.get('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': [
|
||||
{
|
||||
'env': GenerateEnv(context),
|
||||
'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
|
||||
|
||||
def GenerateEnv(context):
|
||||
"""Generates environmental variables for a pod.
|
||||
|
||||
Args:
|
||||
context: Template context, which can contain the following properties:
|
||||
env - Environment variables to set.
|
||||
|
||||
Returns:
|
||||
A list containing env variables in dict format {name: 'name', value: 'value'}
|
||||
"""
|
||||
env = []
|
||||
tmp_env = context.properties.get('env', [])
|
||||
for entry in tmp_env:
|
||||
if isinstance(entry, dict):
|
||||
env.append({'name': entry.get('name'), 'value': entry.get('value')})
|
||||
return env
|
@ -0,0 +1,286 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/kubernetes/deployment-manager/expandybird/expander"
|
||||
"github.com/kubernetes/deployment-manager/manager/manager"
|
||||
"github.com/kubernetes/deployment-manager/registry"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO(jackgr): Implement reading a template from stdin
|
||||
//stdin = flag.Bool("stdin", false, "Reads a template from the standard input")
|
||||
properties = flag.String("properties", "", "Properties to use when deploying a type (e.g., --properties k1=v1,k2=v2)")
|
||||
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")
|
||||
binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
|
||||
)
|
||||
|
||||
var commands = []string{
|
||||
"expand \t\t\t Expands the supplied template(s)",
|
||||
"deploy \t\t\t Deploys the supplied type or template(s)",
|
||||
"list \t\t\t Lists the deployments in the cluster",
|
||||
"get \t\t\t Retrieves the supplied deployment",
|
||||
"delete \t\t\t Deletes the supplied deployment",
|
||||
"update \t\t\t Updates a deployment using the supplied template(s)",
|
||||
"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",
|
||||
"types \t\t\t Lists the types in the current registry",
|
||||
"describe \t\t Describes the supplied type in the current registry",
|
||||
}
|
||||
|
||||
var usage = func() {
|
||||
message := "Usage: %s [<flags>] <command> (<type-name> | <deployment-name> | (<template> [<import1>...<importN>]))\n"
|
||||
fmt.Fprintf(os.Stderr, message, os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "Commands:")
|
||||
for _, command := range commands {
|
||||
fmt.Fprintln(os.Stderr, command)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr)
|
||||
fmt.Fprintln(os.Stderr, "Flags:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func getGitRegistry() *registry.GithubRegistry {
|
||||
s := strings.Split(*type_registry, "/")
|
||||
if len(s) != 2 {
|
||||
log.Fatalf("invalid type registry: %s", type_registry)
|
||||
}
|
||||
|
||||
return registry.NewGithubRegistry(s[0], s[1])
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "No command supplied")
|
||||
usage()
|
||||
}
|
||||
|
||||
command := args[0]
|
||||
switch command {
|
||||
case "types":
|
||||
git := getGitRegistry()
|
||||
types, err := git.List()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot list %v err")
|
||||
}
|
||||
|
||||
fmt.Printf("Types:")
|
||||
for _, t := range types {
|
||||
fmt.Printf("%s:%s", t.Name, t.Version)
|
||||
downloadURL, err := git.GetURL(t)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get download URL for type %s:%s", t.Name, t.Version)
|
||||
}
|
||||
|
||||
fmt.Printf("\tdownload URL: %s", downloadURL)
|
||||
}
|
||||
case "describe":
|
||||
fmt.Printf("this feature is not yet implemented")
|
||||
case "expand":
|
||||
backend := expander.NewExpander(*binary)
|
||||
template := loadTemplate(args)
|
||||
output, err := backend.ExpandTemplate(template)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot expand %s: %s\n", template.Name, err)
|
||||
}
|
||||
|
||||
fmt.Println(output)
|
||||
case "deploy":
|
||||
template := loadTemplate(args)
|
||||
action := fmt.Sprintf("deploy template named %s", template.Name)
|
||||
callService("deployments", "POST", action, marshalTemplate(template))
|
||||
case "list":
|
||||
callService("deployments", "GET", "list deployments", nil)
|
||||
case "get":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No deployment name supplied")
|
||||
usage()
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("deployments/%s", args[1])
|
||||
action := fmt.Sprintf("get deployment named %s", args[1])
|
||||
callService(path, "GET", action, nil)
|
||||
case "delete":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No deployment name supplied")
|
||||
usage()
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("deployments/%s", args[1])
|
||||
action := fmt.Sprintf("delete deployment named %s", args[1])
|
||||
callService(path, "DELETE", action, nil)
|
||||
case "update":
|
||||
template := loadTemplate(args)
|
||||
path := fmt.Sprintf("deployments/%s", template.Name)
|
||||
action := fmt.Sprintf("delete deployment named %s", template.Name)
|
||||
callService(path, "PUT", action, marshalTemplate(template))
|
||||
case "deployed-types":
|
||||
action := fmt.Sprintf("list types in registry %s", *type_registry)
|
||||
callService("types", "GET", action, nil)
|
||||
case "deployed-instances":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No type name supplied")
|
||||
usage()
|
||||
}
|
||||
|
||||
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)
|
||||
callService(path, "GET", action, nil)
|
||||
default:
|
||||
usage()
|
||||
}
|
||||
}
|
||||
|
||||
func callService(path, method, action string, reader io.ReadCloser) {
|
||||
u := fmt.Sprintf("%s/%s", *service, path)
|
||||
request, err := http.NewRequest(method, u, reader)
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot %s: %s\n", action, err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot %s: %s\n", action, err)
|
||||
}
|
||||
|
||||
if response.StatusCode < http.StatusOK ||
|
||||
response.StatusCode >= http.StatusMultipleChoices {
|
||||
message := fmt.Sprintf("status code: %d status: %s : %s", response.StatusCode, response.Status, body)
|
||||
log.Fatalf("cannot %s: %s\n", action, message)
|
||||
}
|
||||
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
func loadTemplate(args []string) *expander.Template {
|
||||
var template *expander.Template
|
||||
var err error
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No type name or template file(s) supplied")
|
||||
usage()
|
||||
}
|
||||
|
||||
if len(args) < 3 {
|
||||
if t := getRegistryType(args[1]); t != nil {
|
||||
template = buildTemplateFromType(args[1], *t)
|
||||
} else {
|
||||
template, err = expander.NewTemplateFromRootTemplate(args[1])
|
||||
}
|
||||
} else {
|
||||
template, err = expander.NewTemplateFromFileNames(args[1], args[2:])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create template from supplied arguments: %s\n", err)
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
// TODO: needs better validation that this is actually a registry type.
|
||||
func getRegistryType(fullType string) *registry.Type {
|
||||
tList := strings.Split(fullType, ":")
|
||||
if len(tList) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ®istry.Type{
|
||||
Name: tList[0],
|
||||
Version: tList[1],
|
||||
}
|
||||
}
|
||||
|
||||
func buildTemplateFromType(name string, t registry.Type) *expander.Template {
|
||||
git := getGitRegistry()
|
||||
downloadURL, err := git.GetURL(t)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get download URL for type %s:%s\n%s\n", t.Name, t.Version, err)
|
||||
}
|
||||
|
||||
props := make(map[string]interface{})
|
||||
if *properties != "" {
|
||||
plist := strings.Split(*properties, ",")
|
||||
for _, p := range plist {
|
||||
ppair := strings.Split(p, "=")
|
||||
if len(ppair) != 2 {
|
||||
log.Fatalf("--properties must be in the form \"p1=v1,p2=v2,...\": %s\n", p)
|
||||
}
|
||||
|
||||
// support ints
|
||||
// TODO: needs to support other types.
|
||||
i, err := strconv.Atoi(ppair[1])
|
||||
if err != nil {
|
||||
props[ppair[0]] = ppair[1]
|
||||
} else {
|
||||
props[ppair[0]] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config := manager.Configuration{Resources: []*manager.Resource{&manager.Resource{
|
||||
Name: name,
|
||||
Type: downloadURL,
|
||||
Properties: props,
|
||||
}}}
|
||||
|
||||
y, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %s\ncannot create configuration for deployment: %v\n", err, config)
|
||||
}
|
||||
|
||||
return &expander.Template{
|
||||
// Name will be set later.
|
||||
Content: string(y),
|
||||
// No imports, as this is a single type from repository.
|
||||
}
|
||||
}
|
||||
|
||||
func marshalTemplate(template *expander.Template) io.ReadCloser {
|
||||
j, err := json.Marshal(template)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot deploy template %s: %s\n", template.Name, err)
|
||||
}
|
||||
|
||||
return ioutil.NopCloser(bytes.NewReader(j))
|
||||
}
|
||||
|
||||
func getRandomName() string {
|
||||
return fmt.Sprintf("manifest-%d", time.Now().UTC().UnixNano())
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
{% set REDIS_PORT = 6379 %}
|
||||
{% set WORKERS = properties['workers'] or 2 %}
|
||||
|
||||
resources:
|
||||
- name: redis-master
|
||||
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/examples/replicatedservice/replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-master
|
||||
service_port: {{ REDIS_PORT }}
|
||||
target_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: 1
|
||||
container_name: master
|
||||
image: redis
|
||||
|
||||
- name: redis-slave
|
||||
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/examples/replicatedservice/replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-slave
|
||||
service_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: {{ WORKERS }}
|
||||
container_name: worker
|
||||
image: kubernetes/redis-slave:v2
|
||||
# An example of how to specify env variables.
|
||||
env:
|
||||
- name: GET_HOSTS_FROM
|
||||
value: env
|
||||
- name: REDIS_MASTER_SERVICE_HOST
|
||||
value: redis-master
|
@ -1,10 +0,0 @@
|
||||
info:
|
||||
title: Redis cluster
|
||||
description: Defines a redis cluster, using a single replica
|
||||
replicatedservice for master and replicatedservice for workers.
|
||||
|
||||
properties:
|
||||
workers:
|
||||
type: int
|
||||
default: 2
|
||||
description: Number of worker replicas.
|
@ -1,169 +0,0 @@
|
||||
"""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. See schema for context properties.
|
||||
|
||||
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 = context.properties.get('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': [
|
||||
{
|
||||
'env': GenerateEnv(context),
|
||||
'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
|
||||
|
||||
def GenerateEnv(context):
|
||||
"""Generates environmental variables for a pod.
|
||||
|
||||
Args:
|
||||
context: Template context, which can contain the following properties:
|
||||
env - Environment variables to set.
|
||||
|
||||
Returns:
|
||||
A list containing env variables in dict format {name: 'name', value: 'value'}
|
||||
"""
|
||||
env = []
|
||||
tmp_env = context.properties.get('env', [])
|
||||
for entry in tmp_env:
|
||||
if isinstance(entry, dict):
|
||||
env.append({'name': entry.get('name'), 'value': entry.get('value')})
|
||||
return env
|
@ -1,57 +0,0 @@
|
||||
info:
|
||||
title: Replicated Service
|
||||
description: |
|
||||
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.
|
||||
|
||||
required:
|
||||
- image
|
||||
|
||||
properties:
|
||||
container_name:
|
||||
type: string
|
||||
description: Name to use for container. If omitted, name is used.
|
||||
service_name:
|
||||
type: string
|
||||
description: Name to use for service. If omitted, name-service is used.
|
||||
namespace:
|
||||
type: string
|
||||
description: Namespace to create resources in. If omitted, 'default' is
|
||||
used.
|
||||
default: default
|
||||
protocol:
|
||||
type: string
|
||||
description: Protocol to use for the service.
|
||||
service_port:
|
||||
type: int
|
||||
description: Port to use for the service.
|
||||
target_port:
|
||||
type: int
|
||||
description: Target port to use for the service.
|
||||
container_port:
|
||||
type: int
|
||||
description: Port to use for the container.
|
||||
replicas:
|
||||
type: int
|
||||
description: Number of replicas to create in RC.
|
||||
image:
|
||||
type: string
|
||||
description: Docker image to use for replicas.
|
||||
labels:
|
||||
type: object
|
||||
description: Labels to apply.
|
||||
env:
|
||||
type: object
|
||||
description: Environment variables to apply.
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
external_service:
|
||||
type: boolean
|
||||
description: If set to true, enable external load balancer.
|
||||
|
Loading…
Reference in new issue