Merge pull request #3775 from zmerlynn/release-0.9

Release 0.9.1
This commit is contained in:
Zach Loafman
2015-01-23 15:59:57 -08:00
54 changed files with 1060 additions and 341 deletions

View File

@@ -551,6 +551,14 @@ function kube::release::package_salt_tarball() {
cp -R "${KUBE_ROOT}/cluster/saltbase" "${release_stage}/"
# TODO(#3579): This is a temporary hack. It gathers up the yaml,
# yaml.in files in cluster/addons (minus any demos) and overlays
# them into kube-addons, where we expect them. (This pipeline is a
# fancy copy, stripping anything but the files we don't want.)
local objects
objects=$(cd "${KUBE_ROOT}/cluster/addons" && find . -name \*.yaml -or -name \*.yaml.in | grep -v demo)
tar c -C "${KUBE_ROOT}/cluster/addons" ${objects} | tar x -C "${release_stage}/saltbase/salt/kube-addons"
local package_name="${RELEASE_DIR}/kubernetes-salt.tar.gz"
kube::release::create_tarball "${package_name}" "${release_stage}/.."
}

View File

@@ -1,6 +1,6 @@
# DNS in Kubernetes
This directory holds an example of how to run
[SkyDNS](https://github.com/skynetservices/skydns) in a Kubernetes cluster.
[SkyDNS](https://github.com/skynetservices/skydns) can be configured
to automatically run in a Kubernetes cluster.
## What things get DNS names?
The only objects to which we are assigning DNS names are Services. Every
@@ -18,23 +18,14 @@ Of course, giving services a name is just half of the problem - DNS names need a
domain also. This implementation uses the variable `DNS_DOMAIN` (see below).
You can configure your docker daemon with the flag `--dns-search`.
## How do I run it?
The first thing you have to do is substitute the variables into the
configuration. You can then feed the result into `kubectl`.
## How do I configure it?
The following environment variables are used at cluster startup to create the SkyDNS pods and configure the kubelets. If you need to, you can reconfigure your provider as necessary (e.g. `cluster/gce/config-default.sh`):
```shell
DNS_SERVER_IP=10.0.0.10
DNS_DOMAIN=kubernetes.local
DNS_REPLICAS=2
sed -e "s/{DNS_DOMAIN}/$DNS_DOMAIN/g" \
-e "s/{DNS_REPLICAS}/$DNS_REPLICAS/g" \
./cluster/addons/dns/skydns-rc.yaml.in \
| ./cluster/kubectl.sh create -f -
sed -e "s/{DNS_SERVER_IP}/$DNS_SERVER_IP/g" \
./cluster/addons/dns/skydns-svc.yaml.in \
| ./cluster/kubectl.sh create -f -
ENABLE_CLUSTER_DNS=true
DNS_SERVER_IP="10.0.0.10"
DNS_DOMAIN="kubernetes.local"
DNS_REPLICAS=1
```
## How does it work?

View File

@@ -5,7 +5,7 @@ namespace: default
labels:
k8s-app: skydns
desiredState:
replicas: {DNS_REPLICAS}
replicas: {{ pillar['dns_replicas'] }}
replicaSelector:
k8s-app: skydns
podTemplate:
@@ -28,7 +28,7 @@ desiredState:
image: kubernetes/kube2sky:1.0
command: [
# entrypoint = "/kube2sky",
"-domain={DNS_DOMAIN}",
"-domain={{ pillar['dns_domain'] }}",
]
- name: skydns
image: kubernetes/skydns:2014-12-23-001
@@ -36,7 +36,7 @@ desiredState:
# entrypoint = "/skydns",
"-machines=http://localhost:4001",
"-addr=0.0.0.0:53",
"-domain={DNS_DOMAIN}.",
"-domain={{ pillar['dns_domain'] }}.",
]
ports:
- name: dns

View File

@@ -4,7 +4,7 @@ id: skydns
namespace: default
protocol: UDP
port: 53
portalIP: {DNS_SERVER_IP}
portalIP: {{ pillar['dns_server'] }}
containerPort: 53
labels:
k8s-app: skydns

View File

@@ -2,7 +2,7 @@ apiVersion: v1beta1
kind: ReplicationController
id: elasticsearch-logging-controller
desiredState:
replicas: {ELASTICSEARCH_LOGGING_REPLICAS}
replicas: {{ pillar['elasticsearch_replicas'] }}
replicaSelector:
name: elasticsearch-logging
podTemplate:

View File

@@ -106,14 +106,14 @@ function ensure-temp-dir {
fi
}
function setup-monitoring {
function setup-monitoring-firewall {
if [[ "${ENABLE_CLUSTER_MONITORING:-false}" == "true" ]]; then
# TODO: Implement this.
echo "Monitoring not currently supported on AWS"
fi
}
function teardown-monitoring {
function teardown-monitoring-firewall {
if [[ "${ENABLE_CLUSTER_MONITORING:-false}" == "true" ]]; then
# TODO: Implement this.
echo "Monitoring not currently supported on AWS"
@@ -296,10 +296,14 @@ function kube-up {
echo "readonly AWS_ZONE='${ZONE}'"
echo "readonly MASTER_HTPASSWD='${htpasswd}'"
echo "readonly PORTAL_NET='${PORTAL_NET}'"
echo "readonly ENABLE_CLUSTER_MONITORING='${ENABLE_CLUSTER_MONITORING:-false}'"
echo "readonly ENABLE_NODE_MONITORING='${ENABLE_NODE_MONITORING:-false}'"
echo "readonly ENABLE_CLUSTER_LOGGING='${ENABLE_CLUSTER_LOGGING:-false}'"
echo "readonly ENABLE_NODE_LOGGING='${ENABLE_NODE_LOGGING:-false}'"
echo "readonly LOGGING_DESTINATION='${LOGGING_DESTINATION:-}'"
echo "readonly ELASTICSEARCH_LOGGING_REPLICAS='${ELASTICSEARCH_LOGGING_REPLICAS:-}'"
echo "readonly ENABLE_CLUSTER_DNS='${ENABLE_CLUSTER_DNS:-false}'"
echo "readonly DNS_REPLICAS='${DNS_REPLICAS:-}'"
echo "readonly DNS_SERVER_IP='${DNS_SERVER_IP:-}'"
echo "readonly DNS_DOMAIN='${DNS_DOMAIN:-}'"
grep -v "^#" "${KUBE_ROOT}/cluster/aws/templates/create-dynamic-salt-files.sh"
@@ -498,10 +502,10 @@ function kube-down {
$AWS_CMD delete-vpc --vpc-id $vpc_id > $LOG
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}
}

View File

@@ -558,18 +558,18 @@ function restart-kube-proxy {
}
# Setup monitoring using heapster and InfluxDB
function setup-monitoring {
function setup-monitoring-firewall {
echo "not implemented" >/dev/null
}
function teardown-monitoring {
function teardown-monitoring-firewall {
echo "not implemented" >/dev/null
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}

View File

@@ -22,10 +22,14 @@ mkdir -p /srv/salt-overlay/pillar
cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
node_instance_prefix: '$(echo "$NODE_INSTANCE_PREFIX" | sed -e "s/'/''/g")'
portal_net: '$(echo "$PORTAL_NET" | sed -e "s/'/''/g")'
enable_cluster_monitoring: '$(echo "$ENABLE_CLUSTER_MONITORING" | sed -e "s/'/''/g")'
enable_node_monitoring: '$(echo "$ENABLE_NODE_MONITORING" | sed -e "s/'/''/g")'
enable_cluster_logging: '$(echo "$ENABLE_CLUSTER_LOGGING" | sed -e "s/'/''/g")'
enable_node_logging: '$(echo "$ENABLE_NODE_LOGGING" | sed -e "s/'/''/g")'
logging_destination: '$(echo "$LOGGING_DESTINATION" | sed -e "s/'/''/g")'
elasticsearch_replicas: '$(echo "$ELASTICSEARCH_LOGGING_REPLICAS" | sed -e "s/'/''/g")'
enable_cluster_dns: '$(echo "$ENABLE_CLUSTER_DNS" | sed -e "s/'/''/g")'
dns_replicas: '$(echo "$DNS_REPLICAS" | sed -e "s/'/''/g")'
dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")'
dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")'
EOF

View File

@@ -393,10 +393,14 @@ function kube-up {
echo "readonly SALT_TAR_URL='${SALT_TAR_URL}'"
echo "readonly MASTER_HTPASSWD='${htpasswd}'"
echo "readonly PORTAL_NET='${PORTAL_NET}'"
echo "readonly ENABLE_CLUSTER_MONITORING='${ENABLE_CLUSTER_MONITORING:-false}'"
echo "readonly ENABLE_NODE_MONITORING='${ENABLE_NODE_MONITORING:-false}'"
echo "readonly ENABLE_CLUSTER_LOGGING='${ENABLE_CLUSTER_LOGGING:-false}'"
echo "readonly ENABLE_NODE_LOGGING='${ENABLE_NODE_LOGGING:-false}'"
echo "readonly LOGGING_DESTINATION='${LOGGING_DESTINATION:-}'"
echo "readonly ELASTICSEARCH_LOGGING_REPLICAS='${ELASTICSEARCH_LOGGING_REPLICAS:-}'"
echo "readonly ENABLE_CLUSTER_DNS='${ENABLE_CLUSTER_DNS:-false}'"
echo "readonly DNS_REPLICAS='${DNS_REPLICAS:-}'"
echo "readonly DNS_SERVER_IP='${DNS_SERVER_IP:-}'"
echo "readonly DNS_DOMAIN='${DNS_DOMAIN:-}'"
grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/common.sh"
@@ -731,106 +735,70 @@ function restart-kube-proxy {
ssh-to-node "$1" "sudo /etc/init.d/kube-proxy restart"
}
# Setup monitoring using heapster and InfluxDB
function setup-monitoring {
if [[ "${ENABLE_CLUSTER_MONITORING}" == "true" ]]; then
echo "Setting up cluster monitoring using Heapster."
# Setup monitoring firewalls using heapster and InfluxDB
function setup-monitoring-firewall {
if [[ "${ENABLE_CLUSTER_MONITORING}" != "true" ]]; then
return
fi
detect-project
if ! gcloud compute firewall-rules --project "${PROJECT}" describe monitoring-heapster &> /dev/null; then
if ! gcloud compute firewall-rules create monitoring-heapster \
--project "${PROJECT}" \
--target-tags="${MINION_TAG}" \
--network="${NETWORK}" \
--allow tcp:80 tcp:8083 tcp:8086; then
echo -e "${color_red}Failed to set up firewall for monitoring ${color_norm}" && false
fi
fi
echo "Setting up firewalls to Heapster based cluster monitoring."
local kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
local grafana_host=""
if "${kubectl}" create -f "${KUBE_ROOT}/cluster/addons/cluster-monitoring/" &> /dev/null; then
# wait for pods to be scheduled on a node.
echo "waiting for monitoring pods to be scheduled."
for i in `seq 1 10`; do
grafana_host=$("${kubectl}" get pods -l name=influxGrafana -o template -t {{range.items}}{{.currentState.hostIP}}:{{end}} | sed s/://g)
if [[ $grafana_host != *"<"* ]]; then
detect-project
gcloud compute firewall-rules create "${INSTANCE_PREFIX}-monitoring-heapster" --project "${PROJECT}" \
--allow tcp:80 tcp:8083 tcp:8086 --target-tags="${MINION_TAG}" --network="${NETWORK}"
local kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
local grafana_host=""
echo "waiting for monitoring pods to be scheduled."
for i in `seq 1 10`; do
grafana_host=$("${kubectl}" get pods -l name=influxGrafana -o template -t {{range.items}}{{.currentState.hostIP}}:{{end}} | sed s/://g)
if [[ ${grafana_host} != *"<"* ]]; then
break
fi
sleep 10
done
if [[ $grafana_host != *"<"* ]]; then
echo
echo -e "${color_green}Grafana dashboard will be available at ${color_yellow}http://$grafana_host${color_green}. Wait for the monitoring dashboard to be online.${color_norm}"
echo
else
echo -e "${color_red}monitoring pods failed to be scheduled.${color_norm}"
fi
else
echo -e "${color_red}Failed to Setup Monitoring ${color_norm}"
teardown-monitoring
fi
sleep 10
done
if [[ ${grafana_host} != *"<"* ]]; then
echo
echo -e "${color_green}Grafana dashboard will be available at ${color_yellow}http://${grafana_host}${color_green}. Wait for the monitoring dashboard to be online.${color_norm}"
echo
else
echo -e "${color_red}Monitoring pods failed to be scheduled!${color_norm}"
fi
}
function teardown-monitoring {
if [[ "${ENABLE_CLUSTER_MONITORING}" == "true" ]]; then
detect-project
local kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
local kubecfg="${KUBE_ROOT}/cluster/kubecfg.sh"
"${kubecfg}" resize monitoring-influxGrafanaController 0 &> /dev/null || true
"${kubecfg}" resize monitoring-heapsterController 0 &> /dev/null || true
"${kubectl}" delete -f "${KUBE_ROOT}/cluster/addons/cluster-monitoring/" &> /dev/null || true
if gcloud compute firewall-rules describe --project "${PROJECT}" monitoring-heapster &> /dev/null; then
gcloud compute firewall-rules delete \
--project "${PROJECT}" \
--quiet \
monitoring-heapster &> /dev/null || true
fi
function teardown-monitoring-firewall {
if [[ "${ENABLE_CLUSTER_MONITORING}" != "true" ]]; then
return
fi
detect-project
gcloud compute firewall-rules delete -q "${INSTANCE_PREFIX}-monitoring-heapster" --project "${PROJECT}" || true
}
function setup-logging {
function setup-logging-firewall {
# If logging with Fluentd to Elasticsearch is enabled then create pods
# and services for Elasticsearch (for ingesting logs) and Kibana (for
# viewing logs).
if [[ "${ENABLE_NODE_LOGGING-}" == "true" ]] && \
[[ "${LOGGING_DESTINATION-}" == "elasticsearch" ]] && \
[[ "${ENABLE_CLUSTER_LOGGING-}" == "true" ]]; then
local -r kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
if sed -e "s/{ELASTICSEARCH_LOGGING_REPLICAS}/${ELASTICSEARCH_LOGGING_REPLICAS}/g" \
"${KUBE_ROOT}"/cluster/addons/fluentd-elasticsearch/es-controller.yaml.in | \
"${kubectl}" create -f - &> /dev/null && \
"${kubectl}" create -f "${KUBE_ROOT}"/cluster/addons/fluentd-elasticsearch/es-service.yaml &> /dev/null && \
"${kubectl}" create -f "${KUBE_ROOT}"/cluster/addons/fluentd-elasticsearch/kibana-controller.yaml &> /dev/null && \
"${kubectl}" create -f "${KUBE_ROOT}"/cluster/addons/fluentd-elasticsearch/kibana-service.yaml &> /dev/null; then
gcloud compute firewall-rules create fluentd-elasticsearch-logging --project "${PROJECT}" \
--allow tcp:5601 tcp:9200 tcp:9300 --target-tags "${INSTANCE_PREFIX}"-minion || true
local -r region="${ZONE::-2}"
local -r es_ip=$(gcloud compute forwarding-rules --project "${PROJECT}" describe --region "${region}" elasticsearch-logging | grep IPAddress | awk '{print $2}')
local -r kibana_ip=$(gcloud compute forwarding-rules --project "${PROJECT}" describe --region "${region}" kibana-logging | grep IPAddress | awk '{print $2}')
echo
echo -e "${color_green}Cluster logs are ingested into Elasticsearch running at ${color_yellow}http://${es_ip}:9200"
echo -e "${color_green}Kibana logging dashboard will be available at ${color_yellow}http://${kibana_ip}:5601${color_norm}"
echo
else
echo -e "${color_red}Failed to launch Elasticsearch and Kibana pods and services for logging.${color_norm}"
fi
if [[ "${ENABLE_NODE_LOGGING-}" != "true" ]] || \
[[ "${LOGGING_DESTINATION-}" != "elasticsearch" ]] || \
[[ "${ENABLE_CLUSTER_LOGGING-}" != "true" ]]; then
return
fi
detect-project
gcloud compute firewall-rules create "${INSTANCE_PREFIX}-fluentd-elasticsearch-logging" --project "${PROJECT}" \
--allow tcp:5601 tcp:9200 tcp:9300 --target-tags "${MINION_TAG}" --network="${NETWORK}"
}
function teardown-logging {
if [[ "${ENABLE_NODE_LOGGING-}" == "true" ]] && \
[[ "${LOGGING_DESTINATION-}" == "elasticsearch" ]] && \
[[ "${ENABLE_CLUSTER_LOGGING-}" == "true" ]]; then
local -r kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
"${kubectl}" delete replicationController elasticsearch-logging-controller &> /dev/null || true
"${kubectl}" delete service elasticsearch-logging &> /dev/null || true
"${kubectl}" delete replicationController kibana-logging-controller &> /dev/null || true
"${kubectl}" delete service kibana-logging &> /dev/null || true
gcloud compute firewall-rules delete -q fluentd-elasticsearch-logging --project "${PROJECT}" || true
function teardown-logging-firewall {
if [[ "${ENABLE_NODE_LOGGING-}" != "true" ]] || \
[[ "${LOGGING_DESTINATION-}" != "elasticsearch" ]] || \
[[ "${ENABLE_CLUSTER_LOGGING-}" != "true" ]]; then
return
fi
detect-project
gcloud compute firewall-rules delete -q "${INSTANCE_PREFIX}-fluentd-elasticsearch-logging" --project "${PROJECT}" || true
}
# Perform preparations required to run e2e tests

View File

@@ -115,8 +115,8 @@ function kube-up() {
}
# Called during cluster/kube-up.sh
function setup-monitoring() {
echo "... in setup-monitoring()" >&2
function setup-monitoring-firewall() {
echo "... in setup-monitoring-firewall()" >&2
# TODO(mbforbes): This isn't currently supported in GKE.
}
@@ -239,8 +239,8 @@ function test-teardown() {
}
# Tears down monitoring.
function teardown-monitoring() {
echo "... in teardown-monitoring()" >&2
function teardown-monitoring-firewall() {
echo "... in teardown-monitoring-firewall()" >&2
# TODO(mbforbes): This isn't currently supported in GKE.
}
@@ -257,10 +257,10 @@ function kube-down() {
--zone="${ZONE}" "${CLUSTER_NAME}"
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}

View File

@@ -27,8 +27,8 @@ source "${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh"
echo "Bringing down cluster using provider: $KUBERNETES_PROVIDER"
verify-prereqs
teardown-monitoring
teardown-logging
teardown-monitoring-firewall
teardown-logging-firewall
kube-down

View File

@@ -39,22 +39,10 @@ kube-up
echo "... calling validate-cluster" >&2
"${KUBE_ROOT}/cluster/validate-cluster.sh"
echo "... calling setup-monitoring" >&2
setup-monitoring
echo "... calling setup-monitoring-firewall" >&2
setup-monitoring-firewall
if [[ "${ENABLE_CLUSTER_DNS}" == "true" ]]; then
echo "... setting up cluster DNS"
sed -e "s/{DNS_DOMAIN}/$DNS_DOMAIN/g" \
-e "s/{DNS_REPLICAS}/$DNS_REPLICAS/g" \
"${KUBE_ROOT}/cluster/addons/dns/skydns-rc.yaml.in" \
| "${KUBE_ROOT}/cluster/kubectl.sh" create -f -
sed -e "s/{DNS_SERVER_IP}/$DNS_SERVER_IP/g" \
"${KUBE_ROOT}/cluster/addons/dns/skydns-svc.yaml.in" \
| "${KUBE_ROOT}/cluster/kubectl.sh" create -f -
fi
echo "... calling setup-logging" >&2
setup-logging
echo "... calling setup-logging-firewall" >&2
setup-logging-firewall
echo "Done" >&2

View File

@@ -342,19 +342,19 @@ kube-up() {
echo
}
function setup-monitoring {
function setup-monitoring-firewall {
echo "TODO"
}
function teardown-monitoring {
function teardown-monitoring-firewall {
echo "TODO"
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}

View File

@@ -0,0 +1,79 @@
{% if pillar.get('enable_cluster_monitoring', '').lower() == 'true' %}
/etc/kubernetes/addons/cluster-monitoring:
file.recurse:
- source: salt://kube-addons/cluster-monitoring
- include_pat: E@^.+\.yaml$
- user: root
- group: root
- dir_mode: 755
- file_mode: 644
{% endif %}
{% if pillar.get('enable_cluster_dns', '').lower() == 'true' %}
/etc/kubernetes/addons/dns/skydns-svc.yaml:
file.managed:
- source: salt://kube-addons/dns/skydns-svc.yaml.in
- template: jinja
- group: root
- dir_mode: 755
- makedirs: True
/etc/kubernetes/addons/dns/skydns-rc.yaml:
file.managed:
- source: salt://kube-addons/dns/skydns-rc.yaml.in
- template: jinja
- group: root
- dir_mode: 755
- makedirs: True
{% endif %}
{% if pillar.get('enable_node_logging', '').lower() == 'true'
and pillar.get('logging_destination').lower() == 'elasticsearch'
and pillar.get('enable_cluster_logging', '').lower() == 'true' %}
/etc/kubernetes/addons/fluentd-elasticsearch:
file.recurse:
- source: salt://kube-addons/fluentd-elasticsearch
- include_pat: E@^.+\.yaml$
- user: root
- group: root
- dir_mode: 755
- file_mode: 644
/etc/kubernetes/addons/fluentd-elasticsearch/es-controller.yaml:
file.managed:
- source: salt://kube-addons/fluentd-elasticsearch/es-controller.yaml.in
- template: jinja
- group: root
- dir_mode: 755
- makedirs: True
{% endif %}
{% if grains['os_family'] == 'RedHat' %}
/usr/lib/systemd/system/kube-addons.service:
file.managed:
- source: salt://kube-addons/kube-addons.service
- user: root
- group: root
/usr/lib/systemd/scripts/kube-addons:
file.managed:
- source: salt://kube-addons/initd
- user: root
- group: root
- mode: 755
{% else %}
/etc/init.d/kube-addons:
file.managed:
- source: salt://kube-addons/initd
- user: root
- group: root
- mode: 755
{% endif %}
kube-addons:
service.running:
- enable: True

View File

@@ -0,0 +1,123 @@
#!/bin/bash
#
### BEGIN INIT INFO
# Provides: kube-addons
# Required-Start: $local_fs $network $syslog kube-apiserver
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Kubernetes Addon Object Manager
# Description:
# Enforces installation of Kubernetes Addon Objects
### END INIT INFO
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Kubernetes Addon Object Manager"
NAME=kube-addons
DAEMON_LOG_FILE=/var/log/$NAME.log
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
KUBECTL=/usr/local/bin/kubectl
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
function addon_manager_async() {
# The business logic for whether a given object should be created
# was already enforced by salt, and /etc/kubernetes/addons is the
# managed result is of that. Start everything below that directory.
echo "== Kubernetes addon manager started at $(date -Is) =="
for obj in $(find /etc/kubernetes/addons -name \*.yaml); do
${KUBECTL} create -f ${obj} &
echo "++ addon ${obj} started in pid $! ++"
done
noerrors="true"
for pid in $(jobs -p); do
wait ${pid} || noerrors="false"
echo "++ pid ${pid} complete ++"
done
if [ ${noerrors} == "true" ]; then
echo "== Kubernetes addon manager completed successfully at $(date -Is) =="
else
echo "== Kubernetes addon manager completed with errors at $(date -Is) =="
fi
# We stay around so that status checks by salt make it look like
# the service is good. (We could do this is other ways, but this
# is simple.)
sleep infinity
}
#
# Function that starts the daemon/service
#
do_start()
{
addon_manager_async </dev/null >>${DAEMON_LOG_FILE} 2>&1 &
echo $! > ${PIDFILE}
disown
}
#
# Function that stops the daemon/service
#
do_stop()
{
kill $(cat ${PIDFILE})
rm ${PIDFILE}
return
}
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) log_end_msg 0 || exit 0 ;;
2) log_end_msg 1 || exit 1 ;;
esac
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) log_end_msg 0 ;;
2) exit 1 ;;
esac
;;
status)
if [ ! -e ${PIDFILE} ]; then
exit 1
fi
pid=$(cat ${PIDFILE})
# Checks that ${pid} is running AND is us.
ps --no-headers ${pid} | grep ${SCRIPTNAME} > /dev/null || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Kubernetes Addon Object Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
[Service]
ExecStart=/usr/lib/systemd/scripts/kube-addons start
[Install]
WantedBy=multi-user.target

View File

@@ -36,6 +36,7 @@ base:
- nginx
- kube-client-tools
- logrotate
- kube-addons
{% if grains['cloud'] is defined and grains['cloud'] == 'azure' %}
- openvpn
{% endif %}

View File

@@ -82,10 +82,14 @@ mkdir -p /srv/salt-overlay/pillar
cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
portal_net: '$(echo "$PORTAL_NET" | sed -e "s/'/''/g")'
cert_ip: '$(echo "$MASTER_IP" | sed -e "s/'/''/g")'
enable_cluster_monitoring: '$(echo "$ENABLE_CLUSTER_MONITORING" | sed -e "s/'/''/g")'
enable_node_monitoring: '$(echo "$ENABLE_NODE_MONITORING" | sed -e "s/'/''/g")'
enable_cluster_logging: '$(echo "$ENABLE_CLUSTER_LOGGING" | sed -e "s/'/''/g")'
enable_node_logging: '$(echo "$ENABLE_NODE_LOGGING" | sed -e "s/'/''/g")'
logging_destination: '$(echo "$LOGGING_DESTINATION" | sed -e "s/'/''/g")'
elasticsearch_replicas: '$(echo "$ELASTICSEARCH_LOGGING_REPLICAS" | sed -e "s/'/''/g")'
enable_cluster_dns: '$(echo "$ENABLE_CLUSTER_DNS" | sed -e "s/'/''/g")'
dns_replicas: '$(echo "$DNS_REPLICAS" | sed -e "s/'/''/g")'
dns_server: '$(echo "$DNS_SERVER_IP" | sed -e "s/'/''/g")'
dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")'
EOF

View File

@@ -261,11 +261,11 @@ function restart-kube-proxy {
ssh-to-node "$1" "sudo systemctl restart kube-proxy"
}
function setup-monitoring {
echo "TODO"
function setup-monitoring-firewall {
echo "TODO"
}
function teardown-monitoring {
function teardown-monitoring-firewall {
echo "TODO"
}
@@ -274,10 +274,10 @@ function prepare-e2e() {
echo "Vagrant doesn't need special preparations for e2e tests"
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}
}

View File

@@ -22,10 +22,14 @@ mkdir -p /srv/salt-overlay/pillar
cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
node_instance_prefix: $NODE_INSTANCE_PREFIX
portal_net: $PORTAL_NET
enable_cluster_monitoring: $ENABLE_CLUSTER_MONITORING
enable_node_monitoring: $ENABLE_NODE_MONITORING
enable_cluster_logging: $ENABLE_CLUSTER_LOGGING
enable_node_logging: $ENABLE_NODE_LOGGING
logging_destination: $LOGGING_DESTINATION
elasticsearch_replicas: $ELASTICSEARCH_LOGGING_REPLICAS
enable_cluster_dns: $ENABLE_CLUSTER_DNS
dns_replicas: $DNS_REPLICAS
dns_server: $DNS_SERVER_IP
dns_domain: $DNS_DOMAIN
EOF

View File

@@ -478,18 +478,18 @@ function test-teardown {
echo "TODO"
}
function setup-monitoring {
function setup-monitoring-firewall {
echo "TODO"
}
function teardown-monitoring {
function teardown-monitoring-firewall {
echo "TODO"
}
function setup-logging {
function setup-logging-firewall {
echo "TODO: setup logging"
}
function teardown-logging {
function teardown-logging-firewall {
echo "TODO: teardown logging"
}

View File

@@ -29,8 +29,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -65,8 +65,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-p, --port=8001: The port on which to run the proxy
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
@@ -122,9 +122,9 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--namespace="": If present, the namespace scope for this CLI request.
--no-headers=false: When using the default output, don't print headers
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-o, --output="": Output format: json|yaml|template|templatefile
--output-version="": Output the formatted object with the given version (default api-version)
-l, --selector="": Selector (label query) to filter on
@@ -167,8 +167,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -213,8 +213,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -262,8 +262,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
--patch="": A JSON document to override the existing resource. The resource is downloaded, then patched with the JSON, the updated
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
@@ -322,8 +322,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-l, --selector="": Selector (label query) to filter on
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
@@ -370,8 +370,8 @@ Available Commands:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -394,6 +394,7 @@ Additional help topics:
kubectl rollingupdate Perform a rolling update of the given ReplicationController
kubectl resize Set a new size for a resizable resource (currently only Replication Controllers)
kubectl run-container Run a particular image on the cluster.
kubectl stop Gracefully shutdown a resource
Use "kubectl help [command]" for more information about that command.
```
@@ -425,8 +426,8 @@ Usage:
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
--merge=false: merge together the full hierarchy of .kubeconfig files
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -466,8 +467,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
--server="": server for the cluster entry in .kubeconfig
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -507,8 +508,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": token for the user entry in .kubeconfig
@@ -549,7 +550,7 @@ Usage:
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
--namespace="": namespace for the context entry in .kubeconfig
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -591,8 +592,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -632,8 +633,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -670,8 +671,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -714,8 +715,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -757,8 +758,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
@@ -805,8 +806,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
--poll-interval="3s": Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
@@ -859,8 +860,8 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
--replicas=-1: The new number desired number of replicas. Required.
--resource-version="": Precondition for resource version. Requires that the current resource version match this value in order to resize
-s, --server="": The address of the Kubernetes API server
@@ -915,9 +916,9 @@ Usage:
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
-n, --namespace="": If present, the namespace scope for this CLI request.
--namespace="": If present, the namespace scope for this CLI request.
--no-headers=false: When using the default output, don't print headers
--ns-path="/home/username/.kubernetes_ns": Path to the namespace info file that holds the namespace context to use for CLI requests.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-o, --output="": Output format: json|yaml|template|templatefile
--output-version="": Output the formatted object with the given version (default api-version)
--overrides="": An inline JSON override for the generated object. If this is non-empty, it is parsed used to override the generated object. Requires that the object supply a valid apiVersion field.
@@ -933,3 +934,46 @@ Usage:
```
#### stop
Gracefully shutdown a resource
Attempts to shutdown and delete a resource that supports graceful termination.
If the resource is resizable it will be resized to 0 before deletion.
Examples:
$ kubectl stop replicationcontroller foo
foo stopped
Usage:
```
kubectl stop <resource> <id> [flags]
Available Flags:
--alsologtostderr=false: log to standard error as well as files
--api-version="": The API version to use when talking to the server
-a, --auth-path="": Path to the auth info file. If missing, prompt the user. Only used if using https.
--certificate-authority="": Path to a cert. file for the certificate authority.
--client-certificate="": Path to a client key file for TLS.
--client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context to use
--insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
--kubeconfig="": Path to the kubeconfig file to use for CLI requests.
--log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
--log_dir=: If non-empty, write log files in this directory
--log_flush_frequency=5s: Maximum number of seconds between log flushes
--logtostderr=true: log to standard error instead of files
--match-server-version=false: Require server version to match client version
--namespace="": If present, the namespace scope for this CLI request.
--ns-path="": Path to the namespace info file that holds the namespace context to use for CLI requests.
-s, --server="": The address of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
--user="": The name of the kubeconfig user to use
--v=0: log level for V logs
--validate=false: If true, use a schema to validate the input before sending it
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
```

View File

@@ -83,6 +83,8 @@ type Context struct {
AuthInfo string `json:"user"`
// Namespace is the default namespace to use on unspecified requests
Namespace string `json:"namespace,omitempty"`
// NamespacePath is the path to a kubernetes ns file (~/.kubernetes_ns)
NamespacePath string `json:"namespace-path,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
}

View File

@@ -83,6 +83,8 @@ type Context struct {
AuthInfo string `json:"user"`
// Namespace is the default namespace to use on unspecified requests
Namespace string `json:"namespace,omitempty"`
// NamespacePath is the path to a kubernetes ns file (~/.kubernetes_ns)
NamespacePath string `json:"namespace-path,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `json:"extensions,omitempty"`
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
clientcmdapi "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
)
@@ -36,9 +37,12 @@ var (
// ClientConfig is used to make it easy to get an api server client
type ClientConfig interface {
// RawConfig returns the merged result of all overrides
RawConfig() (clientcmdapi.Config, error)
// ClientConfig returns a complete client config
ClientConfig() (*client.Config, error)
// Namespace returns the namespace resulting from the merged result of all overrides
Namespace() (string, error)
}
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
@@ -226,6 +230,35 @@ func canIdentifyUser(config client.Config) bool {
}
// Namespace implements KubeConfig
func (config DirectClientConfig) Namespace() (string, error) {
if err := config.ConfirmUsable(); err != nil {
return "", err
}
configContext := config.getContext()
if len(configContext.Namespace) != 0 {
return configContext.Namespace, nil
}
if len(configContext.NamespacePath) != 0 {
nsInfo, err := kubectl.LoadNamespaceInfo(configContext.NamespacePath)
if err != nil {
return "", err
}
return nsInfo.Namespace, nil
}
// if nothing was specified, try the default file
nsInfo, err := kubectl.LoadNamespaceInfo(os.Getenv("HOME") + "/.kubernetes_ns")
if err != nil {
return "", err
}
return nsInfo.Namespace, nil
}
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
func (config DirectClientConfig) ConfirmUsable() error {
@@ -248,21 +281,30 @@ func (config DirectClientConfig) getContextName() string {
}
func (config DirectClientConfig) getAuthInfoName() string {
if len(config.overrides.AuthInfoName) != 0 {
return config.overrides.AuthInfoName
if len(config.overrides.Context.AuthInfo) != 0 {
return config.overrides.Context.AuthInfo
}
return config.getContext().AuthInfo
}
func (config DirectClientConfig) getClusterName() string {
if len(config.overrides.ClusterName) != 0 {
return config.overrides.ClusterName
if len(config.overrides.Context.Cluster) != 0 {
return config.overrides.Context.Cluster
}
return config.getContext().Cluster
}
func (config DirectClientConfig) getContext() clientcmdapi.Context {
return config.config.Contexts[config.getContextName()]
contexts := config.config.Contexts
contextName := config.getContextName()
var mergedContext clientcmdapi.Context
if configContext, exists := contexts[contextName]; exists {
mergo.Merge(&mergedContext, configContext)
}
mergo.Merge(&mergedContext, config.overrides.Context)
return mergedContext
}
func (config DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo {

View File

@@ -48,6 +48,24 @@ func createValidTestConfig() *clientcmdapi.Config {
return config
}
func TestMergeContext(t *testing.T) {
const namespace = "overriden-namespace"
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
Context: clientcmdapi.Context{
Namespace: namespace,
},
})
actual, err := clientBuilder.Namespace()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
matchStringArg(namespace, actual, t)
}
func TestCreateClean(t *testing.T) {
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{})

View File

@@ -78,3 +78,13 @@ func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error)
return mergedClientConfig.ClientConfig()
}
// Namespace implements KubeConfig
func (config DeferredLoadingClientConfig) Namespace() (string, error) {
mergedKubeConfig, err := config.createClientConfig()
if err != nil {
return "", err
}
return mergedKubeConfig.Namespace()
}

View File

@@ -27,10 +27,8 @@ import (
type ConfigOverrides struct {
AuthInfo clientcmdapi.AuthInfo
ClusterInfo clientcmdapi.Cluster
Namespace string
Context clientcmdapi.Context
CurrentContext string
ClusterName string
AuthInfoName string
}
// ConfigOverrideFlags holds the flag names to be used for binding command line flags. Notice that this structure tightly
@@ -38,10 +36,8 @@ type ConfigOverrides struct {
type ConfigOverrideFlags struct {
AuthOverrideFlags AuthOverrideFlags
ClusterOverrideFlags ClusterOverrideFlags
Namespace string
ContextOverrideFlags ContextOverrideFlags
CurrentContext string
ClusterName string
AuthInfoName string
}
// AuthOverrideFlags holds the flag names to be used for binding command line flags for AuthInfo objects
@@ -53,6 +49,14 @@ type AuthOverrideFlags struct {
Token string
}
// ContextOverrideFlags holds the flag names to be used for binding command line flags for Cluster objects
type ContextOverrideFlags struct {
ClusterName string
AuthInfoName string
Namespace string
NamespacePath string
}
// ClusterOverride holds the flag names to be used for binding command line flags for Cluster objects
type ClusterOverrideFlags struct {
APIServer string
@@ -63,18 +67,19 @@ type ClusterOverrideFlags struct {
}
const (
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagAPIVersion = "api-version"
FlagAuthPath = "auth-path"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagBearerToken = "token"
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagNamespacePath = "ns-path"
FlagAPIServer = "server"
FlagAPIVersion = "api-version"
FlagAuthPath = "auth-path"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagBearerToken = "token"
)
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
@@ -102,10 +107,18 @@ func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags {
return ConfigOverrideFlags{
AuthOverrideFlags: RecommendedAuthOverrideFlags(prefix),
ClusterOverrideFlags: RecommendedClusterOverrideFlags(prefix),
Namespace: prefix + FlagNamespace,
ContextOverrideFlags: RecommendedContextOverrideFlags(prefix),
CurrentContext: prefix + FlagContext,
ClusterName: prefix + FlagClusterName,
AuthInfoName: prefix + FlagAuthInfoName,
}
}
// RecommendedContextOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
func RecommendedContextOverrideFlags(prefix string) ContextOverrideFlags {
return ContextOverrideFlags{
ClusterName: prefix + FlagClusterName,
AuthInfoName: prefix + FlagAuthInfoName,
Namespace: prefix + FlagNamespace,
NamespacePath: prefix + FlagNamespacePath,
}
}
@@ -139,9 +152,14 @@ func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, f
func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNames ConfigOverrideFlags) {
BindAuthInfoFlags(&overrides.AuthInfo, flags, flagNames.AuthOverrideFlags)
BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags)
// TODO not integrated yet
// flags.StringVar(&overrides.Namespace, flagNames.Namespace, "", "If present, the namespace scope for this CLI request.")
BindContextFlags(&overrides.Context, flags, flagNames.ContextOverrideFlags)
flags.StringVar(&overrides.CurrentContext, flagNames.CurrentContext, "", "The name of the kubeconfig context to use")
flags.StringVar(&overrides.ClusterName, flagNames.ClusterName, "", "The name of the kubeconfig cluster to use")
flags.StringVar(&overrides.AuthInfoName, flagNames.AuthInfoName, "", "The name of the kubeconfig user to use")
}
// BindFlags is a convenience method to bind the specified flags to their associated variables
func BindContextFlags(contextInfo *clientcmdapi.Context, flags *pflag.FlagSet, flagNames ContextOverrideFlags) {
flags.StringVar(&contextInfo.Cluster, flagNames.ClusterName, "", "The name of the kubeconfig cluster to use")
flags.StringVar(&contextInfo.AuthInfo, flagNames.AuthInfoName, "", "The name of the kubeconfig user to use")
flags.StringVar(&contextInfo.Namespace, flagNames.Namespace, "", "If present, the namespace scope for this CLI request.")
flags.StringVar(&contextInfo.NamespacePath, flagNames.NamespacePath, "", "Path to the namespace info file that holds the namespace context to use for CLI requests.")
}

View File

@@ -64,8 +64,12 @@ type Factory struct {
Printer func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error)
// Returns a Resizer for changing the size of the specified RESTMapping type or an error
Resizer func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Resizer, error)
// Returns a Reaper for gracefully shutting down resources.
Reaper func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Reaper, error)
// Returns a schema that can validate objects stored on disk.
Validator func(*cobra.Command) (validation.Schema, error)
// Returns the default namespace to use in cases where no other namespace is specified
DefaultNamespace func(cmd *cobra.Command) (string, error)
}
// NewFactory creates a factory with the default Kubernetes resources defined
@@ -84,8 +88,11 @@ func NewFactory() *Factory {
flags: flags,
Object: func(cmd *cobra.Command) (meta.RESTMapper, runtime.ObjectTyper) {
version := GetFlagString(cmd, "api-version")
return kubectl.OutputVersionMapper{mapper, version}, api.Scheme
cfg, err := clientConfig.ClientConfig()
checkErr(err)
cmdApiVersion := cfg.Version
return kubectl.OutputVersionMapper{mapper, cmdApiVersion}, api.Scheme
},
Client: func(cmd *cobra.Command) (*client.Client, error) {
return clients.ClientForVersion("")
@@ -119,11 +126,14 @@ func NewFactory() *Factory {
if err != nil {
return nil, err
}
resizer, ok := kubectl.ResizerFor(mapping.Kind, client)
if !ok {
return nil, fmt.Errorf("no resizer has been implemented for %q", mapping.Kind)
return kubectl.ResizerFor(mapping.Kind, client)
},
Reaper: func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.Reaper, error) {
client, err := clients.ClientForVersion(mapping.APIVersion)
if err != nil {
return nil, err
}
return resizer, nil
return kubectl.ReaperFor(mapping.Kind, client)
},
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
if GetFlagBool(cmd, "validate") {
@@ -135,6 +145,9 @@ func NewFactory() *Factory {
}
return validation.NullSchema{}, nil
},
DefaultNamespace: func(cmd *cobra.Command) (string, error) {
return clientConfig.Namespace()
},
}
}
@@ -158,8 +171,6 @@ func (f *Factory) BindFlags(flags *pflag.FlagSet) {
// TODO Add a verbose flag that turns on glog logging. Probably need a way
// to do that automatically for every subcommand.
flags.BoolVar(&f.clients.matchVersion, FlagMatchBinaryVersion, false, "Require server version to match client version")
flags.String("ns-path", os.Getenv("HOME")+"/.kubernetes_ns", "Path to the namespace info file that holds the namespace context to use for CLI requests.")
flags.StringP("namespace", "n", "", "If present, the namespace scope for this CLI request.")
flags.Bool("validate", false, "If true, use a schema to validate the input before sending it")
}
@@ -193,6 +204,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`,
cmds.AddCommand(f.NewCmdResize(out))
cmds.AddCommand(f.NewCmdRunContainer(out))
cmds.AddCommand(f.NewCmdStop(out))
return cmds
}
@@ -269,38 +281,6 @@ func runHelp(cmd *cobra.Command, args []string) {
cmd.Help()
}
// GetKubeNamespace returns the value of the namespace a
// user provided on the command line or use the default
// namespace.
func GetKubeNamespace(cmd *cobra.Command) string {
result := api.NamespaceDefault
if ns := GetFlagString(cmd, "namespace"); len(ns) > 0 {
result = ns
glog.V(2).Infof("Using namespace from -ns flag")
} else {
nsPath := GetFlagString(cmd, "ns-path")
nsInfo, err := kubectl.LoadNamespaceInfo(nsPath)
if err != nil {
glog.Fatalf("Error loading current namespace: %v", err)
}
result = nsInfo.Namespace
}
glog.V(2).Infof("Using namespace %s", result)
return result
}
// GetExplicitKubeNamespace returns the value of the namespace a
// user explicitly provided on the command line, or false if no
// such namespace was specified.
func GetExplicitKubeNamespace(cmd *cobra.Command) (string, bool) {
if ns := GetFlagString(cmd, "namespace"); len(ns) > 0 {
return ns, true
}
// TODO: determine when --ns-path is set but equal to the default
// value and return its value and true.
return "", false
}
type clientSwaggerSchema struct {
c *client.Client
t runtime.ObjectTyper

View File

@@ -26,6 +26,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
. "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
@@ -92,13 +93,15 @@ func (t *testDescriber) Describe(namespace, name string) (output string, err err
}
type testFactory struct {
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client kubectl.RESTClient
Describer kubectl.Describer
Printer kubectl.ResourcePrinter
Validator validation.Schema
Err error
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client kubectl.RESTClient
Describer kubectl.Describer
Printer kubectl.ResourcePrinter
Validator validation.Schema
Namespace string
ClientConfig *client.Config
Err error
}
func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
@@ -124,6 +127,12 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func(cmd *cobra.Command) (string, error) {
return t.Namespace, t.Err
},
ClientConfig: func(cmd *cobra.Command) (*client.Config, error) {
return t.ClientConfig, t.Err
},
}, t, codec
}
@@ -147,6 +156,12 @@ func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) {
Validator: func(cmd *cobra.Command) (validation.Schema, error) {
return t.Validator, t.Err
},
DefaultNamespace: func(cmd *cobra.Command) (string, error) {
return t.Namespace, t.Err
},
ClientConfig: func(cmd *cobra.Command) (*client.Config, error) {
return t.ClientConfig, t.Err
},
}, t, latest.Codec
}

View File

@@ -47,10 +47,13 @@ Examples:
schema, err := f.Validator(cmd)
checkErr(err)
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)).
ContinueOnError().
NamespaceParam(GetKubeNamespace(cmd)).RequireNamespace().
NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(flags.Filenames...).
Flatten().
Do()

View File

@@ -41,10 +41,10 @@ func TestCreateObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdCreate(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Run(cmd, []string{})
@@ -73,10 +73,10 @@ func TestCreateMultipleObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdCreate(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
@@ -107,10 +107,10 @@ func TestCreateDirectory(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdCreate(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook")
cmd.Run(cmd, []string{})

View File

@@ -58,10 +58,13 @@ Examples:
$ kubectl delete pod 1234-56-7890-234234-456456
<delete a pod with ID 1234-56-7890-234234-456456>`,
Run: func(cmd *cobra.Command, args []string) {
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
mapper, typer := f.Object(cmd)
r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)).
ContinueOnError().
NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(flags.Filenames...).
SelectorParam(GetFlagString(cmd, "selector")).
ResourceTypeOrNameArgs(args...).
@@ -69,7 +72,7 @@ Examples:
Do()
found := 0
err := r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error {
err = r.IgnoreErrors(errors.IsNotFound).Visit(func(r *resource.Info) error {
found++
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
return err

View File

@@ -43,10 +43,10 @@ func TestDeleteObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Run(cmd, []string{})
@@ -71,10 +71,10 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Run(cmd, []string{})
@@ -98,12 +98,12 @@ func TestDeleteNoObjects(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
stderr := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.SetOutput(stderr)
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"pods"})
if buf.String() != "" {
@@ -133,10 +133,10 @@ func TestDeleteMultipleObject(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
@@ -165,10 +165,10 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master.json")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.json")
cmd.Run(cmd, []string{})
@@ -199,10 +199,10 @@ func TestDeleteDirectory(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("filename", "../../../examples/guestbook")
cmd.Run(cmd, []string{})
@@ -240,10 +240,10 @@ func TestDeleteMultipleSelector(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDelete(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})

View File

@@ -32,8 +32,11 @@ func (f *Factory) NewCmdDescribe(out io.Writer) *cobra.Command {
This command joins many API calls together to form a detailed description of a
given resource.`,
Run: func(cmd *cobra.Command, args []string) {
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
mapper, _ := f.Object(cmd)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace)
describer, err := f.Describer(cmd, mapping)
checkErr(err)

View File

@@ -34,14 +34,13 @@ func TestDescribeUnknownSchemaObject(t *testing.T) {
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &internalType{Name: "foo"})},
}
tf.Namespace = "non-default"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdDescribe(buf)
cmd.Flags().String("api-version", "default", "")
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"type", "foo"})
if d.Name != "foo" || d.Namespace != "test" {
if d.Name != "foo" || d.Namespace != "non-default" {
t.Errorf("unexpected describer: %#v", d)
}

View File

@@ -73,11 +73,14 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
selector := GetFlagString(cmd, "selector")
mapper, typer := f.Object(cmd)
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
// handle watch separately since we cannot watch multiple resource types
isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only")
if isWatch || isWatchOnly {
r := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)).
NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace().
NamespaceParam(cmdNamespace).DefaultNamespace().
SelectorParam(selector).
ResourceTypeOrNameArgs(args...).
SingleResourceType().
@@ -113,7 +116,7 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
}
b := resource.NewBuilder(mapper, typer, ClientMapperForCommand(cmd, f)).
NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace().
NamespaceParam(cmdNamespace).DefaultNamespace().
SelectorParam(selector).
ResourceTypeOrNameArgs(args...).
Latest()
@@ -121,8 +124,12 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
checkErr(err)
if generic {
clientConfig, err := f.ClientConfig(cmd)
checkErr(err)
defaultVersion := clientConfig.Version
// the outermost object will be converted to the output-version
version := outputVersion(cmd)
version := outputVersion(cmd, defaultVersion)
if len(version) == 0 {
// TODO: add a new ResourceBuilder mode for Object() that attempts to ensure the objects
// are in the appropriate version if one exists (and if not, use the best effort).

View File

@@ -69,12 +69,12 @@ func TestGetUnknownSchemaObject(t *testing.T) {
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &internalType{Name: "foo"})},
}
tf.Namespace = "test"
tf.ClientConfig = &client.Config{Version: latest.Version}
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("api-version", "default", "")
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"type", "foo"})
expected := &internalType{Name: "foo"}
@@ -98,11 +98,11 @@ func TestGetSchemaObject(t *testing.T) {
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})},
}
tf.Namespace = "test"
tf.ClientConfig = &client.Config{Version: "v1beta3"}
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.Flags().String("api-version", "v1beta3", "")
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"replicationcontrollers", "foo"})
if !strings.Contains(buf.String(), "\"foo\"") {
@@ -119,11 +119,11 @@ func TestGetObjects(t *testing.T) {
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"pods", "foo"})
expected := []runtime.Object{&pods.Items[0]}
@@ -145,11 +145,11 @@ func TestGetListObjects(t *testing.T) {
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, pods)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"pods"})
expected := []runtime.Object{pods}
@@ -181,11 +181,11 @@ func TestGetMultipleTypeObjects(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"pods,services"})
expected := []runtime.Object{pods, svc}
@@ -217,12 +217,12 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
}
}),
}
tf.Namespace = "test"
tf.ClientConfig = &client.Config{Version: "v1beta1"}
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().String("api-version", "v1beta1", "")
cmd.Flags().Set("output", "json")
cmd.Run(cmd, []string{"pods,services"})
@@ -269,11 +269,11 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
@@ -345,11 +345,11 @@ func TestWatchSelector(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("watch", "true")
cmd.Flags().Set("selector", "a=b")
@@ -384,11 +384,11 @@ func TestWatchResource(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("watch", "true")
cmd.Run(cmd, []string{"pods", "foo"})
@@ -422,11 +422,11 @@ func TestWatchOnlyResource(t *testing.T) {
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.SetOutput(buf)
cmd.Flags().String("namespace", "test", "")
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods", "foo"})

View File

@@ -43,7 +43,8 @@ Examples:
usageError(cmd, "log <pod> [<container>]")
}
namespace := GetKubeNamespace(cmd)
namespace, err := f.DefaultNamespace(cmd)
checkErr(err)
client, err := f.Client(cmd)
checkErr(err)

View File

@@ -56,10 +56,10 @@ func PrintObject(cmd *cobra.Command, obj runtime.Object, f *Factory, out io.Writ
}
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
func outputVersion(cmd *cobra.Command) string {
func outputVersion(cmd *cobra.Command, defaultVersion string) string {
outputVersion := GetFlagString(cmd, "output-version")
if len(outputVersion) == 0 {
outputVersion = GetFlagString(cmd, "api-version")
outputVersion = defaultVersion
}
return outputVersion
}
@@ -84,7 +84,11 @@ func PrinterForMapping(f *Factory, cmd *cobra.Command, mapping *meta.RESTMapping
return nil, err
}
if ok {
version := outputVersion(cmd)
clientConfig, err := f.ClientConfig(cmd)
checkErr(err)
defaultVersion := clientConfig.Version
version := outputVersion(cmd, defaultVersion)
if len(version) == 0 {
version = mapping.APIVersion
}

View File

@@ -48,8 +48,12 @@ Examples:
if len(args) != 2 || count < 0 {
usageError(cmd, "--replicas=<count> <resource> <id>")
}
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
mapper, _ := f.Object(cmd)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace)
resizer, err := f.Resizer(cmd, mapping)
checkErr(err)

View File

@@ -41,18 +41,20 @@ func ResourcesFromArgsOrFile(
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (resource.RESTClient, error),
schema validation.Schema,
requireNames bool,
cmdNamespace,
cmdVersion string,
) resource.Visitor {
// handling filename & resource id
if len(selector) == 0 {
if requireNames || len(filename) > 0 {
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema)
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema, cmdNamespace, cmdVersion)
client, err := clientBuilder(cmd, mapping)
checkErr(err)
return resource.NewInfo(client, mapping, namespace, name)
}
if len(args) == 2 {
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, mapper)
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, mapper, cmdNamespace, cmdVersion)
client, err := clientBuilder(cmd, mapping)
checkErr(err)
return resource.NewInfo(client, mapping, namespace, name)
@@ -62,7 +64,7 @@ func ResourcesFromArgsOrFile(
labelSelector, err := labels.ParseSelector(selector)
checkErr(err)
namespace := GetKubeNamespace(cmd)
namespace := cmdNamespace
visitors := resource.VisitorList{}
if len(args) < 1 {
@@ -94,7 +96,7 @@ func ResourcesFromArgsOrFile(
// ResourceFromArgsOrFile expects two arguments or a valid file with a given type, and extracts
// the fields necessary to uniquely locate a resource. Displays a usageError if that contract is
// not satisfied, or a generic error if any other problems occur.
func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema) (mapping *meta.RESTMapping, namespace, name string) {
func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdNamespace, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string) {
// If command line args are passed in, use those preferentially.
if len(args) > 0 && len(args) != 2 {
usageError(cmd, "If passing in command line parameters, must be resource and name")
@@ -102,7 +104,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
if len(args) == 2 {
resource := args[0]
namespace = GetKubeNamespace(cmd)
namespace = cmdNamespace
name = args[1]
if len(name) == 0 || len(resource) == 0 {
usageError(cmd, "Must specify filename or command line params")
@@ -113,8 +115,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
// The error returned by mapper is "no resource defined", which is a usage error
usageError(cmd, err.Error())
}
version := GetFlagString(cmd, "api-version")
mapping, err = mapper.RESTMapping(kind, version, defaultVersion)
mapping, err = mapper.RESTMapping(kind, cmdVersion, defaultVersion)
checkErr(err)
return
}
@@ -123,7 +124,7 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
usageError(cmd, "Must specify filename or command line params")
}
mapping, namespace, name, _ = ResourceFromFile(cmd, filename, typer, mapper, schema)
mapping, namespace, name, _ = ResourceFromFile(filename, typer, mapper, schema, cmdVersion)
if len(name) == 0 {
checkErr(fmt.Errorf("the resource in the provided file has no name (or ID) defined"))
}
@@ -134,13 +135,13 @@ func ResourceFromArgsOrFile(cmd *cobra.Command, args []string, filename string,
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
// a generic error if any other problems occur.
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace string) (mapping *meta.RESTMapping, namespace, name string) {
if len(args) != 2 {
usageError(cmd, "Must provide resource and name command line params")
}
resource := args[0]
namespace = GetKubeNamespace(cmd)
namespace = cmdNamespace
name = args[1]
if len(name) == 0 || len(resource) == 0 {
usageError(cmd, "Must provide resource and name command line params")
@@ -157,7 +158,7 @@ func ResourceFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper)
// ResourceFromArgs expects two arguments with a given type, and extracts the fields necessary
// to uniquely locate a resource. Displays a usageError if that contract is not satisfied, or
// a generic error if any other problems occur.
func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper) (mapping *meta.RESTMapping, namespace, name string) {
func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTMapper, cmdNamespace, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string) {
if len(args) == 0 || len(args) > 2 {
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
@@ -167,7 +168,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM
usageError(cmd, "Must provide resource or a resource and name as command line params")
}
namespace = GetKubeNamespace(cmd)
namespace = cmdNamespace
if len(args) == 2 {
name = args[1]
if len(name) == 0 {
@@ -178,8 +179,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM
defaultVersion, kind, err := mapper.VersionAndKindForResource(resource)
checkErr(err)
version := GetFlagString(cmd, "api-version")
mapping, err = mapper.RESTMapping(kind, version, defaultVersion)
mapping, err = mapper.RESTMapping(kind, cmdVersion, defaultVersion)
checkErr(err)
return
@@ -188,7 +188,7 @@ func ResourceOrTypeFromArgs(cmd *cobra.Command, args []string, mapper meta.RESTM
// ResourceFromFile retrieves the name and namespace from a valid file. If the file does not
// resolve to a known type an error is returned. The returned mapping can be used to determine
// the correct REST endpoint to modify this resource with.
func ResourceFromFile(cmd *cobra.Command, filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
func ResourceFromFile(filename string, typer runtime.ObjectTyper, mapper meta.RESTMapper, schema validation.Schema, cmdVersion string) (mapping *meta.RESTMapping, namespace, name string, data []byte) {
configData, err := ReadConfigData(filename)
checkErr(err)
data = configData
@@ -218,20 +218,18 @@ func ResourceFromFile(cmd *cobra.Command, filename string, typer runtime.ObjectT
checkErr(err)
// if the preferred API version differs, get a different mapper
version := GetFlagString(cmd, "api-version")
if version != objVersion {
mapping, err = mapper.RESTMapping(kind, version)
if cmdVersion != objVersion {
mapping, err = mapper.RESTMapping(kind, cmdVersion)
checkErr(err)
}
return
}
// CompareNamespaceFromFile returns an error if the namespace the user has provided on the CLI
// CompareNamespace returns an error if the namespace the user has provided on the CLI
// or via the default namespace file does not match the namespace of an input file. This
// prevents a user from unintentionally updating the wrong namespace.
func CompareNamespaceFromFile(cmd *cobra.Command, namespace string) error {
defaultNamespace := GetKubeNamespace(cmd)
func CompareNamespace(defaultNamespace, namespace string) error {
if len(namespace) > 0 {
if defaultNamespace != namespace {
return fmt.Errorf("the namespace from the provided file %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", namespace, defaultNamespace, namespace)

View File

@@ -61,8 +61,13 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
oldName := args[0]
schema, err := f.Validator(cmd)
checkErr(err)
clientConfig, err := f.ClientConfig(cmd)
checkErr(err)
cmdApiVersion := clientConfig.Version
mapper, typer := f.Object(cmd)
mapping, namespace, newName, data := ResourceFromFile(cmd, filename, typer, mapper, schema)
mapping, namespace, newName, data := ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion)
if mapping.Kind != "ReplicationController" {
usageError(cmd, "%s does not specify a valid ReplicationController", filename)
}
@@ -70,7 +75,10 @@ $ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
usageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
filename, oldName)
}
err = CompareNamespaceFromFile(cmd, namespace)
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
err = CompareNamespace(cmdNamespace, namespace)
checkErr(err)
client, err := f.Client(cmd)

View File

@@ -49,7 +49,9 @@ Examples:
usageError(cmd, "<name> is required for run")
}
namespace := GetKubeNamespace(cmd)
namespace, err := f.DefaultNamespace(cmd)
checkErr(err)
client, err := f.Client(cmd)
checkErr(err)

56
pkg/kubectl/cmd/stop.go Normal file
View File

@@ -0,0 +1,56 @@
/*
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 cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
)
func (f *Factory) NewCmdStop(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "stop <resource> <id>",
Short: "Gracefully shutdown a resource",
Long: `Gracefully shutdown a resource
Attempts to shutdown and delete a resource that supports graceful termination.
If the resource is resizable it will be resized to 0 before deletion.
Examples:
$ kubectl stop replicationcontroller foo
foo stopped
`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
usageError(cmd, "<resource> <id>")
}
cmdNamespace, err := f.DefaultNamespace(cmd)
mapper, _ := f.Object(cmd)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace)
reaper, err := f.Reaper(cmd, mapping)
checkErr(err)
s, err := reaper.Stop(namespace, name)
checkErr(err)
fmt.Fprintf(out, "%s\n", s)
},
}
return cmd
}

View File

@@ -56,6 +56,7 @@ Examples:
} else {
name = updateWithPatch(cmd, args, f, patch)
}
fmt.Fprintf(out, "%s\n", name)
},
}
@@ -65,8 +66,11 @@ Examples:
}
func updateWithPatch(cmd *cobra.Command, args []string, f *Factory, patch string) string {
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
mapper, _ := f.Object(cmd)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper)
mapping, namespace, name := ResourceFromArgs(cmd, args, mapper, cmdNamespace)
client, err := f.RESTClient(cmd, mapping)
checkErr(err)
@@ -89,12 +93,18 @@ func updateWithFile(cmd *cobra.Command, f *Factory, filename string) string {
checkErr(err)
mapper, typer := f.Object(cmd)
mapping, namespace, name, data := ResourceFromFile(cmd, filename, typer, mapper, schema)
clientConfig, err := f.ClientConfig(cmd)
checkErr(err)
cmdApiVersion := clientConfig.Version
mapping, namespace, name, data := ResourceFromFile(filename, typer, mapper, schema, cmdApiVersion)
client, err := f.RESTClient(cmd, mapping)
checkErr(err)
err = CompareNamespaceFromFile(cmd, namespace)
cmdNamespace, err := f.DefaultNamespace(cmd)
checkErr(err)
err = CompareNamespace(cmdNamespace, namespace)
checkErr(err)
err = resource.NewHelper(client, mapping).Update(namespace, name, true, data)

View File

@@ -58,12 +58,12 @@ type Resizer interface {
Resize(namespace, name string, preconditions *ResizePrecondition, newSize uint) (string, error)
}
func ResizerFor(kind string, c *client.Client) (Resizer, bool) {
func ResizerFor(kind string, c client.Interface) (Resizer, error) {
switch kind {
case "ReplicationController":
return &ReplicationControllerResizer{c}, true
return &ReplicationControllerResizer{c}, nil
}
return nil, false
return nil, fmt.Errorf("no resizer has been implemented for %q", kind)
}
type ReplicationControllerResizer struct {

View File

@@ -26,18 +26,18 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
type customFake struct {
type updaterFake struct {
*client.Fake
ctrl client.ReplicationControllerInterface
}
func (c *customFake) ReplicationControllers(namespace string) client.ReplicationControllerInterface {
func (c *updaterFake) ReplicationControllers(namespace string) client.ReplicationControllerInterface {
return c.ctrl
}
func fakeClientFor(namespace string, responses []fakeResponse) client.Interface {
fake := client.Fake{}
return &customFake{
return &updaterFake{
&fake,
&fakeRc{
&client.FakeReplicationControllers{

110
pkg/kubectl/stop.go Normal file
View File

@@ -0,0 +1,110 @@
/*
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 kubectl
import (
"fmt"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
)
const (
interval = time.Second * 3
timeout = time.Minute * 5
)
// A Reaper handles terminating an object as gracefully as possible.
type Reaper interface {
Stop(namespace, name string) (string, error)
}
func ReaperFor(kind string, c client.Interface) (Reaper, error) {
switch kind {
case "ReplicationController":
return &ReplicationControllerReaper{c, interval, timeout}, nil
case "Pod":
return &PodReaper{c}, nil
case "Service":
return &ServiceReaper{c}, nil
}
return nil, fmt.Errorf("no reaper has been implemented for %q", kind)
}
type ReplicationControllerReaper struct {
client.Interface
pollInterval, timeout time.Duration
}
type PodReaper struct {
client.Interface
}
type ServiceReaper struct {
client.Interface
}
type objInterface interface {
Delete(name string) error
Get(name string) (meta.Interface, error)
}
func (reaper *ReplicationControllerReaper) Stop(namespace, name string) (string, error) {
rc := reaper.ReplicationControllers(namespace)
controller, err := rc.Get(name)
if err != nil {
return "", err
}
controller.Spec.Replicas = 0
// TODO: do retry on 409 errors here?
if _, err := rc.Update(controller); err != nil {
return "", err
}
if err := wait.Poll(reaper.pollInterval, reaper.timeout,
client.ControllerHasDesiredReplicas(reaper, controller)); err != nil {
return "", err
}
if err := rc.Delete(name); err != nil {
return "", err
}
return fmt.Sprintf("%s stopped", name), nil
}
func (reaper *PodReaper) Stop(namespace, name string) (string, error) {
pods := reaper.Pods(namespace)
_, err := pods.Get(name)
if err != nil {
return "", err
}
if err := pods.Delete(name); err != nil {
return "", err
}
return fmt.Sprintf("%s stopped", name), nil
}
func (reaper *ServiceReaper) Stop(namespace, name string) (string, error) {
services := reaper.Services(namespace)
_, err := services.Get(name)
if err != nil {
return "", err
}
if err := services.Delete(name); err != nil {
return "", err
}
return fmt.Sprintf("%s stopped", name), nil
}

168
pkg/kubectl/stop_test.go Normal file
View File

@@ -0,0 +1,168 @@
/*
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 kubectl
import (
"fmt"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
func TestReplicationControllerStop(t *testing.T) {
fake := &client.Fake{
Ctrl: api.ReplicationController{
Spec: api.ReplicationControllerSpec{
Replicas: 0,
},
},
}
reaper := ReplicationControllerReaper{fake, time.Millisecond, time.Millisecond}
name := "foo"
s, err := reaper.Stop("default", name)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expected := "foo stopped"
if s != expected {
t.Errorf("expected %s, got %s", expected, s)
}
if len(fake.Actions) != 4 {
t.Errorf("unexpected actions: %v, expected 4 actions (get, update, get, delete)", fake.Actions)
}
for i, action := range []string{"get", "update", "get", "delete"} {
if fake.Actions[i].Action != action+"-controller" {
t.Errorf("unexpected action: %v, expected %s-controller", fake.Actions[i], action)
}
}
}
type noSuchPod struct {
*client.FakePods
}
func (c *noSuchPod) Get(name string) (*api.Pod, error) {
return nil, fmt.Errorf("%s does not exist", name)
}
type noDeleteService struct {
*client.FakeServices
}
func (c *noDeleteService) Delete(service string) error {
return fmt.Errorf("I'm afraid I can't do that, Dave")
}
type reaperFake struct {
*client.Fake
noSuchPod, noDeleteService bool
}
func (c *reaperFake) Pods(namespace string) client.PodInterface {
pods := &client.FakePods{c.Fake, namespace}
if c.noSuchPod {
return &noSuchPod{pods}
}
return pods
}
func (c *reaperFake) Services(namespace string) client.ServiceInterface {
services := &client.FakeServices{c.Fake, namespace}
if c.noDeleteService {
return &noDeleteService{services}
}
return services
}
func TestSimpleStop(t *testing.T) {
tests := []struct {
fake *reaperFake
kind string
actions []string
expectError bool
test string
}{
{
fake: &reaperFake{
Fake: &client.Fake{},
},
kind: "Pod",
actions: []string{"get-pod", "delete-pod"},
expectError: false,
test: "stop pod succeeds",
},
{
fake: &reaperFake{
Fake: &client.Fake{},
},
kind: "Service",
actions: []string{"get-service", "delete-service"},
expectError: false,
test: "stop service succeeds",
},
{
fake: &reaperFake{
Fake: &client.Fake{},
noSuchPod: true,
},
kind: "Pod",
actions: []string{},
expectError: true,
test: "stop pod fails, no pod",
},
{
fake: &reaperFake{
Fake: &client.Fake{},
noDeleteService: true,
},
kind: "Service",
actions: []string{"get-service"},
expectError: true,
test: "stop service fails, can't delete",
},
}
for _, test := range tests {
fake := test.fake
reaper, err := ReaperFor(test.kind, fake)
if err != nil {
t.Errorf("unexpected error: %v (%s)", err, test.test)
}
s, err := reaper.Stop("default", "foo")
if err != nil && !test.expectError {
t.Errorf("unexpected error: %v (%s)", err, test.test)
}
if err == nil {
if test.expectError {
t.Errorf("unexpected non-error: %v (%s)", err, test.test)
}
if s != "foo stopped" {
t.Errorf("unexpected return: %s (%s)", s, test.test)
}
}
if len(test.actions) != len(fake.Actions) {
t.Errorf("unexpected actions: %v; expected %v (%s)", fake.Actions, test.actions, test.test)
}
for i, action := range fake.Actions {
testAction := test.actions[i]
if action.Action != testAction {
t.Errorf("unexpected action: %v; expected %v (%s)", action, testAction, test.test)
}
}
}
}

View File

@@ -168,6 +168,12 @@ func extractFromFile(filename string) (api.BoundPod, error) {
pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:]))
glog.V(5).Infof("Generated UID %q for pod %q from file %s", pod.UID, pod.Name, filename)
}
// This is required for backward compatibility, and should be removed once we
// completely deprecate ContainerManifest.
if len(pod.Name) == 0 {
pod.Name = string(pod.UID)
glog.V(5).Infof("Generated Name %q for UID %q from file %s", pod.Name, pod.UID, filename)
}
if len(pod.Namespace) == 0 {
hasher := adler32.New()
fmt.Fprint(hasher, filename)

View File

@@ -151,6 +151,12 @@ func applyDefaults(pod *api.BoundPod, url string) {
pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:]))
glog.V(5).Infof("Generated UID %q for pod %q from URL %s", pod.UID, pod.Name, url)
}
// This is required for backward compatibility, and should be removed once we
// completely deprecate ContainerManifest.
if len(pod.Name) == 0 {
pod.Name = string(pod.UID)
glog.V(5).Infof("Generate Name %q from UID %q from URL %s", pod.Name, pod.UID, url)
}
if len(pod.Namespace) == 0 {
hasher := adler32.New()
fmt.Fprint(hasher, url)

View File

@@ -138,6 +138,23 @@ func TestExtractFromHTTP(t *testing.T) {
},
}),
},
{
desc: "Single manifest without ID",
manifests: api.ContainerManifest{Version: "v1beta1", UUID: "111"},
expected: CreatePodUpdate(kubelet.SET,
kubelet.HTTPSource,
api.BoundPod{
ObjectMeta: api.ObjectMeta{
UID: "111",
Name: "111",
Namespace: "foobar",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}),
},
{
desc: "Multiple manifests",
manifests: []api.ContainerManifest{

View File

@@ -36,8 +36,8 @@ package version
var (
// TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead.
gitMajor string = "0" // major version, always numeric
gitMinor string = "9.0" // minor version, numeric possibly followed by "+"
gitVersion string = "v0.9.0" // version from git, output of $(git describe)
gitMinor string = "9.1+" // minor version, numeric possibly followed by "+"
gitVersion string = "v0.9.1-dev" // version from git, output of $(git describe)
gitCommit string = "" // sha1 from git, output of $(git rev-parse HEAD)
gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)