|
@@ -8,33 +8,79 @@ import os
|
|
|
|
|
|
|
|
|
class Cluster(object):
|
|
|
- """Python wrapper to ensure environment is correct for running ansible playbooks
|
|
|
"""
|
|
|
-
|
|
|
- def __init__(self, args):
|
|
|
- self.args = args
|
|
|
-
|
|
|
+ Control and Configuration Interface for OpenShift Clusters
|
|
|
+ """
|
|
|
+ def __init__(self):
|
|
|
# setup ansible ssh environment
|
|
|
if 'ANSIBLE_SSH_ARGS' not in os.environ:
|
|
|
os.environ['ANSIBLE_SSH_ARGS'] = (
|
|
|
- '-o ForwardAgent=yes'
|
|
|
- ' -o StrictHostKeyChecking=no'
|
|
|
- ' -o UserKnownHostsFile=/dev/null'
|
|
|
- ' -o ControlMaster=auto'
|
|
|
- ' -o ControlPersist=600s'
|
|
|
+ '-o ForwardAgent=yes '
|
|
|
+ '-o StrictHostKeyChecking=no '
|
|
|
+ '-o UserKnownHostsFile=/dev/null '
|
|
|
+ '-o ControlMaster=auto '
|
|
|
+ '-o ControlPersist=600s '
|
|
|
)
|
|
|
|
|
|
- def apply(self):
|
|
|
- # setup ansible playbook environment
|
|
|
+ def create(self, args):
|
|
|
+ """
|
|
|
+ Create an OpenShift cluster for given provider
|
|
|
+ :param args: command line arguments provided by user
|
|
|
+ :return: exit status from run command
|
|
|
+ """
|
|
|
+ env = {'cluster_id': args.cluster_id}
|
|
|
+ playbook = "playbooks/{}/openshift-cluster/launch.yml".format(args.provider)
|
|
|
+ inventory = self.setup_provider(args.provider)
|
|
|
+
|
|
|
+ env['masters'] = args.masters
|
|
|
+ env['nodes'] = args.nodes
|
|
|
+
|
|
|
+ return self.action(args, inventory, env, playbook)
|
|
|
+
|
|
|
+ def terminate(self, args):
|
|
|
+ """
|
|
|
+ Destroy OpenShift cluster
|
|
|
+ :param args: command line arguments provided by user
|
|
|
+ :return: exit status from run command
|
|
|
+ """
|
|
|
+ env = {'cluster_id': args.cluster_id}
|
|
|
+ playbook = "playbooks/{}/openshift-cluster/terminate.yml".format(args.provider)
|
|
|
+ inventory = self.setup_provider(args.provider)
|
|
|
+
|
|
|
+ return self.action(args, inventory, env, playbook)
|
|
|
+
|
|
|
+ def list(self, args):
|
|
|
+ """
|
|
|
+ List VMs in cluster
|
|
|
+ :param args: command line arguments provided by user
|
|
|
+ :return: exit status from run command
|
|
|
+ """
|
|
|
+ raise NotImplementedError("ACTION [{}] not implemented".format(sys._getframe().f_code.co_name))
|
|
|
+
|
|
|
+ def update(self, args):
|
|
|
+ """
|
|
|
+ Update OpenShift across clustered VMs
|
|
|
+ :param args: command line arguments provided by user
|
|
|
+ :return: exit status from run command
|
|
|
+ """
|
|
|
+ raise NotImplementedError("ACTION [{}] not implemented".format(sys._getframe().f_code.co_name))
|
|
|
+
|
|
|
+
|
|
|
+ def setup_provider(self, provider):
|
|
|
+ """
|
|
|
+ Setup ansible playbook environment
|
|
|
+ :param provider: command line arguments provided by user
|
|
|
+ :return: path to inventory for given provider
|
|
|
+ """
|
|
|
config = ConfigParser.ConfigParser()
|
|
|
- if 'gce' == self.args.provider:
|
|
|
+ if 'gce' == provider:
|
|
|
config.readfp(open('inventory/gce/gce.ini'))
|
|
|
|
|
|
for key in config.options('gce'):
|
|
|
os.environ[key] = config.get('gce', key)
|
|
|
|
|
|
inventory = '-i inventory/gce/gce.py'
|
|
|
- elif 'aws' == self.args.provider:
|
|
|
+ elif 'aws' == provider:
|
|
|
config.readfp(open('inventory/aws/ec2.ini'))
|
|
|
|
|
|
for key in config.options('ec2'):
|
|
@@ -43,30 +89,23 @@ class Cluster(object):
|
|
|
inventory = '-i inventory/aws/ec2.py'
|
|
|
else:
|
|
|
# this code should never be reached
|
|
|
- raise argparse.ArgumentError("invalid PROVIDER {}".format(self.args.provider))
|
|
|
-
|
|
|
- env = {'cluster_id': self.args.cluster_id}
|
|
|
-
|
|
|
- if 'create' == self.args.action:
|
|
|
- playbook = "playbooks/{}/openshift-cluster/launch.yml".format(self.args.provider)
|
|
|
- env['masters'] = self.args.masters
|
|
|
- env['nodes'] = self.args.nodes
|
|
|
-
|
|
|
- elif 'terminate' == self.args.action:
|
|
|
- playbook = "playbooks/{}/openshift-cluster/terminate.yml".format(self.args.provider)
|
|
|
- elif 'list' == self.args.action:
|
|
|
- # todo: implement cluster list
|
|
|
- raise argparse.ArgumentError("ACTION {} not implemented".format(self.args.action))
|
|
|
- elif 'update' == self.args.action:
|
|
|
- # todo: implement cluster update
|
|
|
- raise argparse.ArgumentError("ACTION {} not implemented".format(self.args.action))
|
|
|
- else:
|
|
|
- # this code should never be reached
|
|
|
- raise argparse.ArgumentError("invalid ACTION {}".format(self.args.action))
|
|
|
+ raise ValueError("invalid PROVIDER {}".format(provider))
|
|
|
+
|
|
|
+ return inventory
|
|
|
+
|
|
|
+ def action(self, args, inventory, env, playbook):
|
|
|
+ """
|
|
|
+ Build ansible-playbook command line and execute
|
|
|
+ :param args: command line arguments provided by user
|
|
|
+ :param inventory: derived provider library
|
|
|
+ :param env: environment variables for kubernetes
|
|
|
+ :param playbook: ansible playbook to execute
|
|
|
+ :return: exit status from ansible-playbook command
|
|
|
+ """
|
|
|
|
|
|
verbose = ''
|
|
|
- if self.args.verbose > 0:
|
|
|
- verbose = '-{}'.format('v' * self.args.verbose)
|
|
|
+ if args.verbose > 0:
|
|
|
+ verbose = '-{}'.format('v' * args.verbose)
|
|
|
|
|
|
ansible_env = '-e \'{}\''.format(
|
|
|
' '.join(['%s=%s' % (key, value) for (key, value) in env.items()])
|
|
@@ -76,36 +115,67 @@ class Cluster(object):
|
|
|
verbose, inventory, ansible_env, playbook
|
|
|
)
|
|
|
|
|
|
- if self.args.verbose > 1:
|
|
|
+ if args.verbose > 1:
|
|
|
command = 'time {}'.format(command)
|
|
|
|
|
|
- if self.args.verbose > 0:
|
|
|
+ if args.verbose > 0:
|
|
|
sys.stderr.write('RUN [{}]\n'.format(command))
|
|
|
sys.stderr.flush()
|
|
|
|
|
|
- status = os.system(command)
|
|
|
- if status != 0:
|
|
|
- sys.stderr.write("RUN [{}] failed with exit status %d".format(command, status))
|
|
|
- exit(status)
|
|
|
-
|
|
|
+ return os.system(command)
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
- parser = argparse.ArgumentParser(description='Manage OpenShift Cluster')
|
|
|
- parser.add_argument('-m', '--masters', default=1, type=int, help='number of masters to create in cluster')
|
|
|
- parser.add_argument('-n', '--nodes', default=2, type=int, help='number of nodes to create in cluster')
|
|
|
+ """
|
|
|
+ Implemented to support writing unit tests
|
|
|
+ """
|
|
|
+
|
|
|
+ cluster = Cluster()
|
|
|
+
|
|
|
+ providers = ['gce', 'aws']
|
|
|
+ parser = argparse.ArgumentParser(
|
|
|
+ description='Python wrapper to ensure proper environment for OpenShift ansible playbooks',
|
|
|
+ )
|
|
|
parser.add_argument('-v', '--verbose', action='count', help='Multiple -v options increase the verbosity')
|
|
|
- parser.add_argument('--version', action='version', version='%(prog)s 0.1')
|
|
|
- parser.add_argument('action', choices=['create', 'terminate', 'update', 'list'])
|
|
|
- parser.add_argument('provider', choices=['gce', 'aws'])
|
|
|
- parser.add_argument('cluster_id', help='prefix for cluster VM names')
|
|
|
+ parser.add_argument('--version', action='version', version='%(prog)s 0.2')
|
|
|
+
|
|
|
+ meta_parser = argparse.ArgumentParser(add_help=False)
|
|
|
+ meta_parser.add_argument('provider', choices=providers, help='provider')
|
|
|
+ meta_parser.add_argument('cluster_id', help='prefix for cluster VM names')
|
|
|
+
|
|
|
+ action_parser = parser.add_subparsers(dest='action', title='actions', description='Choose from valid actions')
|
|
|
+
|
|
|
+ create_parser = action_parser.add_parser('create', help='Create a cluster', parents=[meta_parser])
|
|
|
+ create_parser.add_argument('-m', '--masters', default=1, type=int, help='number of masters to create in cluster')
|
|
|
+ create_parser.add_argument('-n', '--nodes', default=2, type=int, help='number of nodes to create in cluster')
|
|
|
+ create_parser.set_defaults(func=cluster.create)
|
|
|
+
|
|
|
+ terminate_parser = action_parser.add_parser('terminate', help='Destroy a cluster', parents=[meta_parser])
|
|
|
+ terminate_parser.add_argument('-f', '--force', action='store_true', help='Destroy cluster without confirmation')
|
|
|
+ terminate_parser.set_defaults(func=cluster.terminate)
|
|
|
+
|
|
|
+ update_parser = action_parser.add_parser('update', help='Update OpenShift across cluster', parents=[meta_parser])
|
|
|
+ update_parser.add_argument('-f', '--force', action='store_true', help='Update cluster without confirmation')
|
|
|
+ update_parser.set_defaults(func=cluster.update)
|
|
|
+
|
|
|
+ list_parser = action_parser.add_parser('list', help='List VMs in cluster', parents=[meta_parser])
|
|
|
+ list_parser.set_defaults(func=cluster.list)
|
|
|
+
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
- if 'terminate' == args.action:
|
|
|
- sys.stderr.write("This will terminate the ENTIRE {} environment. Are you sure? [y/N] ".format(args.cluster_id))
|
|
|
- sys.stderr.flush()
|
|
|
- answer = sys.stdin.read(1)
|
|
|
+ if 'terminate' == args.action and not args.force:
|
|
|
+ answer = raw_input("This will destroy the ENTIRE {} environment. Are you sure? [y/N] ".format(args.cluster_id))
|
|
|
+ if answer not in ['y', 'Y']:
|
|
|
+ sys.stderr.write('\nACTION [terminate] aborted by user!\n')
|
|
|
+ exit(1)
|
|
|
+
|
|
|
+ if 'update' == args.action and not args.force:
|
|
|
+ answer = raw_input("This is destructive and could corrupt {} environment. Continue? [y/N] ".format(args.cluster_id))
|
|
|
if answer not in ['y', 'Y']:
|
|
|
- exit(0)
|
|
|
+ sys.stderr.write('\nACTION [update] aborted by user!\n')
|
|
|
+ exit(1)
|
|
|
|
|
|
- Cluster(args).apply()
|
|
|
+ status = args.func(args)
|
|
|
+ if status != 0:
|
|
|
+ sys.stderr.write("ACTION [{}] failed with exit status {}\n".format(args.action, status))
|
|
|
+ exit(status)
|