예제 #1
0
    def test_failing_call(self):
        kubectl = Kubectl()
        cmd = ['ls', '-l', '/does/probably/not/exist']
        with pytest.raises(KubectlCallFailed) as error:
            kubectl._call(cmd)

        assert error.value.args[0].endswith(b'No such file or directory\n')
예제 #2
0
    def test_default_command(self):
        kubectl = Kubectl()

        default_cmd = kubectl._make_command()
        expected = ['kubectl', 'get', 'pods']

        assert default_cmd == expected
예제 #3
0
    def test_list_deployments(self, mock_call):
        expected = ['get', 'deployments', '-o', 'json']
        expected_call = ['kubectl', '--namespace', 'test-space']
        expected_call.extend(expected)
        kubectl = Kubectl()
        kubectl.namespace = 'test-space'
        kubectl.list_deployments()

        mock_call.assert_called_once_with(expected_call)
예제 #4
0
    def test_get_deployment(self, mock_call):
        name = 'test-deployment'
        expected = ['get', 'deployment', name, '-o', 'json']
        expected_call = ['kubectl', '--namespace', 'test-space']
        expected_call.extend(expected)
        kubectl = Kubectl()
        kubectl.namespace = 'test-space'
        kubectl.get_deployment(name)

        mock_call.assert_called_once_with(expected_call)
예제 #5
0
    def test_list_deployments_by_selector(self, mock_call):
        expected = ['get', 'deployments', '--selector',
                    'servicegroup=twyla,mylabel=myvalue', '-o', 'json']
        expected_call = ['kubectl', '--namespace', 'test-space']
        expected_call.extend(expected)
        kubectl = Kubectl()
        kubectl.namespace = 'test-space'
        kubectl.list_deployments(selectors={'servicegroup': 'twyla',
                                            'mylabel': 'myvalue'})

        mock_call.assert_called_once_with(expected_call)
예제 #6
0
def cluster_info(dump_to: str, group: str, namespace: str):
    kubectl = Kubectl()
    kubectl.namespace = namespace

    state = kubectl.list_deployments(selectors={'servicegroup': group})
    print_cluster_info(state)

    if dump_to is not None:
        deployable = scrub_cluster_info(state)
        with open(dump_to, mode='w') as fd:
            fd.write(yaml.dump(deployable, default_flow_style=False))
예제 #7
0
    def test_call(self, mock_json, mock_subprocess):
        mock_pipe = mock.MagicMock()
        mock_subprocess.PIPE = mock_pipe
        kubectl = Kubectl()
        cmd = ['kubectl', 'get', 'pods']
        kubectl._call(cmd)

        mock_subprocess.run.assert_called_once_with(
            cmd,
            stdout=mock_pipe,
            stderr=mock_pipe)
예제 #8
0
    def test_namespaced_command(self):
        kubectl = Kubectl()
        kubectl.namespace = 'twyla'

        default_cmd = kubectl._make_command()
        expected = ['kubectl',
                    '--namespace',
                    'twyla',
                    'get',
                    'pods']

        assert default_cmd == expected
예제 #9
0
    def test_apply(self, mock_json, mock_subprocess):
        mock_pipe = mock.MagicMock()
        mock_subprocess.PIPE = mock_pipe
        kubectl = Kubectl()
        file_name = 'deployment.yml'
        expected = ['kubectl', 'apply', '-f', file_name]

        kubectl.apply(file_name)

        mock_subprocess.run.assert_called_once_with(
            expected,
            stdout=mock_pipe,
            stderr=mock_pipe)
예제 #10
0
 def __init__(self,
              namespace: str,
              deployment_name: str,
              printer: Callable[[str], int],
              error_printer: Callable[[str], int],
              deployment_template: str=None,
              variants: List[str]=None):
     self.printer = printer
     self.error_printer = error_printer
     self.deployment_name = deployment_name
     self.deployment_template = deployment_template or 'deployment.yml'
     self.kubectl = Kubectl()
     self.kubectl.namespace = namespace
     self.variants = variants or []
예제 #11
0
def apply(from_file: str):
    # Load the deployments from file and get the current count of replicas in
    # the target cluster for each of the deployments. Then update the replicas
    # to match the target cluster. Save the file and pass on to kubectl apply.
    with open(from_file) as fd:
        content = fd.read()
    kube_list = yaml.load(content)

    kubectl = Kubectl()
    kubectl.update_replicas(kube_list)

    with open(from_file, mode='w') as fd:
        fd.write(yaml.dump(kube_list, default_flow_style=False))

    try:
        lines = kubectl.apply(from_file)
        for line in lines.split('\n'):
            prompt(line)
    except KubectlCallFailed as e:
        error_prompt(str(e))
