text: #!/usr/bin/python3
  from argparse import ArgumentParser
  from copy import deepcopy
  from enum import Enum, IntEnum
  from datetime import datetime
  import os
  import subprocess
  import sys
  from tempfile import mkdtemp
  from threading import Timer
  import time
  
  from kubernetes import client as kclient, config as kconfig, watch as kwatch
  import yaml
  
  from utils import (AgRole, AgMode, OperatorYaml, SqlSecretsYaml,
                     PersistentVolumeYaml, PersistentVolumeClaimYaml,
                     SqlServerYaml, AgServiceYaml, FailoverYaml)
  
  DEFAULT_K8S_AGENTS_IMAGE = (
      "mcr.microsoft.com/mssql/ha:2019-CTP2.1-ubuntu")
    
  
  
  TEMPLATES_DIR = "templates"
  SQLSERVER_TEMPLATE = os.path.join(TEMPLATES_DIR, "sqlserver.yaml")
  OPERATOR_TEMPLATE = os.path.join(TEMPLATES_DIR, "operator.yaml")
  AG_SERVICE_TEMPLATE = os.path.join(TEMPLATES_DIR, "ag-service.yaml")
  SQL_SECRETS_TEMPLATE = os.path.join(TEMPLATES_DIR, "sql-secrets.yaml")
  PV_TEMPLATE = os.path.join(TEMPLATES_DIR, "pv.yaml")
  PVC_TEMPLATE = os.path.join(TEMPLATES_DIR, "pvc.yaml")
  FAILOVER_YAML_TEMPLATE = os.path.join(TEMPLATES_DIR, "failover.yaml")
  
  SQLSERVER_YAML_FILENAME = "sqlserver.yaml"
  OPERATOR_YAML_FILENAME = "operator.yaml"
  AG_SERVICES_YAML_FILENAME = "ag-services.yaml"
  SQL_SECRETS_YAML_FILENAME = "sql-secrets.yaml"
  PV_YAML_FILENAME = "pv.yaml"
  PVC_YAML_FILENAME = "pvc.yaml"
  FAILOVER_YAML_FILENAME = "failover.yaml"
  
  SQLSERVER_NAME_PREFIX = "mssql"
  DEFAULT_NUM_SQLSERVER = 3
  DEFAULT_SQLSERVER_NAMES = [
      "{}{}".format(SQLSERVER_NAME_PREFIX, i + 1)
      for i in range(DEFAULT_NUM_SQLSERVER)
  ]
  DEFAULT_AG_NAME = "ag1"
  DEFAULT_NAMESPACE = "default"
  
  TERM_RED = "\x1b[31m"
  TERM_GREEN = "\x1b[32m"
  TERM_END = "\x1b[0m"
  
  
  class LogLevel(IntEnum):
      ALL = 0
      ERROR = 1
      WARNING = 2
      INFO = 3
      DEBUG = 4
  
  
  log_verbosity = LogLevel.ALL
  
  
  def log(level, *args):
      color = ""
      color_end = ""
  
      if level == LogLevel.ERROR:
          color = TERM_RED
          color_end = TERM_END
      if level <= log_verbosity:
          print(color, "[", level.name, "] ", color_end, sep="", end="")
          print(*args)
  
  
  # YAML read/write functions
  def create_operator_yaml(namespace,
                           k8s_agent_image,
                           operator_template=OPERATOR_TEMPLATE,
                           filepath=OPERATOR_YAML_FILENAME):
      with open(operator_template) as f:
          operator_yaml = OperatorYaml(yaml.load_all(f.read()))
          operator_yaml.set_namespace(namespace)
          operator_yaml.set_agent_image(k8s_agent_image)
          with open(filepath, "w") as operator_yaml_file:
              yaml.dump_all(operator_yaml.data, operator_yaml_file)
          log(LogLevel.INFO, "operator YAML file:", filepath)
  
  
  def get_pv_name(namespace, sqlserver_name):
      return "{}-{}-pv".format(namespace, sqlserver_name)
  
  
  def create_pv_yaml(namespace,
                     sqlservers,
                     root_path,
                     pv_template=PV_TEMPLATE,
                     filepath=PV_YAML_FILENAME):
      with open(pv_template) as f:
          pv_yaml_template = PersistentVolumeYaml(yaml.load(f.read()))
          pv_yaml_list = []
          for sqlserver_name in sqlservers:
              pv_yaml = pv_yaml_template.copy()
              pv_yaml.set_storage(namespace)
              pv_name = get_pv_name(namespace, sqlserver_name)
              pv_yaml.set_name(pv_name)
              pv_yaml.set_path(os.path.join(root_path, pv_name))
              pv_yaml_list.append(pv_yaml)
  
          with open(filepath, "w") as pv_yaml_file:
              yaml.dump_all([pv.data for pv in pv_yaml_list], pv_yaml_file)
          log(LogLevel.INFO, "Persistent Volume YAML file:", filepath)
          return pv_yaml_list
  
  
  # def create_pvc_yaml(namespace, name, storage_class_name="default",
  #         pvc_template=PVC_TEMPLATE, filepath=PVC_YAML_FILENAME):
  #     with open(pv_template) as f:
  #         pvc_yaml_template = PersistentVolumeClaimYaml(yaml.load(f.read()))
  
  
  def create_sqlservers_yaml(env,
                             namespace,
                             sqlservers,
                             ags,
                             sa_password,
                             k8s_agent_image,
                             sqlserver_template=SQLSERVER_TEMPLATE,
                             filepath=SQLSERVER_YAML_FILENAME):
      with open(sqlserver_template) as f:
          sqlserver_yaml_template = SqlServerYaml(yaml.load_all(f.read()))
          sqlserver_yaml_list = []
          for sqlserver_name in sqlservers:
              sqlserver_yaml = sqlserver_yaml_template.copy()
              sqlserver_yaml.set_name(sqlserver_name)
              sqlserver_yaml.set_namespace(namespace)
              sqlserver_yaml.set_availability_groups(ags)
              sqlserver_yaml.set_agent_image(k8s_agent_image)
              if env is Environment.ON_PREM:
                  sqlserver_yaml.set_volume_claim_template_with_selector(
                      namespace)
                  sqlserver_yaml.set_service_type("NodePort")
              elif env is Environment.AKS:
                  sqlserver_yaml.set_volume_claim_template_with_storage_class()
                  sqlserver_yaml.set_service_type("LoadBalancer")
              else:
                  raise ValueError("Invalid Environment type")
              for data in sqlserver_yaml.data:
                  sqlserver_yaml_list.append(data)
  
          with open(filepath, "w") as sqlserver_yaml_file:
              yaml.dump_all(sqlserver_yaml_list, sqlserver_yaml_file)
          log(LogLevel.INFO, "SQL Server YAML file:", filepath)
  
  
  def create_ag_services_yaml(env,
                              namespace,
                              ag,
                              ag_service_template=AG_SERVICE_TEMPLATE,
                              filepath=AG_SERVICES_YAML_FILENAME):
      with open(ag_service_template) as f:
          ag_service_yaml = AgServiceYaml(yaml.load(f.read()))
          if env is Environment.ON_PREM:
              ag_service_yaml_list = create_ag_services(
                  "NodePort", ag_service_yaml.data, namespace=namespace, ag=ag,annotations=env.service_annotations)
          elif env is Environment.AKS:
              ag_service_yaml_list = create_ag_services(
                  "LoadBalancer",
                  ag_service_yaml.data,
                  namespace=namespace,
                  ag=ag,
                  annotations=env.service_annotations)
          else:
              raise ValueError("Invalid env")
          ag_services_yaml_raw = [
              ags_yaml.data for ags_yaml in ag_service_yaml_list
          ]
          with open(filepath, "w") as ag_services_yaml_file:
              yaml.dump_all(ag_services_yaml_raw, ag_services_yaml_file)
          log(LogLevel.INFO, "ag-services YAML file:", filepath)
          return ag_service_yaml_list
  
  
  def create_ag_services(service_type, template_data, namespace, ag,
                         annotations):
      primary = AgServiceYaml.create_ag_service(service_type,
                                                deepcopy(template_data),
                                                namespace, ag, AgRole.PRIMARY,
                                                None, annotations)
      sec_sync = AgServiceYaml.create_ag_service(service_type,
                                                 deepcopy(template_data),
                                                 namespace, ag, AgRole.SECONDARY,
                                                 AgMode.SYNC, annotations)
      sec_async = AgServiceYaml.create_ag_service(
          service_type, deepcopy(template_data), namespace, ag, AgRole.SECONDARY,
          AgMode.ASYNC, annotations)
      sec_config_only = AgServiceYaml.create_ag_service(
          service_type, deepcopy(template_data), namespace, ag, AgRole.SECONDARY,
          AgMode.CONFIG, annotations)
  
      return (primary, sec_sync, sec_async, sec_config_only)
  
  
  def create_sql_secrets_yaml(namespace,
                              sapassword,
                              sql_secrets_template=SQL_SECRETS_TEMPLATE,
                              filepath=SQL_SECRETS_YAML_FILENAME):
      with open(sql_secrets_template) as f:
          sql_secrets_yaml = SqlSecretsYaml(yaml.load(f.read()))
          sql_secrets_yaml.set_namespace(namespace)
          sql_secrets_yaml.set_sapassword(sapassword)
          with open(filepath, "w") as sql_secretes_yaml_file:
              yaml.dump_all([sql_secrets_yaml.data], sql_secretes_yaml_file)
          log(LogLevel.INFO, "sql-secrets YAML file:", filepath)
          return sql_secrets_yaml
  
  
  def create_failover_yaml(namespace,
                           agent_image,
                           ag,
                           target_replica,
                           failover_template=FAILOVER_YAML_TEMPLATE,
                           filepath=FAILOVER_YAML_FILENAME):
      with open(failover_template) as f:
          failover_yaml = FailoverYaml(yaml.load_all(f.read()))
          failover_yaml.set_namespace(namespace)
          failover_yaml.set_config_map_name(ag)
          failover_yaml.set_endpoint_name(ag)
          failover_yaml.set_failover_container(
              agent_image=agent_image, ag=ag, new_primary=target_replica)
          with open(filepath, "w") as failover_yaml_file:
              yaml.dump_all(failover_yaml.data, failover_yaml_file)
          log(LogLevel.INFO, "failover YAML file:", filepath)
          return failover_yaml
  
  
  # kubectl functions
  def kubectl(args, **kwargs):
      proc_args = ["kubectl"] + args
      log(LogLevel.ALL, "Running command:", "`{}`".format(" ".join(proc_args)))
      proc = subprocess.run(
          args=proc_args,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          universal_newlines=True,
          **kwargs)
      if proc.stdout:
          log(LogLevel.INFO, proc_args, "\n", proc.stdout)
      if proc.stderr:
          log(LogLevel.ERROR, proc_args, "\n", proc.stderr)
      log(LogLevel.INFO, proc_args, "exit code:", proc.returncode)
      return proc
  
  
  def deploy_operator(namespace, operator_yaml):
      # We use `kubectl` here because for the kubernetes client we would
      # need to handle the document kind individually.
      kubectl(["apply", "-f", operator_yaml])
      operator_deployed = False
  
      def is_operator_deployed(event):
          if (event["type"] == "ADDED"
                  and event["object"].metadata.name == "mssql-operator"):
              return True, event["object"]
          else:
              return False, event["object"]
  
      apps_api = kclient.AppsV1Api()
      operator_success, operator_deployed = kube_watch_event(
          is_operator_deployed,
          apps_api.list_namespaced_deployment,
          namespace=namespace,
          timeout_seconds=10)
      log(LogLevel.ALL, "operator deployed:", operator_success)
  
      def is_crd_deployed(event):
          expected = "sqlservers.mssql.microsoft.com"
          if (event["type"] == "ADDED"
                  and event["object"].metadata.name == expected):
              return True, event["object"]
          else:
              return False, event["object"]
  
      extensions_api = kclient.ApiextensionsV1beta1Api()
  
      # We need to sleep here.
      # Kubernetes client API will raise the following exception if the operator
      # is not fully deployed when trying to list_custom_resource_definition:
      # File "kubernetes/client/models/v1beta1_custom_resource_definition_status.py", line 105, in conditions # noqa: E501
      #   raise ValueError("Invalid value for `conditions`, must not be `None`")
      log(LogLevel.ALL, "Waiting 10 seconds...")
      time.sleep(10)
  
      # Wait for crd to be deployed.
      log(LogLevel.ALL,
          "Waiting for Custom Resource Definition to be initialized")
      log(LogLevel.INFO,
          "This can be slow during the first deployment because we need to",
          "pull docker images")
      kube_watch_event(is_crd_deployed,
                       extensions_api.list_custom_resource_definition)
  
      return operator_success
  
  
  def deploy_pv(namespace, sql_servers, pv_yaml):
      pv_names = [get_pv_name(namespace, sql_name) for sql_name in sql_servers]
      log(LogLevel.INFO, "Deploying Persistent Volumes")
      kubectl(["apply", "-f", pv_yaml])
      pvs_deployed = []
  
      def all_pvs_deployed(event):
          name = event["object"].metadata.name
          if event["type"] == "ADDED" and name in pv_names:
              pvs_deployed.append(name)
          if (len(pv_names) == len(pvs_deployed)
                  and (sorted(pv_names) == sorted(pvs_deployed))):
              return True, pvs_deployed
          else:
              return False, pvs_deployed
  
      core_v1_api = kclient.CoreV1Api()
  
      deployed_pvs = kube_watch_event(
          all_pvs_deployed,
          core_v1_api.list_persistent_volume,
          timeout_seconds=60)
  
      return deployed_pvs
  
  
  def deploy_sqlservers(namespace, sql_servers, sqlserver_yaml):
      log(LogLevel.INFO, "Deploying SQL Servers:", sql_servers)
      kubectl(["apply", "-f", sqlserver_yaml])
      sql_pods_deployed = []
  
      def all_sql_deployed(event):
          # Custom objects API returns dictionaries
          name = event["object"]["metadata"]["name"]
          if event["type"] == "ADDED" and name in sql_servers:
              sql_pods_deployed.append(name)
          if (len(sql_servers) == len(sql_pods_deployed)
                  and (sorted(sql_servers) == sorted(sql_pods_deployed))):
              return True, sql_pods_deployed
          else:
              return False, sql_pods_deployed
  
      custom_objects_api = kclient.CustomObjectsApi()
  
      _, sql_pods_deployed = kube_watch_event(
          all_sql_deployed,
          custom_objects_api.list_namespaced_custom_object,
          group="mssql.microsoft.com",
          version="v1",
          plural="sqlservers",
          namespace=namespace)
  
      return sql_pods_deployed
  
  
  def deploy_ag_services(namespace, expected_names, ag_services_yaml):
      log(LogLevel.INFO, "Deploying AG services in YAML:", ag_services_yaml)
      kubectl(["apply", "-f", ag_services_yaml])
      deployed_ag_services = []
  
      def all_ag_services_deployed(event):
          # service name
          name = event["object"].metadata.name
          if event["type"] == "ADDED" and name in expected_names:
              deployed_ag_services.append(name)
  
          if (len(expected_names) == len(deployed_ag_services)
                  and (sorted(expected_names) == sorted(deployed_ag_services))):
              # all services have been deployed
              return True, deployed_ag_services
          else:
              return False, deployed_ag_services
  
      core_v1_api = kclient.CoreV1Api()
  
      deployed_ag_services = kube_watch_event(
          all_ag_services_deployed,
          core_v1_api.list_namespaced_service,
          namespace=namespace,
          timeout_seconds=60)
  
      return deployed_ag_services
  
  
  def create_namespace(namespace):
      log(LogLevel.INFO, "Creating namespace:", namespace)
      kconfig.load_kube_config()
      core_v1_api = kclient.CoreV1Api()
      v1_ns = kclient.V1Namespace(metadata=kclient.V1ObjectMeta(name=namespace))
      core_v1_api.create_namespace(body=v1_ns)
  
      return v1_ns
  
  
  class KubeWatchEventError(Exception):
      pass
  
  def kube_watch_event(completion_func, api_func, **kwargs):
      watch = kwatch.Watch()
      count = 8
      completed = False
      result = None
      watch_timer = Timer(60.0, lambda: watch.stop())
  
      ex_timer = Timer(300.0, KubeWatchEventError)
  
      # watch.stream generator might throw an exception
      # not sure how to handle.
      watch_timer.start()
      ex_timer.start()
      try:
          for event in watch.stream(api_func, **kwargs):
              log(LogLevel.DEBUG, "Watch count:", count)
              log(LogLevel.DEBUG, "Event:", event["type"], "object:",
                  event["object"])
              count -= 1
              completed, result = completion_func(event)
              log(LogLevel.DEBUG, "Completed:", completed)
              if completed:
                  watch.stop()
              if not count:
                  watch.stop()
      except KubeWatchEventError:
          raise
      except Exception as ex:
          log(LogLevel.ERROR, "Caught exception:", str(ex))
          watch.stop()
  
      watch_timer.cancel()
      ex_timer.cancel()
      if not completed:
          log(LogLevel.ERROR, str(api_func), "Could not complete sucessfully")
  
      return completed, result
  
  
  def apply_specs(namespace,
                  sql_servers,
                  ag_services,
                  operator_yaml_file=OPERATOR_YAML_FILENAME,
                  sql_secrets_yaml_file=SQL_SECRETS_YAML_FILENAME,
                  pv_yaml_file=PV_YAML_FILENAME,
                  sqlserver_yaml_file=SQLSERVER_YAML_FILENAME,
                  ag_services_yaml_file=AG_SERVICES_YAML_FILENAME):
      kconfig.load_kube_config()
      operator_deployed = deploy_operator(namespace, operator_yaml_file)
  
      if operator_deployed:
          log(LogLevel.ALL, "Successfully deployed mssql-operator")
          log(LogLevel.ALL)
  
          kubectl(["apply", "-f", sql_secrets_yaml_file])
  
          if pv_yaml_file:
              pvs_deployed = deploy_pv(namespace, sql_servers, pv_yaml_file)
              log(LogLevel.ALL, "Successfully deployed Persistent Volumes",
                  pvs_deployed)
              log(LogLevel.ALL)
  
          sql_pods_deployed = (deploy_sqlservers(namespace, sql_servers,
                                                 sqlserver_yaml_file))
          log(LogLevel.ALL, "Successfully deployed SQL pods", sql_pods_deployed)
          log(LogLevel.ALL)
  
          ag_services_deployed = (deploy_ag_services(namespace, ag_services,
                                                     ag_services_yaml_file))
  
          log(LogLevel.ALL, "Successfully deployed AG services",
              ag_services_deployed)
          log(LogLevel.ALL)
  
  
  def exit(exitcode):
      color = TERM_GREEN if exitcode == 0 else TERM_RED
      print(color, sys.argv[0], " exitcode: ", exitcode, TERM_END, sep="")
      sys.exit(exitcode)
  
  
  class ActionBase:
      def __init__(self, parser):
          self.parser = parser
          self.init_parser()
          self.parser.set_defaults(obj=self)
  
      def parse_args(self):
          args = self.parser.parse_args()
          return args
  
      def validate_args(self):
          return True
  
      def run(self, args, working_dir):
          pass
  
      def __str__(self):
          return self.parser.prog
  
      @classmethod
      def add_to_parser(cls, parser):
          return cls(parser)
  
  
  class Environment(Enum):
      ON_PREM = 0
      AKS = 1
  
      def __str__(self):
          return self.name
  
      @staticmethod
      def from_str_ignore_case(s):
          try:
              return Environment[s.upper()]
          except KeyError:
              raise ValueError()
  
      @property
      def service_annotations(self):
          # if self is Environment.AKS:
          #     return {
          #         "service.beta.kubernetes.io/azure-load-balancer-internal":
          #         "true"
          #     }
          # else:
          return None
  
  
  class DeployAction(ActionBase):
      def init_parser(self):
          parser = self.parser
          parser.description = (
              "Deploy SQL Server and k8s Agents in namespace(AG name)")
          parser.add_argument(
              "--ag",
              default=DEFAULT_AG_NAME,
              help="name of the Availability Group. Default=" + DEFAULT_AG_NAME)
          parser.add_argument(
              "-n",
              "--namespace",
              help=("name of the k8s namespace. " +
                    "Defaults to AG name if not specified."))
          parser.add_argument(
              "--dry-run",
              action="store_true",
              help="Perform a dry run and not apply the specs.")
          parser.add_argument(
              "-s",
              "--sql-servers",
              nargs="+",
              default=DEFAULT_SQLSERVER_NAMES,
              help="names of SQL Server instances(up to 5, separated by spaces)"
              "Default=" + str(DEFAULT_SQLSERVER_NAMES))
          default_sa_password = "SAPassword2018"
          parser.add_argument(
              "-p",
              "--sa-password",
              default=default_sa_password,
              help="SA Password. Default='{}'".format(default_sa_password))
          parser.add_argument(
              "-e",
              "--env",
              type=Environment.from_str_ignore_case,
              choices=list(Environment),
              default=Environment.ON_PREM)
  
          parser.add_argument(
                  "--skip-create-namespace",
                  action="store_true",
                  help="Skip namespace creation.")
  
      def validate_args(self, args):
          args.sql_servers.sort()
          sql_list = []
          for sql in args.sql_servers:
              if len(sql_list) > 0 and sql_list[-1] == sql:
                  log(LogLevel.WARNING, "duplicate SQL Server = ", sql)
              else:
                  sql_list.append(sql)
  
          if not (0 < len(sql_list) <= 5):
              log(LogLevel.ERROR, "Invalid number of sql_servers =", sql_list,
                  "size =", len(sql_list))
              log(LogLevel.Error, "Expected 0 < number of SQL Server <= 5")
              return False
  
          return True
  
      def run(self, args, working_dir):
          exitcode = 0
  
          log(LogLevel.INFO, "Working directory:", working_dir)
          log(LogLevel.INFO)
  
          operator_yaml_path = os.path.join(working_dir, OPERATOR_YAML_FILENAME)
          sql_secrets_yaml_path = os.path.join(working_dir,
                                               SQL_SECRETS_YAML_FILENAME)
          pv_yaml_path = None
          sqlservers_yaml_path = os.path.join(working_dir,
                                              SQLSERVER_YAML_FILENAME)
          ag_services_yaml_path = os.path.join(working_dir,
                                               AG_SERVICES_YAML_FILENAME)
  
          create_operator_yaml(
              args.namespace,
              DEFAULT_K8S_AGENTS_IMAGE,
              filepath=operator_yaml_path)
          create_sql_secrets_yaml(
              args.namespace, args.sa_password, filepath=sql_secrets_yaml_path)
  
          if args.env is Environment.ON_PREM:
              pv_yaml_path = os.path.join(working_dir, PV_YAML_FILENAME)
              pv_yaml_list = create_pv_yaml(
                  args.namespace,
                  args.sql_servers,
                  root_path=working_dir,
                  filepath=pv_yaml_path)
  
          create_sqlservers_yaml(
              args.env,
              args.namespace,
              args.sql_servers, [args.ag],
              args.sa_password,
              DEFAULT_K8S_AGENTS_IMAGE,
              filepath=sqlservers_yaml_path)
          load_balancer_services = create_ag_services_yaml(
                  args.env,
              args.namespace, args.ag, filepath=ag_services_yaml_path)
  
          if not args.dry_run:
              if not args.skip_create_namespace:
                  create_namespace(args.namespace)
  
              log(LogLevel.ALL)
              if args.env is Environment.ON_PREM:
                  for pv_yaml in pv_yaml_list:
                      log(LogLevel.ALL, "Persistent Volume:", pv_yaml.get_name(),
                          "mkdir:", pv_yaml.get_path())
                      os.mkdir(pv_yaml.get_path())
  
              expected_service_names = [
                  service_yaml.name for service_yaml in load_balancer_services
              ]
              apply_specs(args.namespace, args.sql_servers,
                          expected_service_names, operator_yaml_path,
                          sql_secrets_yaml_path, pv_yaml_path,
                          sqlservers_yaml_path, ag_services_yaml_path)
  
          paths = [
              operator_yaml_path, sql_secrets_yaml_path, pv_yaml_path,
              sqlservers_yaml_path, ag_services_yaml_path
          ]
          paths = [path for path in paths if path]
          return (exitcode, paths)
  
  
  class FailoverAction(ActionBase):
      def init_parser(self):
          parser = self.parser
          parser.description = "Perform a manual failover"
          parser.add_argument(
              "target_replica",
              help="name of target SQL Server replica to failover to")
          parser.add_argument(
              "--ag",
              default=DEFAULT_AG_NAME,
              help="name of the Availability Group. Default=" + DEFAULT_AG_NAME)
          parser.add_argument(
              "--namespace",
              help=("name of the k8s namespace. " +
                    "Defaults to AG name if not specified"))
          parser.add_argument(
              "--dry-run",
              action="store_true",
              help="Perform a dry run and NOT apply the specs")
          # parser.add_argument(
          #     "--force-failover-allow-data-loss",
          #     action="store_true",
          #     help="Perform a force failover allow data loss")
  
      def validate_args(self, args):
          """
          Validate the target replica exists?
          """
          return True
  
      def run(self, args, working_dir):
          exitcode = 0
          failover_yaml_path = os.path.join(working_dir, FAILOVER_YAML_FILENAME)
          failover_yaml = create_failover_yaml(
              namespace=args.namespace,
              agent_image=DEFAULT_K8S_AGENTS_IMAGE,
              ag=args.ag,
              target_replica=args.target_replica,
              filepath=failover_yaml_path)
          if not args.dry_run:
              log(LogLevel.ALL, "Creating failover job")
              proc = kubectl(["apply", "-f", failover_yaml_path])
              log(LogLevel.ALL, "Failover job created")
              exitcode = proc.returncode
          return (exitcode, [failover_yaml_path])
  
  
  def main():
      parent_parser = ArgumentParser(add_help=False)
      parent_parser.add_argument(
          "--verbose",
          "-v",
          default=LogLevel.ERROR,
          action="count",
          help="Verbosity of output")
      root_parser = ArgumentParser()
      subparsers = root_parser.add_subparsers(
          description="Actions on k8s agent", dest="subaction")
      DeployAction.add_to_parser(
          subparsers.add_parser(
              "deploy",
              help="Deploy a set of SQL Servers in an Availability Group",
              parents=[parent_parser]))
      FailoverAction.add_to_parser(
          subparsers.add_parser(
              "failover",
              help="Perform a failover to a target replica.",
              parents=[parent_parser]))
  
      args = root_parser.parse_args()
      # namespace defaults to AG name
      if args.namespace is None or args.namespace == "":
          args.namespace = args.ag
  
      log(LogLevel.INFO, "args:", vars(args))
  
      if not args.subaction:
          root_parser.print_help()
          exit(1)
  
      global log_verbosity
      log_verbosity = args.verbose
  
      if not args.obj.validate_args(args):
          print("action.validate_args failed:", args.obj, args)
          root_parser.print_help()
          exit(1)
  
      log(LogLevel.INFO, sys.argv[0], "Startup Time:", datetime.now(), "UTC:",
          datetime.utcnow())
      log(LogLevel.INFO)
  
      # create temp directory
      working_dir = mkdtemp(
          prefix="kube_agent_{}-".format(args.subaction),
          suffix=(args.namespace))
  
      # Run action here
      exitcode, spec_paths = args.obj.run(args, working_dir)
  
      log(LogLevel.INFO, sys.argv[0], "Completion Time:", datetime.now(), "UTC:",
          datetime.utcnow())
      log(LogLevel.INFO, "----------")
      if spec_paths:
          log(LogLevel.ALL, "Created the following specs:")
          for path in spec_paths:
              log(LogLevel.ALL, "\t", path)
          log(LogLevel.ALL)
          try:
              specs_file = "{}_{}_specs".format(args.subaction, args.namespace)
              log(LogLevel.ALL, "Wrote spec paths:", "'{}'".format(specs_file))
              with open(specs_file, "w") as f:
                  f.write("\n".join(spec_paths) + "\n")
          except IOError as ex:
              log(LogLevel.ALL, "Caught IOError:", ex, "writing to",
                  "'{}'".format(specs_file))
      exit(exitcode)
  
  
  if __name__ == "__main__":
      main()
  