mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Remove bitrotted proof-of-concept generators.
This commit is contained in:
parent
3ae67f8153
commit
3acd101ef9
@ -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
|
|
||||||
```
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 -
|
|
||||||
```
|
|
@ -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])
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
```
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
||||||
```
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
|
|
||||||
```
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user