Example #1
0
class TestTerraform_lookup(unittest.TestCase):

    def setUp(self):
        with Replacer() as r:
            current_dir = os.path.dirname(os.path.abspath(__file__))
            fname = os.path.join(current_dir, 'terraform.tfstate')
            with open(fname) as f:
                tfstate = json.loads(f.read())
            get_mock = r('deployfish.terraform.Terraform._get_state_file_from_s3', Mock())
            get_mock.return_value = tfstate
            self.terraform = Terraform(YAML)

    def test_lookup(self):
        self.assertEqual(self.terraform.lookup('lookup1', {'environment': 'qa'}), 'foobar-proxy-qa')
        self.assertEqual(self.terraform.lookup('lookup1', {'environment': 'prod'}), 'foobar-proxy-prod')
Example #2
0
class Config(object):
    """
    This class reads our ``deployfish.yml`` file and handles the allowed
    variable substitutions in string values for service entries under the
    ``services:`` section.

    Allowed variable substitutions:

    * ``${terraform.<lookup key>}``:  If we have a ``terraform:`` section
      in our YAML, replace this with the terraform lookup value for
      ``<lookup key>``.

    * ``${env.<environment var>}```:  If the environment variable
      ``<environment var>`` exists in our environment, replace this with
      the value of that environment variable.
    """

    TERRAFORM_RE = re.compile('\$\{terraform.(?P<key>[A-Za-z0-9_]+)\}')
    ENVIRONMENT_RE = re.compile('\$\{env.(?P<key>.+)\}')

    def __init__(self,
                 filename='deployfish.yml',
                 env_file=None,
                 import_env=False,
                 interpolate=True,
                 tfe_token=None,
                 use_aws_section=True,
                 raw_config=None,
                 boto3_session=None):
        #Load a raw config if it was provided
        if raw_config:
            self.__raw = raw_config
        else:
            self.__raw = self.load_config(filename)

        # Setup our boto3_session here because we might need it when retrieving
        # the terraform file from S3

        if use_aws_section:
            build_boto3_session(self, boto3_session_override=boto3_session)
        else:
            build_boto3_session(boto3_session_override=boto3_session)
        self.import_env = import_env
        self.env_file = env_file
        self.tfe_token = tfe_token
        self.environ = None
        self.terraform = None
        if interpolate:
            if 'terraform' in self.__raw:
                self.replace_terraform()
                if 'workspace' in self.__raw['terraform']:
                    self.terraform = TerraformE(yml=self.__raw['terraform'],
                                                api_token=self.tfe_token)
                else:
                    self.terraform = Terraform(yml=self.__raw['terraform'])
            else:
                self.terraform = None
            self.replace()

    @property
    def raw(self):
        return self.__raw

    def load_config(self, filename):
        """
        Read our deployfish.yml file from disk and return it as parsed YAML.

        :param filename: the path to our deployfish.yml file
        :type filename: string

        :rtype: dict
        """
        with open(filename) as f:
            return yaml.load(f)

    def load_env_file(self, env_file):
        if env_file and os.path.isfile(env_file):
            lines = []
            with open(env_file) as f:
                lines = f.readlines()
                # Strip the comments and empty lines
                lines = [
                    x.strip() for x in lines
                    if x.strip() and not x.strip().startswith("#")
                ]
            for line in lines:
                # split on the first "="
                parm = str.split(line, '=', 1)
                if len(parm) == 2:
                    key = parm[0]
                    value = parm[1]
                    self.environ[key] = value

    def load_environ(self):
        for key in os.environ.keys():
            self.environ[key] = os.getenv(key)

    def replace(self):
        """
        Do variable replacement in all strings in the YAML data for
        each listed services under the ``services:`` section.
        """
        for service in self.__raw['services']:
            replacers = {
                'environment': service.get('environment', 'prod'),
                'service-name': service['name'],
                'cluster-name': service['cluster']
            }
            self.environ = {}
            if 'env_file' in service:
                self.load_env_file(service['env_file'])
            if self.env_file:
                self.load_env_file(self.env_file)
            if self.import_env:
                self.load_environ()
            # else:
            #     self.environ = os.environ

            self.__do_dict(service, replacers)

    def replace_terraform(self):
        for service in self.__raw['services']:
            replacers = {
                'environment': service.get('environment', 'prod'),
                'service-name': service['name'],
                'cluster-name': service['cluster']
            }
            if 'workspace' in self.__raw['terraform']:
                self.__raw['terraform']['workspace'] = self.__raw['terraform'][
                    'workspace'].format(**replacers)
            else:
                self.__raw['terraform']['statefile'] = self.__raw['terraform'][
                    'statefile'].format(**replacers)

    def __replace(self, raw, key, value, replacers):
        if isinstance(value, dict):
            self.__do_dict(value, replacers)
        elif any(isinstance(value, t) for t in (list, tuple)):
            self.__do_list(value, replacers)
        elif isinstance(value, str):
            self.__do_string(raw, key, value, replacers)

    def __env_replace(self, key, replacers):
        envkey = key.format(**replacers).upper().replace('-', '_')
        value = self.environ.get(envkey, envkey)
        return value

    def __do_string(self, raw, key, value, replacers):
        if self.terraform:
            m = self.TERRAFORM_RE.search(value)
            if m:
                tfvalue = self.terraform.lookup(m.group('key'), replacers)
                if isinstance(tfvalue, (list, tuple, dict)):
                    raw[key] = tfvalue
                    self.__replace(raw, key, tfvalue, replacers)
                    return
                raw[key] = self.TERRAFORM_RE.sub(tfvalue, value)
                value = raw[key]
        m = self.ENVIRONMENT_RE.search(value)
        if m:
            # TODO: using __env_replace here is risky because of {service-name}
            # and {cluster-name}.  If these have a `-` or a '.' in them, the
            # environment variable name will be treated strangely by the shell
            # or just rejected.
            #
            # In each replacer, we should be replacing [.- ] with _ and then
            # uppercasing the result.
            raw[key] = self.ENVIRONMENT_RE.sub(
                self.__env_replace(m.group('key'), replacers), value)

    def __do_list(self, raw, replacers):
        for i, value in enumerate(raw):
            self.__replace(raw, i, value, replacers)

    def __do_dict(self, raw, replacers):
        for key, value in raw.items():
            self.__replace(raw, key, value, replacers)

    def get_service(self, service_name):
        """
        Get the full config for the service named ``service_name`` from our
        parsed YAML file.

        :param service_name: the name of an ECS service listed in our YAML
                             file under the ``services:`` section
        :type service_name: string

        :rtype: dict
        """
        for service in self.__raw['services']:
            if service['name'] == service_name:
                return service
            if 'environment' in service and service[
                    'environment'] == service_name:
                return service
        raise KeyError

    def get_section(self, section):
        """
        Return the contents of a whole top level section from our deployfish.yml file.

        :param section: The name of the top level section to search
        :type section: string

        :rtype: dict
        """
        return self.__raw[section]

    def get_section_item(self, section, item_name):
        """
        Get an item from a top level section with 'name' equal to ``item_name``
        from our parsed ``deployfish.yml`` file.

        :param section: The name of the top level section to search
        :type section: string

        :param item: The name of the instance of the section

        :rtype: dict
        """
        if section in self.__raw:
            for item in self.__raw[section]:
                if item['name'] == item_name:
                    return item
        raise KeyError
