mirror of
https://github.com/kemko/xc.git
synced 2026-01-01 15:55:43 +03:00
* 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"
264 lines
9.3 KiB
Python
Executable File
264 lines
9.3 KiB
Python
Executable File
#!/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)
|
||
# }}
|