├── .gitignore ├── pkg ├── client │ ├── .gitignore │ ├── mocks │ │ ├── dependency.json │ │ ├── pod-definition.json │ │ ├── job-definition.json │ │ └── dependencies.json │ ├── petsets │ │ ├── typed │ │ │ └── apps │ │ │ │ └── v1alpha1 │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── fake │ │ │ │ ├── doc.go │ │ │ │ ├── fake_apps_client.go │ │ │ │ └── fake_petset.go │ │ │ │ ├── doc.go │ │ │ │ └── apps_client.go │ │ └── apis │ │ │ └── apps │ │ │ ├── doc.go │ │ │ ├── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── defaults.go │ │ │ ├── register.go │ │ │ ├── conversion.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── types.go │ │ │ ├── generated.proto │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── install │ │ │ └── install.go │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ └── zz_generated.deepcopy.go │ ├── dependencies.go │ └── resdef.go ├── mocks │ ├── secrets.go │ ├── services.go │ ├── replicasets.go │ ├── jobs.go │ ├── pods.go │ ├── daemonsets.go │ ├── configmaps.go │ ├── persistentvolumeclaims.go │ ├── petsets.go │ ├── deployment.go │ ├── statefulsets.go │ ├── dependencies.go │ ├── counterwithmemo.go │ ├── client.go │ ├── resource.go │ ├── resdefs.go │ └── countingresource.go ├── resources │ ├── daemonset_test.go │ ├── replicaset_test.go │ ├── secrets_test.go │ ├── configmap_test.go │ ├── deployment_test.go │ ├── statefulset_test.go │ ├── secrets.go │ ├── configmap.go │ ├── service_test.go │ ├── job.go │ ├── pod.go │ ├── daemonset.go │ ├── persistentvolumeclaim.go │ └── deployment.go ├── interfaces │ └── interfaces.go └── report │ └── report.go ├── ac-run.sh ├── ac-stop.sh ├── docs └── research │ ├── README.md │ ├── daemonset-dependencies.md │ ├── lcm.md │ └── failure-handling.md ├── run_runit.sh ├── ac_service.sh ├── tests ├── linear │ ├── expected_order.yaml │ ├── dependencies.yaml │ └── definitions.yaml └── README.md ├── .travis.yml ├── examples ├── extended │ ├── secret.yaml │ ├── service.yaml │ ├── pvc.yaml │ ├── configmap1.yaml │ ├── job.yaml │ ├── deployment.yaml │ ├── job2.yaml │ ├── job3.yaml │ ├── job4.yaml │ ├── existing_job.yaml │ ├── daemonset.yaml │ ├── pod.yaml │ ├── pod2.yaml │ ├── pod3.yaml │ ├── pod4.yaml │ ├── pod5.yaml │ ├── pod6.yaml │ ├── pod7.yaml │ ├── pod8.yaml │ ├── pod9.yaml │ ├── replicaset.yaml │ ├── statefulset.yaml │ ├── create.sh │ └── delete.sh ├── services │ ├── service.yaml │ ├── job.yaml │ ├── pod3.yaml │ ├── pod4.yaml │ ├── pod2.yaml │ ├── pod.yaml │ ├── create.sh │ └── deps.yaml ├── simple │ ├── job.yaml │ ├── job2.yaml │ ├── existing_job.yaml │ ├── pod.yaml │ ├── pod2.yaml │ ├── pod3.yaml │ ├── deps.yaml │ └── create.sh └── common.sh ├── glide.yaml ├── e2e ├── e2e_suite_test.go └── utils │ └── utils.go ├── manifests ├── resdefs.json ├── dependencies.json └── appcontroller.yaml ├── .codeclimate.yml ├── hooks └── pre_build ├── Dockerfile ├── scripts ├── checkout_k8s.sh └── import.sh ├── utils └── graph-builder │ └── README.md ├── main.go ├── cmd ├── format │ ├── format.go │ ├── json_test.go │ ├── json.go │ ├── yaml.go │ └── yaml_test.go ├── cmd.go ├── wrap_test.go ├── deploy_test.go ├── wrap.go ├── get-status.go ├── deploy.go └── bootstrap.go └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | kubeac -------------------------------------------------------------------------------- /pkg/client/.gitignore: -------------------------------------------------------------------------------- 1 | retgen 2 | -------------------------------------------------------------------------------- /ac-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec printf o >/etc/sv/ac/supervise/control 4 | -------------------------------------------------------------------------------- /ac-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec printf d >/etc/sv/ac/supervise/control 4 | -------------------------------------------------------------------------------- /docs/research/README.md: -------------------------------------------------------------------------------- 1 | This is a place for adding results of research. 2 | -------------------------------------------------------------------------------- /run_runit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export > /tmp/envvars 4 | exec runsv /etc/sv/ac 5 | -------------------------------------------------------------------------------- /ac_service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec 2>&1 4 | 5 | source /tmp/envvars 6 | 7 | exec /usr/bin/kubeac run 8 | -------------------------------------------------------------------------------- /tests/linear/expected_order.yaml: -------------------------------------------------------------------------------- 1 | - pod/test-pod 2 | - job/test-job 3 | - service/test-service 4 | - replicaset/test-replicaset 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6 4 | - tip 5 | script: 6 | - go get github.com/Masterminds/glide 7 | - make test 8 | -------------------------------------------------------------------------------- /examples/extended/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: example-secret 5 | type: Opaque 6 | data: 7 | password: MWYyZDFlMmU2N2Rm 8 | username: YWRtaW4= 9 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/Mirantis/k8s-AppController 2 | import: 3 | - package: gopkg.in/yaml.v2 4 | - package: k8s.io/client-go 5 | version: v2.0.0-alpha.1 6 | - package: github.com/fatih/color 7 | version: ^1.1.0 8 | -------------------------------------------------------------------------------- /examples/extended/service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: my-service 5 | spec: 6 | selector: 7 | app: service1 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 9376 12 | -------------------------------------------------------------------------------- /examples/services/service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: my-service 5 | spec: 6 | selector: 7 | app: service1 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 9376 12 | -------------------------------------------------------------------------------- /e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | package integration_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestE2e(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "E2e Suite") 13 | } 14 | -------------------------------------------------------------------------------- /examples/extended/pvc.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: myclaim 5 | annotations: 6 | volume.beta.kubernetes.io/storage-class: "slow" 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 8Mi 13 | -------------------------------------------------------------------------------- /manifests/resdefs.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "definition.appcontroller.k8s" 4 | }, 5 | "apiVersion": "extensions/v1beta1", 6 | "kind": "ThirdPartyResource", 7 | "description": "Resource definitions for App Controller", 8 | "versions": [ 9 | { 10 | "name": "v1alpha1" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /manifests/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "dependency.appcontroller.k8s" 4 | }, 5 | "apiVersion": "extensions/v1beta1", 6 | "kind": "ThirdPartyResource", 7 | "description": "Dependencies for App Controller resources", 8 | "versions": [ 9 | { 10 | "name": "v1alpha1" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/extended/configmap1.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | creationTimestamp: 2016-02-18T19:14:38Z 5 | name: example-config 6 | namespace: default 7 | data: 8 | example.property.1: hello 9 | example.property.2: world 10 | example.property.file: |- 11 | property.1=value-1 12 | property.2=value-2 13 | property.3=value-3 14 | -------------------------------------------------------------------------------- /examples/extended/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 10; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/simple/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 10; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/extended/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | replicas: 3 7 | template: 8 | metadata: 9 | labels: 10 | app: nginx 11 | spec: 12 | containers: 13 | - name: nginx 14 | image: nginx:1.7.9 15 | ports: 16 | - containerPort: 80 17 | -------------------------------------------------------------------------------- /examples/extended/job2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job2 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job2 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 20; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/extended/job3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job3 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job3 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 20; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/extended/job4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job4 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job4 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 20; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/simple/job2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job2 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job2 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 20; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/extended/existing_job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: existing-test-job 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 10; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /examples/simple/existing_job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: existing-test-job 5 | spec: 6 | template: 7 | metadata: 8 | name: test-job 9 | spec: 10 | containers: 11 | - name: test-container 12 | image: gcr.io/google_containers/busybox 13 | command: [ "/bin/sh", "-c", "sleep 10; env"] 14 | restartPolicy: Never 15 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | gofmt: 3 | enabled: true 4 | golint: 5 | enabled: true 6 | checks: 7 | GoLint/Comments/DocComments: 8 | enabled: false 9 | govet: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - "**.go" 14 | exclude_paths: 15 | - "utils/graph-builder/js-yaml.min.js" 16 | - "utils/graph-builder/cytoscape-cxtmenu.js" 17 | - "pkg/client/petsets/" -------------------------------------------------------------------------------- /examples/services/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: test-job 5 | labels: 6 | app: service1 7 | spec: 8 | template: 9 | metadata: 10 | name: test-job 11 | spec: 12 | containers: 13 | - name: test-container 14 | image: gcr.io/google_containers/busybox 15 | command: [ "/bin/sh", "-c", "sleep 360; env"] 16 | restartPolicy: Never 17 | -------------------------------------------------------------------------------- /examples/extended/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: DaemonSet 3 | metadata: 4 | name: test-daemonset 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app: daemonset 10 | spec: 11 | containers: 12 | - name: test-daemonset 13 | image: gcr.io/google_containers/busybox 14 | command: 15 | - sh 16 | - -c 17 | - "sleep 6000" 18 | 19 | -------------------------------------------------------------------------------- /examples/extended/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 30; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/simple/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 30; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/simple/pod2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod2 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 20; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/simple/pod3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod3 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod2 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 20; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod3 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod4 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod5.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod5 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod6.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod6 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod7.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod7 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 40; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod8.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod8 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 20; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/extended/pod9.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod9 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 5; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/services/pod3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod3 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 20; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/services/pod4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-pod4 5 | spec: 6 | containers: 7 | - command: ["/bin/sh"] 8 | args: 9 | - -c 10 | - sleep 20; echo ok > /tmp/health; sleep 600 11 | image: gcr.io/google_containers/busybox 12 | readinessProbe: 13 | exec: 14 | command: 15 | - /bin/cat 16 | - /tmp/health 17 | name: test-container 18 | -------------------------------------------------------------------------------- /examples/services/pod2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-service-pod2 5 | labels: 6 | app: service1 7 | spec: 8 | containers: 9 | - command: ["/bin/sh"] 10 | args: 11 | - -c 12 | - sleep 4 ; echo ok > /tmp/health; sleep 600 13 | image: gcr.io/google_containers/busybox 14 | readinessProbe: 15 | exec: 16 | command: 17 | - /bin/cat 18 | - /tmp/health 19 | name: test-container 20 | -------------------------------------------------------------------------------- /hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | docker run --privileged \ 6 | -w /go/src/github.com/Mirantis/k8s-AppController \ 7 | -v $(pwd):/go/src/github.com/Mirantis/k8s-AppController golang:alpine \ 8 | sh -xc \ 9 | "echo \"@community http://dl-cdn.alpinelinux.org/alpine/edge/community\" >> /etc/apk/repositories && \ 10 | apk --no-cache add git glide@community && \ 11 | [ -d vendor ] || glide install --strip-vendor && \ 12 | go build -o kubeac" 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories 4 | RUN apk --no-cache add runit@community &&\ 5 | mkdir -p /etc/sv/ac &&\ 6 | mkdir -p /opt/kubeac/manifests &&\ 7 | touch /etc/sv/ac/down 8 | 9 | ADD manifests /opt/kubeac/manifests 10 | ADD ac_service.sh /etc/sv/ac/run 11 | ADD run_runit.sh /usr/bin/run_runit 12 | ADD ac-run.sh /usr/bin/ac-run 13 | ADD ac-stop.sh /usr/bin/ac-stop 14 | ADD kubeac /usr/bin/kubeac 15 | -------------------------------------------------------------------------------- /examples/services/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: eventually-alive-service-pod 5 | labels: 6 | app: service1 7 | spec: 8 | containers: 9 | - command: ["/bin/sh"] 10 | args: 11 | - -c 12 | - echo ok > /tmp/health; sleep 10 ; rm /tmp/health ; sleep 120 ; echo ok > /tmp/health ; sleep 600 13 | image: gcr.io/google_containers/busybox 14 | readinessProbe: 15 | exec: 16 | command: 17 | - /bin/cat 18 | - /tmp/health 19 | name: test-container 20 | -------------------------------------------------------------------------------- /pkg/client/mocks/dependency.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "appcontroller.k8s/v1alpha1", 3 | "child": "pod/test-pod-2", 4 | "kind": "Dependency", 5 | "metadata": { 6 | "creationTimestamp": "2016-08-02T08:32:11Z", 7 | "name": "dep-name", 8 | "namespace": "default", 9 | "resourceVersion": "90", 10 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/dependencies/dep-name", 11 | "uid": "9b5ec913-588b-11e6-b912-0cc47a430c04" 12 | }, 13 | "parent": "pod/test-pod" 14 | } 15 | -------------------------------------------------------------------------------- /tests/linear/dependencies.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appcontroller.k8s/v1alpha1 2 | kind: Dependency 3 | metadata: 4 | name: 4819ddea-bfe5-f8d0-e40d-8032541b080c 5 | parent: pod/test-pod 6 | child: job/test-job 7 | --- 8 | apiVersion: appcontroller.k8s/v1alpha1 9 | kind: Dependency 10 | metadata: 11 | name: ff8258a9-d396-40f6-092d-531ee73dfe30 12 | parent: job/test-job 13 | child: service/test-service 14 | --- 15 | apiVersion: appcontroller.k8s/v1alpha1 16 | kind: Dependency 17 | metadata: 18 | name: c86ddb3d-d8f9-2bcd-d532-1063a775d75c 19 | parent: service/test-service 20 | child: replicaset/test-replicaset 21 | -------------------------------------------------------------------------------- /scripts/checkout_k8s.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Checks out K8S and kubeadm-dind-cluster with correct versions and stores the 3 | # Location in a given file 4 | 5 | CURRENT=`pwd` 6 | WORKING=`mktemp -d` 7 | K8S_TAG="${K8S_TAG:-v1.5.1}" 8 | 9 | if [ "$#" -ne 1 ]; then 10 | echo "This script accepts only one argument" 11 | exit 1 12 | fi 13 | 14 | set -e 15 | 16 | cd $WORKING 17 | git clone --branch $K8S_TAG --depth 1 --single-branch https://github.com/kubernetes/kubernetes.git 18 | cd kubernetes 19 | git clone --depth 1 --single-branch https://github.com/Mirantis/kubeadm-dind-cluster.git dind 20 | cd $CURRENT 21 | echo $WORKING > $1 22 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # AppController test cases 2 | 3 | Each test case is confined within its directory. 4 | 5 | You need a cluster with AppController pod running to test it (or you need to manually create ThirdPartyResource API extensions in your cluster and provide AppController process with your cluster connection data. 6 | 7 | To create objects from this test case: 8 | 9 | ``` 10 | kubectl create -f dependencies.yaml 11 | kubectl create -f definitions.yaml 12 | ``` 13 | 14 | After that run AppController process. After deployment is done, verify that order of creation of K8s objects matches order defined in expected_order.yaml file. 15 | -------------------------------------------------------------------------------- /examples/extended/replicaset.yaml: -------------------------------------------------------------------------------- 1 | # example from https://github.com/kubernetes/kubernetes/blob/293793cf0d3c13f9ece496d22d1f2353531fa9d5/docs/user-guide/replicaset/frontend.yaml 2 | apiVersion: extensions/v1beta1 3 | kind: ReplicaSet 4 | metadata: 5 | name: frontend 6 | spec: 7 | replicas: 3 8 | template: 9 | metadata: 10 | labels: 11 | app: guestbook 12 | tier: frontend 13 | spec: 14 | containers: 15 | - name: php-redis 16 | image: gcr.io/google_samples/gb-frontend:v3 17 | env: 18 | - name: GET_HOSTS_FROM 19 | value: dns 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /manifests/appcontroller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: k8s-appcontroller 5 | annotations: 6 | pod.alpha.kubernetes.io/init-containers: '[{"name": "kubeac-bootstrap", "image": "mirantis/k8s-appcontroller", "imagePullPolicy": "IfNotPresent", "command": ["kubeac", "bootstrap", "/opt/kubeac/manifests"]}]' 7 | spec: 8 | restartPolicy: Always 9 | containers: 10 | - name: kubeac 11 | image: mirantis/k8s-appcontroller 12 | imagePullPolicy: IfNotPresent 13 | command: ["/usr/bin/run_runit"] 14 | env: 15 | - name: KUBERNETES_AC_LABEL_SELECTOR 16 | value: "" 17 | - name: KUBERNETES_AC_POD_NAMESPACE 18 | valueFrom: 19 | fieldRef: 20 | fieldPath: metadata.namespace 21 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | type PetSetExpansion interface{} 20 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package,register 18 | // +k8s:openapi-gen=true 19 | 20 | package apps 21 | -------------------------------------------------------------------------------- /utils/graph-builder/README.md: -------------------------------------------------------------------------------- 1 | # Running 2 | To run, just open `index.html` in directory of your local copy using `gnome-open index.html` under unixoids, `open index.html` under macs, or just clicking it in file browser of your system/choice. 3 | 4 | You can also serve this directory from http, e.g. `python -m SimpleHTTPServer 9000` and load `0.0.0.0:9000` in your browser. 5 | 6 | # Usage 7 | Right click on a node to remove it, or move it's name to either parent or child input (convenient when you are adding edges to graph). 8 | 9 | Use forms below the graph to add new nodes and edges. Removing edges is not supported yet in the graph itself. 10 | 11 | Use `generate` button to dump your current graph to dependencies yaml. 12 | 13 | Use `load` button to load your yaml from textarea into the graph. 14 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +k8s:deepcopy-gen=package,register 18 | // +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/apps 19 | // +k8s:openapi-gen=true 20 | 21 | package v1alpha1 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/Mirantis/k8s-AppController/cmd" 21 | ) 22 | 23 | func main() { 24 | cmd.Init() 25 | 26 | if err := cmd.RootCmd.Execute(); err != nil { 27 | log.Fatal(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/mocks/secrets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import "k8s.io/client-go/pkg/api/v1" 18 | 19 | func MakeSecret(name string) *v1.Secret { 20 | secret := &v1.Secret{} 21 | secret.Name = name 22 | secret.Namespace = "testing" 23 | return secret 24 | } 25 | -------------------------------------------------------------------------------- /examples/services/create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ../common.sh 4 | 5 | set -x 6 | 7 | $KUBECTL_NAME create -f ../../manifests/appcontroller.yaml 8 | wait-appcontroller 9 | 10 | $KUBECTL_NAME create -f deps.yaml 11 | 12 | cat job.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 13 | 14 | cat service.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 15 | cat pod.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 16 | cat pod2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 17 | cat pod3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 18 | cat pod4.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 19 | 20 | $KUBECTL_NAME exec k8s-appcontroller ac-run 21 | 22 | $KUBECTL_NAME logs -f k8s-appcontroller 23 | -------------------------------------------------------------------------------- /examples/common.sh: -------------------------------------------------------------------------------- 1 | KUBECTL_NAME=${KUBECTL_NAME:-} 2 | APPC=${APPC:-"k8s-appcontroller"} 3 | 4 | if [ -z "$KUBECTL_NAME" ]; then 5 | if [ -x "$(command -v kubectl)" ]; then 6 | KUBECTL_NAME='kubectl' 7 | fi 8 | if [ -x "$(command -v kubectl.sh)" ]; then 9 | KUBECTL_NAME='kubectl.sh' 10 | fi 11 | fi 12 | echo "Using following kubectl - ${KUBECTL_NAME}" 13 | 14 | function wait-appcontroller { 15 | echo "Waiting for pod $APPC to start running" 16 | wait-until "$KUBECTL_NAME get pods | grep Running | grep -q $APPC" 20 17 | echo "Waiting for tprs to register URLs" 18 | wait-until "$KUBECTL_NAME get definitions &> /dev/null" 5 19 | wait-until "$KUBECTL_NAME get dependency &> /dev/null" 5 20 | } 21 | 22 | function wait-until { 23 | local limit=$((SECONDS+$2)) 24 | while [ $SECONDS -lt $limit ]; do 25 | eval $1 && return 26 | printf '.' 27 | done 28 | echo "FAILED: $1" 29 | exit 2 30 | } 31 | -------------------------------------------------------------------------------- /pkg/resources/daemonset_test.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 7 | ) 8 | 9 | // TestDaemonSetSuccessCheck check status for ready DaemonSet 10 | func TestDaemonSetSuccessCheck(t *testing.T) { 11 | c := mocks.NewClient(mocks.MakeDaemonSet("not-fail")) 12 | status, err := daemonSetStatus(c.DaemonSets(), "not-fail") 13 | 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | if status != "ready" { 18 | t.Errorf("Status should be ready , is %s instead", status) 19 | } 20 | } 21 | 22 | // TestDaemonSetFailCheck status of not ready daemonset 23 | func TestDaemonSetFailCheck(t *testing.T) { 24 | c := mocks.NewClient(mocks.MakeDaemonSet("fail")) 25 | status, err := daemonSetStatus(c.DaemonSets(), "fail") 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | if status != "not ready" { 30 | t.Errorf("Status should be not ready, is %s instead.", status) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/mocks/services.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import "k8s.io/client-go/pkg/api/v1" 18 | 19 | // MakeService creates a service based on its name 20 | func MakeService(name string) *v1.Service { 21 | service := &v1.Service{Spec: v1.ServiceSpec{Selector: map[string]string{name: "yes"}}} 22 | service.Name = name 23 | service.Namespace = "testing" 24 | return service 25 | } 26 | -------------------------------------------------------------------------------- /scripts/import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | set -o xtrace 7 | 8 | IMAGE_REPO=${IMAGE_REPO:-mirantis/k8s-appcontroller} 9 | IMAGE_TAG=${IMAGE_TAG:-latest} 10 | NUM_NODES=${NUM_NODES:-2} 11 | TMP_IMAGE_PATH=${TMP_IMAGE_PATH:-/tmp/image.tar} 12 | MASTER_NAME=${MASTER_NAME="kube-master"} 13 | SLAVE_PATTERN=${SLAVE_PATTERN:-"kube-node-"} 14 | 15 | 16 | function import-image { 17 | docker save ${IMAGE_REPO}:${IMAGE_TAG} -o "${TMP_IMAGE_PATH}" 18 | 19 | 20 | if [ -n "$MASTER_NAME" ]; then 21 | docker cp "${TMP_IMAGE_PATH}" kube-master:/image.tar 22 | docker exec -ti "${MASTER_NAME}" docker load -i /image.tar 23 | fi 24 | 25 | for i in `seq 1 "${NUM_NODES}"`; 26 | do 27 | docker cp "${TMP_IMAGE_PATH}" "${SLAVE_PATTERN}$i":/image.tar 28 | docker exec -ti "${SLAVE_PATTERN}$i" docker load -i /image.tar 29 | done 30 | set +o xtrace 31 | echo "Finished copying docker image to dind nodes" 32 | } 33 | 34 | import-image 35 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This package is generated by client-gen with arguments: --clientset-name=release_1_5 --input=[api/v1,apps/v1alpha1,authentication/v1beta1,authorization/v1beta1,autoscaling/v1,batch/v1,certificates/v1alpha1,extensions/v1beta1,policy/v1alpha1,rbac/v1alpha1,storage/v1beta1] 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This package is generated by client-gen with arguments: --clientset-name=release_1_5 --input=[api/v1,apps/v1alpha1,authentication/v1beta1,authorization/v1beta1,autoscaling/v1,batch/v1,certificates/v1alpha1,extensions/v1beta1,policy/v1alpha1,rbac/v1alpha1,storage/v1beta1] 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1alpha1 21 | -------------------------------------------------------------------------------- /examples/simple/deps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appcontroller.k8s/v1alpha1 2 | kind: Dependency 3 | metadata: 4 | name: dependency-1 5 | parent: pod/eventually-alive-pod 6 | child: job/test-job 7 | --- 8 | apiVersion: appcontroller.k8s/v1alpha1 9 | kind: Dependency 10 | metadata: 11 | name: dependency-2 12 | parent: job/test-job 13 | child: pod/eventually-alive-pod2 14 | --- 15 | apiVersion: appcontroller.k8s/v1alpha1 16 | kind: Dependency 17 | metadata: 18 | name: dependency-3 19 | parent: job/test-job 20 | child: pod/eventually-alive-pod3 21 | --- 22 | apiVersion: appcontroller.k8s/v1alpha1 23 | kind: Dependency 24 | metadata: 25 | name: dependency-4 26 | parent: pod/eventually-alive-pod2 27 | child: job/test-job2 28 | --- 29 | apiVersion: appcontroller.k8s/v1alpha1 30 | kind: Dependency 31 | metadata: 32 | name: dependency-5 33 | parent: pod/eventually-alive-pod3 34 | child: job/test-job2 35 | --- 36 | apiVersion: appcontroller.k8s/v1alpha1 37 | kind: Dependency 38 | metadata: 39 | name: dependency-6 40 | parent: job/existing-test-job 41 | child: pod/eventually-alive-pod 42 | -------------------------------------------------------------------------------- /pkg/mocks/replicasets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import extbeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" 18 | 19 | func MakeReplicaSet(name string) *extbeta1.ReplicaSet { 20 | replicaSet := &extbeta1.ReplicaSet{} 21 | replicaSet.Name = name 22 | replicaSet.Namespace = "testing" 23 | replicaSet.Spec.Replicas = pointer(int32(2)) 24 | if name != "fail" { 25 | replicaSet.Status.Replicas = int32(3) 26 | } 27 | 28 | return replicaSet 29 | } 30 | -------------------------------------------------------------------------------- /examples/extended/statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: StatefulSet 3 | metadata: 4 | name: test-petset 5 | spec: 6 | serviceName: "petset" 7 | replicas: 2 8 | template: 9 | metadata: 10 | labels: 11 | app: nginx 12 | annotations: 13 | pod.alpha.kubernetes.io/initialized: "true" 14 | spec: 15 | terminationGracePeriodSeconds: 0 16 | containers: 17 | - command: ["/bin/sh"] 18 | args: 19 | - -c 20 | - sleep 30; echo ok > /tmp/health; sleep 600 21 | image: gcr.io/google_containers/busybox 22 | readinessProbe: 23 | exec: 24 | command: 25 | - /bin/cat 26 | - /tmp/health 27 | name: test-container 28 | - command: ["/bin/sh"] 29 | args: 30 | - -c 31 | - sleep 30; echo ok > /tmp/health; sleep 600 32 | image: gcr.io/google_containers/busybox 33 | readinessProbe: 34 | exec: 35 | command: 36 | - /bin/cat 37 | - /tmp/health 38 | name: test-container2 39 | -------------------------------------------------------------------------------- /cmd/format/format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | // Format is an interface for data formats for wrapper 18 | type Format interface { 19 | ExtractData(k8sObject string) (DataExtractor, error) 20 | Wrap(k8sObject string) (string, error) 21 | IndentLevel() int 22 | } 23 | 24 | // DataExtractor is a type for extracting data relevant for wrap tool from serialized k8s objects 25 | type DataExtractor struct { 26 | Kind string "kind" 27 | Metadata struct { 28 | Name string "name" 29 | } "metadata" 30 | } 31 | -------------------------------------------------------------------------------- /pkg/mocks/jobs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "strings" 19 | 20 | batchapiv1 "k8s.io/client-go/pkg/apis/batch/v1" 21 | ) 22 | 23 | func MakeJob(name string) *batchapiv1.Job { 24 | status := strings.Split(name, "-")[0] 25 | 26 | job := &batchapiv1.Job{} 27 | job.Name = name 28 | job.Namespace = "testing" 29 | if status == "ready" { 30 | job.Status.Conditions = append( 31 | job.Status.Conditions, 32 | batchapiv1.JobCondition{Type: "Complete", Status: "True"}, 33 | ) 34 | } 35 | 36 | return job 37 | } 38 | -------------------------------------------------------------------------------- /pkg/mocks/pods.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "strings" 19 | 20 | "k8s.io/client-go/pkg/api/v1" 21 | ) 22 | 23 | func MakePod(name string) *v1.Pod { 24 | status := strings.Split(name, "-")[0] 25 | pod := &v1.Pod{} 26 | pod.Name = name 27 | pod.Namespace = "testing" 28 | if status == "ready" { 29 | pod.Status.Phase = "Running" 30 | pod.Status.Conditions = append( 31 | pod.Status.Conditions, 32 | v1.PodCondition{Type: "Ready", Status: "True"}, 33 | ) 34 | } else { 35 | pod.Status.Phase = "Pending" 36 | } 37 | 38 | return pod 39 | } 40 | -------------------------------------------------------------------------------- /docs/research/daemonset-dependencies.md: -------------------------------------------------------------------------------- 1 | DaemonSet on DaemonSet dependency should be resolved per-node rather then per k8s DaemonSet object. 2 | 3 | Rationale for above is that DaemonSets are special objects and if one DaemonSet depends on the other, then the dependency is actually on a per-node basis, not for the whole cluster. 4 | 5 | We have several options: 6 | 7 | - Don't do anything, treat DaemonSet as other k8s objects. This will take the least amount of time, but it will hinder DaemonSet functionality as part of dependency graph. 8 | - Implement advanced DaemonSet pods orchestration on top of taint/tolerance/node affinity mechanisms as part of AppController pod. This will require hacking DaemonSet node scheduling to make child DaemonSet pods run on only on nodes which have ready parent DaemonSet pods. This can be done inside AppController process. This require to expand our scheduler logic extensively. The idea is to make a proxy for DaemonSet status checker which will retrieve DaemonSet state from k8s storage and manipulate nodes/DS objects to orchestrate them properly. 9 | - We can do above outside of AppController, but we need to make sure that the information won't be scattered over several components. 10 | -------------------------------------------------------------------------------- /pkg/mocks/daemonsets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import extbeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" 18 | 19 | type daemonSetClient struct { 20 | } 21 | 22 | // MakeDaemonSet creates a daemonset base in its name 23 | func MakeDaemonSet(name string) *extbeta1.DaemonSet { 24 | daemonSet := &extbeta1.DaemonSet{} 25 | daemonSet.Name = name 26 | daemonSet.Namespace = "testing" 27 | daemonSet.Status.DesiredNumberScheduled = 3 28 | if name == "fail" { 29 | daemonSet.Status.CurrentNumberScheduled = 2 30 | } else { 31 | daemonSet.Status.CurrentNumberScheduled = 3 32 | } 33 | 34 | return daemonSet 35 | } 36 | -------------------------------------------------------------------------------- /pkg/client/mocks/pod-definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "appcontroller.k8s/v1alpha1", 3 | "kind": "Definition", 4 | "metadata": { 5 | "creationTimestamp": "2016-08-02T08:32:01Z", 6 | "name": "pod-definition-1", 7 | "namespace": "default", 8 | "resourceVersion": "78", 9 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/definitions/pod-definition-1", 10 | "uid": "956e5588-588b-11e6-b912-0cc47a430c04" 11 | }, 12 | "pod": { 13 | "apiVersion": "v1", 14 | "kind": "Pod", 15 | "metadata": { 16 | "labels": { 17 | "label1": "label1value", 18 | "label2": "label2value" 19 | }, 20 | "name": "test-pod" 21 | }, 22 | "spec": { 23 | "containers": [ 24 | { 25 | "command": [ 26 | "/bin/sh", 27 | "-c", 28 | "env" 29 | ], 30 | "image": "gcr.io/google_containers/busybox", 31 | "name": "test-container" 32 | } 33 | ], 34 | "restartPolicy": "Never" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | TAG ?= mirantis/k8s-appcontroller 3 | K8S_SOURCE_LOCATION = .k8s-source 4 | K8S_CLUSTER_MARKER = .k8s-cluster 5 | 6 | .PHONY: docker 7 | docker: kubeac Makefile 8 | docker build -t $(TAG) . 9 | 10 | vendor: Makefile 11 | glide install --strip-vendor 12 | 13 | test: vendor glide.lock Makefile 14 | go test ./cmd/... 15 | go test ./pkg/... 16 | 17 | kubeac: 18 | bash hooks/pre_build 19 | 20 | .PHONY: img-in-dind 21 | img-in-dind: docker $(K8S_CLUSTER_MARKER) 22 | IMAGE_REPO=mirantis/k8s-appcontroller bash scripts/import.sh 23 | 24 | .PHONY: e2e 25 | e2e: $(K8S_CLUSTER_MARKER) img-in-dind 26 | cd e2e && go test 27 | 28 | .PHONY: clean-all 29 | clean-all: clean clean-k8s 30 | 31 | .PHONY: clean 32 | clean: 33 | rm -f kubeac 34 | docker rmi $(TAG) 35 | 36 | 37 | .PHONY: clean-k8s 38 | clean-k8s: k8s-down 39 | <$(K8S_SOURCE_LOCATION) xargs rm -rf 40 | rm $(K8S_SOURCE_LOCATION) 41 | 42 | 43 | $(K8S_SOURCE_LOCATION): 44 | scripts/checkout_k8s.sh $(K8S_SOURCE_LOCATION) 45 | 46 | $(K8S_CLUSTER_MARKER): $(K8S_SOURCE_LOCATION) 47 | <$(K8S_SOURCE_LOCATION) xargs -I % bash -c "cd %/kubernetes && dind/dind-cluster.sh up" 48 | 49 | .PHONY: k8s-down 50 | k8s-down: 51 | <$(K8S_SOURCE_LOCATION) xargs -I % bash -c "cd %/kubernetes && dind/dind-cluster.sh down" 52 | -------------------------------------------------------------------------------- /examples/services/deps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appcontroller.k8s/v1alpha1 2 | kind: Dependency 3 | metadata: 4 | name: 70328b81-fa4b-6b8f-c487-5f4b6c586edd 5 | parent: pod/eventually-alive-service-pod 6 | child: pod/eventually-alive-service-pod2 7 | --- 8 | apiVersion: appcontroller.k8s/v1alpha1 9 | kind: Dependency 10 | metadata: 11 | name: 8f8f22b2-8610-31b8-3838-ff453f44c257 12 | parent: service/my-service 13 | child: pod/eventually-alive-pod3 14 | --- 15 | apiVersion: appcontroller.k8s/v1alpha1 16 | kind: Dependency 17 | metadata: 18 | name: dec6abd7-2d9d-e27d-ca7f-5d0165e0f210 19 | parent: service/my-service 20 | child: pod/eventually-alive-pod4 21 | --- 22 | apiVersion: appcontroller.k8s/v1alpha1 23 | kind: Dependency 24 | metadata: 25 | name: fede7b45-53f1-b045-3079-bf61cc00b90e 26 | parent: pod/eventually-alive-service-pod 27 | child: service/my-service 28 | --- 29 | apiVersion: appcontroller.k8s/v1alpha1 30 | kind: Dependency 31 | metadata: 32 | name: eb35355b-8d2d-97b5-28bf-1c6b9e47fa15 33 | parent: pod/eventually-alive-service-pod2 34 | child: service/my-service 35 | --- 36 | apiVersion: appcontroller.k8s/v1alpha1 37 | kind: Dependency 38 | metadata: 39 | name: 9da2e824-5ea3-6387-285c-0963c67fc674 40 | parent: pod/eventually-alive-pod3 41 | child: pod/eventually-alive-pod4 42 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/fake/fake_apps_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fake 18 | 19 | import ( 20 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/typed/apps/v1alpha1" 21 | rest "k8s.io/client-go/rest" 22 | testing "k8s.io/client-go/testing" 23 | ) 24 | 25 | type FakeApps struct { 26 | *testing.Fake 27 | } 28 | 29 | func (c *FakeApps) PetSets(namespace string) v1alpha1.PetSetInterface { 30 | return &FakePetSets{c, namespace} 31 | } 32 | 33 | // GetRESTClient returns a RESTClient that is used to communicate 34 | // with API server by this client implementation. 35 | func (c *FakeApps) GetRESTClient() *rest.RESTClient { 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/mocks/configmaps.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "fmt" 19 | 20 | "k8s.io/client-go/pkg/api/v1" 21 | "k8s.io/client-go/pkg/runtime" 22 | ) 23 | 24 | func MakeConfigMap(name string) *v1.ConfigMap { 25 | configMap := &v1.ConfigMap{} 26 | configMap.Name = name 27 | configMap.Namespace = "testing" 28 | return configMap 29 | } 30 | 31 | func ConfigMaps(names ...string) runtime.Object { 32 | var configMaps []v1.ConfigMap 33 | for i := 0; i < 3; i++ { 34 | configMaps = append(configMaps, *MakeConfigMap(fmt.Sprintf("cfgmap-%d", i))) 35 | } 36 | for _, name := range names { 37 | configMaps = append(configMaps, *MakeConfigMap(name)) 38 | } 39 | return &v1.ConfigMapList{Items: configMaps} 40 | } 41 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // RootCmd is top-level AppController command. It is not executable, but it has sub-commands attached 24 | var RootCmd *cobra.Command 25 | 26 | // Init initializes RootCmd, adds flags to subcommands and attaches subcommands to root command 27 | func Init() { 28 | run, err := InitRunCommand() 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | status, err := InitGetStatusCommand() 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | var format string 38 | Wrap.Flags().StringVarP(&format, "format", "f", "yaml", "file format") 39 | 40 | RootCmd = &cobra.Command{Use: "ac"} 41 | RootCmd.AddCommand(Bootstrap, run, Wrap, status) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/defaults.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/client-go/pkg/api/unversioned" 21 | "k8s.io/client-go/pkg/runtime" 22 | ) 23 | 24 | func addDefaultingFuncs(scheme *runtime.Scheme) error { 25 | return scheme.AddDefaultingFuncs( 26 | SetDefaults_PetSet, 27 | ) 28 | } 29 | 30 | func SetDefaults_PetSet(obj *PetSet) { 31 | labels := obj.Spec.Template.Labels 32 | if labels != nil { 33 | if obj.Spec.Selector == nil { 34 | obj.Spec.Selector = &unversioned.LabelSelector{ 35 | MatchLabels: labels, 36 | } 37 | } 38 | if len(obj.Labels) == 0 { 39 | obj.Labels = labels 40 | } 41 | } 42 | if obj.Spec.Replicas == nil { 43 | obj.Spec.Replicas = new(int32) 44 | *obj.Spec.Replicas = 1 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/mocks/persistentvolumeclaims.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "strings" 19 | 20 | "k8s.io/client-go/pkg/api" 21 | "k8s.io/client-go/pkg/api/v1" 22 | ) 23 | 24 | // MakePersistentVolumeClaim creates a persistentVolumeClaim based on its name 25 | func MakePersistentVolumeClaim(name string) *v1.PersistentVolumeClaim { 26 | phase := strings.Split(name, "-")[0] 27 | pvc := &v1.PersistentVolumeClaim{} 28 | pvc.Name = name 29 | pvc.Namespace = "testing" 30 | switch phase { 31 | case string(api.ClaimPending): 32 | pvc.Status.Phase = v1.ClaimPending 33 | case string(api.ClaimBound): 34 | pvc.Status.Phase = v1.ClaimBound 35 | case string(api.ClaimLost): 36 | pvc.Status.Phase = v1.ClaimLost 37 | default: 38 | pvc.Status.Phase = v1.ClaimBound 39 | } 40 | 41 | return pvc 42 | } 43 | -------------------------------------------------------------------------------- /pkg/client/mocks/job-definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "appcontroller.k8s/v1alpha1", 3 | "job": { 4 | "apiVersion": "batch/v1", 5 | "kind": "Job", 6 | "metadata": { 7 | "name": "pi" 8 | }, 9 | "spec": { 10 | "template": { 11 | "metadata": { 12 | "name": "pi" 13 | }, 14 | "spec": { 15 | "containers": [ 16 | { 17 | "command": [ 18 | "perl", 19 | "-Mbignum=bpi", 20 | "-wle", 21 | "print bpi(2000)" 22 | ], 23 | "image": "perl", 24 | "name": "pi" 25 | } 26 | ], 27 | "restartPolicy": "Never" 28 | } 29 | } 30 | } 31 | }, 32 | "kind": "Definition", 33 | "metadata": { 34 | "creationTimestamp": "2016-08-02T08:44:16Z", 35 | "name": "job-definition-1", 36 | "namespace": "default", 37 | "resourceVersion": "817", 38 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/definitions/job-definition-1", 39 | "uid": "4b6931d2-588d-11e6-b912-0cc47a430c04" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/mocks/petsets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import appsalpha1 "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps/v1alpha1" 18 | 19 | // MakePetSet returns a new K8s PetSet object for the client to return. If it's name is "fail" it will have labels that will cause it's underlying mock Pods to fail. 20 | func MakePetSet(name string) *appsalpha1.PetSet { 21 | petSet := &appsalpha1.PetSet{} 22 | petSet.Name = name 23 | petSet.Namespace = "testing" 24 | petSet.Spec.Replicas = pointer(int32(3)) 25 | petSet.Spec.Template.ObjectMeta.Labels = make(map[string]string) 26 | if name == "fail" { 27 | petSet.Spec.Template.ObjectMeta.Labels["failedpod"] = "yes" 28 | petSet.Status.Replicas = int32(2) 29 | } else { 30 | petSet.Status.Replicas = int32(3) 31 | } 32 | 33 | return petSet 34 | } 35 | -------------------------------------------------------------------------------- /pkg/mocks/deployment.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import extbeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" 18 | 19 | type deploymentClient struct { 20 | } 21 | 22 | // MakeDeployment creates mock Deployment 23 | func MakeDeployment(name string) *extbeta1.Deployment { 24 | deployment := &extbeta1.Deployment{} 25 | deployment.Name = name 26 | deployment.Namespace = "testing" 27 | deployment.Spec.Replicas = pointer(int32(3)) 28 | if name == "fail" { 29 | deployment.Status.UpdatedReplicas = int32(2) 30 | deployment.Status.AvailableReplicas = int32(3) 31 | } else if name == "failav" { 32 | deployment.Status.UpdatedReplicas = int32(3) 33 | deployment.Status.AvailableReplicas = int32(2) 34 | } else { 35 | deployment.Status.UpdatedReplicas = int32(3) 36 | deployment.Status.AvailableReplicas = int32(3) 37 | } 38 | 39 | return deployment 40 | } 41 | -------------------------------------------------------------------------------- /pkg/resources/replicaset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 21 | ) 22 | 23 | func TestSuccessCheck(t *testing.T) { 24 | c := mocks.NewClient(mocks.MakeReplicaSet("notfail")) 25 | status, err := replicaSetStatus(c.ReplicaSets(), "notfail", nil) 26 | 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | 31 | if status != "ready" { 32 | t.Errorf("Status should be `ready`, is `%s` instead.", status) 33 | } 34 | } 35 | 36 | func TestFailCheck(t *testing.T) { 37 | c := mocks.NewClient(mocks.MakeReplicaSet("fail")) 38 | status, err := replicaSetStatus(c.ReplicaSets(), "fail", map[string]string{"success_factor": "80"}) 39 | 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | 44 | if status != "not ready" { 45 | t.Errorf("Status should be `not ready`, is `%s` instead.", status) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/resources/secrets_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 21 | ) 22 | 23 | // TestSecretSuccessCheck checks status of ready Secret 24 | func TestSecretSuccessCheck(t *testing.T) { 25 | c := mocks.NewClient(mocks.MakeSecret("notfail")) 26 | status, err := secretStatus(c.Secrets(), "notfail") 27 | 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if status != "ready" { 33 | t.Errorf("Status should be `ready`, is `%s` instead.", status) 34 | } 35 | } 36 | 37 | // TestSecretFailCheck checks status of not existing Secret 38 | func TestSecretFailCheck(t *testing.T) { 39 | c := mocks.NewClient() 40 | status, err := secretStatus(c.Secrets(), "fail") 41 | 42 | if err == nil { 43 | t.Error("Error not found, expected error") 44 | } 45 | 46 | if status != "error" { 47 | t.Errorf("Status should be `error`, is `%s` instead.", status) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/mocks/statefulsets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import appsbeta1 "k8s.io/client-go/pkg/apis/apps/v1beta1" 18 | 19 | type statefulSetClient struct { 20 | } 21 | 22 | func pointer(i int32) *int32 { 23 | return &i 24 | } 25 | 26 | // MakeStatefulSet returns a new K8s StatefulSet object for the client to return. If it's name is "fail" it will have labels that will cause it's underlying mock Pods to fail. 27 | func MakeStatefulSet(name string) *appsbeta1.StatefulSet { 28 | statefulSet := &appsbeta1.StatefulSet{} 29 | statefulSet.Name = name 30 | statefulSet.Namespace = "testing" 31 | statefulSet.Spec.Replicas = pointer(int32(3)) 32 | statefulSet.Spec.Template.ObjectMeta.Labels = make(map[string]string) 33 | if name == "fail" { 34 | statefulSet.Spec.Template.ObjectMeta.Labels["failedpod"] = "yes" 35 | statefulSet.Status.Replicas = int32(2) 36 | } else { 37 | statefulSet.Status.Replicas = int32(3) 38 | } 39 | 40 | return statefulSet 41 | } 42 | -------------------------------------------------------------------------------- /pkg/client/mocks/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "appcontroller.k8s/v1alpha1", 3 | "items": [ 4 | { 5 | "apiVersion": "appcontroller.k8s/v1alpha1", 6 | "child": "pod/test-pod-2", 7 | "kind": "Dependency", 8 | "metadata": { 9 | "creationTimestamp": "2016-08-02T08:32:11Z", 10 | "name": "dependency-1", 11 | "namespace": "default", 12 | "resourceVersion": "90", 13 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/dependencies/dependency-1", 14 | "uid": "9b5ec913-588b-11e6-b912-0cc47a430c04" 15 | }, 16 | "parent": "pod/test-pod" 17 | }, 18 | { 19 | "apiVersion": "appcontroller.k8s/v1alpha1", 20 | "child": "pod/test-pod-2", 21 | "kind": "Dependency", 22 | "metadata": { 23 | "creationTimestamp": "2016-08-02T10:23:32Z", 24 | "name": "dependency-2", 25 | "namespace": "default", 26 | "resourceVersion": "6762", 27 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/dependencies/dependency-2", 28 | "uid": "29969396-589b-11e6-b912-0cc47a430c04" 29 | }, 30 | "parent": "pod/test-pod" 31 | } 32 | ], 33 | "kind": "DependencyList", 34 | "metadata": { 35 | "resourceVersion": "6764", 36 | "selfLink": "/apis/appcontroller.k8s/v1alpha1/namespaces/default/dependencies" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/resources/configmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 21 | ) 22 | 23 | // TestConfigMapSuccessCheck checks status of ready ConfigMap 24 | func TestConfigMapSuccessCheck(t *testing.T) { 25 | c := mocks.NewClient(mocks.ConfigMaps("notfail")) 26 | status, err := configMapStatus(c.ConfigMaps(), "notfail") 27 | 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if status != "ready" { 33 | t.Errorf("Status should be `ready`, is `%s` instead.", status) 34 | } 35 | } 36 | 37 | // TestConfigMapFailCheck checks status of not existing ConfigMap 38 | func TestConfigMapFailCheck(t *testing.T) { 39 | c := mocks.NewClient(mocks.ConfigMaps()) 40 | status, err := configMapStatus(c.ConfigMaps(), "fail") 41 | 42 | if err == nil { 43 | t.Error("Error not found, expected error") 44 | } 45 | 46 | if status != "error" { 47 | t.Errorf("Status should be `error`, is `%s` instead.", status) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/install/install.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package install installs the apps API group, making it available as 18 | // an option to all of the API encoding/decoding machinery. 19 | package install 20 | 21 | import ( 22 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps" 23 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps/v1alpha1" 24 | "k8s.io/client-go/pkg/apimachinery/announced" 25 | ) 26 | 27 | func init() { 28 | if err := announced.NewGroupMetaFactory( 29 | &announced.GroupMetaFactoryArgs{ 30 | GroupName: "alpha" + apps.GroupName, 31 | VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, 32 | ImportPrefix: "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps", 33 | AddInternalObjectsToScheme: apps.AddToScheme, 34 | }, 35 | announced.VersionToSchemeFunc{ 36 | v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, 37 | }, 38 | ).Announce().RegisterAndEnable(); err != nil { 39 | panic(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cmd/format/json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | // TestKindJSON tests data retrieval from k8s objects 22 | func TestKindJSON(t *testing.T) { 23 | f := JSON{} 24 | text := `{"kind": "Job"}` 25 | kind, err := f.ExtractData(text) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | if kind.Kind != "job" { 31 | t.Errorf("Extracted kind should be \"job\", is %s", kind) 32 | } 33 | } 34 | 35 | // TestWrapJSON tests if K8s objects are properly wrapped 36 | func TestWrapJSON(t *testing.T) { 37 | f := JSON{} 38 | text := `{"kind": "Job", "metadata": {"name": "name"}}` + "\n" 39 | 40 | wrapped, err := f.Wrap(text) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | expected := `{ 45 | "apiVersion": "appcontroller.k8s/v1alpha1", 46 | "kind": "Definition", 47 | "metadata": { 48 | "name": "job-name" 49 | }, 50 | "job": {"kind": "Job", "metadata": {"name": "name"}} 51 | }` + "\n" 52 | if wrapped != expected { 53 | t.Errorf("Wrapped doesn't match expected output\nExpected:\n%s\nAactual:\n%s", expected, wrapped) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cmd/wrap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "io" 19 | "io/ioutil" 20 | "os" 21 | "testing" 22 | ) 23 | 24 | // TestInput checks if input is properly retrieved from files 25 | func TestInput(t *testing.T) { 26 | var inputTests = []struct { 27 | content string 28 | indent int 29 | expected string 30 | }{ 31 | {"trololo\n lololo\n lololo\n\n", 1, " trololo\n lololo\n lololo\n \n"}, 32 | {"trololo\n lololo\n lololo\n\n", 2, " trololo\n lololo\n lololo\n \n"}, 33 | } 34 | 35 | for tc, tt := range inputTests { 36 | in, err := ioutil.TempFile("", "") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | defer in.Close() 41 | 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | _, err = io.WriteString(in, tt.content) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | _, err = in.Seek(0, os.SEEK_SET) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | result := getInput(in, tt.indent) 57 | 58 | if result != tt.expected { 59 | t.Errorf("\"%s\" is not equal to \"%s\" in test case %d", result, tt.expected, tc+1) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cmd/format/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "encoding/json" 19 | "strings" 20 | ) 21 | 22 | // JSON implements format.Format interface 23 | type JSON struct { 24 | } 25 | 26 | // ExtractData returns data relevant for wrap tool from serialized k8s object 27 | func (f JSON) ExtractData(k8sObject string) (DataExtractor, error) { 28 | var data DataExtractor 29 | err := json.Unmarshal([]byte(k8sObject), &data) 30 | data.Kind = strings.ToLower(data.Kind) 31 | return data, err 32 | } 33 | 34 | // Wrap wraps k8sObject into Definition ThirdPArtyResource 35 | func (f JSON) Wrap(k8sObject string) (string, error) { 36 | data, err := f.ExtractData(k8sObject) 37 | 38 | base := `{ 39 | "apiVersion": "appcontroller.k8s/v1alpha1", 40 | "kind": "Definition", 41 | "metadata": { 42 | "name": "` + data.Kind + "-" + data.Metadata.Name + `" 43 | },` + "\n" 44 | 45 | if err != nil { 46 | return "", err 47 | } 48 | return base + ` "` + data.Kind + `": ` + strings.TrimLeft(k8sObject, " ") + "}\n", nil 49 | } 50 | 51 | // IndentLevel returns indent level for JSON format 52 | func (f JSON) IndentLevel() int { 53 | return 4 54 | } 55 | -------------------------------------------------------------------------------- /pkg/mocks/dependencies.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "github.com/Mirantis/k8s-AppController/pkg/client" 19 | "k8s.io/client-go/pkg/api" 20 | ) 21 | 22 | type Dependency struct { 23 | Parent string 24 | Child string 25 | Meta map[string]string 26 | } 27 | 28 | type dependencyClient struct { 29 | dependencies []Dependency 30 | } 31 | 32 | func (d *dependencyClient) List(opts api.ListOptions) (*client.DependencyList, error) { 33 | list := &client.DependencyList{} 34 | 35 | for _, dep := range d.dependencies { 36 | list.Items = append( 37 | list.Items, 38 | client.Dependency{ 39 | Parent: dep.Parent, 40 | Child: dep.Child, 41 | Meta: make(map[string]string), 42 | }, 43 | ) 44 | } 45 | 46 | return list, nil 47 | } 48 | 49 | func (d *dependencyClient) Create(_ *client.Dependency) (*client.Dependency, error) { 50 | panic("Not implemented") 51 | } 52 | 53 | func (d *dependencyClient) Delete(_ string, _ *api.DeleteOptions) error { 54 | panic("Not implemented") 55 | } 56 | 57 | func NewDependencyClient(dependencies ...Dependency) client.DependenciesInterface { 58 | return &dependencyClient{dependencies} 59 | } 60 | -------------------------------------------------------------------------------- /pkg/interfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package interfaces 16 | 17 | import "github.com/Mirantis/k8s-AppController/pkg/client" 18 | 19 | // BaseResource is an interface for AppController supported resources 20 | type BaseResource interface { 21 | Key() string 22 | // Ensure that Status() supports nil as meta 23 | Status(meta map[string]string) (string, error) 24 | Create() error 25 | Delete() error 26 | Meta(string) string 27 | } 28 | 29 | // DependencyReport is a report of a single dependency of a node in graph 30 | type DependencyReport struct { 31 | Dependency string 32 | Blocks bool 33 | Percentage int 34 | Needed int 35 | Message string 36 | } 37 | 38 | // Resource is an interface for a base resource that implements getting dependency reports 39 | type Resource interface { 40 | BaseResource 41 | GetDependencyReport(map[string]string) DependencyReport 42 | } 43 | 44 | // ResourceTemplate is an interface for AppController supported resource templates 45 | type ResourceTemplate interface { 46 | NameMatches(client.ResourceDefinition, string) bool 47 | New(client.ResourceDefinition, client.Interface) Resource 48 | NewExisting(string, client.Interface) Resource 49 | } 50 | -------------------------------------------------------------------------------- /pkg/mocks/counterwithmemo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import "sync" 18 | 19 | // CounterWithMemo is a counter with atomic increment and decrememt and 20 | // memoization of miminum and maximum values of the counter 21 | type CounterWithMemo struct { 22 | counter int 23 | max int 24 | min int 25 | sync.Mutex 26 | } 27 | 28 | // Inc atomically increments the value of the counter 29 | func (c *CounterWithMemo) Inc() { 30 | c.Lock() 31 | defer c.Unlock() 32 | 33 | c.counter++ 34 | 35 | if c.max < c.counter { 36 | c.max = c.counter 37 | } 38 | } 39 | 40 | // Dec atomically decrements the value of the counter 41 | func (c *CounterWithMemo) Dec() { 42 | c.Lock() 43 | defer c.Unlock() 44 | 45 | c.counter-- 46 | if c.min < c.counter { 47 | c.min = c.counter 48 | } 49 | } 50 | 51 | // Min returns minimum value that counter reached 52 | func (c *CounterWithMemo) Min() int { 53 | c.Lock() 54 | defer c.Unlock() 55 | 56 | return c.min 57 | } 58 | 59 | // Max returns maximum value that counter reached 60 | func (c *CounterWithMemo) Max() int { 61 | c.Lock() 62 | defer c.Unlock() 63 | 64 | return c.max 65 | } 66 | 67 | // NewCounterWithMemo creates new instance of CounterWithMemo 68 | func NewCounterWithMemo() *CounterWithMemo { 69 | return &CounterWithMemo{} 70 | } 71 | -------------------------------------------------------------------------------- /pkg/mocks/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "github.com/Mirantis/k8s-AppController/pkg/client" 19 | alphafake "github.com/Mirantis/k8s-AppController/pkg/client/petsets/typed/apps/v1alpha1/fake" 20 | 21 | "k8s.io/client-go/kubernetes/fake" 22 | "k8s.io/client-go/pkg/api/unversioned" 23 | "k8s.io/client-go/pkg/apis/apps/v1beta1" 24 | "k8s.io/client-go/pkg/runtime" 25 | ) 26 | 27 | func newClient(objects ...runtime.Object) *client.Client { 28 | fakeClientset := fake.NewSimpleClientset(objects...) 29 | apps := &alphafake.FakeApps{&fakeClientset.Fake} 30 | return &client.Client{ 31 | Clientset: fakeClientset, 32 | AlphaApps: apps, 33 | Deps: NewDependencyClient(), 34 | ResDefs: NewResourceDefinitionClient(), 35 | Namespace: "testing", 36 | } 37 | } 38 | 39 | func makeVersionsList(version unversioned.GroupVersion) *unversioned.APIGroupList { 40 | return &unversioned.APIGroupList{Groups: []unversioned.APIGroup{ 41 | { 42 | Name: version.Group, 43 | Versions: []unversioned.GroupVersionForDiscovery{ 44 | {GroupVersion: version.Version}, 45 | }, 46 | }, 47 | }} 48 | } 49 | 50 | func NewClient(objects ...runtime.Object) *client.Client { 51 | c := newClient(objects...) 52 | c.APIVersions = makeVersionsList(v1beta1.SchemeGroupVersion) 53 | return c 54 | } 55 | -------------------------------------------------------------------------------- /cmd/format/yaml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "gopkg.in/yaml.v2" 22 | ) 23 | 24 | // Yaml implements format.Format interface 25 | type Yaml struct { 26 | } 27 | 28 | // ExtractData returns data relevant for wrap tool from serialized k8s object 29 | func (f Yaml) ExtractData(k8sObject string) (DataExtractor, error) { 30 | var data DataExtractor 31 | err := yaml.Unmarshal([]byte(k8sObject), &data) 32 | data.Kind = strings.ToLower(data.Kind) 33 | return data, err 34 | } 35 | 36 | // Wrap wraps k8sObject into Definition ThirdPArtyResource 37 | func (f Yaml) Wrap(k8sObject string) (string, error) { 38 | objects := strings.Split(k8sObject, fmt.Sprintf("%s---", strings.Repeat(" ", f.IndentLevel()))) 39 | 40 | result := make([]string, 0, len(objects)) 41 | for _, o := range objects { 42 | data, err := f.ExtractData(o) 43 | if err != nil { 44 | return "", err 45 | } 46 | base := `apiVersion: appcontroller.k8s/v1alpha1 47 | kind: Definition 48 | metadata: 49 | name: ` + data.Kind + "-" + data.Metadata.Name + "\n" 50 | result = append(result, base+data.Kind+":\n"+strings.Trim(o, "\n")) 51 | } 52 | 53 | return strings.Join(result, "\n---\n"), nil 54 | } 55 | 56 | // IndentLevel returns indent level for Yaml format 57 | func (f Yaml) IndentLevel() int { 58 | return 2 59 | } 60 | -------------------------------------------------------------------------------- /cmd/deploy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | ) 21 | 22 | // TestEmptyLabel checks if label is empty if no values are provided 23 | func TestEmptyLabel(t *testing.T) { 24 | cmd, _ := InitRunCommand() 25 | label, _ := getLabelSelector(cmd) 26 | 27 | if label != "" { 28 | t.Errorf("label selector should be empty, is `%s` instead", label) 29 | } 30 | } 31 | 32 | // TestLabelEnv checks if label selector is retrieved from env variable 33 | func TestLabelEnv(t *testing.T) { 34 | cmd, _ := InitRunCommand() 35 | val := "TEST_KEY=TEST_VALUE" 36 | os.Setenv("KUBERNETES_AC_LABEL_SELECTOR", val) 37 | label, _ := getLabelSelector(cmd) 38 | 39 | if label != val { 40 | t.Errorf("label selector should be equal to `%s`, is `%s` instead", val, label) 41 | } 42 | } 43 | 44 | // TestLabelFlag checks if label selector is retrieved from command flag and if it overwrites env var 45 | func TestLabelFlag(t *testing.T) { 46 | cmd, _ := InitRunCommand() 47 | 48 | val := "TEST_KEY=TEST_VALUE" 49 | val2 := "TEST_OTHER_KEY=TEST_OTHER_VALUE" 50 | os.Setenv("KUBERNETES_AC_LABEL_SELECTOR", val) 51 | cmd.Flags().Parse([]string{"-l", val2}) 52 | 53 | label, _ := getLabelSelector(cmd) 54 | 55 | if label != val2 { 56 | t.Errorf("label selector should be equal to `%s`, is `%s` instead", val2, label) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cmd/wrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "log" 21 | "os" 22 | "strings" 23 | 24 | "github.com/spf13/cobra" 25 | 26 | "github.com/Mirantis/k8s-AppController/cmd/format" 27 | ) 28 | 29 | func getInput(stream *os.File, indent int) string { 30 | result := "" 31 | spaces := strings.Repeat(" ", indent) 32 | 33 | scanner := bufio.NewScanner(stream) 34 | for scanner.Scan() { 35 | // add spaces for identation 36 | result += spaces + scanner.Text() + "\n" 37 | } 38 | return result 39 | } 40 | 41 | func wrap(cmd *cobra.Command, args []string) { 42 | fileFormat, err := cmd.Flags().GetString("format") 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | var f format.Format 48 | switch fileFormat { 49 | case "yaml": 50 | f = format.Yaml{} 51 | case "json": 52 | f = format.JSON{} 53 | default: 54 | log.Fatal("Unknonwn file format. Expected one of: yaml, json") 55 | } 56 | 57 | definition := getInput(os.Stdin, f.IndentLevel()) 58 | 59 | out, err := f.Wrap(definition) 60 | if err != nil { 61 | panic(err) 62 | } 63 | fmt.Print(out) 64 | } 65 | 66 | // Wrap is cobra command for wrapping K8s objects in AppController definitions 67 | var Wrap = &cobra.Command{ 68 | Use: "wrap", 69 | Short: "Echo wrapped k8s object to stdout", 70 | Long: "Echo wrapped k8s object to stdout", 71 | Run: wrap, 72 | } 73 | -------------------------------------------------------------------------------- /pkg/resources/deployment_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 21 | ) 22 | 23 | // TestDeploymentSuccessCheck checks status of ready Deployment 24 | func TestDeploymentSuccessCheck(t *testing.T) { 25 | c := mocks.NewClient(mocks.MakeDeployment("notfail")) 26 | status, err := deploymentStatus(c.Deployments(), "notfail") 27 | 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if status != "ready" { 33 | t.Errorf("Status should be `ready`, is `%s` instead.", status) 34 | } 35 | } 36 | 37 | // TestDeploymentFailUpdatedCheck checks status of not ready deployment 38 | func TestDeploymentFailUpdatedCheck(t *testing.T) { 39 | c := mocks.NewClient(mocks.MakeDeployment("fail")) 40 | status, err := deploymentStatus(c.Deployments(), "fail") 41 | 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | 46 | if status != "not ready" { 47 | t.Errorf("Status should be `not ready`, is `%s` instead.", status) 48 | } 49 | } 50 | 51 | // TestDeploymentFailAvailableCheck checks status of not ready deployment 52 | func TestDeploymentFailAvailableCheck(t *testing.T) { 53 | c := mocks.NewClient(mocks.MakeDeployment("failav")) 54 | status, err := deploymentStatus(c.Deployments(), "failav") 55 | 56 | if err != nil { 57 | t.Error(err) 58 | } 59 | 60 | if status != "not ready" { 61 | t.Errorf("Status should be `not ready`, is `%s` instead.", status) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/client-go/pkg/api/unversioned" 21 | "k8s.io/client-go/pkg/api/v1" 22 | "k8s.io/client-go/pkg/runtime" 23 | versionedwatch "k8s.io/client-go/pkg/watch/versioned" 24 | ) 25 | 26 | // GroupName is the group name use in this package 27 | const GroupName = "apps" 28 | 29 | // SchemeGroupVersion is group version used to register these objects 30 | var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1alpha1"} 31 | var OriginalGroupVersion = unversioned.GroupVersion{Group: "alphaapps", Version: "v1alpha1"} 32 | 33 | var ( 34 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs, addConversionFuncs) 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | 38 | // Adds the list of known types to api.Scheme. 39 | func addKnownTypes(scheme *runtime.Scheme) error { 40 | scheme.AddKnownTypes(SchemeGroupVersion, 41 | &PetSet{}, 42 | &PetSetList{}, 43 | &v1.ListOptions{}, 44 | &v1.DeleteOptions{}, 45 | ) 46 | scheme.AddKnownTypes(OriginalGroupVersion, 47 | &PetSet{}, 48 | &PetSetList{}, 49 | &v1.ListOptions{}, 50 | &v1.DeleteOptions{}) 51 | versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) 52 | return nil 53 | } 54 | 55 | func (obj *PetSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } 56 | func (obj *PetSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } 57 | -------------------------------------------------------------------------------- /tests/linear/definitions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appcontroller.k8s/v1alpha1 2 | kind: Definition 3 | metadata: 4 | name: test-pod 5 | labels: 6 | app: service1 7 | pod: 8 | apiVersion: v1 9 | kind: Pod 10 | metadata: 11 | name: test-pod 12 | spec: 13 | containers: 14 | - command: ["/bin/sh"] 15 | args: 16 | - -c 17 | - sleep 30; echo ok > /tmp/health; sleep 600 18 | image: gcr.io/google_containers/busybox 19 | readinessProbe: 20 | exec: 21 | command: 22 | - /bin/cat 23 | - /tmp/health 24 | name: test-container 25 | --- 26 | apiVersion: appcontroller.k8s/v1alpha1 27 | kind: Definition 28 | metadata: 29 | name: test-job 30 | job: 31 | apiVersion: batch/v1 32 | kind: Job 33 | metadata: 34 | name: test-job 35 | spec: 36 | template: 37 | metadata: 38 | name: test-job 39 | spec: 40 | containers: 41 | - name: test-container 42 | image: gcr.io/google_containers/busybox 43 | command: [ "/bin/sh", "-c", "sleep 10; env"] 44 | restartPolicy: Never 45 | --- 46 | apiVersion: appcontroller.k8s/v1alpha1 47 | kind: Definition 48 | metadata: 49 | name: test-service 50 | service: 51 | kind: Service 52 | apiVersion: v1 53 | metadata: 54 | name: test-service 55 | spec: 56 | selector: 57 | app: service1 58 | ports: 59 | - protocol: TCP 60 | port: 80 61 | targetPort: 9376 62 | --- 63 | apiVersion: appcontroller.k8s/v1alpha1 64 | kind: Definition 65 | metadata: 66 | name: test-replicaset 67 | replicaset: 68 | apiVersion: extensions/v1beta1 69 | kind: ReplicaSet 70 | metadata: 71 | name: test-replicaset 72 | spec: 73 | replicas: 3 74 | template: 75 | metadata: 76 | labels: 77 | app: guestbook 78 | tier: frontend 79 | spec: 80 | containers: 81 | - name: php-redis 82 | image: gcr.io/google_samples/gb-frontend:v3 83 | env: 84 | - name: GET_HOSTS_FROM 85 | value: dns 86 | ports: 87 | - containerPort: 80 88 | -------------------------------------------------------------------------------- /pkg/resources/statefulset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "k8s.io/client-go/pkg/apis/apps/v1beta1" 21 | 22 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 23 | ) 24 | 25 | // TestStatefulSetSuccessCheck checks status of ready StatefulSet 26 | func TestStatefulSetSuccessCheck(t *testing.T) { 27 | c := mocks.NewClient(mocks.MakeStatefulSet("notfail")) 28 | status, err := statefulsetStatus(c.StatefulSets(), "notfail", c) 29 | 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | if status != "ready" { 35 | t.Errorf("Status should be `ready`, is `%s` instead.", status) 36 | } 37 | } 38 | 39 | // TestStatefulSetFailCheck checks status of not ready statefulset 40 | func TestStatefulSetFailCheck(t *testing.T) { 41 | ss := mocks.MakeStatefulSet("fail") 42 | pod := mocks.MakePod("fail") 43 | pod.Labels = ss.Spec.Template.ObjectMeta.Labels 44 | c := mocks.NewClient(ss, pod) 45 | status, err := statefulsetStatus(c.StatefulSets(), "fail", c) 46 | 47 | expectedError := "Resource pod/fail is not ready" 48 | if err.Error() != expectedError { 49 | t.Errorf("Expected `%s` as error, got `%s`", expectedError, err.Error()) 50 | } 51 | 52 | if status != "not ready" { 53 | t.Errorf("Status should be `not ready`, is `%s` instead.", status) 54 | } 55 | } 56 | 57 | func TestStatefulSetIsEnabled(t *testing.T) { 58 | c := mocks.NewClient() 59 | if !c.IsEnabled(v1beta1.SchemeGroupVersion) { 60 | t.Errorf("%v expected to be enabled", v1beta1.SchemeGroupVersion) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apps 18 | 19 | import ( 20 | "k8s.io/client-go/pkg/api" 21 | "k8s.io/client-go/pkg/api/unversioned" 22 | "k8s.io/client-go/pkg/runtime" 23 | ) 24 | 25 | var ( 26 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 27 | AddToScheme = SchemeBuilder.AddToScheme 28 | ) 29 | 30 | // GroupName is the group name use in this package 31 | const GroupName = "apps" 32 | 33 | // SchemeGroupVersion is group version used to register these objects 34 | var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} 35 | var OriginalGroupVersion = unversioned.GroupVersion{Group: "alphaapps", Version: runtime.APIVersionInternal} 36 | 37 | // Kind takes an unqualified kind and returns a Group qualified GroupKind 38 | func Kind(kind string) unversioned.GroupKind { 39 | return SchemeGroupVersion.WithKind(kind).GroupKind() 40 | } 41 | 42 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 43 | func Resource(resource string) unversioned.GroupResource { 44 | return SchemeGroupVersion.WithResource(resource).GroupResource() 45 | } 46 | 47 | // Adds the list of known types to api.Scheme. 48 | func addKnownTypes(scheme *runtime.Scheme) error { 49 | // TODO this will get cleaned up with the scheme types are fixed 50 | scheme.AddKnownTypes(SchemeGroupVersion, 51 | &PetSet{}, 52 | &PetSetList{}, 53 | &api.ListOptions{}, 54 | &api.DeleteOptions{}, 55 | ) 56 | scheme.AddKnownTypes(OriginalGroupVersion, 57 | &PetSet{}, 58 | &PetSetList{}, 59 | &api.ListOptions{}, 60 | &api.DeleteOptions{}) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /examples/simple/create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ../common.sh 4 | 5 | echo "Let's create the job that existed before (not being created by AppController)" 6 | 7 | echo "$KUBECTL_NAME create -f existing_job.yaml" 8 | $KUBECTL_NAME create -f existing_job.yaml 9 | 10 | echo "Creating pod with AppController binary. This is going to be our entry point." 11 | echo "$KUBECTL_NAME create -f ../../appcontroller.yaml" 12 | $KUBECTL_NAME create -f ../../manifests/appcontroller.yaml 13 | wait-appcontroller 14 | 15 | echo "Let's create dependencies. Please refer to https://github.com/Mirantis/k8s-AppController/tree/demo/examples/simple/graph.svg to see the graph composition." 16 | echo "$KUBECTL_NAME create -f deps.yaml" 17 | $KUBECTL_NAME create -f deps.yaml 18 | 19 | echo "Let's create resource definitions. We are wrapping existing job and pod definitions in ResourceDefinitions. We are not creating the pods and jobs themselves - yet!" 20 | echo "cat job.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job1 | $KUBECTL_NAME create -f -" 21 | cat job.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 22 | echo "cat job2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job2 | $KUBECTL_NAME create -f -" 23 | cat job2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 24 | 25 | echo "cat pod.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod1 | $KUBECTL_NAME create -f -" 26 | cat pod.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 27 | echo "cat pod2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod2 | $KUBECTL_NAME create -f -" 28 | cat pod2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 29 | echo "cat pod3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod3 | $KUBECTL_NAME create -f -" 30 | cat pod3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 31 | 32 | echo "Here we are running appcontroller binary itself. As the log will say, it retrieves dependencies and resource definitions from the k8s cluster and creates underlying objects accordingly." 33 | echo "$KUBECTL_NAME exec k8s-appcontroller ac-run" 34 | $KUBECTL_NAME exec k8s-appcontroller ac-run 35 | -------------------------------------------------------------------------------- /pkg/mocks/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "github.com/Mirantis/k8s-AppController/pkg/client" 19 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 20 | "github.com/Mirantis/k8s-AppController/pkg/report" 21 | ) 22 | 23 | // Resource is a fake resource 24 | type Resource struct { 25 | key string 26 | status string 27 | } 28 | 29 | // Key returns a key of the Resource 30 | func (c Resource) Key() string { 31 | return c.key 32 | } 33 | 34 | // Status returns a status of the Resource 35 | func (c *Resource) Status(meta map[string]string) (string, error) { 36 | return c.status, nil 37 | } 38 | 39 | // Create does nothing 40 | func (c *Resource) Create() error { 41 | return nil 42 | } 43 | 44 | // Delete does nothing 45 | func (c *Resource) Delete() error { 46 | return nil 47 | } 48 | 49 | // Meta returns empty string 50 | func (c *Resource) Meta(string) string { 51 | return "" 52 | } 53 | 54 | // NameMatches returns true 55 | func (c *Resource) NameMatches(_ client.ResourceDefinition, _ string) bool { 56 | return true 57 | } 58 | 59 | // New returns new fake resource 60 | func (c *Resource) New(_ client.ResourceDefinition, _ client.Interface) interfaces.Resource { 61 | return report.SimpleReporter{BaseResource: NewResource("fake", "ready")} 62 | } 63 | 64 | // NewExisting returns new existing resource 65 | func (c *Resource) NewExisting(name string, _ client.Interface) interfaces.BaseResource { 66 | return NewResource(name, "ready") 67 | } 68 | 69 | // NewResource creates new instance of Resource 70 | func NewResource(key string, status string) *Resource { 71 | return &Resource{ 72 | key: key, 73 | status: status, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cmd/get-status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/Mirantis/k8s-AppController/pkg/client" 10 | "github.com/Mirantis/k8s-AppController/pkg/scheduler" 11 | 12 | "github.com/spf13/cobra" 13 | "k8s.io/client-go/pkg/labels" 14 | ) 15 | 16 | // GetStatus is a command that prints the deployment status 17 | func getStatus(cmd *cobra.Command, args []string) { 18 | var err error 19 | 20 | labelSelector, err := getLabelSelector(cmd) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | getJSON, err := cmd.Flags().GetBool("json") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | getReport, err := cmd.Flags().GetBool("report") 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | var url string 35 | if len(args) > 0 { 36 | url = args[0] 37 | } 38 | if url == "" { 39 | url = os.Getenv("KUBERNETES_CLUSTER_URL") 40 | } 41 | 42 | c, err := client.New(url) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | sel, err := labels.Parse(labelSelector) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | graph, err := scheduler.BuildDependencyGraph(c, sel) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | status, report := graph.GetStatus() 55 | if getJSON { 56 | data, err := json.Marshal(report) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | fmt.Printf(string(data)) 61 | } else { 62 | fmt.Printf("STATUS: %s\n", status) 63 | if getReport { 64 | data := report.AsText(0) 65 | for _, line := range data { 66 | fmt.Println(line) 67 | } 68 | } 69 | } 70 | } 71 | 72 | // InitGetStatusCommand is an initialiser for get-status 73 | func InitGetStatusCommand() (*cobra.Command, error) { 74 | var err error 75 | run := &cobra.Command{ 76 | Use: "get-status", 77 | Short: "Get status of deployment", 78 | Long: "Get status of deployment", 79 | Run: getStatus, 80 | } 81 | var labelSelector string 82 | run.Flags().StringVarP(&labelSelector, "label", "l", "", "Label selector. Overrides KUBERNETES_AC_LABEL_SELECTOR env variable in AppController pod.") 83 | 84 | var getJSON, report bool 85 | run.Flags().BoolVarP(&getJSON, "json", "j", false, "Output JSON") 86 | run.Flags().BoolVarP(&report, "report", "r", false, "Get human-readable full report") 87 | return run, err 88 | } 89 | -------------------------------------------------------------------------------- /examples/extended/create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ../common.sh 4 | 5 | $KUBECTL_NAME create -f existing_job.yaml 6 | 7 | $KUBECTL_NAME create -f ../../manifests/appcontroller.yaml 8 | wait-appcontroller 9 | 10 | $KUBECTL_NAME create -f deps.yaml 11 | 12 | cat job.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 13 | cat job2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 14 | cat job3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 15 | cat job4.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 16 | 17 | cat pod.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 18 | cat pod2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 19 | cat pod3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 20 | cat pod4.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 21 | cat pod5.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 22 | cat pod6.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 23 | cat pod7.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 24 | cat pod8.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 25 | cat pod9.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 26 | 27 | cat replicaset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 28 | 29 | cat service.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 30 | 31 | cat statefulset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 32 | 33 | cat daemonset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 34 | 35 | cat configmap1.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 36 | 37 | cat secret.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 38 | 39 | cat deployment.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME create -f - 40 | 41 | $KUBECTL_NAME exec k8s-appcontroller ac-run 42 | $KUBECTL_NAME logs -f k8s-appcontroller 43 | -------------------------------------------------------------------------------- /examples/extended/delete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ../common.sh 4 | 5 | $KUBECTL_NAME delete -f existing_job.yaml 6 | 7 | $KUBECTL_NAME delete -f deps.yaml 8 | 9 | cat job.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job1 | $KUBECTL_NAME delete -f - 10 | cat job2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job2 | $KUBECTL_NAME delete -f - 11 | cat job3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job3 | $KUBECTL_NAME delete -f - 12 | cat job4.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap job4 | $KUBECTL_NAME delete -f - 13 | 14 | cat pod.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod1 | $KUBECTL_NAME delete -f - 15 | cat pod2.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod2 | $KUBECTL_NAME delete -f - 16 | cat pod3.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod3 | $KUBECTL_NAME delete -f - 17 | cat pod4.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod4 | $KUBECTL_NAME delete -f - 18 | cat pod5.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod5 | $KUBECTL_NAME delete -f - 19 | cat pod6.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod6 | $KUBECTL_NAME delete -f - 20 | cat pod7.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod7 | $KUBECTL_NAME delete -f - 21 | cat pod8.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod8 | $KUBECTL_NAME delete -f - 22 | cat pod9.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap pod9 | $KUBECTL_NAME delete -f - 23 | 24 | cat replicaset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap frontend | $KUBECTL_NAME delete -f - 25 | 26 | cat daemonset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap daemonset | $KUBECTL_NAME delete -f - 27 | 28 | cat secret.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap secret | $KUBECTL_NAME delete -f - 29 | 30 | cat service.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME delete -f - 31 | 32 | cat statefulset.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME delete -f - 33 | 34 | cat configmap1.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME delete -f - 35 | 36 | cat deployment.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME delete -f - 37 | 38 | cat pvc.yaml | $KUBECTL_NAME exec -i k8s-appcontroller kubeac wrap | $KUBECTL_NAME delete -f - 39 | 40 | $KUBECTL_NAME delete -f ../../manifests/appcontroller.yaml 41 | -------------------------------------------------------------------------------- /pkg/mocks/resdefs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "log" 19 | "strings" 20 | 21 | "k8s.io/client-go/pkg/api" 22 | 23 | "github.com/Mirantis/k8s-AppController/pkg/client" 24 | ) 25 | 26 | type resDefClient struct { 27 | Names []string 28 | } 29 | 30 | func (r *resDefClient) List(opts api.ListOptions) (*client.ResourceDefinitionList, error) { 31 | list := &client.ResourceDefinitionList{} 32 | 33 | for _, name := range r.Names { 34 | rd := client.ResourceDefinition{} 35 | 36 | splitted := strings.Split(name, "/") 37 | objectType := splitted[0] 38 | n := strings.Join(splitted[1:], "/") 39 | 40 | switch objectType { 41 | case "pod": 42 | rd.Pod = MakePod(n) 43 | case "job": 44 | rd.Job = MakeJob(n) 45 | case "service": 46 | rd.Service = MakeService(n) 47 | case "replicaset": 48 | rd.ReplicaSet = MakeReplicaSet(n) 49 | case "statefulset": 50 | rd.StatefulSet = MakeStatefulSet(n) 51 | case "petset": 52 | rd.PetSet = MakePetSet(n) 53 | case "daemonset": 54 | rd.DaemonSet = MakeDaemonSet(n) 55 | case "configmap": 56 | rd.ConfigMap = MakeConfigMap(n) 57 | case "secret": 58 | rd.Secret = MakeSecret(n) 59 | case "deployment": 60 | rd.Deployment = MakeDeployment(n) 61 | case "persistentvolumeclaim": 62 | rd.PersistentVolumeClaim = MakePersistentVolumeClaim(n) 63 | default: 64 | log.Fatal("Unrecognized resource type for name ", objectType) 65 | } 66 | 67 | list.Items = append(list.Items, rd) 68 | } 69 | 70 | return list, nil 71 | } 72 | 73 | func (r *resDefClient) Create(_ *client.ResourceDefinition) (*client.ResourceDefinition, error) { 74 | panic("Not implemented") 75 | } 76 | 77 | func (r *resDefClient) Delete(_ string, _ *api.DeleteOptions) error { 78 | panic("Not implemented") 79 | } 80 | 81 | func NewResourceDefinitionClient(names ...string) client.ResourceDefinitionsInterface { 82 | return &resDefClient{names} 83 | } 84 | -------------------------------------------------------------------------------- /docs/research/lcm.md: -------------------------------------------------------------------------------- 1 | # AppController Mysql Multi Slave research 2 | 3 | This research is meant to provide a guidance on how to achieve ability of AppController-deployed Mysql Multi Slave cluster to cope with losing the master node by promoting one of slave nodes to master. 4 | This is an example which will allow us to use AC for complicated lcm cases. 5 | 6 | MySQL Masters should not be restarted. If the master node crashes, the pod eligible for promotion is transformed into master. Each pod eligible for promotion should have an executable which will perform it's promotion. 7 | 8 | AC will need additional process traversing the deployment graph and checking the state of vertices. If the vertex is not ready and has deployed children, we need to take action, which will be defined in an annotation in the pod OR in the AC ResourceDefinition Metadata. We need a DSL for defining these actions, which will allow AC to read specification of these action and run them. This could be the implementation of `failurePolicy` from failure-handling research. This DSL can be used for both LCM and error-handling purposes. 9 | 10 | Suggested annotation format: 11 | ``` 12 | { 13 | "onFail": [ 14 | {"type": "exec", "cmd": "promote_to_master.sh", "oneOf": "role:slave"}, # promote one of slaves to master 15 | {"type": "create", "template": "slave pod template"}, # create new slave pod which will replicate new master 16 | {"type": "rerun", "root": "subgraph-vertice-id"} # redeploy subgraph 17 | ] 18 | } 19 | ``` 20 | - `onFail` is a list of actions which need to be performed by ac. 21 | - `type` is a type of action which should be performed on detected failure. Two types are proposed, exec and create. 22 | - `exec` type executes the `cmd` in a pod. We need to specify the target pod on which the `cmd` should be executed. In this case, `oneOf` is K8s selector which will be used to retrieve slaves that are eligible for the promotion, one of which will be selected. 23 | - `create` type creates new k8s object (or AC resource definition) using the `template`. The template will be either a name of k8s object which template we should use, or plain-text object template. We need to see which is preferrable when we implement this. 24 | - `rerun` type causes part of the graph that starts in `root` to be redeployed. 25 | 26 | The AC run will be as follows: 27 | - Check if all graph vertices are created (don't check their status, just their existence). If not, go to 2. if Yes, go to 3. 28 | - Run deployment (this is what happens when you run ac process right now). 29 | - Run new monitoring process of AC. 30 | 31 | Above flow allows ac to remain stateless. 32 | -------------------------------------------------------------------------------- /pkg/mocks/countingresource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mocks 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/client" 21 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 22 | "github.com/Mirantis/k8s-AppController/pkg/report" 23 | ) 24 | 25 | // CountingResource is a fake resource that becomes ready after given timeout. 26 | // It also increases the counter when started and decreases it when becomes ready 27 | type CountingResource struct { 28 | key string 29 | status string 30 | counter *CounterWithMemo 31 | timeout time.Duration 32 | startTime time.Time 33 | } 34 | 35 | // Key returns a key of the CountingResource 36 | func (c CountingResource) Key() string { 37 | return c.key 38 | } 39 | 40 | // Status returns a status of the CountingResource. It also updates the status 41 | // after provided timeout and decrements counter 42 | func (c *CountingResource) Status(meta map[string]string) (string, error) { 43 | if time.Since(c.startTime) >= c.timeout && c.status != "ready" { 44 | c.counter.Dec() 45 | c.status = "ready" 46 | } 47 | 48 | return c.status, nil 49 | } 50 | 51 | // Create increments counter and sets creation time 52 | func (c *CountingResource) Create() error { 53 | c.counter.Inc() 54 | c.startTime = time.Now() 55 | return nil 56 | } 57 | 58 | // Delete does nothing 59 | func (c *CountingResource) Delete() error { 60 | return nil 61 | } 62 | 63 | // Meta returns empty string 64 | func (c *CountingResource) Meta(string) string { 65 | return "" 66 | } 67 | 68 | // NameMatches returns true 69 | func (c *CountingResource) NameMatches(_ client.ResourceDefinition, _ string) bool { 70 | return true 71 | } 72 | 73 | // New returns new fake resource 74 | func (c *CountingResource) New(_ client.ResourceDefinition, _ client.Interface) interfaces.BaseResource { 75 | return report.SimpleReporter{BaseResource: NewResource("fake", "ready")} 76 | } 77 | 78 | // NewExisting returns new existing resource 79 | func (c *CountingResource) NewExisting(name string, _ client.Interface) interfaces.BaseResource { 80 | return report.SimpleReporter{BaseResource: NewResource(name, "ready")} 81 | } 82 | 83 | // NewCountingResource creates new instance of CountingResource 84 | func NewCountingResource(key string, counter *CounterWithMemo, timeout time.Duration) *CountingResource { 85 | return &CountingResource{ 86 | key: key, 87 | status: "not ready", 88 | counter: counter, 89 | timeout: timeout, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/client/dependencies.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | 21 | "k8s.io/client-go/pkg/api" 22 | "k8s.io/client-go/pkg/api/unversioned" 23 | "k8s.io/client-go/rest" 24 | ) 25 | 26 | type Dependency struct { 27 | unversioned.TypeMeta `json:",inline"` 28 | 29 | // Standard object metadata 30 | api.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 31 | 32 | Parent string `json:"parent"` 33 | Child string `json:"child"` 34 | Meta map[string]string `json:"meta,omitempty"` 35 | } 36 | 37 | type DependencyList struct { 38 | unversioned.TypeMeta `json:",inline"` 39 | 40 | // Standard list metadata. 41 | unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 42 | 43 | Items []Dependency `json:"items" protobuf:"bytes,2,rep,name=items"` 44 | } 45 | 46 | type DependenciesInterface interface { 47 | List(opts api.ListOptions) (*DependencyList, error) 48 | Create(*Dependency) (*Dependency, error) 49 | Delete(name string, opts *api.DeleteOptions) error 50 | } 51 | 52 | type dependencies struct { 53 | rc *rest.RESTClient 54 | } 55 | 56 | func newDependencies(c rest.Config) (*dependencies, error) { 57 | rc, err := thirdPartyResourceRESTClient(&c) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | return &dependencies{rc}, nil 63 | } 64 | 65 | func (c dependencies) List(opts api.ListOptions) (*DependencyList, error) { 66 | resp, err := c.rc.Get(). 67 | Namespace("default"). 68 | Resource("dependencies"). 69 | LabelsSelectorParam(opts.LabelSelector). 70 | DoRaw() 71 | 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | result := &DependencyList{} 77 | err = json.NewDecoder(bytes.NewReader(resp)).Decode(result) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | return result, nil 83 | } 84 | 85 | func (c dependencies) Create(d *Dependency) (result *Dependency, err error) { 86 | result = &Dependency{} 87 | err = c.rc.Post(). 88 | Namespace("default"). 89 | Resource("Dependencies"). 90 | Body(d). 91 | Do(). 92 | Into(result) 93 | return 94 | } 95 | 96 | func (c *dependencies) Delete(name string, opts *api.DeleteOptions) error { 97 | return c.rc.Delete(). 98 | Namespace("default"). 99 | Resource("dependencies"). 100 | Name(name). 101 | Body(opts). 102 | Do(). 103 | Error() 104 | } 105 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/apps_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | api "k8s.io/client-go/pkg/api" 21 | registered "k8s.io/client-go/pkg/apimachinery/registered" 22 | "k8s.io/client-go/pkg/apis/apps/v1beta1" 23 | serializer "k8s.io/client-go/pkg/runtime/serializer" 24 | rest "k8s.io/client-go/rest" 25 | ) 26 | 27 | type AppsInterface interface { 28 | GetRESTClient() *rest.RESTClient 29 | PetSetsGetter 30 | } 31 | 32 | // AppsClient is used to interact with features provided by the Apps group. 33 | type AppsClient struct { 34 | *rest.RESTClient 35 | } 36 | 37 | func (c *AppsClient) PetSets(namespace string) PetSetInterface { 38 | return newPetSets(c, namespace) 39 | } 40 | 41 | // NewForConfig creates a new AppsClient for the given config. 42 | func NewForConfig(c *rest.Config) (*AppsClient, error) { 43 | config := *c 44 | if err := setConfigDefaults(&config); err != nil { 45 | return nil, err 46 | } 47 | client, err := rest.RESTClientFor(&config) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &AppsClient{client}, nil 52 | } 53 | 54 | // NewForConfigOrDie creates a new AppsClient for the given config and 55 | // panics if there is an error in the config. 56 | func NewForConfigOrDie(c *rest.Config) *AppsClient { 57 | client, err := NewForConfig(c) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return client 62 | } 63 | 64 | // New creates a new AppsClient for the given RESTClient. 65 | func New(c *rest.RESTClient) *AppsClient { 66 | return &AppsClient{c} 67 | } 68 | 69 | func setConfigDefaults(config *rest.Config) error { 70 | // if apps group is not registered, return an error 71 | g, err := registered.Group("alphaapps") 72 | if err != nil { 73 | return err 74 | } 75 | config.APIPath = "/apis" 76 | if config.UserAgent == "" { 77 | config.UserAgent = rest.DefaultKubernetesUserAgent() 78 | } 79 | // TODO: Unconditionally set the config.Version, until we fix the config. 80 | //if config.Version == "" { 81 | copyGroupVersion := g.GroupVersion 82 | config.GroupVersion = ©GroupVersion 83 | // NOTE(dshulyak) this is a workaround to construct correct URL for k8s 84 | config.GroupVersion.Group = v1beta1.GroupName 85 | //} 86 | config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: api.Codecs} 87 | 88 | return nil 89 | } 90 | 91 | // GetRESTClient returns a RESTClient that is used to communicate 92 | // with API server by this client implementation. 93 | func (c *AppsClient) GetRESTClient() *rest.RESTClient { 94 | if c == nil { 95 | return nil 96 | } 97 | return c.RESTClient 98 | } 99 | -------------------------------------------------------------------------------- /docs/research/failure-handling.md: -------------------------------------------------------------------------------- 1 | ================ 2 | Failure Handling 3 | ================ 4 | 5 | As currently implemented, AppController works well if all requested K8s objects get created properly. 6 | If any of them failed to create, AppController quits immediately. We need to improve failure handling in 7 | AppController. 8 | 9 | Generally we should allow user to provide desired reaction of AppController on possible failure of creating 10 | Kubernetes object. Possible reactions include: 11 | 12 | 1. **Retry**: AC tries to delete failed K8s resource and then re-create it. We should be able to set maximum retries and delay before the next retry. 13 | 2. **Rollback**: AC deletes all resourses that were created during current run. 14 | 3. **Abort**: Immediate quit, keep all created resources. 15 | 4. **Ignore current**: AC proceeds with creating resources as if current one was created successfully. 16 | 5. **Ignore children**: AC skips creation of all resources depending on the current one and all cascade dependencies, but proceeds with creating the rest of resources. 17 | 18 | Also during discussion in the community several additional features related to the topic were requested: 19 | 20 | - **Debug**: In case of failure one might want to understand the reason and to fix it. This could imply two possible scenarios: 21 | - **Fix and restart**: In case of failure AC immediately quits (failure policy **Abort**), then user inspects failed resource and restarts execution. 22 | - **Pause and resume**: In case of failure AC pauses creation of current dependency graph branch, allows user to inspect failed resource, possibly update resource definition, and then resume execution with retrying, ignoring this or all dependent resources. Unfortunately to implement such feature we need to execute AC interactively, but currently AC executes as a batch. 23 | - **Per-resource failure handling policy**: User might want to set per-resource failure handling policy as well as general (default) one and per resource type. We should take into account that failure handling policy could not be _per-dependency_. 24 | - **Timeout**: If resource does not get ready during given timeout, it should be considered as failed. It is not clear, should we set timeout per-resource, per-dependency or both. 25 | - **Alternative dependency graph branch**: In case of resource creation failure we might want to execute alternative branch/path of dependency graph. This could be achieved via two additional features: 26 | - **Anti-dependency**: An attribute in dependency's metadata that requires creation of the child only if creation of the parent failed. 27 | - **Alternative dependency** or **Optional dependency**: To be able to join _direct_ and _alternative_ branches of dependency graph. 28 | 29 | Implementation details 30 | ---------------------- 31 | 32 | 1. All resources should have _delete_ method. 33 | 2. **Abort** should be default failure policy. _Alternatively_ **Rollback** should be default failure policy. 34 | 3. It should be possible to set up default failure policy via CLI flags or environment variables. 35 | 4. Resource could be in _Ignored_ state. 36 | 5. Dependency metadata can include _timeout_ attribute and _inverse_ flag. 37 | 6. 3rdPartyResource that contains resource definition can include _failurePolicy_ attribute. 38 | 7. TBD syntax for _alternative dependency_. 39 | 8. Corresponding changes should be made to scheduler. 40 | -------------------------------------------------------------------------------- /pkg/resources/secrets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "log" 19 | 20 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 21 | "k8s.io/client-go/pkg/api/v1" 22 | 23 | "github.com/Mirantis/k8s-AppController/pkg/client" 24 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 25 | "github.com/Mirantis/k8s-AppController/pkg/report" 26 | ) 27 | 28 | type Secret struct { 29 | Base 30 | Secret *v1.Secret 31 | Client corev1.SecretInterface 32 | } 33 | 34 | type ExistingSecret struct { 35 | Base 36 | Name string 37 | Client corev1.SecretInterface 38 | } 39 | 40 | func secretKey(name string) string { 41 | return "secret/" + name 42 | } 43 | 44 | func (s Secret) Key() string { 45 | return secretKey(s.Secret.Name) 46 | } 47 | 48 | func (s ExistingSecret) Key() string { 49 | return secretKey(s.Name) 50 | } 51 | 52 | func secretStatus(s corev1.SecretInterface, name string) (string, error) { 53 | _, err := s.Get(name) 54 | if err != nil { 55 | return "error", err 56 | } 57 | 58 | return "ready", nil 59 | } 60 | 61 | func (s Secret) Status(meta map[string]string) (string, error) { 62 | return secretStatus(s.Client, s.Secret.Name) 63 | } 64 | 65 | func (s Secret) Create() error { 66 | if err := checkExistence(s); err != nil { 67 | log.Println("Creating ", s.Key()) 68 | s.Secret, err = s.Client.Create(s.Secret) 69 | return err 70 | } 71 | return nil 72 | } 73 | 74 | func (s Secret) Delete() error { 75 | return s.Client.Delete(s.Secret.Name, nil) 76 | } 77 | 78 | func (s Secret) NameMatches(def client.ResourceDefinition, name string) bool { 79 | return def.Secret != nil && def.Secret.Name == name 80 | } 81 | 82 | func NewSecret(s *v1.Secret, client corev1.SecretInterface, meta map[string]string) interfaces.Resource { 83 | return report.SimpleReporter{BaseResource: Secret{Base: Base{meta}, Secret: s, Client: client}} 84 | } 85 | 86 | func NewExistingSecret(name string, client corev1.SecretInterface) interfaces.Resource { 87 | return report.SimpleReporter{BaseResource: ExistingSecret{Name: name, Client: client}} 88 | } 89 | 90 | func (s Secret) New(def client.ResourceDefinition, ci client.Interface) interfaces.Resource { 91 | return NewSecret(def.Secret, ci.Secrets(), def.Meta) 92 | } 93 | 94 | func (s Secret) NewExisting(name string, ci client.Interface) interfaces.Resource { 95 | return NewExistingSecret(name, ci.Secrets()) 96 | } 97 | 98 | func (s ExistingSecret) Status(meta map[string]string) (string, error) { 99 | return secretStatus(s.Client, s.Name) 100 | } 101 | 102 | func (s ExistingSecret) Create() error { 103 | return createExistingResource(s) 104 | } 105 | 106 | func (s ExistingSecret) Delete() error { 107 | return s.Client.Delete(s.Name, nil) 108 | } 109 | -------------------------------------------------------------------------------- /e2e/utils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "io" 21 | "time" 22 | 23 | "k8s.io/client-go/kubernetes" 24 | "k8s.io/client-go/pkg/api/v1" 25 | "k8s.io/client-go/rest" 26 | "k8s.io/client-go/tools/clientcmd" 27 | 28 | . "github.com/onsi/ginkgo" 29 | . "github.com/onsi/gomega" 30 | 31 | "github.com/Mirantis/k8s-AppController/pkg/client" 32 | ) 33 | 34 | var url string 35 | 36 | func init() { 37 | flag.StringVar(&url, "cluster-url", "http://127.0.0.1:8080", "apiserver address to use with restclient") 38 | } 39 | 40 | func Logf(format string, a ...interface{}) { 41 | fmt.Fprintf(GinkgoWriter, format, a...) 42 | } 43 | 44 | func LoadConfig() *rest.Config { 45 | config, err := clientcmd.BuildConfigFromFlags(url, "") 46 | Expect(err).NotTo(HaveOccurred()) 47 | return config 48 | } 49 | 50 | func KubeClient() (*kubernetes.Clientset, error) { 51 | Logf("Using master %v\n", url) 52 | config := LoadConfig() 53 | clientset, err := kubernetes.NewForConfig(config) 54 | Expect(err).NotTo(HaveOccurred()) 55 | return clientset, nil 56 | } 57 | 58 | func GetAcClient() (client.Interface, error) { 59 | client, err := client.New(url) 60 | return client, err 61 | } 62 | 63 | func DeleteNS(clientset *kubernetes.Clientset, namespace *v1.Namespace) { 64 | defer GinkgoRecover() 65 | pods, err := clientset.Pods(namespace.Name).List(v1.ListOptions{}) 66 | Expect(err).NotTo(HaveOccurred()) 67 | for _, pod := range pods.Items { 68 | clientset.Pods(namespace.Name).Delete(pod.Name, nil) 69 | } 70 | clientset.Namespaces().Delete(namespace.Name, nil) 71 | } 72 | 73 | func WaitForPod(clientset *kubernetes.Clientset, namespace string, name string, phase v1.PodPhase) *v1.Pod { 74 | defer GinkgoRecover() 75 | var podUpdated *v1.Pod 76 | Eventually(func() error { 77 | podUpdated, err := clientset.Core().Pods(namespace).Get(name) 78 | if err != nil { 79 | return err 80 | } 81 | if phase != "" && podUpdated.Status.Phase != phase { 82 | return fmt.Errorf("pod %v is not %v phase: %v", podUpdated.Name, phase, podUpdated.Status.Phase) 83 | } 84 | return nil 85 | }, 120*time.Second, 5*time.Second).Should(BeNil()) 86 | return podUpdated 87 | } 88 | 89 | func DumpLogs(clientset *kubernetes.Clientset, pods ...v1.Pod) { 90 | for _, pod := range pods { 91 | dumpLogs(clientset, pod) 92 | } 93 | } 94 | 95 | func dumpLogs(clientset *kubernetes.Clientset, pod v1.Pod) { 96 | req := clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, &v1.PodLogOptions{}) 97 | readCloser, err := req.Stream() 98 | Expect(err).NotTo(HaveOccurred()) 99 | defer readCloser.Close() 100 | Logf("\n Dumping logs for %v:%v \n", pod.Namespace, pod.Name) 101 | _, err = io.Copy(GinkgoWriter, readCloser) 102 | Expect(err).NotTo(HaveOccurred()) 103 | } 104 | -------------------------------------------------------------------------------- /pkg/resources/configmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "log" 19 | 20 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 21 | "k8s.io/client-go/pkg/api/v1" 22 | 23 | "github.com/Mirantis/k8s-AppController/pkg/client" 24 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 25 | "github.com/Mirantis/k8s-AppController/pkg/report" 26 | ) 27 | 28 | type ConfigMap struct { 29 | Base 30 | ConfigMap *v1.ConfigMap 31 | Client corev1.ConfigMapInterface 32 | } 33 | 34 | type ExistingConfigMap struct { 35 | Base 36 | Name string 37 | Client corev1.ConfigMapInterface 38 | } 39 | 40 | func configMapKey(name string) string { 41 | return "configmap/" + name 42 | } 43 | 44 | func (c ConfigMap) Key() string { 45 | return configMapKey(c.ConfigMap.Name) 46 | } 47 | 48 | func configMapStatus(c corev1.ConfigMapInterface, name string) (string, error) { 49 | _, err := c.Get(name) 50 | if err != nil { 51 | return "error", err 52 | } 53 | 54 | return "ready", nil 55 | } 56 | 57 | func (c ConfigMap) Status(meta map[string]string) (string, error) { 58 | return configMapStatus(c.Client, c.ConfigMap.Name) 59 | } 60 | 61 | func (c ConfigMap) Create() error { 62 | if err := checkExistence(c); err != nil { 63 | log.Println("Creating ", c.Key()) 64 | c.ConfigMap, err = c.Client.Create(c.ConfigMap) 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | func (c ConfigMap) Delete() error { 71 | return c.Client.Delete(c.ConfigMap.Name, &v1.DeleteOptions{}) 72 | } 73 | 74 | func (c ConfigMap) NameMatches(def client.ResourceDefinition, name string) bool { 75 | return def.ConfigMap != nil && def.ConfigMap.Name == name 76 | } 77 | 78 | func NewConfigMap(c *v1.ConfigMap, client corev1.ConfigMapInterface, meta map[string]string) interfaces.Resource { 79 | return report.SimpleReporter{BaseResource: ConfigMap{Base: Base{meta}, ConfigMap: c, Client: client}} 80 | } 81 | 82 | func NewExistingConfigMap(name string, client corev1.ConfigMapInterface) interfaces.Resource { 83 | return report.SimpleReporter{BaseResource: ExistingConfigMap{Name: name, Client: client}} 84 | } 85 | 86 | // New returns a new object wrapped as Resource 87 | func (c ConfigMap) New(def client.ResourceDefinition, ci client.Interface) interfaces.Resource { 88 | return NewConfigMap(def.ConfigMap, ci.ConfigMaps(), def.Meta) 89 | } 90 | 91 | // NewExisting returns a new object based on existing one wrapped as Resource 92 | func (c ConfigMap) NewExisting(name string, ci client.Interface) interfaces.Resource { 93 | return NewExistingConfigMap(name, ci.ConfigMaps()) 94 | } 95 | 96 | func (c ExistingConfigMap) Key() string { 97 | return configMapKey(c.Name) 98 | } 99 | 100 | func (c ExistingConfigMap) Status(meta map[string]string) (string, error) { 101 | return configMapStatus(c.Client, c.Name) 102 | } 103 | 104 | func (c ExistingConfigMap) Create() error { 105 | return createExistingResource(c) 106 | } 107 | 108 | func (c ExistingConfigMap) Delete() error { 109 | return c.Client.Delete(c.Name, nil) 110 | } 111 | -------------------------------------------------------------------------------- /pkg/resources/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "testing" 19 | 20 | "fmt" 21 | 22 | "github.com/Mirantis/k8s-AppController/pkg/mocks" 23 | ) 24 | 25 | // TestCheckServiceStatusReady checks if the service status check is fine for healthy service 26 | func TestCheckServiceStatusReady(t *testing.T) { 27 | c := mocks.NewClient(mocks.MakeService("success")) 28 | status, err := serviceStatus(c.Services(), "success", c) 29 | 30 | if err != nil { 31 | t.Errorf("%s", err) 32 | } 33 | 34 | if status != "ready" { 35 | t.Errorf("service should be `ready`, is `%s` instead", status) 36 | } 37 | } 38 | 39 | // TestCheckServiceStatusPodNotReady tests if service which selects failed pods is not ready 40 | func TestCheckServiceStatusPodNotReady(t *testing.T) { 41 | svc := mocks.MakeService("failedpod") 42 | pod := mocks.MakePod("error") 43 | pod.Labels = svc.Spec.Selector 44 | c := mocks.NewClient(svc, pod) 45 | status, err := serviceStatus(c.Services(), "failedpod", c) 46 | 47 | if err == nil { 48 | t.Fatal("Error should be returned, got nil") 49 | } 50 | expectedError := fmt.Sprintf("Resource pod/%v is not ready", pod.Name) 51 | if err.Error() != expectedError { 52 | t.Errorf("Expected `%s` as error, got `%s`", expectedError, err.Error()) 53 | } 54 | 55 | if status != "not ready" { 56 | t.Errorf("service should be `not ready`, is `%s` instead", status) 57 | } 58 | } 59 | 60 | // TestCheckServiceStatusJobNotReady tests if service which selects failed pods is not ready 61 | func TestCheckServiceStatusJobNotReady(t *testing.T) { 62 | svc := mocks.MakeService("failedjob") 63 | job := mocks.MakeJob("error") 64 | job.Labels = svc.Spec.Selector 65 | c := mocks.NewClient(svc, job) 66 | status, err := serviceStatus(c.Services(), "failedjob", c) 67 | 68 | if err == nil { 69 | t.Error("Error should be returned, got nil") 70 | } 71 | 72 | expectedError := fmt.Sprintf("Resource job/%v is not ready", job.Name) 73 | if err.Error() != expectedError { 74 | t.Errorf("Expected `%s` as error, got `%s`", expectedError, err.Error()) 75 | } 76 | 77 | if status != "not ready" { 78 | t.Errorf("service should be `not ready`, is `%s` instead", status) 79 | } 80 | } 81 | 82 | // TestCheckServiceStatusReplicaSetNotReady tests if service which selects failed replicasets is not ready 83 | func TestCheckServiceStatusReplicaSetNotReady(t *testing.T) { 84 | svc := mocks.MakeService("failedrc") 85 | rc := mocks.MakeReplicaSet("fail") 86 | rc.Labels = svc.Spec.Selector 87 | c := mocks.NewClient(svc, rc) 88 | status, err := serviceStatus(c.Services(), "failedrc", c) 89 | 90 | if err == nil { 91 | t.Error("Error should be returned, got nil") 92 | } 93 | 94 | expectedError := fmt.Sprintf("Resource replicaset/%v is not ready", rc.Name) 95 | if err.Error() != expectedError { 96 | t.Errorf("Expected `%s` as error, got `%s`", expectedError, err.Error()) 97 | } 98 | 99 | if status != "not ready" { 100 | t.Errorf("service should be `not ready`, is `%s` instead", status) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /pkg/report/report.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 8 | ) 9 | 10 | const ReportIndentSize = 4 11 | 12 | // NodeReport is a report of a node in graph 13 | type NodeReport struct { 14 | Dependent string 15 | Blocked bool 16 | Ready bool 17 | Dependencies []interfaces.DependencyReport 18 | } 19 | 20 | // AsText returns a human-readable representation of the report as a slice 21 | func (n NodeReport) AsText(indent int) []string { 22 | var blockedStr, readyStr string 23 | if n.Blocked { 24 | blockedStr = "BLOCKED" 25 | } else { 26 | blockedStr = "NOT BLOCKED" 27 | } 28 | 29 | if n.Ready { 30 | readyStr = "READY" 31 | } else { 32 | readyStr = "NOT READY" 33 | } 34 | 35 | ret := []string{ 36 | fmt.Sprintf("Resource: %s", n.Dependent), 37 | blockedStr, 38 | readyStr, 39 | } 40 | for _, dependency := range n.Dependencies { 41 | ret = append(ret, dependencyReportAsText(dependency, ReportIndentSize)...) 42 | } 43 | return Indent(indent, ret) 44 | } 45 | 46 | // DeploymentReport is a full report of the status of deployment 47 | type DeploymentReport []NodeReport 48 | 49 | // AsText returns a human-readable representation of the report as a slice 50 | func (d DeploymentReport) AsText(indent int) []string { 51 | ret := make([]string, 0, len(d)*4) 52 | for _, n := range d { 53 | ret = append(ret, n.AsText(ReportIndentSize)...) 54 | } 55 | return Indent(indent, ret) 56 | } 57 | 58 | // SimpleReporter creates report for simple binary cases 59 | type SimpleReporter struct { 60 | interfaces.BaseResource 61 | } 62 | 63 | // GetDependencyReport returns a dependency report for this reporter 64 | func (r SimpleReporter) GetDependencyReport(meta map[string]string) interfaces.DependencyReport { 65 | status, err := r.Status(meta) 66 | if err != nil { 67 | return ErrorReport(r.Key(), err) 68 | } 69 | if status == "ready" { 70 | return interfaces.DependencyReport{ 71 | Dependency: r.Key(), 72 | Blocks: false, 73 | Percentage: 100, 74 | Needed: 100, 75 | Message: status, 76 | } 77 | } 78 | return interfaces.DependencyReport{ 79 | Dependency: r.Key(), 80 | Blocks: true, 81 | Percentage: 0, 82 | Needed: 0, 83 | Message: status, 84 | } 85 | } 86 | 87 | // GetResource returns the underlying resource 88 | func (r SimpleReporter) GetResource() interfaces.BaseResource { 89 | return r.BaseResource 90 | } 91 | 92 | // ErrorReport creates a report for error cases 93 | func ErrorReport(name string, err error) interfaces.DependencyReport { 94 | return interfaces.DependencyReport{ 95 | Dependency: name, 96 | Blocks: true, 97 | Percentage: 0, 98 | Needed: 100, 99 | Message: err.Error(), 100 | } 101 | } 102 | 103 | // Indent indents every line 104 | func Indent(indent int, data []string) []string { 105 | ret := make([]string, 0, cap(data)) 106 | for _, line := range data { 107 | ret = append(ret, strings.Repeat(" ", indent)+line) 108 | } 109 | return ret 110 | } 111 | 112 | // dependencyReportAsText returns a human-readable representation of the report as a slice 113 | func dependencyReportAsText(d interfaces.DependencyReport, indent int) []string { 114 | var blocksStr, percStr string 115 | if d.Blocks { 116 | blocksStr = "BLOCKS" 117 | } else { 118 | blocksStr = "DOESN'T BLOCK" 119 | } 120 | if d.Percentage == 100 { 121 | percStr = "" 122 | } else { 123 | percStr = fmt.Sprintf("%d%%/%d%%", d.Percentage, d.Needed) 124 | } 125 | ret := []string{ 126 | fmt.Sprintf("Dependency: %s", d.Dependency), 127 | blocksStr, 128 | } 129 | if percStr != "" { 130 | ret = append(ret, percStr) 131 | } 132 | return Indent(indent, ret) 133 | } 134 | -------------------------------------------------------------------------------- /pkg/resources/job.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/Mirantis/k8s-AppController/pkg/client" 21 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 22 | "github.com/Mirantis/k8s-AppController/pkg/report" 23 | 24 | batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" 25 | "k8s.io/client-go/pkg/apis/batch/v1" 26 | ) 27 | 28 | type Job struct { 29 | Base 30 | Job *v1.Job 31 | Client batchv1.JobInterface 32 | } 33 | 34 | func jobKey(name string) string { 35 | return "job/" + name 36 | } 37 | 38 | func jobStatus(j batchv1.JobInterface, name string) (string, error) { 39 | job, err := j.Get(name) 40 | if err != nil { 41 | return "error", err 42 | } 43 | 44 | for _, cond := range job.Status.Conditions { 45 | if cond.Type == "Complete" && cond.Status == "True" { 46 | return "ready", nil 47 | } 48 | } 49 | 50 | return "not ready", nil 51 | } 52 | 53 | // Key returns job name 54 | func (j Job) Key() string { 55 | return jobKey(j.Job.Name) 56 | } 57 | 58 | // Status returns job status 59 | func (j Job) Status(meta map[string]string) (string, error) { 60 | return jobStatus(j.Client, j.Job.Name) 61 | } 62 | 63 | // Create creates k8s job object 64 | func (j Job) Create() error { 65 | if err := checkExistence(j); err != nil { 66 | log.Println("Creating ", j.Key()) 67 | j.Job, err = j.Client.Create(j.Job) 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | // Delete deletes Job from the cluster 74 | func (j Job) Delete() error { 75 | return j.Client.Delete(j.Job.Name, nil) 76 | } 77 | 78 | // NameMatches gets resource definition and a name and checks if 79 | // the Job part of resource definition has matching name. 80 | func (j Job) NameMatches(def client.ResourceDefinition, name string) bool { 81 | return def.Job != nil && def.Job.Name == name 82 | } 83 | 84 | // New returns new Job on resource definition 85 | func (j Job) New(def client.ResourceDefinition, c client.Interface) interfaces.Resource { 86 | return NewJob(def.Job, c.Jobs(), def.Meta) 87 | } 88 | 89 | // NewExisting returns new ExistingJob based on resource definition 90 | func (j Job) NewExisting(name string, c client.Interface) interfaces.Resource { 91 | return NewExistingJob(name, c.Jobs()) 92 | } 93 | 94 | func NewJob(job *v1.Job, client batchv1.JobInterface, meta map[string]string) interfaces.Resource { 95 | return report.SimpleReporter{BaseResource: Job{Base: Base{meta}, Job: job, Client: client}} 96 | } 97 | 98 | type ExistingJob struct { 99 | Base 100 | Name string 101 | Client batchv1.JobInterface 102 | } 103 | 104 | func (j ExistingJob) Key() string { 105 | return jobKey(j.Name) 106 | } 107 | 108 | func (j ExistingJob) Status(meta map[string]string) (string, error) { 109 | return jobStatus(j.Client, j.Name) 110 | } 111 | 112 | func (j ExistingJob) Create() error { 113 | return createExistingResource(j) 114 | } 115 | 116 | // Delete deletes Job from the cluster 117 | func (j ExistingJob) Delete() error { 118 | return j.Client.Delete(j.Name, nil) 119 | } 120 | 121 | func NewExistingJob(name string, client batchv1.JobInterface) interfaces.Resource { 122 | return report.SimpleReporter{BaseResource: ExistingJob{Name: name, Client: client}} 123 | } 124 | -------------------------------------------------------------------------------- /cmd/deploy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "os" 21 | "strconv" 22 | "strings" 23 | 24 | "github.com/spf13/cobra" 25 | "k8s.io/client-go/pkg/labels" 26 | 27 | "github.com/Mirantis/k8s-AppController/pkg/client" 28 | "github.com/Mirantis/k8s-AppController/pkg/scheduler" 29 | ) 30 | 31 | func deploy(cmd *cobra.Command, args []string) { 32 | var err error 33 | 34 | concurrency, err := cmd.Flags().GetInt("concurrency") 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | labelSelector, err := getLabelSelector(cmd) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | log.Println("Using concurrency:", concurrency) 45 | 46 | var url string 47 | if len(args) > 0 { 48 | url = args[0] 49 | } 50 | if url == "" { 51 | url = os.Getenv("KUBERNETES_CLUSTER_URL") 52 | } 53 | 54 | c, err := client.New(url) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | sel, err := labels.Parse(labelSelector) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | log.Println("Using label selector:", labelSelector) 65 | 66 | depGraph, err := scheduler.BuildDependencyGraph(c, sel) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | log.Println("Checking for circular dependencies.") 72 | cycles := scheduler.DetectCycles(depGraph) 73 | if len(cycles) > 0 { 74 | message := "Cycles detected, terminating:\n" 75 | for _, cycle := range cycles { 76 | keys := make([]string, 0, len(cycle)) 77 | for _, vertex := range cycle { 78 | keys = append(keys, vertex.Key()) 79 | } 80 | message = fmt.Sprintf("%sCycle: %s\n", message, strings.Join(keys, ", ")) 81 | } 82 | 83 | log.Fatal(message) 84 | } else { 85 | log.Println("No cycles detected.") 86 | } 87 | 88 | scheduler.Create(depGraph, concurrency) 89 | 90 | log.Println("Done") 91 | 92 | } 93 | 94 | func getLabelSelector(cmd *cobra.Command) (string, error) { 95 | labelSelector, err := cmd.Flags().GetString("label") 96 | if labelSelector == "" { 97 | labelSelector = os.Getenv("KUBERNETES_AC_LABEL_SELECTOR") 98 | } 99 | return labelSelector, err 100 | } 101 | 102 | // InitRunCommand returns cobra command for performing AppController graph deployment 103 | func InitRunCommand() (*cobra.Command, error) { 104 | run := &cobra.Command{ 105 | Use: "run", 106 | Short: "Start deployment of AppController graph", 107 | Long: "Start deployment of AppController graph", 108 | Run: deploy, 109 | } 110 | 111 | var labelSelector string 112 | run.Flags().StringVarP(&labelSelector, "label", "l", "", "Label selector. Overrides KUBERNETES_AC_LABEL_SELECTOR env variable in AppController pod.") 113 | 114 | concurrencyString := os.Getenv("KUBERNETES_AC_CONCURRENCY") 115 | 116 | var err error 117 | var concurrencyDefault int 118 | if len(concurrencyString) > 0 { 119 | concurrencyDefault, err = strconv.Atoi(concurrencyString) 120 | if err != nil { 121 | log.Printf("KUBERNETES_AC_CONCURRENCY is set to '%s' but it does not look like an integer: %v", 122 | concurrencyString, err) 123 | concurrencyDefault = 0 124 | } 125 | } 126 | var concurrency int 127 | run.Flags().IntVarP(&concurrency, "concurrency", "c", concurrencyDefault, "concurrency") 128 | return run, err 129 | } 130 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps" 23 | "k8s.io/client-go/pkg/api" 24 | "k8s.io/client-go/pkg/api/unversioned" 25 | v1 "k8s.io/client-go/pkg/api/v1" 26 | "k8s.io/client-go/pkg/conversion" 27 | "k8s.io/client-go/pkg/runtime" 28 | ) 29 | 30 | func addConversionFuncs(scheme *runtime.Scheme) error { 31 | // Add non-generated conversion functions to handle the *int32 -> int 32 | // conversion. A pointer is useful in the versioned type so we can default 33 | // it, but a plain int32 is more convenient in the internal type. These 34 | // functions are the same as the autogenerated ones in every other way. 35 | err := scheme.AddConversionFuncs( 36 | Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec, 37 | Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec, 38 | ) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return api.Scheme.AddFieldLabelConversionFunc("apps/v1alpha1", "PetSet", 44 | func(label, value string) (string, string, error) { 45 | switch label { 46 | case "metadata.name", "metadata.namespace", "status.successful": 47 | return label, value, nil 48 | default: 49 | return "", "", fmt.Errorf("field label not supported: %s", label) 50 | } 51 | }, 52 | ) 53 | } 54 | 55 | func Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec(in *PetSetSpec, out *apps.PetSetSpec, s conversion.Scope) error { 56 | if in.Replicas != nil { 57 | out.Replicas = int(*in.Replicas) 58 | } 59 | if in.Selector != nil { 60 | in, out := &in.Selector, &out.Selector 61 | *out = new(unversioned.LabelSelector) 62 | if err := s.Convert(*in, *out, 0); err != nil { 63 | return err 64 | } 65 | } else { 66 | out.Selector = nil 67 | } 68 | if err := s.Convert(&in.Template, &out.Template, 0); err != nil { 69 | return err 70 | } 71 | if in.VolumeClaimTemplates != nil { 72 | in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates 73 | *out = make([]api.PersistentVolumeClaim, len(*in)) 74 | for i := range *in { 75 | if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil { 76 | return err 77 | } 78 | } 79 | } else { 80 | out.VolumeClaimTemplates = nil 81 | } 82 | out.ServiceName = in.ServiceName 83 | return nil 84 | } 85 | 86 | func Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec(in *apps.PetSetSpec, out *PetSetSpec, s conversion.Scope) error { 87 | out.Replicas = new(int32) 88 | *out.Replicas = int32(in.Replicas) 89 | if in.Selector != nil { 90 | in, out := &in.Selector, &out.Selector 91 | *out = new(unversioned.LabelSelector) 92 | if err := s.Convert(*in, *out, 0); err != nil { 93 | return err 94 | } 95 | } else { 96 | out.Selector = nil 97 | } 98 | if err := s.Convert(&in.Template, &out.Template, 0); err != nil { 99 | return err 100 | } 101 | if in.VolumeClaimTemplates != nil { 102 | in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates 103 | *out = make([]v1.PersistentVolumeClaim, len(*in)) 104 | for i := range *in { 105 | if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil { 106 | return err 107 | } 108 | } 109 | } else { 110 | out.VolumeClaimTemplates = nil 111 | } 112 | out.ServiceName = in.ServiceName 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/resources/pod.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "log" 19 | 20 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 21 | "k8s.io/client-go/pkg/api/v1" 22 | 23 | "github.com/Mirantis/k8s-AppController/pkg/client" 24 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 25 | "github.com/Mirantis/k8s-AppController/pkg/report" 26 | ) 27 | 28 | type Pod struct { 29 | Base 30 | Pod *v1.Pod 31 | Client corev1.PodInterface 32 | } 33 | 34 | func podKey(name string) string { 35 | return "pod/" + name 36 | } 37 | 38 | func (p Pod) Key() string { 39 | return podKey(p.Pod.Name) 40 | } 41 | 42 | func podStatus(p corev1.PodInterface, name string) (string, error) { 43 | pod, err := p.Get(name) 44 | if err != nil { 45 | return "error", err 46 | } 47 | 48 | if pod.Status.Phase == "Succeeded" { 49 | return "ready", nil 50 | } 51 | 52 | if pod.Status.Phase == "Running" && isReady(pod) { 53 | return "ready", nil 54 | } 55 | 56 | return "not ready", nil 57 | } 58 | 59 | func isReady(pod *v1.Pod) bool { 60 | for _, cond := range pod.Status.Conditions { 61 | if cond.Type == "Ready" && cond.Status == "True" { 62 | return true 63 | } 64 | } 65 | 66 | return false 67 | } 68 | 69 | func (p Pod) Create() error { 70 | if err := checkExistence(p); err != nil { 71 | log.Println("Creating ", p.Key()) 72 | p.Pod, err = p.Client.Create(p.Pod) 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | // Delete deletes pod from the cluster 79 | func (p Pod) Delete() error { 80 | return p.Client.Delete(p.Pod.Name, nil) 81 | } 82 | 83 | func (p Pod) Status(meta map[string]string) (string, error) { 84 | return podStatus(p.Client, p.Pod.Name) 85 | } 86 | 87 | // NameMatches gets resource definition and a name and checks if 88 | // the Pod part of resource definition has matching name. 89 | func (p Pod) NameMatches(def client.ResourceDefinition, name string) bool { 90 | return def.Pod != nil && def.Pod.Name == name 91 | } 92 | 93 | // New returns new Pod based on resource definition 94 | func (p Pod) New(def client.ResourceDefinition, c client.Interface) interfaces.Resource { 95 | return NewPod(def.Pod, c.Pods(), def.Meta) 96 | } 97 | 98 | // NewExisting returns new ExistingPod based on resource definition 99 | func (p Pod) NewExisting(name string, c client.Interface) interfaces.Resource { 100 | return NewExistingPod(name, c.Pods()) 101 | } 102 | 103 | func NewPod(pod *v1.Pod, client corev1.PodInterface, meta map[string]string) interfaces.Resource { 104 | return report.SimpleReporter{BaseResource: Pod{Base: Base{meta}, Pod: pod, Client: client}} 105 | } 106 | 107 | type ExistingPod struct { 108 | Base 109 | Name string 110 | Client corev1.PodInterface 111 | } 112 | 113 | func (p ExistingPod) Key() string { 114 | return podKey(p.Name) 115 | } 116 | 117 | func (p ExistingPod) Create() error { 118 | return createExistingResource(p) 119 | } 120 | 121 | func (p ExistingPod) Status(meta map[string]string) (string, error) { 122 | return podStatus(p.Client, p.Name) 123 | } 124 | 125 | // Delete deletes pod from the cluster 126 | func (p ExistingPod) Delete() error { 127 | return p.Client.Delete(p.Name, nil) 128 | } 129 | 130 | func NewExistingPod(name string, client corev1.PodInterface) interfaces.Resource { 131 | return report.SimpleReporter{BaseResource: ExistingPod{Name: name, Client: client}} 132 | } 133 | -------------------------------------------------------------------------------- /pkg/resources/daemonset.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/Mirantis/k8s-AppController/pkg/client" 7 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 8 | "github.com/Mirantis/k8s-AppController/pkg/report" 9 | 10 | "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" 11 | "k8s.io/client-go/pkg/api/v1" 12 | extbeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" 13 | ) 14 | 15 | // DaemonSet is wrapper for K8s DaemonSet object 16 | type DaemonSet struct { 17 | Base 18 | DaemonSet *extbeta1.DaemonSet 19 | Client v1beta1.DaemonSetInterface 20 | } 21 | 22 | func daemonSetKey(name string) string { 23 | return "daemonset/" + name 24 | } 25 | 26 | func daemonSetStatus(d v1beta1.DaemonSetInterface, name string) (string, error) { 27 | daemonSet, err := d.Get(name) 28 | if err != nil { 29 | return "error", err 30 | } 31 | if daemonSet.Status.CurrentNumberScheduled == daemonSet.Status.DesiredNumberScheduled { 32 | return "ready", nil 33 | } 34 | return "not ready", nil 35 | } 36 | 37 | // Key return DaemonSet key 38 | func (d DaemonSet) Key() string { 39 | return daemonSetKey(d.DaemonSet.Name) 40 | } 41 | 42 | // Status returns DaemonSet status as a string "ready" means that its dependencies can be created 43 | func (d DaemonSet) Status(meta map[string]string) (string, error) { 44 | return daemonSetStatus(d.Client, d.DaemonSet.Name) 45 | } 46 | 47 | // Create looks for DaemonSet in K8s and creates it if not present 48 | func (d DaemonSet) Create() error { 49 | if err := checkExistence(d); err != nil { 50 | log.Println("Creating ", d.Key()) 51 | d.DaemonSet, err = d.Client.Create(d.DaemonSet) 52 | return err 53 | } 54 | return nil 55 | } 56 | 57 | // Delete deletes DaemonSet from the cluster 58 | func (d DaemonSet) Delete() error { 59 | return d.Client.Delete(d.DaemonSet.Name, &v1.DeleteOptions{}) 60 | } 61 | 62 | // NameMatches gets resource definition and a name and checks if 63 | // the DaemonSet part of resource definition has matching name. 64 | func (d DaemonSet) NameMatches(def client.ResourceDefinition, name string) bool { 65 | return def.DaemonSet != nil && def.DaemonSet.Name == name 66 | } 67 | 68 | // New returns new DaemonSet based on resource definition 69 | func (d DaemonSet) New(def client.ResourceDefinition, c client.Interface) interfaces.Resource { 70 | return NewDaemonSet(def.DaemonSet, c.DaemonSets(), def.Meta) 71 | } 72 | 73 | // NewExisting returns new ExistingDaemonSet based on resource definition 74 | func (d DaemonSet) NewExisting(name string, c client.Interface) interfaces.Resource { 75 | return NewExistingDaemonSet(name, c.DaemonSets()) 76 | } 77 | 78 | // NewDaemonSet is a constructor 79 | func NewDaemonSet(daemonset *extbeta1.DaemonSet, client v1beta1.DaemonSetInterface, meta map[string]string) interfaces.Resource { 80 | return report.SimpleReporter{BaseResource: DaemonSet{Base: Base{meta}, DaemonSet: daemonset, Client: client}} 81 | } 82 | 83 | // ExistingDaemonSet is a wrapper for K8s DaemonSet object which is deployed on a cluster before AppController 84 | type ExistingDaemonSet struct { 85 | Base 86 | Name string 87 | Client v1beta1.DaemonSetInterface 88 | } 89 | 90 | // Key returns DaemonSet name 91 | func (d ExistingDaemonSet) Key() string { 92 | return daemonSetKey(d.Name) 93 | } 94 | 95 | // Status returns DaemonSet status as a string "ready" means that its dependencies can be created 96 | func (d ExistingDaemonSet) Status(meta map[string]string) (string, error) { 97 | return daemonSetStatus(d.Client, d.Name) 98 | } 99 | 100 | // Create looks for existing DaemonSet and returns error if there is no such DaemonSet 101 | func (d ExistingDaemonSet) Create() error { 102 | return createExistingResource(d) 103 | } 104 | 105 | // Delete deletes DaemonSet from the cluster 106 | func (d ExistingDaemonSet) Delete() error { 107 | return d.Client.Delete(d.Name, nil) 108 | } 109 | 110 | // NewExistingDaemonSet is a constructor 111 | func NewExistingDaemonSet(name string, client v1beta1.DaemonSetInterface) interfaces.Resource { 112 | return report.SimpleReporter{BaseResource: ExistingDaemonSet{Name: name, Client: client}} 113 | } 114 | -------------------------------------------------------------------------------- /cmd/format/yaml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package format 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | // TestKind tests data retrieval from k8s objects 22 | func TestKind(t *testing.T) { 23 | f := Yaml{} 24 | yaml := `apiVersion: batch/v1 25 | kind: Job 26 | metadata: 27 | name: pi 28 | spec: 29 | template: 30 | metadata: 31 | name: pi 32 | spec: 33 | containers: 34 | - name: pi 35 | image: perl 36 | command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] 37 | restartPolicy: Never` 38 | kind, err := f.ExtractData(yaml) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | if kind.Kind != "job" { 44 | t.Errorf("Extracted kind should be \"job\", is %s", kind) 45 | } 46 | } 47 | 48 | // TestWrap tests if K8s objects are properly wrapped 49 | func TestWrap(t *testing.T) { 50 | f := Yaml{} 51 | yaml := ` apiVersion: batch/v1 52 | kind: Job 53 | metadata: 54 | name: pi 55 | spec: 56 | template: 57 | metadata: 58 | name: pi 59 | spec: 60 | containers: 61 | - name: pi 62 | image: perl 63 | command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] 64 | restartPolicy: Never` 65 | 66 | wrapped, err := f.Wrap(yaml) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | expected := `apiVersion: appcontroller.k8s/v1alpha1 71 | kind: Definition 72 | metadata: 73 | name: job-pi 74 | job: 75 | apiVersion: batch/v1 76 | kind: Job 77 | metadata: 78 | name: pi 79 | spec: 80 | template: 81 | metadata: 82 | name: pi 83 | spec: 84 | containers: 85 | - name: pi 86 | image: perl 87 | command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] 88 | restartPolicy: Never` 89 | if wrapped != expected { 90 | t.Errorf("Wrapped doesn't match expected output\nExpected:\n%s\nAactual:\n%s", expected, wrapped) 91 | } 92 | } 93 | 94 | // TestMultiDoc checks if multi-document yaml file is wrapped properly 95 | func TestMultiDoc(t *testing.T) { 96 | f := Yaml{} 97 | yaml := ` apiVersion: batch/v1 98 | kind: Job 99 | metadata: 100 | name: pi 101 | spec: 102 | trolo: 103 | lolo: lo 104 | --- 105 | apiVersion: batch/v1 106 | kind: Job 107 | metadata: 108 | name: pi2 109 | spec: 110 | trolo: 111 | lolo: lo 112 | --- 113 | apiVersion: batch/v1 114 | kind: Job 115 | metadata: 116 | name: pi3 117 | spec: 118 | trolo: 119 | lolo: lo` 120 | 121 | wrapped, err := f.Wrap(yaml) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | expected := `apiVersion: appcontroller.k8s/v1alpha1 126 | kind: Definition 127 | metadata: 128 | name: job-pi 129 | job: 130 | apiVersion: batch/v1 131 | kind: Job 132 | metadata: 133 | name: pi 134 | spec: 135 | trolo: 136 | lolo: lo 137 | --- 138 | apiVersion: appcontroller.k8s/v1alpha1 139 | kind: Definition 140 | metadata: 141 | name: job-pi2 142 | job: 143 | apiVersion: batch/v1 144 | kind: Job 145 | metadata: 146 | name: pi2 147 | spec: 148 | trolo: 149 | lolo: lo 150 | --- 151 | apiVersion: appcontroller.k8s/v1alpha1 152 | kind: Definition 153 | metadata: 154 | name: job-pi3 155 | job: 156 | apiVersion: batch/v1 157 | kind: Job 158 | metadata: 159 | name: pi3 160 | spec: 161 | trolo: 162 | lolo: lo` 163 | 164 | if wrapped != expected { 165 | t.Errorf("Wrapped doesn't match expected output\nExpected:\n%s\nactual:\n%s", expected, wrapped) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /cmd/bootstrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bufio" 19 | "encoding/json" 20 | "fmt" 21 | "log" 22 | "os" 23 | "regexp" 24 | "strconv" 25 | 26 | "github.com/spf13/cobra" 27 | "k8s.io/client-go/kubernetes" 28 | "k8s.io/client-go/pkg/api/errors" 29 | "k8s.io/client-go/pkg/apis/extensions/v1beta1" 30 | 31 | "github.com/Mirantis/k8s-AppController/pkg/client" 32 | ) 33 | 34 | // KubernetesRequiredMajorVersion is minimal required major version of Kubernetes cluster 35 | const KubernetesRequiredMajorVersion = 1 36 | 37 | // KubernetesRequiredMinorVersion is minimal required minor version of Kubernetes cluster 38 | const KubernetesRequiredMinorVersion = 4 39 | 40 | func getFileContents(stream *os.File) string { 41 | result := "" 42 | scanner := bufio.NewScanner(stream) 43 | for scanner.Scan() { 44 | result += scanner.Text() + "\n" 45 | } 46 | return result 47 | } 48 | 49 | func createTPRIfNotExists(tpr v1beta1.ThirdPartyResource, client kubernetes.Interface) { 50 | _, err := client.Extensions().ThirdPartyResources().Create(&tpr) 51 | switch err.(type) { 52 | case (*errors.StatusError): 53 | e := err.(*errors.StatusError) 54 | if e.ErrStatus.Code != 409 { 55 | log.Fatal(e) 56 | } else { 57 | log.Printf("%s already exists, skipping", e.ErrStatus.Details.Name) 58 | } 59 | case nil: 60 | log.Printf("Created %s", tpr.ObjectMeta.Name) 61 | default: 62 | log.Fatal(err) 63 | } 64 | return 65 | } 66 | 67 | func getDependencyFromPath(path string) v1beta1.ThirdPartyResource { 68 | file, err := os.Open(path) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | var tpr v1beta1.ThirdPartyResource 74 | err = json.Unmarshal([]byte(getFileContents(file)), &tpr) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | return tpr 79 | } 80 | 81 | func checkVersion(c kubernetes.Interface) { 82 | v, err := c.Discovery().ServerVersion() 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | re := regexp.MustCompile("[0-9]+") 87 | major, err := strconv.Atoi(re.FindString(v.Major)) 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | minor, err := strconv.Atoi(re.FindString(v.Minor)) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | if major < KubernetesRequiredMajorVersion || (major == KubernetesRequiredMajorVersion && minor < KubernetesRequiredMinorVersion) { 97 | log.Fatal(fmt.Errorf("AppController is not compatible with Kubernetes version older than %d.%d", KubernetesRequiredMajorVersion, KubernetesRequiredMinorVersion)) 98 | } 99 | 100 | } 101 | 102 | func bootstrap(cmd *cobra.Command, args []string) { 103 | thirdPartyResourcesPath := os.Args[2] 104 | 105 | dependencyTPR := getDependencyFromPath(thirdPartyResourcesPath + "/dependencies.json") 106 | definitionTPR := getDependencyFromPath(thirdPartyResourcesPath + "/resdefs.json") 107 | 108 | url := os.Getenv("KUBERNETES_CLUSTER_URL") 109 | config, err := client.GetConfig(url) 110 | if err != nil { 111 | log.Fatal(err) 112 | } 113 | 114 | c, err := kubernetes.NewForConfig(config) 115 | if err != nil { 116 | log.Fatal(err) 117 | } 118 | 119 | checkVersion(c) 120 | 121 | createTPRIfNotExists(dependencyTPR, c) 122 | createTPRIfNotExists(definitionTPR, c) 123 | } 124 | 125 | // Bootstrap is cobra command for bootstrapping AppController, meant to be run in an init container 126 | var Bootstrap = &cobra.Command{ 127 | Use: "bootstrap", 128 | Short: "Bootstrap AppController", 129 | Long: "Create ThirdPartyResources required for AppController pod to function properly", 130 | Run: bootstrap, 131 | } 132 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apps 18 | 19 | import ( 20 | "k8s.io/client-go/pkg/api" 21 | "k8s.io/client-go/pkg/api/unversioned" 22 | ) 23 | 24 | // +genclient=true 25 | 26 | // PetSet represents a set of pods with consistent identities. 27 | // Identities are defined as: 28 | // - Network: A single stable DNS and hostname. 29 | // - Storage: As many VolumeClaims as requested. 30 | // The PetSet guarantees that a given network identity will always 31 | // map to the same storage identity. PetSet is currently in alpha and 32 | // and subject to change without notice. 33 | type PetSet struct { 34 | unversioned.TypeMeta `json:",inline"` 35 | api.ObjectMeta `json:"metadata,omitempty"` 36 | 37 | // Spec defines the desired identities of pets in this set. 38 | Spec PetSetSpec `json:"spec,omitempty"` 39 | 40 | // Status is the current status of Pets in this PetSet. This data 41 | // may be out of date by some window of time. 42 | Status PetSetStatus `json:"status,omitempty"` 43 | } 44 | 45 | // A PetSetSpec is the specification of a PetSet. 46 | type PetSetSpec struct { 47 | // Replicas is the desired number of replicas of the given Template. 48 | // These are replicas in the sense that they are instantiations of the 49 | // same Template, but individual replicas also have a consistent identity. 50 | // If unspecified, defaults to 1. 51 | // TODO: Consider a rename of this field. 52 | Replicas int `json:"replicas,omitempty"` 53 | 54 | // Selector is a label query over pods that should match the replica count. 55 | // If empty, defaulted to labels on the pod template. 56 | // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors 57 | Selector *unversioned.LabelSelector `json:"selector,omitempty"` 58 | 59 | // Template is the object that describes the pod that will be created if 60 | // insufficient replicas are detected. Each pod stamped out by the PetSet 61 | // will fulfill this Template, but have a unique identity from the rest 62 | // of the PetSet. 63 | Template api.PodTemplateSpec `json:"template"` 64 | 65 | // VolumeClaimTemplates is a list of claims that pets are allowed to reference. 66 | // The PetSet controller is responsible for mapping network identities to 67 | // claims in a way that maintains the identity of a pet. Every claim in 68 | // this list must have at least one matching (by name) volumeMount in one 69 | // container in the template. A claim in this list takes precedence over 70 | // any volumes in the template, with the same name. 71 | // TODO: Define the behavior if a claim already exists with the same name. 72 | VolumeClaimTemplates []api.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"` 73 | 74 | // ServiceName is the name of the service that governs this PetSet. 75 | // This service must exist before the PetSet, and is responsible for 76 | // the network identity of the set. Pets get DNS/hostnames that follow the 77 | // pattern: pet-specific-string.serviceName.default.svc.cluster.local 78 | // where "pet-specific-string" is managed by the PetSet controller. 79 | ServiceName string `json:"serviceName"` 80 | } 81 | 82 | // PetSetStatus represents the current state of a PetSet. 83 | type PetSetStatus struct { 84 | // most recent generation observed by this autoscaler. 85 | ObservedGeneration *int64 `json:"observedGeneration,omitempty"` 86 | 87 | // Replicas is the number of actual replicas. 88 | Replicas int `json:"replicas"` 89 | } 90 | 91 | // PetSetList is a collection of PetSets. 92 | type PetSetList struct { 93 | unversioned.TypeMeta `json:",inline"` 94 | unversioned.ListMeta `json:"metadata,omitempty"` 95 | Items []PetSet `json:"items"` 96 | } 97 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/types_swagger_doc_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | // This file contains a collection of methods that can be used from go-restful to 20 | // generate Swagger API documentation for its models. Please read this PR for more 21 | // information on the implementation: https://github.com/emicklei/go-restful/pull/215 22 | // 23 | // TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if 24 | // they are on one line! For multiple line or blocks that you want to ignore use ---. 25 | // Any context after a --- is ignored. 26 | // 27 | // Those methods can be generated by using hack/update-generated-swagger-docs.sh 28 | 29 | // AUTO-GENERATED FUNCTIONS START HERE 30 | var map_PetSet = map[string]string{ 31 | "": "PetSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe PetSet guarantees that a given network identity will always map to the same storage identity. PetSet is currently in alpha and subject to change without notice.", 32 | "spec": "Spec defines the desired identities of pets in this set.", 33 | "status": "Status is the current status of Pets in this PetSet. This data may be out of date by some window of time.", 34 | } 35 | 36 | func (PetSet) SwaggerDoc() map[string]string { 37 | return map_PetSet 38 | } 39 | 40 | var map_PetSetList = map[string]string{ 41 | "": "PetSetList is a collection of PetSets.", 42 | } 43 | 44 | func (PetSetList) SwaggerDoc() map[string]string { 45 | return map_PetSetList 46 | } 47 | 48 | var map_PetSetSpec = map[string]string{ 49 | "": "A PetSetSpec is the specification of a PetSet.", 50 | "replicas": "Replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.", 51 | "selector": "Selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", 52 | "template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the PetSet will fulfill this Template, but have a unique identity from the rest of the PetSet.", 53 | "volumeClaimTemplates": "VolumeClaimTemplates is a list of claims that pets are allowed to reference. The PetSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pet. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.", 54 | "serviceName": "ServiceName is the name of the service that governs this PetSet. This service must exist before the PetSet, and is responsible for the network identity of the set. Pets get DNS/hostnames that follow the pattern: pet-specific-string.serviceName.default.svc.cluster.local where \"pet-specific-string\" is managed by the PetSet controller.", 55 | } 56 | 57 | func (PetSetSpec) SwaggerDoc() map[string]string { 58 | return map_PetSetSpec 59 | } 60 | 61 | var map_PetSetStatus = map[string]string{ 62 | "": "PetSetStatus represents the current state of a PetSet.", 63 | "observedGeneration": "most recent generation observed by this autoscaler.", 64 | "replicas": "Replicas is the number of actual replicas.", 65 | } 66 | 67 | func (PetSetStatus) SwaggerDoc() map[string]string { 68 | return map_PetSetStatus 69 | } 70 | 71 | // AUTO-GENERATED FUNCTIONS END HERE 72 | -------------------------------------------------------------------------------- /pkg/client/petsets/typed/apps/v1alpha1/fake/fake_petset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fake 18 | 19 | import ( 20 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps/v1alpha1" 21 | api "k8s.io/client-go/pkg/api" 22 | unversioned "k8s.io/client-go/pkg/api/unversioned" 23 | labels "k8s.io/client-go/pkg/labels" 24 | watch "k8s.io/client-go/pkg/watch" 25 | testing "k8s.io/client-go/testing" 26 | ) 27 | 28 | // FakePetSets implements PetSetInterface 29 | type FakePetSets struct { 30 | Fake *FakeApps 31 | ns string 32 | } 33 | 34 | var petsetsResource = unversioned.GroupVersionResource{Group: "alphaapps", Version: "v1alpha1", Resource: "petsets"} 35 | 36 | func (c *FakePetSets) Create(petSet *v1alpha1.PetSet) (result *v1alpha1.PetSet, err error) { 37 | obj, err := c.Fake. 38 | Invokes(testing.NewCreateAction(petsetsResource, c.ns, petSet), &v1alpha1.PetSet{}) 39 | 40 | if obj == nil { 41 | return nil, err 42 | } 43 | return obj.(*v1alpha1.PetSet), err 44 | } 45 | 46 | func (c *FakePetSets) Update(petSet *v1alpha1.PetSet) (result *v1alpha1.PetSet, err error) { 47 | obj, err := c.Fake. 48 | Invokes(testing.NewUpdateAction(petsetsResource, c.ns, petSet), &v1alpha1.PetSet{}) 49 | 50 | if obj == nil { 51 | return nil, err 52 | } 53 | return obj.(*v1alpha1.PetSet), err 54 | } 55 | 56 | func (c *FakePetSets) UpdateStatus(petSet *v1alpha1.PetSet) (*v1alpha1.PetSet, error) { 57 | obj, err := c.Fake. 58 | Invokes(testing.NewUpdateSubresourceAction(petsetsResource, "status", c.ns, petSet), &v1alpha1.PetSet{}) 59 | 60 | if obj == nil { 61 | return nil, err 62 | } 63 | return obj.(*v1alpha1.PetSet), err 64 | } 65 | 66 | func (c *FakePetSets) Delete(name string, options *api.DeleteOptions) error { 67 | _, err := c.Fake. 68 | Invokes(testing.NewDeleteAction(petsetsResource, c.ns, name), &v1alpha1.PetSet{}) 69 | 70 | return err 71 | } 72 | 73 | func (c *FakePetSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { 74 | action := testing.NewDeleteCollectionAction(petsetsResource, c.ns, listOptions) 75 | 76 | _, err := c.Fake.Invokes(action, &v1alpha1.PetSetList{}) 77 | return err 78 | } 79 | 80 | func (c *FakePetSets) Get(name string) (result *v1alpha1.PetSet, err error) { 81 | obj, err := c.Fake. 82 | Invokes(testing.NewGetAction(petsetsResource, c.ns, name), &v1alpha1.PetSet{}) 83 | 84 | if obj == nil { 85 | return nil, err 86 | } 87 | return obj.(*v1alpha1.PetSet), err 88 | } 89 | 90 | func (c *FakePetSets) List(opts api.ListOptions) (result *v1alpha1.PetSetList, err error) { 91 | obj, err := c.Fake. 92 | Invokes(testing.NewListAction(petsetsResource, c.ns, opts), &v1alpha1.PetSetList{}) 93 | 94 | if obj == nil { 95 | return nil, err 96 | } 97 | 98 | label := opts.LabelSelector 99 | if label == nil { 100 | label = labels.Everything() 101 | } 102 | list := &v1alpha1.PetSetList{} 103 | for _, item := range obj.(*v1alpha1.PetSetList).Items { 104 | if label.Matches(labels.Set(item.Labels)) { 105 | list.Items = append(list.Items, item) 106 | } 107 | } 108 | return list, err 109 | } 110 | 111 | // Watch returns a watch.Interface that watches the requested petSets. 112 | func (c *FakePetSets) Watch(opts api.ListOptions) (watch.Interface, error) { 113 | return c.Fake. 114 | InvokesWatch(testing.NewWatchAction(petsetsResource, c.ns, opts)) 115 | 116 | } 117 | 118 | // Patch applies the patch and returns the patched petSet. 119 | func (c *FakePetSets) Patch(name string, pt api.PatchType, data []byte, subresources ...string) (result *v1alpha1.PetSet, err error) { 120 | obj, err := c.Fake. 121 | Invokes(testing.NewPatchSubresourceAction(petsetsResource, c.ns, name, data, subresources...), &v1alpha1.PetSet{}) 122 | 123 | if obj == nil { 124 | return nil, err 125 | } 126 | return obj.(*v1alpha1.PetSet), err 127 | } 128 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2016 The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // This file was autogenerated by deepcopy-gen. Do not edit it manually! 20 | 21 | package apps 22 | 23 | import ( 24 | reflect "reflect" 25 | 26 | api "k8s.io/client-go/pkg/api" 27 | unversioned "k8s.io/client-go/pkg/api/unversioned" 28 | conversion "k8s.io/client-go/pkg/conversion" 29 | runtime "k8s.io/client-go/pkg/runtime" 30 | ) 31 | 32 | func init() { 33 | SchemeBuilder.Register(RegisterDeepCopies) 34 | } 35 | 36 | // RegisterDeepCopies adds deep-copy functions to the given scheme. Public 37 | // to allow building arbitrary schemes. 38 | func RegisterDeepCopies(scheme *runtime.Scheme) error { 39 | return scheme.AddGeneratedDeepCopyFuncs( 40 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_apps_PetSet, InType: reflect.TypeOf(&PetSet{})}, 41 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_apps_PetSetList, InType: reflect.TypeOf(&PetSetList{})}, 42 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_apps_PetSetSpec, InType: reflect.TypeOf(&PetSetSpec{})}, 43 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_apps_PetSetStatus, InType: reflect.TypeOf(&PetSetStatus{})}, 44 | ) 45 | } 46 | 47 | func DeepCopy_apps_PetSet(in interface{}, out interface{}, c *conversion.Cloner) error { 48 | { 49 | in := in.(*PetSet) 50 | out := out.(*PetSet) 51 | out.TypeMeta = in.TypeMeta 52 | if err := api.DeepCopy_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { 53 | return err 54 | } 55 | if err := DeepCopy_apps_PetSetSpec(&in.Spec, &out.Spec, c); err != nil { 56 | return err 57 | } 58 | if err := DeepCopy_apps_PetSetStatus(&in.Status, &out.Status, c); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | } 64 | 65 | func DeepCopy_apps_PetSetList(in interface{}, out interface{}, c *conversion.Cloner) error { 66 | { 67 | in := in.(*PetSetList) 68 | out := out.(*PetSetList) 69 | out.TypeMeta = in.TypeMeta 70 | out.ListMeta = in.ListMeta 71 | if in.Items != nil { 72 | in, out := &in.Items, &out.Items 73 | *out = make([]PetSet, len(*in)) 74 | for i := range *in { 75 | if err := DeepCopy_apps_PetSet(&(*in)[i], &(*out)[i], c); err != nil { 76 | return err 77 | } 78 | } 79 | } else { 80 | out.Items = nil 81 | } 82 | return nil 83 | } 84 | } 85 | 86 | func DeepCopy_apps_PetSetSpec(in interface{}, out interface{}, c *conversion.Cloner) error { 87 | { 88 | in := in.(*PetSetSpec) 89 | out := out.(*PetSetSpec) 90 | out.Replicas = in.Replicas 91 | if in.Selector != nil { 92 | in, out := &in.Selector, &out.Selector 93 | *out = new(unversioned.LabelSelector) 94 | if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { 95 | return err 96 | } 97 | } else { 98 | out.Selector = nil 99 | } 100 | if err := api.DeepCopy_api_PodTemplateSpec(&in.Template, &out.Template, c); err != nil { 101 | return err 102 | } 103 | if in.VolumeClaimTemplates != nil { 104 | in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates 105 | *out = make([]api.PersistentVolumeClaim, len(*in)) 106 | for i := range *in { 107 | if err := api.DeepCopy_api_PersistentVolumeClaim(&(*in)[i], &(*out)[i], c); err != nil { 108 | return err 109 | } 110 | } 111 | } else { 112 | out.VolumeClaimTemplates = nil 113 | } 114 | out.ServiceName = in.ServiceName 115 | return nil 116 | } 117 | } 118 | 119 | func DeepCopy_apps_PetSetStatus(in interface{}, out interface{}, c *conversion.Cloner) error { 120 | { 121 | in := in.(*PetSetStatus) 122 | out := out.(*PetSetStatus) 123 | if in.ObservedGeneration != nil { 124 | in, out := &in.ObservedGeneration, &out.ObservedGeneration 125 | *out = new(int64) 126 | **out = **in 127 | } else { 128 | out.ObservedGeneration = nil 129 | } 130 | out.Replicas = in.Replicas 131 | return nil 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /pkg/client/resdef.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | 21 | "github.com/Mirantis/k8s-AppController/pkg/client/petsets/apis/apps/v1alpha1" 22 | 23 | "k8s.io/client-go/pkg/api" 24 | "k8s.io/client-go/pkg/api/meta" 25 | "k8s.io/client-go/pkg/api/unversioned" 26 | "k8s.io/client-go/pkg/api/v1" 27 | appsbeta1 "k8s.io/client-go/pkg/apis/apps/v1beta1" 28 | batchv1 "k8s.io/client-go/pkg/apis/batch/v1" 29 | "k8s.io/client-go/pkg/apis/extensions/v1beta1" 30 | "k8s.io/client-go/rest" 31 | ) 32 | 33 | type ResourceDefinition struct { 34 | unversioned.TypeMeta `json:",inline"` 35 | 36 | // Standard object metadata 37 | api.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 38 | 39 | Meta map[string]string `json:"meta,omitempty"` 40 | 41 | //TODO: add other object types 42 | Pod *v1.Pod `json:"pod,omitempty"` 43 | Job *batchv1.Job `json:"job,omitempty"` 44 | Service *v1.Service `json:"service,omitempty"` 45 | ReplicaSet *v1beta1.ReplicaSet `json:"replicaset,omitempty"` 46 | StatefulSet *appsbeta1.StatefulSet `json:"statefulset,omitempty"` 47 | PetSet *v1alpha1.PetSet `json:"petset,omitempty"` 48 | DaemonSet *v1beta1.DaemonSet `json:"daemonset,omitempty"` 49 | ConfigMap *v1.ConfigMap `json:"configmap,omitempty"` 50 | Secret *v1.Secret `json:"secret,omitempty"` 51 | Deployment *v1beta1.Deployment `json:"deployment, omitempty"` 52 | PersistentVolumeClaim *v1.PersistentVolumeClaim `json:"persistentvolumeclaim, omitempty"` 53 | } 54 | 55 | type ResourceDefinitionList struct { 56 | unversioned.TypeMeta `json:",inline"` 57 | 58 | // Standard list metadata. 59 | unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 60 | 61 | Items []ResourceDefinition `json:"items"` 62 | } 63 | 64 | type ResourceDefinitionsInterface interface { 65 | Create(*ResourceDefinition) (*ResourceDefinition, error) 66 | List(opts api.ListOptions) (*ResourceDefinitionList, error) 67 | Delete(name string, opts *api.DeleteOptions) error 68 | } 69 | 70 | type resourceDefinitions struct { 71 | rc *rest.RESTClient 72 | } 73 | 74 | func (r *ResourceDefinition) GetObjectKind() unversioned.ObjectKind { 75 | return &r.TypeMeta 76 | } 77 | 78 | func (r *ResourceDefinition) GetObjectMeta() meta.Object { 79 | return &r.ObjectMeta 80 | } 81 | 82 | func newResourceDefinitions(c rest.Config) (*resourceDefinitions, error) { 83 | rc, err := thirdPartyResourceRESTClient(&c) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return &resourceDefinitions{rc}, nil 89 | } 90 | 91 | func (c *resourceDefinitions) List(opts api.ListOptions) (*ResourceDefinitionList, error) { 92 | resp, err := c.rc.Get(). 93 | Namespace("default"). 94 | Resource("definitions"). 95 | LabelsSelectorParam(opts.LabelSelector). 96 | DoRaw() 97 | 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | result := &ResourceDefinitionList{} 103 | err = json.NewDecoder(bytes.NewReader(resp)).Decode(result) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | return result, nil 109 | } 110 | 111 | func (c *resourceDefinitions) Create(rd *ResourceDefinition) (result *ResourceDefinition, err error) { 112 | result = &ResourceDefinition{} 113 | req := c.rc.Post(). 114 | Namespace("default"). 115 | Resource("definitions"). 116 | Body(rd) 117 | err = req. 118 | Do(). 119 | Into(result) 120 | return 121 | } 122 | 123 | func (c *resourceDefinitions) Delete(name string, opts *api.DeleteOptions) error { 124 | return c.rc.Delete(). 125 | Namespace("default"). 126 | Resource("definitions"). 127 | Name(name). 128 | Body(opts). 129 | Do(). 130 | Error() 131 | } 132 | -------------------------------------------------------------------------------- /pkg/resources/persistentvolumeclaim.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Mirantis 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | import ( 18 | "log" 19 | 20 | corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 21 | "k8s.io/client-go/pkg/api/v1" 22 | 23 | "github.com/Mirantis/k8s-AppController/pkg/client" 24 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 25 | "github.com/Mirantis/k8s-AppController/pkg/report" 26 | ) 27 | 28 | type PersistentVolumeClaim struct { 29 | Base 30 | PersistentVolumeClaim *v1.PersistentVolumeClaim 31 | Client corev1.PersistentVolumeClaimInterface 32 | } 33 | 34 | func persistentVolumeClaimKey(name string) string { 35 | return "persistentvolumeclaim/" + name 36 | } 37 | 38 | func (p PersistentVolumeClaim) Key() string { 39 | return persistentVolumeClaimKey(p.PersistentVolumeClaim.Name) 40 | } 41 | 42 | func persistentVolumeClaimStatus(p corev1.PersistentVolumeClaimInterface, name string) (string, error) { 43 | persistentVolumeClaim, err := p.Get(name) 44 | if err != nil { 45 | return "error", err 46 | } 47 | 48 | if persistentVolumeClaim.Status.Phase == v1.ClaimBound { 49 | return "ready", nil 50 | } 51 | 52 | return "not ready", nil 53 | } 54 | 55 | func (p PersistentVolumeClaim) Create() error { 56 | if err := checkExistence(p); err != nil { 57 | log.Println("Creating ", p.Key()) 58 | p.PersistentVolumeClaim, err = p.Client.Create(p.PersistentVolumeClaim) 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | // Delete deletes persistentVolumeClaim from the cluster 65 | func (p PersistentVolumeClaim) Delete() error { 66 | return p.Client.Delete(p.PersistentVolumeClaim.Name, &v1.DeleteOptions{}) 67 | } 68 | 69 | func (p PersistentVolumeClaim) Status(meta map[string]string) (string, error) { 70 | return persistentVolumeClaimStatus(p.Client, p.PersistentVolumeClaim.Name) 71 | } 72 | 73 | // NameMatches gets resource definition and a name and checks if 74 | // the PersistentVolumeClaim part of resource definition has matching name. 75 | func (p PersistentVolumeClaim) NameMatches(def client.ResourceDefinition, name string) bool { 76 | return def.PersistentVolumeClaim != nil && def.PersistentVolumeClaim.Name == name 77 | } 78 | 79 | // New returns new PersistentVolumeClaim based on resource definition 80 | func (p PersistentVolumeClaim) New(def client.ResourceDefinition, c client.Interface) interfaces.Resource { 81 | return NewPersistentVolumeClaim(def.PersistentVolumeClaim, c.PersistentVolumeClaims(), def.Meta) 82 | } 83 | 84 | // NewExisting returns new ExistingPersistentVolumeClaim based on resource definition 85 | func (p PersistentVolumeClaim) NewExisting(name string, c client.Interface) interfaces.Resource { 86 | return NewExistingPersistentVolumeClaim(name, c.PersistentVolumeClaims()) 87 | } 88 | 89 | func NewPersistentVolumeClaim(persistentVolumeClaim *v1.PersistentVolumeClaim, client corev1.PersistentVolumeClaimInterface, meta map[string]string) interfaces.Resource { 90 | return report.SimpleReporter{BaseResource: PersistentVolumeClaim{Base: Base{meta}, PersistentVolumeClaim: persistentVolumeClaim, Client: client}} 91 | } 92 | 93 | type ExistingPersistentVolumeClaim struct { 94 | Base 95 | Name string 96 | Client corev1.PersistentVolumeClaimInterface 97 | } 98 | 99 | func (p ExistingPersistentVolumeClaim) Key() string { 100 | return persistentVolumeClaimKey(p.Name) 101 | } 102 | 103 | func (p ExistingPersistentVolumeClaim) Create() error { 104 | return createExistingResource(p) 105 | } 106 | 107 | func (p ExistingPersistentVolumeClaim) Status(meta map[string]string) (string, error) { 108 | return persistentVolumeClaimStatus(p.Client, p.Name) 109 | } 110 | 111 | // Delete deletes persistentVolumeClaim from the cluster 112 | func (p ExistingPersistentVolumeClaim) Delete() error { 113 | return p.Client.Delete(p.Name, nil) 114 | } 115 | 116 | func NewExistingPersistentVolumeClaim(name string, client corev1.PersistentVolumeClaimInterface) interfaces.Resource { 117 | return report.SimpleReporter{BaseResource: ExistingPersistentVolumeClaim{Name: name, Client: client}} 118 | } 119 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/client-go/pkg/api/unversioned" 21 | "k8s.io/client-go/pkg/api/v1" 22 | ) 23 | 24 | // +genclient=true 25 | 26 | // PetSet represents a set of pods with consistent identities. 27 | // Identities are defined as: 28 | // - Network: A single stable DNS and hostname. 29 | // - Storage: As many VolumeClaims as requested. 30 | // The PetSet guarantees that a given network identity will always 31 | // map to the same storage identity. PetSet is currently in alpha 32 | // and subject to change without notice. 33 | type PetSet struct { 34 | unversioned.TypeMeta `json:",inline"` 35 | v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 36 | 37 | // Spec defines the desired identities of pets in this set. 38 | Spec PetSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` 39 | 40 | // Status is the current status of Pets in this PetSet. This data 41 | // may be out of date by some window of time. 42 | Status PetSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` 43 | } 44 | 45 | // A PetSetSpec is the specification of a PetSet. 46 | type PetSetSpec struct { 47 | // Replicas is the desired number of replicas of the given Template. 48 | // These are replicas in the sense that they are instantiations of the 49 | // same Template, but individual replicas also have a consistent identity. 50 | // If unspecified, defaults to 1. 51 | // TODO: Consider a rename of this field. 52 | Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` 53 | 54 | // Selector is a label query over pods that should match the replica count. 55 | // If empty, defaulted to labels on the pod template. 56 | // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors 57 | Selector *unversioned.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` 58 | 59 | // Template is the object that describes the pod that will be created if 60 | // insufficient replicas are detected. Each pod stamped out by the PetSet 61 | // will fulfill this Template, but have a unique identity from the rest 62 | // of the PetSet. 63 | Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` 64 | 65 | // VolumeClaimTemplates is a list of claims that pets are allowed to reference. 66 | // The PetSet controller is responsible for mapping network identities to 67 | // claims in a way that maintains the identity of a pet. Every claim in 68 | // this list must have at least one matching (by name) volumeMount in one 69 | // container in the template. A claim in this list takes precedence over 70 | // any volumes in the template, with the same name. 71 | // TODO: Define the behavior if a claim already exists with the same name. 72 | VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,4,rep,name=volumeClaimTemplates"` 73 | 74 | // ServiceName is the name of the service that governs this PetSet. 75 | // This service must exist before the PetSet, and is responsible for 76 | // the network identity of the set. Pets get DNS/hostnames that follow the 77 | // pattern: pet-specific-string.serviceName.default.svc.cluster.local 78 | // where "pet-specific-string" is managed by the PetSet controller. 79 | ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` 80 | } 81 | 82 | // PetSetStatus represents the current state of a PetSet. 83 | type PetSetStatus struct { 84 | // most recent generation observed by this autoscaler. 85 | ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` 86 | 87 | // Replicas is the number of actual replicas. 88 | Replicas int32 `json:"replicas" protobuf:"varint,2,opt,name=replicas"` 89 | } 90 | 91 | // PetSetList is a collection of PetSets. 92 | type PetSetList struct { 93 | unversioned.TypeMeta `json:",inline"` 94 | unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` 95 | Items []PetSet `json:"items" protobuf:"bytes,2,rep,name=items"` 96 | } 97 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/generated.proto: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 18 | // This file was autogenerated by go-to-protobuf. Do not edit it manually! 19 | 20 | syntax = 'proto2'; 21 | 22 | package k8s.io.kubernetes.pkg.apis.apps.v1alpha1; 23 | 24 | import "k8s.io/kubernetes/pkg/api/resource/generated.proto"; 25 | import "k8s.io/kubernetes/pkg/api/unversioned/generated.proto"; 26 | import "k8s.io/kubernetes/pkg/api/v1/generated.proto"; 27 | import "k8s.io/kubernetes/pkg/runtime/generated.proto"; 28 | import "k8s.io/kubernetes/pkg/util/intstr/generated.proto"; 29 | 30 | // Package-wide variables from generator "generated". 31 | option go_package = "v1alpha1"; 32 | 33 | // PetSet represents a set of pods with consistent identities. 34 | // Identities are defined as: 35 | // - Network: A single stable DNS and hostname. 36 | // - Storage: As many VolumeClaims as requested. 37 | // The PetSet guarantees that a given network identity will always 38 | // map to the same storage identity. PetSet is currently in alpha 39 | // and subject to change without notice. 40 | message PetSet { 41 | optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1; 42 | 43 | // Spec defines the desired identities of pets in this set. 44 | optional PetSetSpec spec = 2; 45 | 46 | // Status is the current status of Pets in this PetSet. This data 47 | // may be out of date by some window of time. 48 | optional PetSetStatus status = 3; 49 | } 50 | 51 | // PetSetList is a collection of PetSets. 52 | message PetSetList { 53 | optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1; 54 | 55 | repeated PetSet items = 2; 56 | } 57 | 58 | // A PetSetSpec is the specification of a PetSet. 59 | message PetSetSpec { 60 | // Replicas is the desired number of replicas of the given Template. 61 | // These are replicas in the sense that they are instantiations of the 62 | // same Template, but individual replicas also have a consistent identity. 63 | // If unspecified, defaults to 1. 64 | // TODO: Consider a rename of this field. 65 | optional int32 replicas = 1; 66 | 67 | // Selector is a label query over pods that should match the replica count. 68 | // If empty, defaulted to labels on the pod template. 69 | // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors 70 | optional k8s.io.kubernetes.pkg.api.unversioned.LabelSelector selector = 2; 71 | 72 | // Template is the object that describes the pod that will be created if 73 | // insufficient replicas are detected. Each pod stamped out by the PetSet 74 | // will fulfill this Template, but have a unique identity from the rest 75 | // of the PetSet. 76 | optional k8s.io.kubernetes.pkg.api.v1.PodTemplateSpec template = 3; 77 | 78 | // VolumeClaimTemplates is a list of claims that pets are allowed to reference. 79 | // The PetSet controller is responsible for mapping network identities to 80 | // claims in a way that maintains the identity of a pet. Every claim in 81 | // this list must have at least one matching (by name) volumeMount in one 82 | // container in the template. A claim in this list takes precedence over 83 | // any volumes in the template, with the same name. 84 | // TODO: Define the behavior if a claim already exists with the same name. 85 | repeated k8s.io.kubernetes.pkg.api.v1.PersistentVolumeClaim volumeClaimTemplates = 4; 86 | 87 | // ServiceName is the name of the service that governs this PetSet. 88 | // This service must exist before the PetSet, and is responsible for 89 | // the network identity of the set. Pets get DNS/hostnames that follow the 90 | // pattern: pet-specific-string.serviceName.default.svc.cluster.local 91 | // where "pet-specific-string" is managed by the PetSet controller. 92 | optional string serviceName = 5; 93 | } 94 | 95 | // PetSetStatus represents the current state of a PetSet. 96 | message PetSetStatus { 97 | // most recent generation observed by this autoscaler. 98 | optional int64 observedGeneration = 1; 99 | 100 | // Replicas is the number of actual replicas. 101 | optional int32 replicas = 2; 102 | } 103 | 104 | -------------------------------------------------------------------------------- /pkg/client/petsets/apis/apps/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2016 The Kubernetes Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // This file was autogenerated by deepcopy-gen. Do not edit it manually! 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | reflect "reflect" 25 | 26 | unversioned "k8s.io/client-go/pkg/api/unversioned" 27 | v1 "k8s.io/client-go/pkg/api/v1" 28 | conversion "k8s.io/client-go/pkg/conversion" 29 | runtime "k8s.io/client-go/pkg/runtime" 30 | ) 31 | 32 | func init() { 33 | SchemeBuilder.Register(RegisterDeepCopies) 34 | } 35 | 36 | // RegisterDeepCopies adds deep-copy functions to the given scheme. Public 37 | // to allow building arbitrary schemes. 38 | func RegisterDeepCopies(scheme *runtime.Scheme) error { 39 | return scheme.AddGeneratedDeepCopyFuncs( 40 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1alpha1_PetSet, InType: reflect.TypeOf(&PetSet{})}, 41 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1alpha1_PetSetList, InType: reflect.TypeOf(&PetSetList{})}, 42 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1alpha1_PetSetSpec, InType: reflect.TypeOf(&PetSetSpec{})}, 43 | conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1alpha1_PetSetStatus, InType: reflect.TypeOf(&PetSetStatus{})}, 44 | ) 45 | } 46 | 47 | func DeepCopy_v1alpha1_PetSet(in interface{}, out interface{}, c *conversion.Cloner) error { 48 | { 49 | in := in.(*PetSet) 50 | out := out.(*PetSet) 51 | out.TypeMeta = in.TypeMeta 52 | if err := v1.DeepCopy_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil { 53 | return err 54 | } 55 | if err := DeepCopy_v1alpha1_PetSetSpec(&in.Spec, &out.Spec, c); err != nil { 56 | return err 57 | } 58 | if err := DeepCopy_v1alpha1_PetSetStatus(&in.Status, &out.Status, c); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | } 64 | 65 | func DeepCopy_v1alpha1_PetSetList(in interface{}, out interface{}, c *conversion.Cloner) error { 66 | { 67 | in := in.(*PetSetList) 68 | out := out.(*PetSetList) 69 | out.TypeMeta = in.TypeMeta 70 | out.ListMeta = in.ListMeta 71 | if in.Items != nil { 72 | in, out := &in.Items, &out.Items 73 | *out = make([]PetSet, len(*in)) 74 | for i := range *in { 75 | if err := DeepCopy_v1alpha1_PetSet(&(*in)[i], &(*out)[i], c); err != nil { 76 | return err 77 | } 78 | } 79 | } else { 80 | out.Items = nil 81 | } 82 | return nil 83 | } 84 | } 85 | 86 | func DeepCopy_v1alpha1_PetSetSpec(in interface{}, out interface{}, c *conversion.Cloner) error { 87 | { 88 | in := in.(*PetSetSpec) 89 | out := out.(*PetSetSpec) 90 | if in.Replicas != nil { 91 | in, out := &in.Replicas, &out.Replicas 92 | *out = new(int32) 93 | **out = **in 94 | } else { 95 | out.Replicas = nil 96 | } 97 | if in.Selector != nil { 98 | in, out := &in.Selector, &out.Selector 99 | *out = new(unversioned.LabelSelector) 100 | if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { 101 | return err 102 | } 103 | } else { 104 | out.Selector = nil 105 | } 106 | if err := v1.DeepCopy_v1_PodTemplateSpec(&in.Template, &out.Template, c); err != nil { 107 | return err 108 | } 109 | if in.VolumeClaimTemplates != nil { 110 | in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates 111 | *out = make([]v1.PersistentVolumeClaim, len(*in)) 112 | for i := range *in { 113 | if err := v1.DeepCopy_v1_PersistentVolumeClaim(&(*in)[i], &(*out)[i], c); err != nil { 114 | return err 115 | } 116 | } 117 | } else { 118 | out.VolumeClaimTemplates = nil 119 | } 120 | out.ServiceName = in.ServiceName 121 | return nil 122 | } 123 | } 124 | 125 | func DeepCopy_v1alpha1_PetSetStatus(in interface{}, out interface{}, c *conversion.Cloner) error { 126 | { 127 | in := in.(*PetSetStatus) 128 | out := out.(*PetSetStatus) 129 | if in.ObservedGeneration != nil { 130 | in, out := &in.ObservedGeneration, &out.ObservedGeneration 131 | *out = new(int64) 132 | **out = **in 133 | } else { 134 | out.ObservedGeneration = nil 135 | } 136 | out.Replicas = in.Replicas 137 | return nil 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /pkg/resources/deployment.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | 7 | "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" 8 | extbeta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1" 9 | 10 | "github.com/Mirantis/k8s-AppController/pkg/client" 11 | "github.com/Mirantis/k8s-AppController/pkg/interfaces" 12 | "github.com/Mirantis/k8s-AppController/pkg/report" 13 | ) 14 | 15 | // Deployment is wrapper for K8s Deployment object 16 | type Deployment struct { 17 | Base 18 | Deployment *extbeta1.Deployment 19 | Client v1beta1.DeploymentInterface 20 | } 21 | 22 | func deploymentKey(name string) string { 23 | return "deployment/" + name 24 | } 25 | 26 | func deploymentStatus(d v1beta1.DeploymentInterface, name string) (string, error) { 27 | deployment, err := d.Get(name) 28 | if err != nil { 29 | return "error", err 30 | } 31 | 32 | if deployment.Status.UpdatedReplicas >= *deployment.Spec.Replicas && deployment.Status.AvailableReplicas >= *deployment.Spec.Replicas { 33 | return "ready", nil 34 | } 35 | return "not ready", nil 36 | } 37 | 38 | // Key return Deployment key 39 | func (d Deployment) Key() string { 40 | return deploymentKey(d.Deployment.Name) 41 | } 42 | 43 | // Status returns Deployment status as a string "ready" means that its dependencies can be created 44 | func (d Deployment) Status(meta map[string]string) (string, error) { 45 | return deploymentStatus(d.Client, d.Deployment.Name) 46 | } 47 | 48 | // Create looks for Deployment in K8s and creates it if not present 49 | func (d Deployment) Create() error { 50 | log.Println("Looking for deployment", d.Deployment.Name) 51 | status, err := d.Status(nil) 52 | 53 | if err == nil { 54 | log.Printf("Found deployment %s, status: %s", d.Deployment.Name, status) 55 | log.Println("Skipping creation of deployment", d.Deployment.Name) 56 | } 57 | log.Println("Creating deployment", d.Deployment.Name) 58 | d.Deployment, err = d.Client.Create(d.Deployment) 59 | return err 60 | } 61 | 62 | // Delete deletes Deployment from the cluster 63 | func (d Deployment) Delete() error { 64 | return d.Client.Delete(d.Deployment.Name, nil) 65 | } 66 | 67 | // NameMatches gets resource definition and a name and checks if 68 | // the Deployment part of resource definition has matching name. 69 | func (d Deployment) NameMatches(def client.ResourceDefinition, name string) bool { 70 | return def.Deployment != nil && def.Deployment.Name == name 71 | } 72 | 73 | // New returns new Deployment based on resource definition 74 | func (d Deployment) New(def client.ResourceDefinition, c client.Interface) interfaces.Resource { 75 | return NewDeployment(def.Deployment, c.Deployments(), def.Meta) 76 | } 77 | 78 | // NewExisting returns new ExistingDeployment based on resource definition 79 | func (d Deployment) NewExisting(name string, c client.Interface) interfaces.Resource { 80 | return NewExistingDeployment(name, c.Deployments()) 81 | } 82 | 83 | // NewDeployment is a constructor 84 | func NewDeployment(deployment *extbeta1.Deployment, client v1beta1.DeploymentInterface, meta map[string]string) interfaces.Resource { 85 | return report.SimpleReporter{BaseResource: Deployment{Base: Base{meta}, Deployment: deployment, Client: client}} 86 | } 87 | 88 | // ExistingDeployment is a wrapper for K8s Deployment object which is deployed on a cluster before AppController 89 | type ExistingDeployment struct { 90 | Base 91 | Name string 92 | Client v1beta1.DeploymentInterface 93 | } 94 | 95 | // UpdateMeta does nothing at the moment 96 | func (d ExistingDeployment) UpdateMeta(meta map[string]string) error { 97 | return nil 98 | } 99 | 100 | // Key returns Deployment name 101 | func (d ExistingDeployment) Key() string { 102 | return deploymentKey(d.Name) 103 | } 104 | 105 | // Status returns Deployment status as a string "ready" means that its dependencies can be created 106 | func (d ExistingDeployment) Status(meta map[string]string) (string, error) { 107 | return deploymentStatus(d.Client, d.Name) 108 | } 109 | 110 | // Create looks for existing Deployment and returns error if there is no such Deployment 111 | func (d ExistingDeployment) Create() error { 112 | log.Println("Looking for deployment", d.Name) 113 | status, err := d.Status(nil) 114 | 115 | if err == nil { 116 | log.Printf("Found deployment %s, status: %s", d.Name, status) 117 | return nil 118 | } 119 | 120 | log.Fatalf("Deployment %s not found", d.Name) 121 | return errors.New("Deployment not found") 122 | } 123 | 124 | // Delete deletes Deployment from the cluster 125 | func (d ExistingDeployment) Delete() error { 126 | return d.Client.Delete(d.Name, nil) 127 | } 128 | 129 | // NewExistingDeployment is a constructor 130 | func NewExistingDeployment(name string, client v1beta1.DeploymentInterface) interfaces.Resource { 131 | return report.SimpleReporter{BaseResource: ExistingDeployment{Name: name, Client: client}} 132 | } 133 | --------------------------------------------------------------------------------