Remove bitrotted proof-of-concept generators.

pull/6/head
Brian Grant 2015-01-22 04:54:22 +00:00
parent 3ae67f8153
commit 3acd101ef9
11 changed files with 0 additions and 1403 deletions

View File

@ -1,214 +0,0 @@
# enscope
Typically a configuration is comprised of a set of objects (e.g., a simple service, replication controller, and template). Within that configuration, objects may refer to each other by object reference (as with replication controller to template, as of v1beta3) and/or by label selector (as with service and replication controller to pods generated from the pod template).
If one wants to create multiple instances of that configuration, such as for dev and prod deployments (aka horizontal composition) or to embed in composite macro-services (aka hierarchical composition), the names must be uniquified and the label selectors must be scoped to just one instance of the configuration, by adding deployment-specific labels and label selector requirements (e.g., env=prod, app==coolapp).
Enscope is a standalone minimally schema-aware transformation pass for this purpose. It identifies all names, references, label sets, and label selectors that must be uniquified/scoped. An alternative would be to use a generic templating mechanism, such as [Mustache](http://mustache.github.io), but the scoping mechanism would need to be reimplemented in every templating language, and it would also make configurations more complex.
Currently targets only v1beta3, which isn't yet fully implemented.
## Usage
```
$ enscope specFilename configFilename
```
## Scope schema
```
type EnscopeSpec struct {
NameSuffix string `json:"nameSuffix,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
```
## Example
The following name suffix and labels applied to the output from the [contrib/srvexpand example](../srvexpand/README.md):
```
nameSuffix: -coolapp-prod
labels:
app: coolapp
env: prod
```
Output:
```
- apiVersion: v1beta3
kind: Service
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
name: foo-coolapp-prod
spec:
containerPort: 8080
port: 80
selector:
app: coolapp
env: prod
service: foo
status: {}
- apiVersion: v1beta3
kind: PodTemplate
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: canary
name: foo-canary-coolapp-prod
spec:
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: canary
spec:
containers:
- image: me/coolappserver:canary
imagePullPolicy: ""
name: web
restartPolicy: {}
volumes: []
- apiVersion: v1beta3
kind: ReplicationController
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: canary
name: foo-canary-coolapp-prod
spec:
replicas: 2
selector:
app: coolapp
env: prod
service: foo
track: canary
template:
apiVersion: v1beta3
kind: PodTemplate
name: foo-canary-coolapp-prod
status:
replicas: 0
- apiVersion: v1beta3
kind: PodTemplate
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: stable
name: foo-stable-coolapp-prod
spec:
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: stable
spec:
containers:
- image: me/coolappserver:stable
imagePullPolicy: ""
name: web
restartPolicy: {}
volumes: []
- apiVersion: v1beta3
kind: ReplicationController
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: foo
track: stable
name: foo-stable-coolapp-prod
spec:
replicas: 10
selector:
app: coolapp
env: prod
service: foo
track: stable
template:
apiVersion: v1beta3
kind: PodTemplate
name: foo-stable-coolapp-prod
status:
replicas: 0
- apiVersion: v1beta3
kind: Service
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: bar
name: bar-coolapp-prod
spec:
containerPort: 3306
port: 3306
selector:
app: coolapp
env: prod
service: bar
status: {}
- apiVersion: v1beta3
kind: PodTemplate
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: bar
track: solo
name: bar-solo-coolapp-prod
spec:
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: bar
track: solo
spec:
containers:
- image: mysql
imagePullPolicy: ""
name: db
restartPolicy: {}
volumes:
- name: dbdir
source: null
- apiVersion: v1beta3
kind: ReplicationController
metadata:
creationTimestamp: "null"
labels:
app: coolapp
env: prod
service: bar
track: solo
name: bar-solo-coolapp-prod
spec:
replicas: 1
selector:
app: coolapp
env: prod
service: bar
track: solo
template:
apiVersion: v1beta3
kind: PodTemplate
name: bar-solo-coolapp-prod
status:
replicas: 0
```

View File

@ -1,191 +0,0 @@
/*
Copyright 2014 Google Inc. 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 (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
const usage = "usage: enscope specFilename configFilename"
func checkErr(err error) {
if err != nil {
glog.FatalDepth(1, err)
}
}
// TODO: If name suffix is not specified, deterministically generate it by hashing the labels.
type EnscopeSpec struct {
NameSuffix string `json:"nameSuffix,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
func main() {
if len(os.Args) != 3 {
checkErr(fmt.Errorf(usage))
}
specFilename := os.Args[1]
configFilename := os.Args[2]
specData, err := ReadConfigData(specFilename)
checkErr(err)
spec := EnscopeSpec{}
err = yaml.Unmarshal(specData, &spec)
checkErr(err)
configData, err := ReadConfigData(configFilename)
checkErr(err)
var data interface{}
err = yaml.Unmarshal([]byte(configData), &data)
checkErr(err)
xData, err := enscope("", spec, data)
checkErr(err)
out, err := yaml.Marshal(xData)
checkErr(err)
fmt.Print(string(out))
}
func enscope(parent string, spec EnscopeSpec, in interface{}) (out interface{}, err error) {
var ok bool
switch in.(type) {
case map[interface{}]interface{}:
o := make(map[interface{}]interface{})
for k, v := range in.(map[interface{}]interface{}) {
var kstring string
if kstring, ok = k.(string); !ok {
kstring = parent
}
v, err = enscope(kstring, spec, v)
if err != nil {
return nil, err
}
o[k] = v
}
var ifc interface{}
var name string
// TODO: Figure out a more general way to identify references
if parent == "metadata" || parent == "template" {
if ifc, ok = o["name"]; ok {
if name, ok = ifc.(string); ok {
o["name"] = name + spec.NameSuffix
}
}
if ifc, ok = o["labels"]; ok {
var labels map[interface{}]interface{}
if labels, ok = ifc.(map[interface{}]interface{}); ok {
for k, v := range spec.Labels {
labels[k] = v
}
o["labels"] = labels
}
}
}
if parent == "spec" {
// Note that nodeSelector doesn't match, so we won't modify it
if ifc, ok = o["selector"]; ok {
var selector map[interface{}]interface{}
if selector, ok = ifc.(map[interface{}]interface{}); ok {
for k, v := range spec.Labels {
selector[k] = v
}
o["selector"] = selector
}
}
}
return o, nil
case []interface{}:
in1 := in.([]interface{})
len1 := len(in1)
o := make([]interface{}, len1)
for i := 0; i < len1; i++ {
o[i], err = enscope(parent, spec, in1[i])
if err != nil {
return nil, err
}
}
return o, nil
}
return in, nil
}
//////////////////////////////////////////////////////////////////////
// Client tool utility functions copied from kubectl, kubecfg, and podex.
// This should probably be a separate package, but the right solution is
// to refactor the copied code and delete it from here.
func ReadConfigData(location string) ([]byte, error) {
if len(location) == 0 {
return nil, fmt.Errorf("location given but empty")
}
if location == "-" {
// Read from stdin.
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf(`Read from stdin specified ("-") but no data found`)
}
return data, nil
}
// Use the location as a file path or URL.
return readConfigDataFromLocation(location)
}
func readConfigDataFromLocation(location string) ([]byte, error) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
resp, err := http.Get(location)
if err != nil {
return nil, fmt.Errorf("unable to access URL %s: %v\n", location, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read URL %s: %v\n", location, err)
}
return data, nil
} else {
data, err := ioutil.ReadFile(location)
if err != nil {
return nil, fmt.Errorf("unable to read %s: %v\n", location, err)
}
return data, nil
}
}

