|
| 1 | +# vim:set sw=4 ts=4 et: |
| 2 | +# |
| 3 | +# Copyright (c) 2016-2017 Torchbox Ltd. |
| 4 | +# |
| 5 | +# Permission is granted to anyone to use this software for any purpose, |
| 6 | +# including commercial applications, and to alter it and redistribute it |
| 7 | +# freely. This software is provided 'as-is', without any express or implied |
| 8 | +# warranty. |
| 9 | + |
| 10 | +import json |
| 11 | +from sys import stdout, stderr, exit |
| 12 | +from kubernetes.client.apis import core_v1_api, extensions_v1beta1_api |
| 13 | + |
| 14 | +import deployment, kubectl, kubeutil |
| 15 | + |
| 16 | +def undeploy(args): |
| 17 | + try: |
| 18 | + dp = deployment.get_deployment(args.namespace, args.name) |
| 19 | + except Exception as e: |
| 20 | + stderr.write('cannot load deployment {0}: {1}\n'.format( |
| 21 | + args.name, kubeutil.get_error(e))) |
| 22 | + exit(1) |
| 23 | + |
| 24 | + resources = None |
| 25 | + try: |
| 26 | + resources = json.loads(dp['metadata']['annotations']['kdtool.torchbox.com/attached-resources']) |
| 27 | + except KeyError: |
| 28 | + pass |
| 29 | + except ValueError as e: |
| 30 | + stderr.write("error: could not decode kdtool.torchbox.com/attached-resources annotation: {0}\n".format(str(e))) |
| 31 | + exit(1) |
| 32 | + |
| 33 | + stdout.write("\nthis deployment will be removed:\n") |
| 34 | + stdout.write("- {0}/{1}\n".format( |
| 35 | + dp['metadata']['namespace'], |
| 36 | + dp['metadata']['name'], |
| 37 | + )) |
| 38 | + |
| 39 | + if len(resources): |
| 40 | + if args.all: |
| 41 | + stdout.write("\nthe following attached resources will also be deleted:\n") |
| 42 | + for res in resources: |
| 43 | + extra = '' |
| 44 | + if res['kind'] == 'database': |
| 45 | + extra = ' (database will be dropped)' |
| 46 | + elif res['kind'] == 'volume': |
| 47 | + extra = ' (contents will be deleted)' |
| 48 | + |
| 49 | + stdout.write("- {0}: {1}{2}\n".format( |
| 50 | + res['kind'], |
| 51 | + res['name'], |
| 52 | + extra |
| 53 | + )) |
| 54 | + else: |
| 55 | + stdout.write("\nthe following attached resources will NOT be deleted (use --all):\n") |
| 56 | + for res in resources: |
| 57 | + stdout.write("- {0}: {1}\n".format( |
| 58 | + res['kind'], |
| 59 | + res['name'], |
| 60 | + )) |
| 61 | + |
| 62 | + stdout.write('\n') |
| 63 | + |
| 64 | + if not args.force: |
| 65 | + pr = input('continue [y/N]? ') |
| 66 | + if pr.lower() not in ['yes', 'y']: |
| 67 | + stdout.write("okay, aborting\n") |
| 68 | + exit(0) |
| 69 | + |
| 70 | + client = kubeutil.get_client() |
| 71 | + extv1beta1 = extensions_v1beta1_api.ExtensionsV1beta1Api(client) |
| 72 | + v1 = core_v1_api.CoreV1Api(client) |
| 73 | + |
| 74 | + stdout.write('deleting deployment <{}/{}>: '.format(args.namespace, args.name)) |
| 75 | + extv1beta1.delete_namespaced_deployment(args.name, args.namespace, body={}) |
| 76 | + stdout.write('ok\n') |
| 77 | + |
| 78 | + if not args.all: |
| 79 | + exit(0) |
| 80 | + |
| 81 | + for res in resources: |
| 82 | + stdout.write('deleting {} <{}>: '.format(res['kind'], res['name'])) |
| 83 | + if res['kind'] == 'volume': |
| 84 | + v1.delete_namespaced_persistent_volume_claim( |
| 85 | + res['name'], args.namespace, body={}) |
| 86 | + elif res['kind'] == 'secret': |
| 87 | + v1.delete_namespaced_secret(res['name'], args.namespace, body={}) |
| 88 | + elif res['kind'] == 'database': |
| 89 | + resource_path = ('/apis/torchbox.com/v1/namespaces/' |
| 90 | + + dp['metadata']['namespace'] |
| 91 | + + '/databases/' |
| 92 | + + res['name']) |
| 93 | + |
| 94 | + header_params = {} |
| 95 | + header_params['Accept'] = client.select_header_accept(['application/json']) |
| 96 | + header_params['Content-Type'] = client.select_header_content_type(['*/*']) |
| 97 | + header_params.update(kubeutil.config.api_key) |
| 98 | + |
| 99 | + (resp, code, header) = client.call_api( |
| 100 | + resource_path, 'DELETE', {}, {}, header_params, None, [], _preload_content=False) |
| 101 | + elif res['kind'] == 'service': |
| 102 | + v1.delete_namespaced_service(res['name'], args.namespace) |
| 103 | + elif res['kind'] == 'ingress': |
| 104 | + extv1beta1.delete_namespaced_ingress(res['name'], args.namespace, body={}) |
| 105 | + |
| 106 | + stdout.write('ok\n') |
| 107 | + |
| 108 | +undeploy.help = "undeploy an application" |
| 109 | +undeploy.arguments = ( |
| 110 | + ( ('-M', '--manifest'), { |
| 111 | + 'type': str, |
| 112 | + 'metavar': 'FILE', |
| 113 | + 'help': 'deploy from Kubernetes manifest with environment substitution', |
| 114 | + }), |
| 115 | + ( ('-f', '--force'), { |
| 116 | + 'action': 'store_true', |
| 117 | + 'help': 'do not prompt for confirmation', |
| 118 | + }), |
| 119 | + ( ('-A', '--all'), { |
| 120 | + 'action': 'store_true', |
| 121 | + 'help': 'undeploy attached resources', |
| 122 | + }), |
| 123 | + ( ('name',), { |
| 124 | + 'type': str, |
| 125 | + 'help': 'application name', |
| 126 | + }) |
| 127 | +) |
| 128 | + |
| 129 | +commands = { |
| 130 | + 'undeploy': undeploy, |
| 131 | +} |
| 132 | + |
0 commit comments