예제 #12
0
 def test_make_selector_args(self):
     kubectl = Kubectl()
     res = kubectl._make_selector_args(None)
     assert res == []
     res = kubectl._make_selector_args({})
     assert res == []
     res = kubectl._make_selector_args({'one': 'val'})
     assert res == ['--selector', 'one=val']
     res = kubectl._make_selector_args({'one': 'val', 'two': 'val2'})
     assert res == ['--selector', 'one=val,two=val2']
예제 #13
0
    def test_update_replicas_no_remote(self, mock_call):
        kube_list = json.loads('''
{
    "apiVersion": "v1",
    "items": [
        {
            "apiVersion": "extensions/v1beta1",
            "kind": "Deployment",
            "metadata": {
                "labels": {
                    "app": "test-service-one",
                    "servicegroup": "twyla"
                },
                "name": "test-service-one",
                "namespace": "twyla"
            },
            "spec": {
                "replicas": 2,
                "selector": {
                    "matchLabels": {
                        "app": "test-service-one",
                        "name": "test-service-one"
                    }
                },
                "strategy": {
                    "rollingUpdate": {
                        "maxSurge": "0%",
                        "maxUnavailable": "100%"
                    },
                    "type": "RollingUpdate"
                },
                "template": {
                    "metadata": {
                        "creationTimestamp": null,
                        "labels": {
                            "app": "test-service-one",
                            "name": "test-service-one"
                        }
                    },
                    "spec": {
                        "containers": [
                            {
                                "env": [
                                    {
                                        "name": "TWYLA_CLUSTER_NAME",
                                        "valueFrom": {
                                            "configMapKeyRef": {
                                                "key": "cluster-name",
                                                "name": "cluster-vars"
                                            }
                                        }
                                    }
                                ],
                                "image": "twyla.azurecr.io/test-service-one:cc0cd960",
                                "imagePullPolicy": "Always",
                                "name": "test-service-one",
                                "resources": {},
                                "terminationMessagePath": "/dev/termination-log",
                                "terminationMessagePolicy": "File"
                            }
                        ],
                        "dnsPolicy": "ClusterFirst",
                        "imagePullSecrets": [
                            {
                                "name": "twyla-registry-login"
                            }
                        ],
                        "restartPolicy": "Always",
                        "schedulerName": "default-scheduler",
                        "securityContext": {},
                        "terminationGracePeriodSeconds": 30
                    }
                }
            }
        },
        {
            "apiVersion": "extensions/v1beta1",
            "kind": "Deployment",
            "metadata": {
                "labels": {
                    "app": "test-service-two",
                    "servicegroup": "twyla"
                },
                "name": "test-service-two",
                "namespace": "twyla"
            },
            "spec": {
                "progressDeadlineSeconds": 600,
                "replicas": 1,
                "revisionHistoryLimit": 2,
                "selector": {
                    "matchLabels": {
                        "app": "test-service-two"
                    }
                },
                "strategy": {
                    "rollingUpdate": {
                        "maxSurge": 1,
                        "maxUnavailable": 1
                    },
                    "type": "RollingUpdate"
                },
                "template": {
                    "metadata": {
                        "creationTimestamp": null,
                        "labels": {
                            "app": "test-service-two",
                            "name": "test-service-two"
                        }
                    },
                    "spec": {
                        "containers": [
                            {
                                "env": [
                                    {
                                        "name": "TWYLA_CLUSTER_NAME",
                                        "valueFrom": {
                                            "configMapKeyRef": {
                                                "key": "cluster-name",
                                                "name": "cluster-vars"
                                            }
                                        }
                                    }
                                ],
                                "image": "twyla.azurecr.io/test-service-two:c42d0ebe",
                                "imagePullPolicy": "Always",
                                "name": "test-service-two",
                                "ports": [
                                    {
                                        "containerPort": 5000,
                                        "protocol": "TCP"
                                    }
                                ],
                                "resources": {},
                                "terminationMessagePath": "/dev/termination-log",
                                "terminationMessagePolicy": "File"
                            }
                        ],
                        "dnsPolicy": "ClusterFirst",
                        "imagePullSecrets": [
                            {
                                "name": "twyla-registry-login"
                            }
                        ],
                        "restartPolicy": "Always",
                        "schedulerName": "default-scheduler",
                        "securityContext": {},
                        "terminationGracePeriodSeconds": 30
                    }
                }
            }
        }
    ],
    "kind": "List",
    "metadata": {
        "resourceVersion": "",
        "selfLink": ""
    }
}
        ''')
        dep1 = json.loads('''{
   "apiVersion":"extensions/v1beta1",
   "kind":"Deployment",
   "metadata":{
      "annotations":{
         "deployment.kubernetes.io/revision":"89",
         "kubectl.kubernetes.io/last-applied-configuration":"{\\"apiVersion\\":\\"extensions/v1beta1\\",\\"kind\\":\\"Deployment\\",\\"metadata\\":{\\"annotations\\":{},\\"labels\\":{\\"app\\":\\"test-service-one\\",\\"servicegroup\\":\\"twyla\\"},\\"name\\":\\"test-service-one\\",\\"namespace\\":\\"twyla\\"},\\"spec\\":{\\"progressDeadlineSeconds\\":600,\\"replicas\\":1,\\"revisionHistoryLimit\\":2,\\"selector\\":{\\"matchLabels\\":{\\"app\\":\\"test-service-one\\"}},\\"strategy\\":{\\"rollingUpdate\\":{\\"maxSurge\\":\\"50%\\",\\"maxUnavailable\\":\\"50%\\"},\\"type\\":\\"RollingUpdate\\"},\\"template\\":{\\"metadata\\":{\\"creationTimestamp\\":null,\\"labels\\":{\\"app\\":\\"test-service-one\\",\\"name\\":\\"test-service-one\\"}},\\"spec\\":{\\"containers\\":[{\\"env\\":[{\\"name\\":\\"TWYLA_CLUSTER_NAME\\",\\"valueFrom\\":{\\"configMapKeyRef\\":{\\"key\\":\\"cluster-name\\",\\"name\\":\\"cluster-vars\\"}}},{\\"name\\":\\"TWYLA_DOCUMENT_STORE_URI\\",\\"valueFrom\\":{\\"secretKeyRef\\":{\\"key\\":\\"twyla_document_store_string\\",\\"name\\":\\"document-store-secrets\\"}}}],\\"image\\":\\"twyla.azurecr.io/test-service-one:356dcef4\\",\\"imagePullPolicy\\":\\"Always\\",\\"name\\":\\"test-service-one\\",\\"resources\\":{},\\"terminationMessagePath\\":\\"/dev/termination-log\\",\\"terminationMessagePolicy\\":\\"File\\"}],\\"dnsPolicy\\":\\"ClusterFirst\\",\\"imagePullSecrets\\":[{\\"name\\":\\"twyla-registry-login\\"}],\\"restartPolicy\\":\\"Always\\",\\"schedulerName\\":\\"default-scheduler\\",\\"securityContext\\":{},\\"terminationGracePeriodSeconds\\":30}}}}\\n"
      },
      "creationTimestamp":"2017-10-16T14:55:37Z",
      "generation":95,
      "labels":{
         "app":"test-service-one",
         "servicegroup":"twyla"
      },
      "name":"test-service-one",
      "namespace":"twyla",
      "resourceVersion":"16810663",
      "selfLink":"/apis/extensions/v1beta1/namespaces/twyla/deployments/test-service-one",
      "uid":"120abf54-b282-11e7-b58f-000d3a2bee3e"
   },
   "spec":{
      "progressDeadlineSeconds":600,
      "replicas":1,
      "revisionHistoryLimit":2,
      "selector":{
         "matchLabels":{
            "app":"test-service-one"
         }
      },
      "strategy":{
         "rollingUpdate":{
            "maxSurge":"50%",
            "maxUnavailable":"50%"
         },
         "type":"RollingUpdate"
      },
      "template":{
         "metadata":{
            "creationTimestamp":null,
            "labels":{
               "app":"test-service-one",
               "name":"test-service-one"
            }
         },
         "spec":{
            "containers":[
               {
                  "env":[
                     {
                        "name":"TWYLA_CLUSTER_NAME",
                        "valueFrom":{
                           "configMapKeyRef":{
                              "key":"cluster-name",
                              "name":"cluster-vars"
                           }
                        }
                     },
                     {
                        "name":"TWYLA_DOCUMENT_STORE_URI",
                        "valueFrom":{
                           "secretKeyRef":{
                              "key":"twyla_document_store_string",
                              "name":"document-store-secrets"
                           }
                        }
                     }
                  ],
                  "image":"twyla.azurecr.io/test-service-one:356dcef4",
                  "imagePullPolicy":"Always",
                  "name":"test-service-one",
                  "resources":{

                  },
                  "terminationMessagePath":"/dev/termination-log",
                  "terminationMessagePolicy":"File"
               }
            ],
            "dnsPolicy":"ClusterFirst",
            "imagePullSecrets":[
               {
                  "name":"twyla-registry-login"
               }
            ],
            "restartPolicy":"Always",
            "schedulerName":"default-scheduler",
            "securityContext":{

            },
            "terminationGracePeriodSeconds":30
         }
      }
   },
   "status":{
      "availableReplicas":1,
      "conditions":[
         {
            "lastTransitionTime":"2018-01-08T16:38:21Z",
            "lastUpdateTime":"2018-02-13T18:47:02Z",
            "message":"ReplicaSet \\"test-service-one-3114667387\\" has successfully progressed.",
            "reason":"NewReplicaSetAvailable",
            "status":"True",
            "type":"Progressing"
         },
         {
            "lastTransitionTime":"2018-02-14T16:36:34Z",
            "lastUpdateTime":"2018-02-14T16:36:34Z",
            "message":"Deployment has minimum availability.",
            "reason":"MinimumReplicasAvailable",
            "status":"True",
            "type":"Available"
         }
      ],
      "observedGeneration":95,
      "readyReplicas":1,
      "replicas":1,
      "updatedReplicas":1
   }
}''')
        mock_call.side_effect = [
            dep1,
            KubectlCallFailed
        ]

        # before (this makes the test more obvious)
        assert kube_list['items'][0]['spec']['replicas'] == 2
        assert kube_list['items'][1]['spec']['replicas'] == 1

        kubectl = Kubectl()
        kubectl.update_replicas(kube_list)

        # after
        assert kube_list['items'][0]['spec']['replicas'] == 1
        assert kube_list['items'][1]['spec']['replicas'] == 1