View File

@ -1,8 +0,0 @@
# flags2yaml
`flags2yaml` is a command-line tool to generate flat YAML from command-line flags
### Usage
```
$ flags2yaml image=dockerfile/nginx | simplegen - | cluster/kubectl.sh create -f -
```

View File

@ -1,41 +0,0 @@
/*
Copyright 2014 Google Inc. 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.
*/
// flags2yaml is a tool to generate flat YAML from command-line flags
//
// $ flags2yaml name=foo image=busybox | simplegen - | kubectl apply -
package main
import (
"fmt"
"os"
"strings"
"github.com/golang/glog"
)
const usage = "usage: flags2yaml key1=value1 [key2=value2 ...]"
func main() {
for i := 1; i < len(os.Args); i++ {
pieces := strings.Split(os.Args[i], "=")
if len(pieces) != 2 {
glog.Fatalf("Bad arg: %s", os.Args[i])
}
fmt.Printf("%s: %s\n", pieces[0], pieces[1])
}
}

View File

@ -1,17 +0,0 @@
# Replication controller tools
## resize.sh
Resizes a replication controller to the specified number of pods.
```
$ resize.sh
usage: resize.sh <replication controller name> <size>
$ resize.sh redisslave 4
```
## stop.sh
Resizes a replication controller to 0 pods and waits until the pods are deleted.
```
$ stop.sh
usage: stop.sh <replication controller name>
$ stop.sh redisslave
```

