diff options
Diffstat (limited to 'inventory')
| -rwxr-xr-x | inventory/gce/hosts/gce.py | 32 | ||||
| -rw-r--r-- | inventory/multi_ec2.yaml.example | 32 | ||||
| -rwxr-xr-x | inventory/multi_inventory.py (renamed from inventory/multi_ec2.py) | 144 | ||||
| -rw-r--r-- | inventory/multi_inventory.yaml.example | 51 | 
4 files changed, 164 insertions, 95 deletions
| diff --git a/inventory/gce/hosts/gce.py b/inventory/gce/hosts/gce.py index 6ed12e011..99746cdbf 100755 --- a/inventory/gce/hosts/gce.py +++ b/inventory/gce/hosts/gce.py @@ -66,12 +66,22 @@ Examples:    $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"    Use the GCE inventory script to print out instance specific information -  $ plugins/inventory/gce.py --host my_instance +  $ contrib/inventory/gce.py --host my_instance  Author: Eric Johnson <erjohnso@google.com>  Version: 0.0.1  ''' +__requires__ = ['pycrypto>=2.6'] +try: +    import pkg_resources +except ImportError: +    # Use pkg_resources to find the correct versions of libraries and set +    # sys.path appropriately when there are multiversion installs.  We don't +    # fail here as there is code that better expresses the errors where the +    # library is used. +    pass +  USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"  USER_AGENT_VERSION="v1" @@ -102,9 +112,9 @@ class GceInventory(object):          # Just display data for specific host          if self.args.host: -            print self.json_format_dict(self.node_to_dict( +            print(self.json_format_dict(self.node_to_dict(                      self.get_instance(self.args.host)), -                    pretty=self.args.pretty) +                    pretty=self.args.pretty))              sys.exit(0)          # Otherwise, assume user wants all instances grouped @@ -120,7 +130,6 @@ class GceInventory(object):              os.path.dirname(os.path.realpath(__file__)), "gce.ini")          gce_ini_path = os.environ.get('GCE_INI_PATH', gce_ini_default_path) -          # Create a ConfigParser.          # This provides empty defaults to each key, so that environment          # variable configuration (as opposed to INI configuration) is able @@ -174,7 +183,6 @@ class GceInventory(object):          args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1])          kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project']) -                  # Retrieve and return the GCE driver.          gce = get_driver(Provider.GCE)(*args, **kwargs)          gce.connection.user_agent_append( @@ -213,8 +221,7 @@ class GceInventory(object):              'gce_image': inst.image,              'gce_machine_type': inst.size,              'gce_private_ip': inst.private_ips[0], -            # Hosts don't always have a public IP name -            #'gce_public_ip': inst.public_ips[0], +            'gce_public_ip': inst.public_ips[0] if len(inst.public_ips) >= 1 else None,              'gce_name': inst.name,              'gce_description': inst.extra['description'],              'gce_status': inst.extra['status'], @@ -222,15 +229,15 @@ class GceInventory(object):              'gce_tags': inst.extra['tags'],              'gce_metadata': md,              'gce_network': net, -            # Hosts don't always have a public IP name -            #'ansible_ssh_host': inst.public_ips[0] +            # Hosts don't have a public name, so we add an IP +            'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]          }      def get_instance(self, instance_name):          '''Gets details about a specific instance '''          try:              return self.driver.ex_get_node(instance_name) -        except Exception, e: +        except Exception as e:              return None      def group_instances(self): @@ -250,7 +257,10 @@ class GceInventory(object):              tags = node.extra['tags']              for t in tags: -                tag = 'tag_%s' % t +                if t.startswith('group-'): +                    tag = t[6:] +                else: +                    tag = 'tag_%s' % t                  if groups.has_key(tag): groups[tag].append(name)                  else: groups[tag] = [name] diff --git a/inventory/multi_ec2.yaml.example b/inventory/multi_ec2.yaml.example deleted file mode 100644 index bbd81ad20..000000000 --- a/inventory/multi_ec2.yaml.example +++ /dev/null @@ -1,32 +0,0 @@ -# multi ec2 inventory configs -# -cache_location: ~/.ansible/tmp/multi_ec2_inventory.cache - -accounts: -  - name: aws1 -    provider: aws/hosts/ec2.py -    provider_config: -      ec2: -        regions: all -        regions_exclude:  us-gov-west-1,cn-north-1 -        destination_variable: public_dns_name -        route53: False -        cache_path: ~/.ansible/tmp -        cache_max_age: 300 -        vpc_destination_variable: ip_address -    env_vars: -      AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX -      AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -    all_group: ec2 -    extra_vars: -      cloud: aws -      account: aws1 - -- name: aws2 -    provider: aws/hosts/ec2.py -    env_vars: -      AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX -      AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -      EC2_INI_PATH: /etc/ansible/ec2.ini - -cache_max_age: 60 diff --git a/inventory/multi_ec2.py b/inventory/multi_inventory.py index 98dde3f3c..354a8c10c 100755 --- a/inventory/multi_ec2.py +++ b/inventory/multi_inventory.py @@ -1,6 +1,6 @@  #!/usr/bin/env python2  ''' -    Fetch and combine multiple ec2 account settings into a single +    Fetch and combine multiple inventory account settings into a single      json hash.  '''  # vim: expandtab:tabstop=4:shiftwidth=4 @@ -15,13 +15,19 @@ import errno  import fcntl  import tempfile  import copy +from string import Template +import shutil -CONFIG_FILE_NAME = 'multi_ec2.yaml' -DEFAULT_CACHE_PATH = os.path.expanduser('~/.ansible/tmp/multi_ec2_inventory.cache') +CONFIG_FILE_NAME = 'multi_inventory.yaml' +DEFAULT_CACHE_PATH = os.path.expanduser('~/.ansible/tmp/multi_inventory.cache') -class MultiEc2(object): +class MultiInventoryException(Exception): +    '''Exceptions for MultiInventory class''' +    pass + +class MultiInventory(object):      ''' -       MultiEc2 class: +       MultiInventory class:              Opens a yaml config file and reads aws credentials.              Stores a json hash of resources in result.      ''' @@ -35,7 +41,7 @@ class MultiEc2(object):          self.cache_path = DEFAULT_CACHE_PATH          self.config = None -        self.all_ec2_results = {} +        self.all_inventory_results = {}          self.result = {}          self.file_path = os.path.join(os.path.dirname(os.path.realpath(__file__))) @@ -56,7 +62,7 @@ class MultiEc2(object):             cache is valid for the inventory.             if the cache is valid; return cache -           else the credentials are loaded from multi_ec2.yaml or from the env +           else the credentials are loaded from multi_inventory.yaml or from the env             and we attempt to get the inventory from the provider specified.          '''          # load yaml @@ -111,6 +117,10 @@ class MultiEc2(object):          with open(conf_file) as conf:              config = yaml.safe_load(conf) +        # Provide a check for unique account names +        if len(set([acc['name'] for acc in config['accounts']])) != len(config['accounts']): +            raise MultiInventoryException('Duplicate account names in config file') +          return config      def get_provider_tags(self, provider, env=None): @@ -136,23 +146,25 @@ class MultiEc2(object):          else:              cmds.append('--list') -        cmds.append('--refresh-cache') +        if 'aws' in provider.lower(): +            cmds.append('--refresh-cache')          return subprocess.Popen(cmds, stderr=subprocess.PIPE, \                                  stdout=subprocess.PIPE, env=env)      @staticmethod -    def generate_config(config_data): -        """Generate the ec2.ini file in as a secure temp file. -           Once generated, pass it to the ec2.py as an environment variable. +    def generate_config(provider_files): +        """Generate the provider_files in a temporary directory.          """ -        fildes, tmp_file_path = tempfile.mkstemp(prefix='multi_ec2.ini.') -        for section, values in config_data.items(): -            os.write(fildes, "[%s]\n" % section) -            for option, value  in values.items(): -                os.write(fildes, "%s = %s\n" % (option, value)) -        os.close(fildes) -        return tmp_file_path +        prefix = 'multi_inventory.' +        tmp_dir_path = tempfile.mkdtemp(prefix=prefix) +        for provider_file in provider_files: +            filedes = open(os.path.join(tmp_dir_path, provider_file['name']), 'w+') +            content = Template(provider_file['contents']).substitute(tmpdir=tmp_dir_path) +            filedes.write(content) +            filedes.close() + +        return tmp_dir_path      def run_provider(self):          '''Setup the provider call with proper variables @@ -160,13 +172,21 @@ class MultiEc2(object):          '''          try:              all_results = [] -            tmp_file_paths = [] +            tmp_dir_paths = []              processes = {}              for account in self.config['accounts']: -                env = account['env_vars'] -                if account.has_key('provider_config'): -                    tmp_file_paths.append(MultiEc2.generate_config(account['provider_config'])) -                    env['EC2_INI_PATH'] = tmp_file_paths[-1] +                tmp_dir = None +                if account.has_key('provider_files'): +                    tmp_dir = MultiInventory.generate_config(account['provider_files']) +                    tmp_dir_paths.append(tmp_dir) + +                # Update env vars after creating provider_config_files +                # so that we can grab the tmp_dir if it exists +                env = account.get('env_vars', {}) +                if env and tmp_dir: +                    for key, value in env.items(): +                        env[key] = Template(value).substitute(tmpdir=tmp_dir) +                  name = account['name']                  provider = account['provider']                  processes[name] = self.get_provider_tags(provider, env) @@ -182,9 +202,9 @@ class MultiEc2(object):                  })          finally: -            # Clean up the mkstemp file -            for tmp_file in tmp_file_paths: -                os.unlink(tmp_file) +            # Clean up the mkdtemp dirs +            for tmp_dir in tmp_dir_paths: +                shutil.rmtree(tmp_dir)          return all_results @@ -223,7 +243,7 @@ class MultiEc2(object):                                ]                      raise RuntimeError('\n'.join(err_msg).format(**result))                  else: -                    self.all_ec2_results[result['name']] = json.loads(result['out']) +                    self.all_inventory_results[result['name']] = json.loads(result['out'])              # Check if user wants extra vars in yaml by              # having hostvars and all_group defined @@ -231,29 +251,52 @@ class MultiEc2(object):                  self.apply_account_config(acc_config)              # Build results by merging all dictionaries -            values = self.all_ec2_results.values() +            values = self.all_inventory_results.values()              values.insert(0, self.result)              for result in  values: -                MultiEc2.merge_destructively(self.result, result) +                MultiInventory.merge_destructively(self.result, result) + +    def add_entry(self, data, keys, item): +        ''' Add an item to a dictionary with key notation a.b.c +            d = {'a': {'b': 'c'}}} +            keys = a.b +            item = c +        ''' +        if "." in keys: +            key, rest = keys.split(".", 1) +            if key not in data: +                data[key] = {} +            self.add_entry(data[key], rest, item) +        else: +            data[keys] = item + +    def get_entry(self, data, keys): +        ''' Get an item from a dictionary with key notation a.b.c +            d = {'a': {'b': 'c'}}} +            keys = a.b +            return c +        ''' +        if keys and "." in keys: +            key, rest = keys.split(".", 1) +            return self.get_entry(data[key], rest) +        else: +            return data.get(keys, None)      def apply_account_config(self, acc_config):          ''' Apply account config settings          ''' -        results = self.all_ec2_results[acc_config['name']] +        results = self.all_inventory_results[acc_config['name']] +        results['all_hosts'] = results['_meta']['hostvars'].keys()          # Update each hostvar with the newly desired key: value from extra_* -        for _extra in ['extra_groups', 'extra_vars']: +        for _extra in ['extra_vars', 'extra_groups']:              for new_var, value in acc_config.get(_extra, {}).items(): -                # Verify the account results look sane -                # by checking for these keys ('_meta' and 'hostvars' exist) -                if results.has_key('_meta') and results['_meta'].has_key('hostvars'): -                    for data in results['_meta']['hostvars'].values(): -                        data[str(new_var)] = str(value) +                for data in results['_meta']['hostvars'].values(): +                    self.add_entry(data, new_var, value)                  # Add this group -                if _extra == 'extra_groups' and results.has_key(acc_config['all_group']): -                    results["%s_%s" % (new_var, value)] = \ -                     copy.copy(results[acc_config['all_group']]) +                if _extra == 'extra_groups': +                    results["%s_%s" % (new_var, value)] = copy.copy(results['all_hosts'])          # Clone groups goes here          for to_name, from_name in acc_config.get('clone_groups', {}).items(): @@ -262,14 +305,11 @@ class MultiEc2(object):          # Clone vars goes here          for to_name, from_name in acc_config.get('clone_vars', {}).items(): -            # Verify the account results look sane -            # by checking for these keys ('_meta' and 'hostvars' exist) -            if results.has_key('_meta') and results['_meta'].has_key('hostvars'): -                for data in results['_meta']['hostvars'].values(): -                    data[str(to_name)] = data.get(str(from_name), 'nil') +            for data in results['_meta']['hostvars'].values(): +                self.add_entry(data, to_name, self.get_entry(data, from_name)) -        # store the results back into all_ec2_results -        self.all_ec2_results[acc_config['name']] = results +        # store the results back into all_inventory_results +        self.all_inventory_results[acc_config['name']] = results      @staticmethod      def merge_destructively(input_a, input_b): @@ -277,7 +317,7 @@ class MultiEc2(object):          for key in input_b:              if key in input_a:                  if isinstance(input_a[key], dict) and isinstance(input_b[key], dict): -                    MultiEc2.merge_destructively(input_a[key], input_b[key]) +                    MultiInventory.merge_destructively(input_a[key], input_b[key])                  elif input_a[key] == input_b[key]:                      pass # same leaf value                  # both lists so add each element in b to a if it does ! exist @@ -333,7 +373,7 @@ class MultiEc2(object):                  if exc.errno != errno.EEXIST or not os.path.isdir(path):                      raise -        json_data = MultiEc2.json_format_dict(self.result, True) +        json_data = MultiInventory.json_format_dict(self.result, True)          with open(self.cache_path, 'w') as cache:              try:                  fcntl.flock(cache, fcntl.LOCK_EX) @@ -369,7 +409,7 @@ class MultiEc2(object):  if __name__ == "__main__": -    MEC2 = MultiEc2() -    MEC2.parse_cli_args() -    MEC2.run() -    print MEC2.result_str() +    MI2 = MultiInventory() +    MI2.parse_cli_args() +    MI2.run() +    print MI2.result_str() diff --git a/inventory/multi_inventory.yaml.example b/inventory/multi_inventory.yaml.example new file mode 100644 index 000000000..0f0788d18 --- /dev/null +++ b/inventory/multi_inventory.yaml.example @@ -0,0 +1,51 @@ +# multi ec2 inventory configs +# +cache_location: ~/.ansible/tmp/multi_inventory.cache + +accounts: +  - name: aws1 +    provider: aws/ec2.py +    provider_files: +    - name: ec2.ini +      content: |- +        [ec2] +        regions = all +        regions_exclude =  us-gov-west-1,cn-north-1 +        destination_variable = public_dns_name +        route53 = False +        cache_path = ~/.ansible/tmp +        cache_max_age = 300 +        vpc_destination_variable = ip_address +    env_vars: +      AWS_ACCESS_KEY_ID: XXXXXXXXXXXXXXXXXXXX +      AWS_SECRET_ACCESS_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +      EC2_INI_PATH: ${tmpdir}/ec2.ini # we replace ${tmpdir} with the temporary directory that we've created for the provider. +    extra_vars: +      cloud: aws +      account: aws1 + +-   name: mygce +    extra_vars: +      cloud: gce +      account: gce1 +    env_vars: +      GCE_INI_PATH: ${tmpdir}/gce.ini # we replace ${tmpdir} with the temporary directory that we've created for the provider. +    provider: gce/gce.py +    provider_files: +    - name: priv_key.pem +      contents: |- +        -----BEGIN PRIVATE KEY----- +        yourprivatekeydatahere +        -----END PRIVATE KEY----- +    - name: gce.ini +      contents: |- +        [gce] +        gce_service_account_email_address = <uuid>@developer.gserviceaccount.com +        gce_service_account_pem_file_path = ${tmpdir}/priv_key.pem # we replace ${tmpdir} with the temporary directory that we've created for the provider. +        gce_project_id = gce-project +        zone = us-central1-a +        network = default +        gce_machine_type = n1-standard-2 +        gce_machine_image = rhel7 + +cache_max_age: 600 | 
