Files
xc/aws/xcAwsInventory.py
Grigory Efimov bb7013f5ef added xcAwsInventory.py (#15)
* add .github/workflows/build.yml
add Makefile
go.mod: add govvv
go.sum: add govvv

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* remote/serial.go "panic: bytes: negative Repeat count"

* fix .github/workflows/build.yml

* fix .github/workflows/build.yml

* remove cache

* add .github/workflows/release.yml

* add .github/workflows/release.yml

* .github/workflows/build.yml enable ARM

* remote/serial.go fix syscall.Dup2 for ARM

* .github/workflows/release.yml enable ARM

* .github/workflows/release.yml delete zip

* add aws/xcAwsInventory.py

* aws/xcAwsInventory.py: rename iniFileName -> iniFilePath

* aws/xcAwsInventory.py fix python run

* README.md added section "remote environment settings"

* xcAwsInventory.py: fixed groupName and added "workgroups"
2023-05-23 11:36:37 +01:00

264 lines
9.3 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# base import {{
import os
from os.path import expanduser
import sys
import argparse
import yaml
import re
import logging
import inspect
# }}
# local import {{
import traceback
import subprocess
import json
# }}
# base {{
# default vars
scriptName = os.path.basename(sys.argv[0]).split('.')[0]
homeDir = expanduser("~")
defaultConfigFiles = [
'/etc/' + scriptName + '/config.yaml',
homeDir + '/.' + scriptName + '.yaml',
]
cfg = {
'logFile': '/var/log/' + scriptName + '/' + scriptName + '.log',
'logFile': 'stdout',
'logLevel': 'info',
'regions': [],
'iniFilePath': '~/xcdata.ini',
'tagForMainGroup': 'Name', # имя тега для "основной группы" хоста, все остальные запихнуться в "parent" или в "tags"
'tagForParentGroup': 'role', # имя тега для "родительской группы"
'workgroup': 'devops', # имя для дефолтной workgroup
}
# parse args
parser = argparse.ArgumentParser( description = '''
default config files: %s
''' % ', '.join(defaultConfigFiles),
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
'-c',
'--config',
help = 'path to config file',
)
args = parser.parse_args()
argConfigFile = args.config
# get settings
if argConfigFile:
if os.path.isfile(argConfigFile):
try:
with open(argConfigFile, 'r') as ymlfile:
cfg.update(yaml.load(ymlfile,Loader=yaml.Loader))
except Exception as e:
logging.error('failed load config file: "%s", error: "%s"', argConfigFile, e)
exit(1)
else:
for configFile in defaultConfigFiles:
if os.path.isfile(configFile):
try:
with open(configFile, 'r') as ymlfile:
try:
cfg.update(yaml.load(ymlfile,Loader=yaml.Loader))
except Exception as e:
logging.warning('skipping load load config file: "%s", error "%s"', configFile, e)
continue
except:
continue
# fix logDir
cfg['logDir'] = os.path.dirname(cfg['logFile'])
if cfg['logDir'] == '':
cfg['logDir'] = '.'
# }}
# defs
def runCmd(commands,communicate=True,stdoutJson=True):
""" запуск shell команд, вернёт хеш:
{
"stdout": stdout,
"stderr": stderr,
"exitCode": exitCode,
}
"""
defName = inspect.stack()[0][3]
logging.debug("%s: '%s'" % (defName,commands))
if communicate:
process = subprocess.Popen('/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out, err = process.communicate(commands.encode())
returnCode = process.returncode
try:
outFormatted = out.rstrip().decode("utf-8")
except:
outFormatted = out.decode("utf-8")
if stdoutJson:
try:
stdout = json.loads(outFormatted)
except Exception:
logging.error("%s: failed runCmd, cmd='%s', error='%s'" % (defName,commands,traceback.format_exc()))
return None
else:
stdout = outFormatted
return {
"stdout": stdout,
"stderr": err,
"exitCode": returnCode,
}
else:
subprocess.call(commands, shell=True)
return None
def getInstancesInfo(region):
defName = inspect.stack()[0][3]
instancesInfo = list()
describeInstances = runCmd("aws ec2 --region %s describe-instances" % region)['stdout']
reservations = describeInstances['Reservations']
for reservation in reservations:
instances = reservation['Instances']
for instance in instances:
if instance['State']['Name'] != 'running':
continue
info = {
'dc': instance['Placement']['AvailabilityZone'],
'tags': instance['Tags'],
'host': instance['PublicDnsName'],
}
if info not in instancesInfo:
instancesInfo.append(info)
return instancesInfo
if __name__ == "__main__":
# basic config {{
for dirPath in [
cfg['logDir'],
]:
try:
os.makedirs(dirPath)
except OSError:
if not os.path.isdir(dirPath):
raise
# выбор логлевела
if re.match(r"^(warn|warning)$", cfg['logLevel'], re.IGNORECASE):
logLevel = logging.WARNING
elif re.match(r"^debug$", cfg['logLevel'], re.IGNORECASE):
logLevel = logging.DEBUG
else:
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
logLevel = logging.INFO
if cfg['logFile'] == 'stdout':
logging.basicConfig(
level = logLevel,
format = '%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s',
datefmt = '%Y-%m-%dT%H:%M:%S',
)
else:
logging.basicConfig(
filename = cfg['logFile'],
level = logLevel,
format = '%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s',
datefmt = '%Y-%m-%dT%H:%M:%S',
)
# }}
defName = "main"
if cfg['regions']:
regions = cfg['regions']
else:
regions = runCmd("aws ec2 describe-regions --query 'Regions[].RegionName'")['stdout']
instancesInfoArray = list()
for region in regions:
instancesInfoArray = instancesInfoArray + getInstancesInfo(region)
logging.debug("%s: instancesInfoArray='%s'" % (defName,json.dumps(instancesInfoArray,indent=4)))
datacenters = list()
groups = list()
hosts = list()
for instanceInfo in instancesInfoArray:
# добавляем датацентр в общий список дц
if instanceInfo['dc'] not in datacenters:
datacenters.append(instanceInfo['dc'])
# формируем workgroups
# workgroups это обязательный параметр, без него не будут работать tags=
# берём дефолт из конфига
workgroups = [ cfg['workgroup'] ]
# проходиться по тегам и сортируем их по типам {{
mainGroupName = None
parentGroupName = None
tagsGroupNames = None
for tag in instanceInfo['tags']:
groupName = 'tag' + '_' + tag['Key'].replace('-','_') + '_' + tag['Value'].replace('-','_')
groupName = re.sub(r'\s+', '_', groupName)
if tag['Key'] == cfg['tagForMainGroup']:
# из этого тега делаем "основную группу"
mainGroupName = groupName
elif tag['Key'] == cfg['tagForParentGroup']:
# из этого тега делаем "родительскую группу"
parentGroupName = groupName
# сразу добавляем её в список "всех групп" как есть
if parentGroupName not in groups:
groups.append(parentGroupName)
else:
# если ни с чём не совпало, то закидываем их в сущность 'tags'
if tagsGroupNames:
tagsGroupNames = tagsGroupNames + ',' + groupName
else:
tagsGroupNames = groupName
# }}
# формируем строку для группу в секции [groups] {{
if mainGroupName:
groupLine = mainGroupName + ' wg=' + cfg['workgroup']
else:
# mainGroupName обязательный
logging.warning("%s: mainGroupName not found for host, instanceInfo='%s', skipping" % (defName,json.dumps(instanceInfo)))
continue
if parentGroupName:
groupLine = groupLine + ' parent=' + parentGroupName
if tagsGroupNames:
groupLine = groupLine + ' tags=' + tagsGroupNames
if groupLine not in groups:
groups.append(groupLine)
# }}
# формируем строку host в секции [hosts]
host = instanceInfo['host'] + ' group=' + mainGroupName + ' dc=' + instanceInfo['dc']
if host not in hosts:
hosts.append(host)
logging.debug("%s: datacenters='%s'" % (defName,datacenters))
logging.debug("%s: workgroups='%s'" % (defName,workgroups))
logging.debug("%s: groups='%s'" % (defName,groups))
logging.debug("%s: hosts='%s'" % (defName,hosts))
# генерим data file для инвентори {{
config = '[datacenters]'
for datacenter in datacenters:
config = config + '\n' + str(datacenter)
config = config + '\n\n' + '[workgroups]'
for workgroup in workgroups:
config = config + '\n' + str(workgroup)
config = config + '\n\n' + '[groups]'
for group in groups:
config = config + '\n' + str(group)
config = config + '\n\n' + '[hosts]'
for host in hosts:
config = config + '\n' + str(host)
config = config + '\n'
logging.debug("%s: config='%s'" % (defName,config))
with open(os.path.expanduser(cfg['iniFilePath']), 'w') as f:
f.write(config)
# }}