View File

@ -1,30 +0,0 @@
#!/bin/bash
# Copyright 2014 Google Inc. 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.
# This command resizes a replication controller using kubectl.
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
KUBECTL="${KUBE_ROOT}/cluster/kubectl.sh"
if [[ $# != 2 ]] ; then
echo "usage: $0 <replication controller name> <size>" >&2
exit 1
fi
rc="$1"
size="$2"
"${KUBECTL}" get -o json rc "$rc" | sed 's/"replicas": [0-9][0-9]*/"replicas": '"$size"'/' | "${KUBECTL}" update -f - rc "$rc"

View File

@ -1,46 +0,0 @@
#!/bin/bash
# Copyright 2014 Google Inc. 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.
# This command resizes a replication controller to 0.
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
KUBECTL="${KUBE_ROOT}/cluster/kubectl.sh"
RESIZE="${KUBE_ROOT}/contrib/rctools/resize.sh"
if [[ $# != 1 ]] ; then
echo "usage: $0 <replication controller name>" >&2
exit 1
fi
rc="$1"
"${RESIZE}" "$rc" 0
# kubectl describe output includes a line like:
# Replicas: 2 current / 2 desired
# Wait until it shows 0 pods
while true; do
pods=$(${KUBECTL} describe rc "$rc" | awk '/^Replicas:/{print $2}')
if [[ "$pods" -eq 0 ]] ; then
exit 0
else
echo "$pods remaining..."
fi
sleep 1
done
exit 1

View File

@ -1,61 +0,0 @@
# Simple configuration generation tool
`simplegen` is a command-line tool to expand a simple container
description into Kubernetes API objects, such as for consumption by
kubectl or other tools.
Currently targets only v1beta1.
### Usage
```
$ simplegen myservice.json
$ simplegen myservice.yaml
$ simplegen -
$ simplegen http://some.blog.site.com/k8s-example.yaml
```
### Schema
```
// Optional: Defaults to image base name if not specified
Name string `json:"name,omitempty"`
// Required.
Image string `json:"image"`
// Optional: Defaults to one
Replicas int `json:"replicas,omitempty"`
// Optional: Creates a service if specified: servicePort:containerPort
PortSpec string `json:"portSpec,omitempty"`
```
### Example
```
redismaster.yaml:
name: redismaster
image: dockerfile/redis
portSpec: 6379:6379
redisslave.yaml:
name: redisslave
image: brendanburns/redis-slave
replicas: 2
portSpec: 10001:6379
```
Output:
```
$ simplegen redismaster.yaml | cluster/kubectl.sh create -f -
$ simplegen redisslave.yaml | cluster/kubectl.sh create -f -
$ cluster/kubectl.sh get services
NAME LABELS SELECTOR IP PORT
kubernetes-ro component=apiserver,provider=kubernetes 10.0.0.2 80
kubernetes component=apiserver,provider=kubernetes 10.0.0.1 443
redismaster simpleservice=redismaster simpleservice=redismaster 10.0.0.3 6379
redisslave simpleservice=redisslave simpleservice=redisslave 10.0.0.4 10001
$ cluster/kubectl.sh get replicationcontrollers
NAME IMAGE(S) SELECTOR REPLICAS
redismaster dockerfile/redis simpleservice=redismaster 1
redisslave brendanburns/redis-slave simpleservice=redisslave 2
$ cluster/kubectl.sh get pods
NAME IMAGE(S) HOST LABELS STATUS
89adf546-6457-11e4-9f97-42010af0d824 dockerfile/redis kubernetes-minion-3/146.148.79.186 simpleservice=redismaster Running
93a555ac-6457-11e4-9f97-42010af0d824 brendanburns/redis-slave kubernetes-minion-4/130.211.186.4 simpleservice=redisslave Running
93a862d1-6457-11e4-9f97-42010af0d824 brendanburns/redis-slave kubernetes-minion-1/130.211.117.14 simpleservice=redisslave Running
```

View File

@ -1,266 +0,0 @@
/*
Copyright 2014 Google Inc. 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.
*/
// simplegen is a tool to generate simple services from a simple description
//
// $ simplegen myservice.json | kubectl create -f -
// $ simplegen myservice.yaml | kubectl create -f -
//
// This is completely separate from kubectl at the moment, until we figure out
// what the right integration approach is.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
// TODO: handle multiple versions correctly
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
// TODO: Also handle lists of simple services, and multiple input files
const usage = "usage: simplegen filename"
type SimpleService struct {
// Optional: Defaults to image base name if not specified
Name string `json:"name,omitempty"`
// Required.
Image string `json:"image"`
// Optional: Defaults to one
Replicas int `json:"replicas,omitempty"`
// Optional: Creates a service if specified: servicePort:containerPort
PortSpec string `json:"portSpec,omitempty"`
}
func checkErr(err error) {
if err != nil {
glog.FatalDepth(1, err)
}
}
func main() {
if len(os.Args) != 2 {
checkErr(fmt.Errorf(usage))
}
filename := os.Args[1]
simpleService := readSimpleService(filename)
var servicePort, containerPort int
var err error
var ports []v1beta1.Port
if simpleService.PortSpec != "" {
servicePort, containerPort, err = portsFromString(simpleService.PortSpec)
checkErr(err)
generateService(simpleService.Name, servicePort, containerPort)
// For replication controller
ports = []v1beta1.Port{{Name: "main", ContainerPort: containerPort}}
}
generateReplicationController(simpleService.Name, simpleService.Image, simpleService.Replicas, ports)
}
func generateService(name string, servicePort int, containerPort int) {
svc := []v1beta1.Service{{
TypeMeta: v1beta1.TypeMeta{APIVersion: "v1beta1", Kind: "Service", ID: name},
Port: servicePort,
ContainerPort: util.NewIntOrStringFromInt(containerPort),
Labels: map[string]string{
"simpleservice": name,
},
Selector: map[string]string{
"simpleservice": name,
},
}}
svcOutData, err := yaml.Marshal(svc)
checkErr(err)
fmt.Print(string(svcOutData))
}
func generateReplicationController(name string, image string, replicas int, ports []v1beta1.Port) {
controller := []v1beta1.ReplicationController{{
TypeMeta: v1beta1.TypeMeta{APIVersion: "v1beta1", Kind: "ReplicationController", ID: name},
DesiredState: v1beta1.ReplicationControllerState{
Replicas: replicas,
ReplicaSelector: map[string]string{
"simpleservice": name,
},
PodTemplate: v1beta1.PodTemplate{
DesiredState: v1beta1.PodState{
Manifest: v1beta1.ContainerManifest{
Version: "v1beta2",
Containers: []v1beta1.Container{
{
Name: name,
Image: image,
Ports: ports,
},
},
},
},
Labels: map[string]string{
"simpleservice": name,
},
},
},
Labels: map[string]string{
"simpleservice": name,
},
}}
controllerOutData, err := yaml.Marshal(controller)
checkErr(err)
fmt.Print(string(controllerOutData))
}
func readSimpleService(filename string) SimpleService {
inData, err := ReadConfigData(filename)
checkErr(err)
simpleService := SimpleService{}
err = yaml.Unmarshal(inData, &simpleService)
checkErr(err)
if simpleService.Name == "" {
_, simpleService.Name = ParseDockerImage(simpleService.Image)
// TODO: encode/scrub the name
}
simpleService.Name = strings.ToLower(simpleService.Name)
// TODO: Validate the image name and extract exposed ports
// TODO: Do more validation
if !util.IsDNSLabel(simpleService.Name) {
checkErr(fmt.Errorf("name (%s) is not a valid DNS label", simpleService.Name))
}
if simpleService.Replicas == 0 {
simpleService.Replicas = 1
}
return simpleService
}
// TODO: what defaults make the most sense?
func portsFromString(spec string) (servicePort int, containerPort int, err error) {
if spec == "" {
return 0, 0, fmt.Errorf("empty port spec")
}
pieces := strings.Split(spec, ":")
if len(pieces) != 2 {
glog.Infof("Bad port spec: %s", spec)
return 0, 0, fmt.Errorf("bad port spec: %s", spec)
}
servicePort, err = strconv.Atoi(pieces[0])
if err != nil {
glog.Errorf("Service port is not integer: %s %v", pieces[0], err)
return 0, 0, err
}
if servicePort < 1 {
glog.Errorf("Service port is not valid: %d", servicePort)
return 0, 0, err
}
containerPort, err = strconv.Atoi(pieces[1])
if err != nil {
glog.Errorf("Container port is not integer: %s %v", pieces[1], err)
return 0, 0, err
}
if containerPort < 1 {
glog.Errorf("Container port is not valid: %d", containerPort)
return 0, 0, err
}
return
}
//////////////////////////////////////////////////////////////////////
// Client tool utility functions copied from kubectl, kubecfg, and podex.
// This should probably be a separate package, but the right solution is
// to refactor the copied code and delete it from here.
func ReadConfigData(location string) ([]byte, error) {
if len(location) == 0 {
return nil, fmt.Errorf("location given but empty")
}
if location == "-" {
// Read from stdin.
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf(`Read from stdin specified ("-") but no data found`)
}
return data, nil
}
// Use the location as a file path or URL.
return readConfigDataFromLocation(location)
}
func readConfigDataFromLocation(location string) ([]byte, error) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
resp, err := http.Get(location)
if err != nil {
return nil, fmt.Errorf("unable to access URL %s: %v\n", location, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read URL %s: %v\n", location, err)
}
return data, nil
} else {
data, err := ioutil.ReadFile(location)
if err != nil {
return nil, fmt.Errorf("unable to read %s: %v\n", location, err)
}
return data, nil
}
}
// ParseDockerImage split a docker image name of the form [REGISTRYHOST/][USERNAME/]NAME[:TAG]
// TODO: handle the TAG
// Returns array of images name parts and base image name
func ParseDockerImage(imageName string) (parts []string, baseName string) {
// Parse docker image name
// IMAGE: [REGISTRYHOST/][USERNAME/]NAME[:TAG]
// NAME: [a-z0-9-_.]
parts = strings.Split(imageName, "/")
baseName = parts[len(parts)-1]
return
}

View File

@ -1,216 +0,0 @@
# srvexpand
srvexpand is a tool to generate non-trivial but regular services
from a description free of most boilerplate.
Currently targets only v1beta3, which isn't yet fully implemented.
## Usage
```
$ srvexpand myservice.json
$ srvexpand myservice.yaml
```
## Schema
```
type HierarchicalController struct {
// Optional: Defaults to one
Replicas int `json:"replicas,omitempty"`
// Spec defines the behavior of a pod.
Spec v1beta3.PodSpec `json:"spec,omitempty"`
}
type ControllerMap map[string]HierarchicalController
type HierarchicalService struct {
// Optional: Creates a service if specified: servicePort:containerPort
// TODO: Support multiple protocols
PortSpec string `json:"portSpec,omitempty"`
// Map of replication controllers to create
ControllerMap ControllerMap `json:"controllers,omitempty"`
}
type ServiceMap map[string]HierarchicalService
```
## Example
```
foo:
portSpec: 80:8080
controllers:
canary:
replicas: 2
spec:
containers:
- name: web
image: me/myappserver:canary
stable:
replicas: 10
spec:
containers:
- name: web
image: me/myappserver:stable
bar:
portSpec: 3306:3306
controllers:
solo:
replicas: 1
spec:
containers:
- name: db
image: mysql
volumes:
- name: dbdir
```
Output:
```
- kind: Service
apiVersion: v1beta3
metadata:
name: foo
creationTimestamp: "null"
labels:
service: foo
spec:
port: 80
selector:
service: foo
containerPort: 8080
status: {}
- kind: PodTemplate
apiVersion: v1beta3
metadata:
name: foo-canary
creationTimestamp: "null"
labels:
service: foo
track: canary
spec:
metadata:
creationTimestamp: "null"
labels:
service: foo
track: canary
spec:
volumes: []
containers:
- name: web
image: me/myappserver:canary
imagePullPolicy: ""
restartPolicy: {}
- kind: ReplicationController
apiVersion: v1beta3
metadata:
name: foo-canary
creationTimestamp: "null"
labels:
service: foo
track: canary
spec:
replicas: 2
selector:
service: foo
track: canary
template:
kind: PodTemplate
name: foo-canary
apiVersion: v1beta3
status:
replicas: 0
- kind: PodTemplate
apiVersion: v1beta3
metadata:
name: foo-stable
creationTimestamp: "null"
labels:
service: foo
track: stable
spec:
metadata:
creationTimestamp: "null"
labels:
service: foo
track: stable
spec:
volumes: []
containers:
- name: web
image: me/myappserver:stable
imagePullPolicy: ""
restartPolicy: {}
- kind: ReplicationController
apiVersion: v1beta3
metadata:
name: foo-stable
creationTimestamp: "null"
labels:
service: foo
track: stable
spec:
replicas: 10
selector:
service: foo
track: stable
template:
kind: PodTemplate
name: foo-stable
apiVersion: v1beta3
status:
replicas: 0
- kind: Service
apiVersion: v1beta3
metadata:
name: bar
creationTimestamp: "null"
labels:
service: bar
spec:
port: 3306
selector:
service: bar
containerPort: 3306
status: {}
- kind: PodTemplate
apiVersion: v1beta3
metadata:
name: bar-solo
creationTimestamp: "null"
labels:
service: bar
track: solo
spec:
metadata:
creationTimestamp: "null"
labels:
service: bar
track: solo
spec:
volumes:
- name: dbdir
source: null
containers:
- name: db
image: mysql
imagePullPolicy: ""
restartPolicy: {}
- kind: ReplicationController
apiVersion: v1beta3
metadata:
name: bar-solo
creationTimestamp: "null"
labels:
service: bar
track: solo
spec:
replicas: 1
selector:
service: bar
track: solo
template:
kind: PodTemplate
name: bar-solo
apiVersion: v1beta3
status:
replicas: 0
```

View File

@ -1,313 +0,0 @@
/*
Copyright 2014 Google Inc. 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.
*/
// srvexpand is a tool to generate non-trivial but regular services
// from a description free of most boilerplate
//
// $ srvexpand myservice.json | kubectl create -f -
// $ srvexpand myservice.yaml | kubectl create -f -
//
// This is completely separate from kubectl at the moment, until we figure out
// what the right integration approach is.
//
// Whether this type of wrapper should be encouraged is debatable. It eliminates
// some boilerplate, at the cost of needing to be updated whenever the generated
// API objects change. For instance, this initial version does not expose the
// protocol and createExternalLoadBalancer fields of Service. It's likely that we
// should support boilerplate elimination in the API itself, such as with more
// intelligent defaults, and generic transformations such as map keys to names.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
// TODO: handle multiple versions correctly. Targeting v1beta3 because
// v1beta1 is too much of a mess. Once we do support multiple versions,
// it should be possible to specify the version for the whole map.
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/ghodss/yaml"
"github.com/golang/glog"
)
const usage = "usage: srvexpand filename"
// Hierarchical service structures are a common pattern and allows omission
// of kind fields on input.
// TODO: Enable apiversion and namespace to be provided for the whole map.
// Note that I don't provide a way to specify labels and annotations to be
// propagated to all the objects (except those required to distinguish and
// connect the objects read in) because I expect that to be done as a
// separate pass.
type HierarchicalController struct {
// Optional: Defaults to one
Replicas int `json:"replicas,omitempty"`
// Spec defines the behavior of a pod.
Spec v1beta3.PodSpec `json:"spec,omitempty"`
}
type ControllerMap map[string]HierarchicalController
type HierarchicalService struct {
// Optional: Creates a service if specified: servicePort:containerPort
// TODO: Support multiple protocols
PortSpec string `json:"portSpec,omitempty"`
// Map of replication controllers to create
ControllerMap ControllerMap `json:"controllers,omitempty"`
}
type ServiceMap map[string]HierarchicalService
func checkErr(err error) {
if err != nil {
glog.FatalDepth(1, err)
}
}
func main() {
if len(os.Args) != 2 {
checkErr(fmt.Errorf(usage))
}
filename := os.Args[1]
serviceMap := readServiceMap(filename)
expandServiceMap(serviceMap)
}
func readServiceMap(filename string) ServiceMap {
inData, err := ReadConfigData(filename)
checkErr(err)
serviceMap := ServiceMap{}
err = yaml.Unmarshal(inData, &serviceMap)
checkErr(err)
return serviceMap
}
func canonicalizeName(name *string) {
*name = strings.ToLower(*name)
if !util.IsDNSLabel(*name) {
checkErr(fmt.Errorf("name (%s) is not a valid DNS label", *name))
}
}
func expandServiceMap(serviceMap ServiceMap) {
for name, service := range serviceMap {
canonicalizeName(&name)
generateService(name, service.PortSpec)
generateReplicationControllers(name, service.ControllerMap)
}
}
func generateService(name string, portSpec string) {
if portSpec == "" {
return
}
servicePort, containerPort, err := portsFromString(portSpec)
checkErr(err)
svc := []v1beta3.Service{{
TypeMeta: v1beta3.TypeMeta{APIVersion: "v1beta3", Kind: "Service"},
ObjectMeta: v1beta3.ObjectMeta{
Name: name,
Labels: map[string]string{
"service": name,
},
},
Spec: v1beta3.ServiceSpec{
Port: servicePort,
ContainerPort: util.NewIntOrStringFromInt(containerPort),
Selector: map[string]string{
"service": name,
},
},
}}
svcOutData, err := yaml.Marshal(svc)
checkErr(err)
fmt.Print(string(svcOutData))
}
func generateReplicationControllers(sname string, controllerMap ControllerMap) {
for cname, controller := range controllerMap {
canonicalizeName(&cname)
generatePodTemplate(sname, cname, controller.Spec)
generateReplicationController(sname, cname, controller.Replicas)
}
}
func generatePodTemplate(sname string, cname string, podSpec v1beta3.PodSpec) {
name := fmt.Sprintf("%s-%s", sname, cname)
pt := []v1beta3.PodTemplate{{
TypeMeta: v1beta3.TypeMeta{APIVersion: "v1beta3", Kind: "PodTemplate"},
ObjectMeta: v1beta3.ObjectMeta{
Name: name,
Labels: map[string]string{
"service": sname,
"track": cname,
},
},
Spec: v1beta3.PodTemplateSpec{
ObjectMeta: v1beta3.ObjectMeta{
Labels: map[string]string{
"service": sname,
"track": cname,
},
},
Spec: podSpec,
},
}}
ptOutData, err := yaml.Marshal(pt)
checkErr(err)
fmt.Print(string(ptOutData))
}
func generateReplicationController(sname string, cname string, replicas int) {
if replicas < 1 {
replicas = 1
}
name := fmt.Sprintf("%s-%s", sname, cname)
rc := []v1beta3.ReplicationController{{
TypeMeta: v1beta3.TypeMeta{APIVersion: "v1beta3", Kind: "ReplicationController"},
ObjectMeta: v1beta3.ObjectMeta{
Name: name,
Labels: map[string]string{
"service": sname,
"track": cname,
},
},
Spec: v1beta3.ReplicationControllerSpec{
Replicas: replicas,
Selector: map[string]string{
"service": sname,
"track": cname,
},
Template: v1beta3.ObjectReference{
Kind: "PodTemplate",
Name: name,
APIVersion: "v1beta3",
},
},
}}
rcOutData, err := yaml.Marshal(rc)
checkErr(err)
fmt.Print(string(rcOutData))
}
// TODO: what defaults make the most sense?
func portsFromString(spec string) (servicePort int, containerPort int, err error) {
if spec == "" {
return 0, 0, fmt.Errorf("empty port spec")
}
pieces := strings.Split(spec, ":")
if len(pieces) != 2 {
glog.Infof("Bad port spec: %s", spec)
return 0, 0, fmt.Errorf("bad port spec: %s", spec)
}
servicePort, err = strconv.Atoi(pieces[0])
if err != nil {
glog.Errorf("Service port is not integer: %s %v", pieces[0], err)
return 0, 0, err
}
if servicePort < 1 {
glog.Errorf("Service port is not valid: %d", servicePort)
return 0, 0, err
}
containerPort, err = strconv.Atoi(pieces[1])
if err != nil {
glog.Errorf("Container port is not integer: %s %v", pieces[1], err)
return 0, 0, err
}
if containerPort < 1 {
glog.Errorf("Container port is not valid: %d", containerPort)
return 0, 0, err
}
return
}
//////////////////////////////////////////////////////////////////////
// Client tool utility functions copied from kubectl, kubecfg, and podex.
// This should probably be a separate package, but the right solution is
// to refactor the copied code and delete it from here.
func ReadConfigData(location string) ([]byte, error) {
if len(location) == 0 {
return nil, fmt.Errorf("location given but empty")
}
if location == "-" {
// Read from stdin.
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf(`Read from stdin specified ("-") but no data found`)
}
return data, nil
}
// Use the location as a file path or URL.
return readConfigDataFromLocation(location)
}
func readConfigDataFromLocation(location string) ([]byte, error) {
// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
resp, err := http.Get(location)
if err != nil {
return nil, fmt.Errorf("unable to access URL %s: %v\n", location, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unable to read URL, server reported %d %s", resp.StatusCode, resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read URL %s: %v\n", location, err)
}
return data, nil
} else {
data, err := ioutil.ReadFile(location)
if err != nil {
return nil, fmt.Errorf("unable to read %s: %v\n", location, err)
}
return data, nil
}
}