Example #3
0
class Config(object):
    """
    This class reads our ``deployfish.yml`` file and handles the allowed
    variable substitutions in string values for service entries under the
    ``services:`` section.

    Allowed variable substitutions:

    * ``${terraform.<lookup key>}``:  If we have a ``terraform:`` section
      in our YAML, replace this with the terraform lookup value for
      ``<lookup key>``.

    * ``${env.<environment var>}```:  If the environment variable
      ``<environment var>`` exists in our environment, replace this with
      the value of that environment variable.
    """

    TERRAFORM_RE = re.compile('\$\{terraform.(?P<key>[A-Za-z0-9_]+)\}')
    ENVIRONMENT_RE = re.compile('\$\{env.(?P<key>.+)\}$')

    def __init__(self,
                 filename='deployfish.yml',
                 env_file=None,
                 interpolate=True):
        self.__raw = self.load_config(filename)
        self.env_file = env_file
        self.environ = None
        self.terraform = None
        if interpolate:
            if 'terraform' in self.__raw:
                self.terraform = Terraform(yml=self.__raw['terraform'])
            else:
                self.terraform = None
            self.replace()

    def load_config(self, filename):
        with open(filename) as f:
            return yaml.load(f)

    def load_env_file(self, env_file):
        if env_file and os.path.isfile(env_file):
            lines = []
            with open(env_file) as f:
                lines = f.readlines()
                # Strip the comments and empty lines
                lines = [
                    x.strip() for x in lines
                    if x.strip() and not x.strip().startswith("#")
                ]
            for line in lines:
                # split on the first "="
                parm = str.split(line, '=', 1)
                if len(parm) == 2:
                    key = parm[0]
                    value = parm[1]
                    self.environ[key] = value

    def replace(self):
        """
        Do variable replacement in all strings in the YAML data for
        each listed services under the ``services:`` section.
        """
        for service in self.__raw['services']:
            replacers = {
                'environment': service.get('environment', 'prod'),
                'service-name': service['name'],
                'cluster-name': service['cluster']
            }
            self.environ = {}
            if 'env_file' in service:
                self.load_env_file(service['env_file'])
            if self.env_file:
                self.load_env_file(self.env_file)

            self.__do_dict(service, replacers)

    def __replace(self, raw, key, value, replacers):
        if isinstance(value, dict):
            self.__do_dict(value, replacers)
        elif any(isinstance(value, t) for t in (list, tuple)):
            self.__do_list(value, replacers)
        elif isinstance(value, str):
            self.__do_string(raw, key, value, replacers)

    def __env_replace(self, key, replacers):
        envkey = key.format(**replacers).upper().replace('-', '_')
        value = self.environ.get(envkey, envkey)
        return value

    def __do_string(self, raw, key, value, replacers):
        if self.terraform:
            m = self.TERRAFORM_RE.search(value)
            if m:
                raw[key] = self.TERRAFORM_RE.sub(
                    self.terraform.lookup(m.group('key'), replacers), value)
                value = raw[key]
        m = self.ENVIRONMENT_RE.search(value)
        if m:
            # TODO: using __env_replace here is risky because of {service-name}
            # and {cluster-name}.  If these have a `-` or a '.' in them, the
            # environment variable name will be treated strangely by the shell
            # or just rejected.
            #
            # In each replacer, we should be replacing [.- ] with _ and then
            # uppercasing the result.
            raw[key] = self.ENVIRONMENT_RE.sub(
                self.__env_replace(m.group('key'), replacers), value)

    def __do_list(self, raw, replacers):
        for i, value in enumerate(raw):
            self.__replace(raw, i, value, replacers)

    def __do_dict(self, raw, replacers):
        for key, value in raw.items():
            self.__replace(raw, key, value, replacers)

    def get_service(self, service_name):
        """
        Get the full config for the service named ``service_name`` from our
        parsed YAML file.

        :param service_name: the name of an ECS service listed in our YAML file under the ``services:`` section
        :type service_name: string

        :rtype: dict
        """
        for service in self.__raw['services']:
            if service['name'] == service_name:
                return service
            if 'environment' in service and service[
                    'environment'] == service_name:
                return service
        raise KeyError