예제 #14
0
class Kube:
    def __init__(self,
                 namespace: str,
                 deployment_name: str,
                 printer: Callable[[str], int],
                 error_printer: Callable[[str], int],
                 deployment_template: str=None,
                 variants: List[str]=None):
        self.printer = printer
        self.error_printer = error_printer
        self.deployment_name = deployment_name
        self.deployment_template = deployment_template or 'deployment.yml'
        self.kubectl = Kubectl()
        self.kubectl.namespace = namespace
        self.variants = variants or []


    def get_remote_deployment(self):
        return self.kubectl.get_deployment(self.deployment_name)


    def apply(self, tag: str):
        # Load the deployment definition
        file_name = self.render_template(tag)
        output = self.kubectl.apply(file_name)
        for line in output.split('\n'):
            if line is not '':
                self.printer(line)


    def info(self):
        try:
            deployment = self.get_remote_deployment()
            self.print_deployment_info(
                'Current {}'.format(self.deployment_name),
                deployment)
        except KubectlCallFailed as e:
            self.error_printer(self.exception(e))


    def exception(self, e):
        return e.args[0].decode('utf8').strip()


    def print_deployment_info(
            self,
            title: str,
            deployment):

        info_template = Template('''
{{ meta.title }}:
{% for c in deployment.spec.template.spec.containers %}
  name: {{ c.name }}
  image: {{ c.image }}
{% endfor %}
  replicas: {{ deployment.status.readyReplicas }}/{{ deployment.status.replicas -}}
        ''')

        rendered = info_template.render(meta={'name': self.deployment_name,
                                              'title': title},
                                        deployment=deployment)

        for line in rendered.split('\n'):
            if line:
                self.printer(line)

        return


    def render_template(self, tag: str):
        jinja = Environment(loader=FileSystemLoader('./'))
        template = jinja.get_template(self.deployment_template)

        replicas = None
        try:
            deployment = self.get_remote_deployment()
            replicas = deployment['spec']['replicas']

            # make sure to use the same number of replicas as remote to honor
            # scaling
            if deployment.get('status'):
                replicas = deployment['status']['replicas']
        except KubectlCallFailed as e:
            self.error_printer(self.exception(e))

        data = {
            'image': tag,
            'name': self.deployment_name,
            'namespace': self.kubectl.namespace,
            'replicas': replicas,
            'variants': self.variants
        }

        rendered = template.render(data=data)
        tmp_file = tempfile.NamedTemporaryFile(delete=False)

        tmp_file.write(rendered.encode('utf8'))

        return tmp_file.name