Remove cfntools and jeos
These files are now available in the heat-jeos repository. Change-Id: I392e7443348a31e8454ae14d957b0b54560c2ec3 Signed-off-by: Steven Dake <sdake@redhat.com>
This commit is contained in:
parent
4b5c3fee7b
commit
4c359db1b0
14
MANIFEST.in
14
MANIFEST.in
@ -7,20 +7,6 @@ include MANIFEST.in pylintrc
|
||||
include openstack-common.conf
|
||||
include babel.cfg
|
||||
graft templates
|
||||
include heat/jeos/F16-i386-gold-jeos.tdl
|
||||
include heat/jeos/F17-i386-gold-jeos.tdl
|
||||
include heat/jeos/F16-i386-cfntools-jeos.tdl
|
||||
include heat/jeos/F17-i386-cfntools-jeos.tdl
|
||||
include heat/jeos/F16-x86_64-gold-jeos.tdl
|
||||
include heat/jeos/F17-x86_64-gold-jeos.tdl
|
||||
include heat/jeos/F16-x86_64-cfntools-jeos.tdl
|
||||
include heat/jeos/F17-x86_64-cfntools-jeos.tdl
|
||||
include heat/jeos/U10-amd64-cfntools-jeos.tdl
|
||||
include heat/cfntools/cfn-init
|
||||
include heat/cfntools/cfn-hup
|
||||
include heat/cfntools/cfn-signal
|
||||
include heat/cfntools/cfn-get-metadata
|
||||
include heat/cfntools/cfn-push-stats
|
||||
include heat/cloudinit/config
|
||||
include heat/cloudinit/part-handler.py
|
||||
include heat/db/sqlalchemy/migrate_repo/migrate.cfg
|
||||
|
25
bin/heat
25
bin/heat
@ -270,25 +270,6 @@ def stack_list(options, arguments):
|
||||
print json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@utils.catch_error('jeos-create')
|
||||
def jeos_create(options, arguments):
|
||||
'''
|
||||
Create a new JEOS (Just Enough Operating System) image.
|
||||
|
||||
Usage: heat jeos-create <distribution> <architecture> <image type>
|
||||
|
||||
Distribution: Distribution such as 'F16', 'F17', 'U10', 'D6'.
|
||||
Architecture: Architecture such as 'i386' 'i686' or 'x86_64'.
|
||||
Image Type: Image type such as 'gold' or 'cfntools'.
|
||||
'gold' is a basic gold JEOS.
|
||||
'cfntools' contains the cfntools helper scripts.
|
||||
|
||||
The command must be run as root in order for libvirt to have permissions
|
||||
to create virtual machines and read the raw DVDs.
|
||||
'''
|
||||
utils.jeos_create(options, arguments, jeos_path, cfntools_path)
|
||||
|
||||
|
||||
def get_client(options):
|
||||
"""
|
||||
Returns a new client object to a heat server
|
||||
@ -466,9 +447,7 @@ def lookup_command(parser, command_name):
|
||||
'event-list': stack_events_list,
|
||||
'validate': template_validate,
|
||||
'gettemplate': get_template,
|
||||
'describe': stack_describe,
|
||||
'jeos_create': jeos_create, # DEPRECATED
|
||||
'jeos-create': jeos_create}
|
||||
'describe': stack_describe}
|
||||
|
||||
commands = {}
|
||||
for command_set in (base_commands, stack_commands):
|
||||
@ -507,8 +486,6 @@ Commands:
|
||||
|
||||
validate Validate a template
|
||||
|
||||
jeos-create Create a JEOS image
|
||||
|
||||
event-list List events for a stack
|
||||
|
||||
"""
|
||||
|
@ -121,29 +121,27 @@ This is for Heat to associate with the virtual machines.
|
||||
|
||||
nova keypair-add --pub_key ~/.ssh/id_rsa.pub ${USER}_key
|
||||
|
||||
Install Oz
|
||||
----------
|
||||
|
||||
Verify that Oz_ is installed ::
|
||||
|
||||
sudo yum -y install oz
|
||||
|
||||
Oz is used below to create the JEOS.
|
||||
|
||||
.. _Oz: http://aeolusproject.org/oz.html
|
||||
|
||||
Create a JEOS
|
||||
-------------
|
||||
Download and install heat_jeos via git
|
||||
--------------------------------------
|
||||
Download heat_jeos via git
|
||||
|
||||
::
|
||||
git clone git://github.com/heat-api/heat-jeos.git
|
||||
cd heat-jeos
|
||||
setup.py install
|
||||
|
||||
sudo -E heat -y jeos-create F16 x86_64 cfntools
|
||||
Create a JEOS with heat_jeos tools
|
||||
----------------------------------
|
||||
::
|
||||
|
||||
Note: The ``-E`` option to ``sudo`` preserves the environment, specifically the keystone credentials, when ``jeos-create`` is run as root.
|
||||
sudo -E heat-jeos -y create F16 x86_64 cfntools
|
||||
|
||||
Note: ``jeos-create`` must be run as root in order to create the cfntools disk image.
|
||||
Note: The ``-E`` option to ``sudo`` preserves the environment, specifically the keystone credentials, when ``heat-jeos`` is run as root.
|
||||
|
||||
Note: If you want to enable debugging output from Oz, add '``-d``' (debugging) to the ``jeos-create`` command.
|
||||
Note: ``heat-jeos`` must be run as root in order to create the cfntools disk image.
|
||||
|
||||
Note: If you want to enable debugging output from Oz, add '``-d``' (debugging) to the ``heat-jeos`` command.
|
||||
|
||||
Verify JEOS registration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1,13 +0,0 @@
|
||||
There are several bootstrap methods for cloudformations:
|
||||
1. Create image with application ready to go
|
||||
2. Use cloud-init to run a startup script passed as userdata to the nova
|
||||
server create
|
||||
3. Use the CloudFormation instance helper scripts
|
||||
|
||||
This directory contains files required for choice #3.
|
||||
|
||||
cfn-init - Reads the AWS::CloudFormation::Init for the instance resource,
|
||||
installs packages, and starts services
|
||||
cfn-signal - Waits for an application to be ready before continuing, ie:
|
||||
supporting the WaitCondition feature
|
||||
cfn-hup - Handle updates from the UpdateStack CloudFormation API call
|
@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn-get-metadata CloudFormation functionality
|
||||
"""
|
||||
import argparse
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
if os.path.exists('/opt/aws/bin'):
|
||||
sys.path.insert(0, '/opt/aws/bin')
|
||||
from cfn_helper import *
|
||||
else:
|
||||
from heat.cfntools.cfn_helper import *
|
||||
|
||||
description = " "
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('-s', '--stack',
|
||||
dest="stack_name",
|
||||
help="A Heat stack name",
|
||||
required=True)
|
||||
parser.add_argument('-r', '--resource',
|
||||
dest="logical_resource_id",
|
||||
help="A Heat logical resource ID",
|
||||
required=True)
|
||||
parser.add_argument('--access-key',
|
||||
dest="access_key",
|
||||
help="A Keystone access key",
|
||||
required=False)
|
||||
parser.add_argument('--secret-key',
|
||||
dest="secret_key",
|
||||
help="A Keystone secret key",
|
||||
required=False)
|
||||
parser.add_argument('--region',
|
||||
dest="region",
|
||||
help="Openstack region",
|
||||
required=False)
|
||||
parser.add_argument('--credential-file',
|
||||
dest="credential_file",
|
||||
help="credential-file",
|
||||
required=False)
|
||||
parser.add_argument('-u', '--url',
|
||||
dest="url",
|
||||
help="service url",
|
||||
required=False)
|
||||
parser.add_argument('-k', '--key',
|
||||
dest="key",
|
||||
help="key",
|
||||
required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.stack_name:
|
||||
print 'The Stack name must not be empty.'
|
||||
exit(1)
|
||||
|
||||
if not args.logical_resource_id:
|
||||
print 'The Resource ID must not be empty'
|
||||
exit(1)
|
||||
|
||||
log_format = '%(levelname)s [%(asctime)s] %(message)s'
|
||||
logging.basicConfig(format=log_format, level=logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger('cfn-get-metadata')
|
||||
log_file_name = "/var/log/cfn-get-metadata.log"
|
||||
file_handler = logging.FileHandler(log_file_name)
|
||||
file_handler.setFormatter(logging.Formatter(log_format))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
metadata = Metadata(args.stack_name,
|
||||
args.logical_resource_id,
|
||||
access_key=args.access_key,
|
||||
secret_key=args.secret_key,
|
||||
region=args.region)
|
||||
metadata.retrieve()
|
||||
print str(metadata)
|
@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn-hup CloudFormation functionality
|
||||
"""
|
||||
import argparse
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
if os.path.exists('/opt/aws/bin'):
|
||||
sys.path.insert(0, '/opt/aws/bin')
|
||||
from cfn_helper import *
|
||||
else:
|
||||
from heat.cfntools.cfn_helper import *
|
||||
|
||||
description = " "
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('-c', '--config',
|
||||
dest="config_dir",
|
||||
help="Hook Config Directory",
|
||||
required=False,
|
||||
default='/etc/cfn/hooks.d')
|
||||
parser.add_argument('-f', '--no-daemon',
|
||||
dest="no_deamon",
|
||||
action="store_true",
|
||||
help="Do not run as a deamon",
|
||||
required=False)
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action="store_true",
|
||||
dest="verbose",
|
||||
help="Verbose logging",
|
||||
required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
log_format = '%(levelname)s [%(asctime)s] %(message)s'
|
||||
if args.verbose:
|
||||
logging.basicConfig(format=log_format, level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(format=log_format, level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger('cfntools')
|
||||
log_file_name = "/var/log/cfn-hup.log"
|
||||
file_handler = logging.FileHandler(log_file_name)
|
||||
file_handler.setFormatter(logging.Formatter(log_format))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
main_conf_path = '/etc/cfn/cfn-hup.conf'
|
||||
try:
|
||||
main_config_file = open(main_conf_path)
|
||||
except IOError as exc:
|
||||
logger.error('Could not open main configuration at %s' % main_conf_path)
|
||||
exit(1)
|
||||
|
||||
config_files = []
|
||||
hooks_conf_path = '/etc/cfn/hooks.conf'
|
||||
if os.path.exists(hooks_conf_path):
|
||||
try:
|
||||
config_files.append(open(hooks_conf_path))
|
||||
except IOError as exc:
|
||||
logger.exception(exc)
|
||||
|
||||
if args.config_dir and os.path.exists(args.config_dir):
|
||||
try:
|
||||
for f in os.listdir(args.config_dir):
|
||||
config_files.append(open(os.path.join(args.config_dir, f)))
|
||||
|
||||
except OSError as exc:
|
||||
logger.exception(exc)
|
||||
|
||||
if not config_files:
|
||||
logger.error('No hook files found at %s or %s' % (hooks_conf_path,
|
||||
args.config_dir))
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
mainconfig = HupConfig([main_config_file] + config_files)
|
||||
except Exception as ex:
|
||||
logger.error('Cannot load configuration: %s' % str(ex))
|
||||
exit(1)
|
||||
|
||||
if not mainconfig.unique_resources_get():
|
||||
logger.error('No hooks were found. Add some to %s or %s' % (hooks_conf_path,
|
||||
args.config_dir))
|
||||
exit(1)
|
||||
|
||||
|
||||
for r in mainconfig.unique_resources_get():
|
||||
print r
|
||||
metadata = Metadata(mainconfig.stack,
|
||||
r,
|
||||
credentials_file=mainconfig.credential_file,
|
||||
region=mainconfig.region)
|
||||
metadata.retrieve()
|
||||
try:
|
||||
metadata.cfn_hup(mainconfig.hooks)
|
||||
except Exception as e:
|
||||
logger.exception("Error processing metadata")
|
||||
exit(1)
|
@ -1,74 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn-init CloudFormation functionality
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
if os.path.exists('/opt/aws/bin'):
|
||||
sys.path.insert(0, '/opt/aws/bin')
|
||||
from cfn_helper import *
|
||||
else:
|
||||
from heat.cfntools.cfn_helper import *
|
||||
|
||||
description = " "
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('-s', '--stack',
|
||||
dest="stack_name",
|
||||
help="A Heat stack name",
|
||||
required=False)
|
||||
parser.add_argument('-r', '--resource',
|
||||
dest="logical_resource_id",
|
||||
help="A Heat logical resource ID",
|
||||
required=False)
|
||||
parser.add_argument('--access-key',
|
||||
dest="access_key",
|
||||
help="A Keystone access key",
|
||||
required=False)
|
||||
parser.add_argument('--secret-key',
|
||||
dest="secret_key",
|
||||
help="A Keystone secret key",
|
||||
required=False)
|
||||
parser.add_argument('--region',
|
||||
dest="region",
|
||||
help="Openstack region",
|
||||
required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
log_format = '%(levelname)s [%(asctime)s] %(message)s'
|
||||
logging.basicConfig(format=log_format, level=logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger('cfn-init')
|
||||
log_file_name = "/var/log/cfn-init.log"
|
||||
file_handler = logging.FileHandler(log_file_name)
|
||||
file_handler.setFormatter(logging.Formatter(log_format))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
metadata = Metadata(args.stack_name,
|
||||
args.logical_resource_id,
|
||||
access_key=args.access_key,
|
||||
secret_key=args.secret_key,
|
||||
region=args.region)
|
||||
metadata.retrieve()
|
||||
try:
|
||||
metadata.cfn_init()
|
||||
except Exception as e:
|
||||
logger.exception("Error processing metadata")
|
||||
exit(1)
|
@ -1,169 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn-signal CloudFormation functionality
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
|
||||
log_format = '%(levelname)s [%(asctime)s] %(message)s'
|
||||
logging.basicConfig(format=log_format, level=logging.DEBUG)
|
||||
logger = logging.getLogger('cfn-push-stats')
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
logger.warn("psutil not available. If you want process and memory "
|
||||
"statistics, you need to install it.")
|
||||
#
|
||||
# --aws-credential-file=PATH Specifies the location of the file with AWS
|
||||
# credentials.
|
||||
# --aws-access-key-id=VALUE Specifies the AWS access key ID to use to
|
||||
# identify the caller.
|
||||
# --aws-secret-key=VALUE Specifies the AWS secret key to use to sign
|
||||
# the request.
|
||||
# --from-cron Specifies that this script is running from cron.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# To perform a simple test run without posting data to Amazon CloudWatch
|
||||
#
|
||||
# ./mon-put-instance-data.pl --mem-util --verify --verbose
|
||||
#
|
||||
# To set a five-minute cron schedule to report memory and disk space utilization to CloudWatch
|
||||
#
|
||||
# */5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl --mem-util --disk-space-util --disk-path=/ --from-cron
|
||||
#
|
||||
|
||||
if os.path.exists('/opt/aws/bin'):
|
||||
sys.path.insert(0, '/opt/aws/bin')
|
||||
from cfn_helper import *
|
||||
else:
|
||||
from heat.cfntools.cfn_helper import *
|
||||
|
||||
KILO = 1024
|
||||
MEGA = 1048576
|
||||
GIGA = 1073741824
|
||||
unit_map = {'bytes': 1,
|
||||
'kilobytes': KILO,
|
||||
'megabytes': MEGA,
|
||||
'gigabytes': GIGA}
|
||||
|
||||
description = " "
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('-v', '--verbose', action="store_true",
|
||||
help="Verbose logging", required=False)
|
||||
parser.add_argument('--service-failure', required=False, action="store_true",
|
||||
help='Reports a service falure.')
|
||||
parser.add_argument('--mem-util', required=False, action="store_true",
|
||||
help='Reports memory utilization in percentages.')
|
||||
parser.add_argument('--mem-used', required=False, action="store_true",
|
||||
help='Reports memory used (excluding cache and buffers) in megabytes.')
|
||||
parser.add_argument('--mem-avail', required=False, action="store_true",
|
||||
help='Reports available memory (including cache and buffers) in megabytes.')
|
||||
parser.add_argument('--swap-util', required=False, action="store_true",
|
||||
help='Reports swap utilization in percentages.')
|
||||
parser.add_argument('--swap-used', required=False, action="store_true",
|
||||
help='Reports allocated swap space in megabytes.')
|
||||
parser.add_argument('--disk-space-util', required=False, action="store_true",
|
||||
help='Reports disk space utilization in percentages.')
|
||||
parser.add_argument('--disk-space-used', required=False, action="store_true",
|
||||
help='Reports allocated disk space in gigabytes.')
|
||||
parser.add_argument('--disk-space-avail',required=False, action="store_true",
|
||||
help='Reports available disk space in gigabytes.')
|
||||
parser.add_argument('--memory-units', required=False, default='megabytes',
|
||||
help='Specifies units for memory metrics.')
|
||||
parser.add_argument('--disk-units', required=False, default='megabytes',
|
||||
help='Specifies units for disk metrics.')
|
||||
parser.add_argument('--disk-path', required=False, default='/',
|
||||
help='Selects the disk by the path on which to report.')
|
||||
parser.add_argument('--watch', required=True,
|
||||
help='the name of the watch to post to.')
|
||||
args = parser.parse_args()
|
||||
|
||||
data = {'Namespace': 'system/linux'}
|
||||
|
||||
# service failure
|
||||
# ===============
|
||||
if args.service_failure:
|
||||
data['ServiceFailure'] = {
|
||||
'Value': 1,
|
||||
'Units': 'Counter'}
|
||||
|
||||
# memory space
|
||||
# ==========
|
||||
if args.mem_util or args.mem_used or args.mem_avail:
|
||||
mem = psutil.phymem_usage()
|
||||
if args.mem_util:
|
||||
data['MemoryUtilization'] = {
|
||||
'Value': mem.percent,
|
||||
'Units': 'Percent'}
|
||||
if args.mem_used:
|
||||
data['MemoryUsed'] = {
|
||||
'Value': mem.used / unit_map[args.memory_units],
|
||||
'Units': args.memory_units}
|
||||
if args.mem_avail:
|
||||
data['MemoryAvailable'] = {
|
||||
'Value': mem.free / unit_map[args.memory_units],
|
||||
'Units': args.memory_units}
|
||||
|
||||
# swap space
|
||||
# ==========
|
||||
if args.swap_util or args.swap_used:
|
||||
swap = psutil.virtmem_usage()
|
||||
if args.swap_util:
|
||||
data['SwapUtilization'] = {
|
||||
'Value': swap.percent,
|
||||
'Units': 'Percent'}
|
||||
if args.swap_used:
|
||||
data['SwapUsed'] = {
|
||||
'Value': swap.used / unit_map[args.memory_units],
|
||||
'Units': args.memory_units}
|
||||
|
||||
# disk space
|
||||
# ==========
|
||||
if args.disk_space_util or args.disk_space_used or args.disk_space_avail:
|
||||
disk = psutil.disk_usage(args.disk_path)
|
||||
if args.disk_space_util:
|
||||
data['DiskSpaceUtilization'] = {
|
||||
'Value': disk.percent,
|
||||
'Units': 'Percent'}
|
||||
if args.disk_space_used:
|
||||
data['DiskSpaceUsed'] = {
|
||||
'Value': disk.used / unit_map[args.disk_units],
|
||||
'Units': args.disk_units}
|
||||
if args.disk_space_avail:
|
||||
data['DiskSpaceAvailable'] = {
|
||||
'Value': disk.free / unit_map[args.disk_units],
|
||||
'Units': args.disk_units}
|
||||
|
||||
logger.info(str(data))
|
||||
|
||||
server_url = metadata_server_url()
|
||||
if not server_url:
|
||||
logger.error('can not get the metadata_server_url')
|
||||
exit(1)
|
||||
url = '%sstats/%s/data/' % (server_url, args.watch)
|
||||
|
||||
cmd_str = "curl -X POST -H \'Content-Type:\' --data-binary \'%s\' %s" % \
|
||||
(json.dumps(data), url)
|
||||
|
||||
cmd = CommandRunner(cmd_str)
|
||||
cmd.run()
|
||||
logger.info(cmd.stdout)
|
@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn-signal CloudFormation functionality
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
if os.path.exists('/opt/aws/bin'):
|
||||
sys.path.insert(0, '/opt/aws/bin')
|
||||
from cfn_helper import *
|
||||
else:
|
||||
from heat.cfntools.cfn_helper import *
|
||||
|
||||
|
||||
description = " "
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument('-s', '--success',
|
||||
dest="success",
|
||||
help="signal status to report",
|
||||
default='true',
|
||||
required=False)
|
||||
parser.add_argument('-r', '--reason',
|
||||
dest="reason",
|
||||
help="The reason for the failure",
|
||||
default="Configuration Complete",
|
||||
required=False)
|
||||
parser.add_argument('--data',
|
||||
dest="data",
|
||||
default="Application has completed configuration.",
|
||||
help="The data to send",
|
||||
required=False)
|
||||
parser.add_argument('-i', '--id',
|
||||
dest="unique_id",
|
||||
help="the unique id to send back to the WaitCondition",
|
||||
default='00000',
|
||||
required=False)
|
||||
parser.add_argument('-e', '--exit',
|
||||
dest="exit_code",
|
||||
help="The exit code from a procecc to interpret",
|
||||
default=None,
|
||||
required=False)
|
||||
parser.add_argument('url',
|
||||
help='the url to post to')
|
||||
args = parser.parse_args()
|
||||
|
||||
log_format = '%(levelname)s [%(asctime)s] %(message)s'
|
||||
logging.basicConfig(format=log_format, level=logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger('cfn-init')
|
||||
log_file_name = "/var/log/cfn-signal.log"
|
||||
file_handler = logging.FileHandler(log_file_name)
|
||||
file_handler.setFormatter(logging.Formatter(log_format))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
logger.debug('cfn-signal called %s ' % (str(args)))
|
||||
|
||||
status = 'FAILURE'
|
||||
if args.exit_code:
|
||||
# "exit_code" takes presedence over "success".
|
||||
if args.exit_code == '0':
|
||||
status = 'SUCCESS'
|
||||
else:
|
||||
if args.success == 'true':
|
||||
status = 'SUCCESS'
|
||||
|
||||
body = {
|
||||
"Status" : status,
|
||||
"Reason" : args.reason,
|
||||
"UniqueId" : args.unique_id,
|
||||
"Data" : args.data
|
||||
}
|
||||
|
||||
cmd_str = "curl -X PUT -H \'Content-Type:\' --data-binary \'%s\' %s" % \
|
||||
(json.dumps(body), args.url)
|
||||
CommandRunner(cmd_str).run()
|
@ -1,893 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Implements cfn metadata handling
|
||||
|
||||
Resource metadata currently implemented:
|
||||
* config/packages
|
||||
* config/services
|
||||
|
||||
Not implemented yet:
|
||||
* config sets
|
||||
* config/sources
|
||||
* config/commands
|
||||
* config/files
|
||||
* config/users
|
||||
* config/groups
|
||||
* command line args
|
||||
- placeholders are ignored
|
||||
"""
|
||||
|
||||
import ConfigParser
|
||||
import errno
|
||||
import grp
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import pwd
|
||||
try:
|
||||
import rpmUtils.updates as rpmupdates
|
||||
import rpmUtils.miscutils as rpmutils
|
||||
rpmutils_present = True
|
||||
except:
|
||||
rpmutils_present = False
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib2 import urlopen, Request
|
||||
from urlparse import urlparse, urlunparse
|
||||
|
||||
|
||||
logger = logging.getLogger('cfntools')
|
||||
|
||||
|
||||
def to_boolean(b):
|
||||
val = b.lower().strip() if isinstance(b, basestring) else b
|
||||
return val in [True, 'true', 'yes', '1', 1]
|
||||
|
||||
|
||||
class HupConfig(object):
|
||||
def __init__(self, fp_list):
|
||||
self.config = ConfigParser.SafeConfigParser()
|
||||
for fp in fp_list:
|
||||
self.config.readfp(fp)
|
||||
|
||||
self.load_main_section()
|
||||
|
||||
self.hooks = {}
|
||||
for s in self.config.sections():
|
||||
if s != 'main':
|
||||
self.hooks[s] = Hook(s,
|
||||
self.config.get(s, 'triggers'),
|
||||
self.config.get(s, 'path'),
|
||||
self.config.get(s, 'runas'),
|
||||
self.config.get(s, 'action'))
|
||||
|
||||
def load_main_section(self):
|
||||
# required values
|
||||
self.stack = self.config.get('main', 'stack')
|
||||
self.credential_file = self.config.get('main', 'credential-file')
|
||||
try:
|
||||
with open(self.credential_file) as f:
|
||||
self.credentials = f.read()
|
||||
except:
|
||||
raise Exception("invalid credentials file %s" %
|
||||
self.credential_file)
|
||||
|
||||
# optional values
|
||||
try:
|
||||
self.region = self.config.get('main', 'region')
|
||||
except ConfigParser.NoOptionError:
|
||||
self.region = 'nova'
|
||||
|
||||
try:
|
||||
self.interval = self.config.getint('main', 'interval')
|
||||
except ConfigParser.NoOptionError:
|
||||
self.interval = 10
|
||||
|
||||
def __str__(self):
|
||||
return '{stack: %s, credential_file: %s, region: %s, interval:%d}' % \
|
||||
(self.stack, self.credential_file, self.region, self.interval)
|
||||
|
||||
def unique_resources_get(self):
|
||||
resources = []
|
||||
for h in self.hooks:
|
||||
r = self.hooks[h].resource_name_get()
|
||||
if not r in resources:
|
||||
resources.append(self.hooks[h].resource_name_get())
|
||||
return resources
|
||||
|
||||
|
||||
class Hook(object):
|
||||
def __init__(self, name, triggers, path, runas, action):
|
||||
self.name = name
|
||||
self.triggers = triggers
|
||||
self.path = path
|
||||
self.runas = runas
|
||||
self.action = action
|
||||
|
||||
def resource_name_get(self):
|
||||
sp = self.path.split('.')
|
||||
return sp[1]
|
||||
|
||||
def event(self, ev_name, ev_object, ev_resource):
|
||||
if self.resource_name_get() == ev_resource and \
|
||||
ev_name in self.triggers:
|
||||
CommandRunner(self.action).run(user=self.runas)
|
||||
else:
|
||||
logger.debug('event: {%s, %s, %s} did not match %s' %
|
||||
(ev_name, ev_object, ev_resource, self.__str__()))
|
||||
|
||||
def __str__(self):
|
||||
return '{%s, %s, %s, %s, %s}' % \
|
||||
(self.name,
|
||||
self.triggers,
|
||||
self.path,
|
||||
self.runas,
|
||||
self.action)
|
||||
|
||||
|
||||
class CommandRunner(object):
|
||||
"""
|
||||
Helper class to run a command and store the output.
|
||||
"""
|
||||
|
||||
def __init__(self, command, nextcommand=None):
|
||||
self._command = command
|
||||
self._next = nextcommand
|
||||
self._stdout = None
|
||||
self._stderr = None
|
||||
self._status = None
|
||||
|
||||
def __str__(self):
|
||||
s = "CommandRunner:"
|
||||
s += "\n\tcommand: %s" % self._command
|
||||
if self._status:
|
||||
s += "\n\tstatus: %s" % self._status
|
||||
if self._stdout:
|
||||
s += "\n\tstdout: %s" % self._stdout
|
||||
if self._stderr:
|
||||
s += "\n\tstderr: %s" % self._stderr
|
||||
return s
|
||||
|
||||
def run(self, user='root'):
|
||||
"""
|
||||
Run the Command and return the output.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
logger.debug("Running command: %s" % self._command)
|
||||
cmd = ['su', user, '-c', self._command]
|
||||
subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
output = subproc.communicate()
|
||||
|
||||
self._status = subproc.returncode
|
||||
self._stdout = output[0]
|
||||
self._stderr = output[1]
|
||||
if self._next:
|
||||
self._next.run()
|
||||
return self
|
||||
|
||||
@property
|
||||
def stdout(self):
|
||||
return self._stdout
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
return self._stderr
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
|
||||
class RpmHelper(object):
|
||||
|
||||
if rpmutils_present:
|
||||
_rpm_util = rpmupdates.Updates([], [])
|
||||
|
||||
@classmethod
|
||||
def prepcache(cls):
|
||||
"""
|
||||
Prepare the yum cache
|
||||
"""
|
||||
CommandRunner("yum -y makecache").run()
|
||||
|
||||
@classmethod
|
||||
def compare_rpm_versions(cls, v1, v2):
|
||||
"""
|
||||
Compare two RPM version strings.
|
||||
|
||||
Arguments:
|
||||
v1 -- a version string
|
||||
v2 -- a version string
|
||||
|
||||
Returns:
|
||||
0 -- the versions are equal
|
||||
1 -- v1 is greater
|
||||
-1 -- v2 is greater
|
||||
"""
|
||||
if v1 and v2:
|
||||
return rpmutils.compareVerOnly(v1, v2)
|
||||
elif v1:
|
||||
return 1
|
||||
elif v2:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def newest_rpm_version(cls, versions):
|
||||
"""
|
||||
Returns the highest (newest) version from a list of versions.
|
||||
|
||||
Arguments:
|
||||
versions -- A list of version strings
|
||||
e.g., ['2.0', '2.2', '2.2-1.fc16', '2.2.22-1.fc16']
|
||||
"""
|
||||
if versions:
|
||||
if isinstance(versions, basestring):
|
||||
return versions
|
||||
versions = sorted(versions, rpmutils.compareVerOnly,
|
||||
reverse=True)
|
||||
return versions[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def rpm_package_version(cls, pkg):
|
||||
"""
|
||||
Returns the version of an installed RPM.
|
||||
|
||||
Arguments:
|
||||
pkg -- A package name
|
||||
"""
|
||||
cmd = "rpm -q --queryformat '%{VERSION}-%{RELEASE}' %s" % pkg
|
||||
command = CommandRunner(cmd).run()
|
||||
return command.stdout
|
||||
|
||||
@classmethod
|
||||
def rpm_package_installed(cls, pkg):
|
||||
"""
|
||||
Indicates whether pkg is in rpm database.
|
||||
|
||||
Arguments:
|
||||
pkg -- A package name (with optional version and release spec).
|
||||
e.g., httpd
|
||||
e.g., httpd-2.2.22
|
||||
e.g., httpd-2.2.22-1.fc16
|
||||
"""
|
||||
command = CommandRunner("rpm -q %s" % pkg).run()
|
||||
return command.status == 0
|
||||
|
||||
@classmethod
|
||||
def yum_package_available(cls, pkg):
|
||||
"""
|
||||
Indicates whether pkg is available via yum
|
||||
|
||||
Arguments:
|
||||
pkg -- A package name (with optional version and release spec).
|
||||
e.g., httpd
|
||||
e.g., httpd-2.2.22
|
||||
e.g., httpd-2.2.22-1.fc16
|
||||
"""
|
||||
cmd_str = "yum -C -y --showduplicates list available %s" % pkg
|
||||
command = CommandRunner(cmd_str).run()
|
||||
return command.status == 0
|
||||
|
||||
@classmethod
|
||||
def install(cls, packages, rpms=True):
|
||||
"""
|
||||
Installs (or upgrades) a set of packages via RPM or via Yum.
|
||||
|
||||
Arguments:
|
||||
packages -- a list of packages to install
|
||||
rpms -- if True:
|
||||
* use RPM to install the packages
|
||||
* packages must be a list of URLs to retrieve RPMs
|
||||
if False:
|
||||
* use Yum to install packages
|
||||
* packages is a list of:
|
||||
- pkg name (httpd), or
|
||||
- pkg name with version spec (httpd-2.2.22), or
|
||||
- pkg name with version-release spec
|
||||
(httpd-2.2.22-1.fc16)
|
||||
"""
|
||||
if rpms:
|
||||
cmd = "rpm -U --force --nosignature "
|
||||
cmd += " ".join(packages)
|
||||
logger.info("Installing packages: %s" % cmd)
|
||||
else:
|
||||
cmd = "yum -y install "
|
||||
cmd += " ".join(packages)
|
||||
logger.info("Installing packages: %s" % cmd)
|
||||
command = CommandRunner(cmd).run()
|
||||
if command.status:
|
||||
logger.warn("Failed to install packages: %s" % cmd)
|
||||
|
||||
@classmethod
|
||||
def downgrade(cls, packages, rpms=True):
|
||||
"""
|
||||
Downgrades a set of packages via RPM or via Yum.
|
||||
|
||||
Arguments:
|
||||
packages -- a list of packages to downgrade
|
||||
rpms -- if True:
|
||||
* use RPM to downgrade (replace) the packages
|
||||
* packages must be a list of URLs to retrieve the RPMs
|
||||
if False:
|
||||
* use Yum to downgrade packages
|
||||
* packages is a list of:
|
||||
- pkg name with version spec (httpd-2.2.22), or
|
||||
- pkg name with version-release spec
|
||||
(httpd-2.2.22-1.fc16)
|
||||
"""
|
||||
if rpms:
|
||||
cls.install(packages)
|
||||
else:
|
||||
cmd = "yum -y downgrade "
|
||||
cmd += " ".join(packages)
|
||||
logger.info("Downgrading packages: %s" % cmd)
|
||||
command = Command(cmd).run()
|
||||
if command.status:
|
||||
logger.warn("Failed to downgrade packages: %s" % cmd)
|
||||
|
||||
|
||||
class PackagesHandler(object):
|
||||
_packages = {}
|
||||
|
||||
_package_order = ["dpkg", "rpm", "apt", "yum"]
|
||||
|
||||
@staticmethod
|
||||
def _pkgsort(pkg1, pkg2):
|
||||
order = PackagesHandler._package_order
|
||||
p1_name = pkg1[0]
|
||||
p2_name = pkg2[0]
|
||||
if p1_name in order and p2_name in order:
|
||||
return cmp(order.index(p1_name), order.index(p2_name))
|
||||
elif p1_name in order:
|
||||
return -1
|
||||
elif p2_name in order:
|
||||
return 1
|
||||
else:
|
||||
return cmp(p1_name.lower(), p2_name.lower())
|
||||
|
||||
def __init__(self, packages):
|
||||
self._packages = packages
|
||||
|
||||
def _handle_gem_packages(self, packages):
|
||||
"""
|
||||
very basic support for gems
|
||||
"""
|
||||
# TODO(asalkeld) support versions
|
||||
# -b == local & remote install
|
||||
# -y == install deps
|
||||
opts = '-b -y'
|
||||
for pkg_name, versions in packages.iteritems():
|
||||
if len(versions) > 0:
|
||||
cmd_str = 'gem install %s --version %s %s' % (opts,
|
||||
versions[0],
|
||||
pkg_name)
|
||||
CommandRunner(cmd_str).run()
|
||||
else:
|
||||
CommandRunner('gem install %s %s' % (opts, pkg_name)).run()
|
||||
|
||||
def _handle_python_packages(self, packages):
|
||||
"""
|
||||
very basic support for easy_install
|
||||
"""
|
||||
# TODO(asalkeld) support versions
|
||||
for pkg_name, versions in packages.iteritems():
|
||||
cmd_str = 'easy_install %s' % (pkg_name)
|
||||
CommandRunner(cmd_str).run()
|
||||
|
||||
def _handle_yum_packages(self, packages):
|
||||
"""
|
||||
Handle installation, upgrade, or downgrade of a set of
|
||||
packages via yum.
|
||||
|
||||
Arguments:
|
||||
packages -- a package entries map of the form:
|
||||
"pkg_name" : "version",
|
||||
"pkg_name" : ["v1", "v2"],
|
||||
"pkg_name" : []
|
||||
|
||||
For each package entry:
|
||||
* if no version is supplied and the package is already installed, do
|
||||
nothing
|
||||
* if no version is supplied and the package is _not_ already
|
||||
installed, install it
|
||||
* if a version string is supplied, and the package is already
|
||||
installed, determine whether to downgrade or upgrade (or do nothing
|
||||
if version matches installed package)
|
||||
* if a version array is supplied, choose the highest version from the
|
||||
array and follow same logic for version string above
|
||||
"""
|
||||
# collect pkgs for batch processing at end
|
||||
installs = []
|
||||
downgrades = []
|
||||
# update yum cache
|
||||
RpmHelper.prepcache()
|
||||
for pkg_name, versions in packages.iteritems():
|
||||
ver = RpmHelper.newest_rpm_version(versions)
|
||||
pkg = "%s-%s" % (pkg_name, ver) if ver else pkg_name
|
||||
if RpmHelper.rpm_package_installed(pkg):
|
||||
# FIXME:print non-error, but skipping pkg
|
||||
pass
|
||||
elif not RpmHelper.yum_package_available(pkg):
|
||||
logger.warn("Skipping package '%s'. Not available via yum" %
|
||||
pkg)
|
||||
elif not ver:
|
||||
installs.append(pkg)
|
||||
else:
|
||||
current_ver = RpmHelper.rpm_package_version(pkg)
|
||||
rc = RpmHelper.compare_rpm_versions(current_ver, ver)
|
||||
if rc < 0:
|
||||
installs.append(pkg)
|
||||
elif rc > 0:
|
||||
downgrades.append(pkg)
|
||||
if installs:
|
||||
RpmHelper.install(installs, rpms=False)
|
||||
if downgrades:
|
||||
RpmHelper.downgrade(downgrades)
|
||||
|
||||
def _handle_rpm_packages(sef, packages):
|
||||
"""
|
||||
Handle installation, upgrade, or downgrade of a set of
|
||||
packages via rpm.
|
||||
|
||||
Arguments:
|
||||
packages -- a package entries map of the form:
|
||||
"pkg_name" : "url"
|
||||
|
||||
For each package entry:
|
||||
* if the EXACT package is already installed, skip it
|
||||
* if a different version of the package is installed, overwrite it
|
||||
* if the package isn't installed, install it
|
||||
"""
|
||||
#FIXME: handle rpm installs
|
||||
pass
|
||||
|
||||
def _handle_apt_packages(self, packages):
|
||||
"""
|
||||
very basic support for apt
|
||||
"""
|
||||
# TODO(asalkeld) support versions
|
||||
pkg_list = ' '.join([p for p in packages])
|
||||
|
||||
cmd_str = 'apt-get -y install %s' % pkg_list
|
||||
CommandRunner(cmd_str).run()
|
||||
|
||||
# map of function pionters to handle different package managers
|
||||
_package_handlers = {
|
||||
"yum": _handle_yum_packages,
|
||||
"rpm": _handle_rpm_packages,
|
||||
"apt": _handle_apt_packages,
|
||||
"rubygems": _handle_gem_packages,
|
||||
"python": _handle_python_packages
|
||||
}
|
||||
|
||||
def _package_handler(self, manager_name):
|
||||
handler = None
|
||||
if manager_name in self._package_handlers:
|
||||
handler = self._package_handlers[manager_name]
|
||||
return handler
|
||||
|
||||
def apply_packages(self):
|
||||
"""
|
||||
Install, upgrade, or downgrade packages listed
|
||||
Each package is a dict containing package name and a list of versions
|
||||
Install order:
|
||||
* dpkg
|
||||
* rpm
|
||||
* apt
|
||||
* yum
|
||||
"""
|
||||
if not self._packages:
|
||||
return
|
||||
packages = sorted(self._packages.iteritems(), PackagesHandler._pkgsort)
|
||||
|
||||
for manager, package_entries in packages:
|
||||
handler = self._package_handler(manager)
|
||||
if not handler:
|
||||
logger.warn("Skipping invalid package type: %s" % manager)
|
||||
else:
|
||||
handler(self, package_entries)
|
||||
|
||||
|
||||
class FilesHandler(object):
|
||||
def __init__(self, files):
|
||||
self._files = files
|
||||
|
||||
def apply_files(self):
|
||||
if not self._files:
|
||||
return
|
||||
for fdest, meta in self._files.iteritems():
|
||||
dest = fdest.encode()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(dest))
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
logger.debug(str(e))
|
||||
else:
|
||||
logger.exception(e)
|
||||
|
||||
if 'content' in meta:
|
||||
if isinstance(meta['content'], basestring):
|
||||
f = open(dest, 'w')
|
||||
f.write(meta['content'])
|
||||
f.close()
|
||||
else:
|
||||
f = open(dest, 'w')
|
||||
f.write(json.dumps(meta['content'], indent=4))
|
||||
f.close()
|
||||
elif 'source' in meta:
|
||||
CommandRunner('wget -O %s %s' % (dest, meta['source'])).run()
|
||||
else:
|
||||
logger.error('%s %s' % (dest, str(meta)))
|
||||
continue
|
||||
|
||||
uid = -1
|
||||
gid = -1
|
||||
if 'owner' in meta:
|
||||
try:
|
||||
user_info = pwd.getpwnam(meta['owner'])
|
||||
uid = user_info[2]
|
||||
except KeyError as ex:
|
||||
pass
|
||||
|
||||
if 'group' in meta:
|
||||
try:
|
||||
group_info = grp.getgrnam(meta['group'])
|
||||
gid = group_info[2]
|
||||
except KeyError as ex:
|
||||
pass
|
||||
|
||||
os.chown(dest, uid, gid)
|
||||
if 'mode' in meta:
|
||||
os.chmod(dest, int(meta['mode'], 8))
|
||||
|
||||
|
||||
class SourcesHandler(object):
|
||||
'''
|
||||
tar, tar+gzip,tar+bz2 and zip
|
||||
'''
|
||||
_sources = {}
|
||||
|
||||
def __init__(self, sources):
|
||||
self._sources = sources
|
||||
|
||||
def _url_to_tmp_filename(self, url):
|
||||
sp = url.split('/')
|
||||
if 'https://github.com' in url:
|
||||
if 'zipball' == sp[-2]:
|
||||
return '/tmp/%s-%s.zip' % (sp[-3], sp[-1])
|
||||
elif 'tarball' == sp[-2]:
|
||||
return '/tmp/%s-%s.tar.gz' % (sp[-3], sp[-1])
|
||||
else:
|
||||
pass
|
||||
|
||||
return '/tmp/%s' % (sp[-1])
|
||||
|
||||
def _decompress(self, archive, dest_dir):
|
||||
cmd_str = ''
|
||||
logger.debug("Decompressing")
|
||||
(r, ext) = os.path.splitext(archive)
|
||||
if ext == '.tgz':
|
||||
cmd_str = 'tar -C %s -xzf %s' % (dest_dir, archive)
|
||||
elif ext == '.tbz2':
|
||||
cmd_str = 'tar -C %s -xjf %s' % (dest_dir, archive)
|
||||
elif ext == '.zip':
|
||||
cmd_str = 'unzip -d %s %s' % (dest_dir, archive)
|
||||
elif ext == '.tar':
|
||||
cmd_str = 'tar -C %s -xf %s' % (dest_dir, archive)
|
||||
elif ext == '.gz':
|
||||
(r, ext) = os.path.splitext(r)
|
||||
if ext:
|
||||
cmd_str = 'tar -C %s -xzf %s' % (dest_dir, archive)
|
||||
else:
|
||||
cmd_str = 'gunzip -c %s > %s' % (archive, dest_dir)
|
||||
elif ext == 'bz2':
|
||||
(r, ext) = os.path.splitext(r)
|
||||
if ext:
|
||||
cmd_str = 'tar -C %s -xjf %s' % (dest_dir, archive)
|
||||
else:
|
||||
cmd_str = 'bunzip2 -c %s > %s' % (archive, dest_dir)
|
||||
else:
|
||||
pass
|
||||
return CommandRunner(cmd_str)
|
||||
|
||||
def apply_sources(self):
|
||||
if not self._sources:
|
||||
return
|
||||
for dest, url in self._sources.iteritems():
|
||||
tmp_name = self._url_to_tmp_filename(url)
|
||||
cmd_str = 'wget -O %s %s' % (tmp_name, url)
|
||||
decompress_command = self._decompress(tmp_name, dest)
|
||||
CommandRunner(cmd_str, decompress_command).run()
|
||||
try:
|
||||
os.makedirs(dest)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
logger.debug(str(e))
|
||||
else:
|
||||
logger.exception(e)
|
||||
|
||||
|
||||
class ServicesHandler(object):
|
||||
_services = {}
|
||||
|
||||
def __init__(self, services, resource=None, hooks=None):
|
||||
self._services = services
|
||||
self.resource = resource
|
||||
self.hooks = hooks
|
||||
|
||||
def _handle_sysv_command(self, service, command):
|
||||
service_exe = "/sbin/service"
|
||||
enable_exe = "/sbin/chkconfig"
|
||||
cmd = ""
|
||||
if "enable" == command:
|
||||
cmd = "%s %s on" % (enable_exe, service)
|
||||
elif "disable" == command:
|
||||
cmd = "%s %s off" % (enable_exe, service)
|
||||
elif "start" == command:
|
||||
cmd = "%s %s start" % (service_exe, service)
|
||||
elif "stop" == command:
|
||||
cmd = "%s %s stop" % (service_exe, service)
|
||||
elif "status" == command:
|
||||
cmd = "%s %s status" % (service_exe, service)
|
||||
command = CommandRunner(cmd)
|
||||
command.run()
|
||||
return command
|
||||
|
||||
def _handle_systemd_command(self, service, command):
|
||||
exe = "/bin/systemctl"
|
||||
cmd = ""
|
||||
service = '%s.service' % service
|
||||
if "enable" == command:
|
||||
cmd = "%s enable %s" % (exe, service)
|
||||
elif "disable" == command:
|
||||
cmd = "%s disable %s" % (exe, service)
|
||||
elif "start" == command:
|
||||
cmd = "%s start %s" % (exe, service)
|
||||
elif "stop" == command:
|
||||
cmd = "%s stop %s" % (exe, service)
|
||||
elif "status" == command:
|
||||
cmd = "%s status %s" % (exe, service)
|
||||
command = CommandRunner(cmd)
|
||||
command.run()
|
||||
return command
|
||||
|
||||
def _initialize_service(self, handler, service, properties):
|
||||
if "enabled" in properties:
|
||||
enable = to_boolean(properties["enabled"])
|
||||
if enable:
|
||||
logger.info("Enabling service %s" % service)
|
||||
handler(self, service, "enable")
|
||||
else:
|
||||
logger.info("Disabling service %s" % service)
|
||||
handler(self, service, "disable")
|
||||
|
||||
if "ensureRunning" in properties:
|
||||
ensure_running = to_boolean(properties["ensureRunning"])
|
||||
command = handler(self, service, "status")
|
||||
running = command.status == 0
|
||||
if ensure_running and not running:
|
||||
logger.info("Starting service %s" % service)
|
||||
handler(self, service, "start")
|
||||
elif not ensure_running and running:
|
||||
logger.info("Stopping service %s" % service)
|
||||
handler(self, service, "stop")
|
||||
|
||||
def _monitor_service(self, handler, service, properties):
|
||||
if "ensureRunning" in properties:
|
||||
ensure_running = to_boolean(properties["ensureRunning"])
|
||||
command = handler(self, service, "status")
|
||||
running = command.status == 0
|
||||
if ensure_running and not running:
|
||||
logger.warn("Restarting service %s" % service)
|
||||
start_cmd = handler(self, service, "start")
|
||||
if start_cmd.status != 0:
|
||||
logger.warning('Service %s did not start. STDERR: %s' %
|
||||
(service, start_cmd.stderr))
|
||||
return
|
||||
for h in self.hooks:
|
||||
self.hooks[h].event('service.restarted',
|
||||
service, self.resource)
|
||||
|
||||
def _monitor_services(self, handler, services):
|
||||
for service, properties in services.iteritems():
|
||||
self._monitor_service(handler, service, properties)
|
||||
|
||||
def _initialize_services(self, handler, services):
|
||||
for service, properties in services.iteritems():
|
||||
self._initialize_service(handler, service, properties)
|
||||
|
||||
# map of function pointers to various service handlers
|
||||
_service_handlers = {
|
||||
"sysvinit": _handle_sysv_command,
|
||||
"systemd": _handle_systemd_command
|
||||
}
|
||||
|
||||
def _service_handler(self, manager_name):
|
||||
handler = None
|
||||
if manager_name in self._service_handlers:
|
||||
handler = self._service_handlers[manager_name]
|
||||
return handler
|
||||
|
||||
def apply_services(self):
|
||||
"""
|
||||
Starts, stops, enables, disables services
|
||||
"""
|
||||
if not self._services:
|
||||
return
|
||||
for manager, service_entries in self._services.iteritems():
|
||||
handler = self._service_handler(manager)
|
||||
if not handler:
|
||||
logger.warn("Skipping invalid service type: %s" % manager)
|
||||
else:
|
||||
self._initialize_services(handler, service_entries)
|
||||
|
||||
def monitor_services(self):
|
||||
"""
|
||||
Restarts failed services, and runs hooks.
|
||||
"""
|
||||
if not self._services:
|
||||
return
|
||||
for manager, service_entries in self._services.iteritems():
|
||||
handler = self._service_handler(manager)
|
||||
if not handler:
|
||||
logger.warn("Skipping invalid service type: %s" % manager)
|
||||
else:
|
||||
self._monitor_services(handler, service_entries)
|
||||
|
||||
|
||||
def metadata_server_url():
|
||||
"""
|
||||
Return the url to the metadata server.
|
||||
"""
|
||||
try:
|
||||
f = open("/var/lib/cloud/data/cfn-metadata-server")
|
||||
server_url = f.read().strip()
|
||||
f.close()
|
||||
if not server_url[-1] == '/':
|
||||
server_url += '/'
|
||||
return server_url
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
class MetadataServerConnectionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Metadata(object):
|
||||
_metadata = None
|
||||
_init_key = "AWS::CloudFormation::Init"
|
||||
|
||||
def __init__(self, stack, resource, access_key=None,
|
||||
secret_key=None, credentials_file=None, region=None):
|
||||
|
||||
self.stack = stack
|
||||
self.resource = resource
|
||||
self.access_key = access_key
|
||||
self.secret_key = secret_key
|
||||
self.credentials_file = credentials_file
|
||||
self.region = region
|
||||
|
||||
# TODO(asalkeld) is this metadata for the local resource?
|
||||
self._is_local_metadata = True
|
||||
self._metadata = None
|
||||
|
||||
def metadata_resource_url(self):
|
||||
server_url = metadata_server_url()
|
||||
if not server_url:
|
||||
return
|
||||
return server_url + 'stacks/%s/resources/%s' % (self.stack,
|
||||
self.resource)
|
||||
|
||||
def remote_metadata(self):
|
||||
"""
|
||||
Connect to the metadata server and retreive the metadata from there.
|
||||
"""
|
||||
url = self.metadata_resource_url()
|
||||
if not url:
|
||||
raise MetadataServerConnectionError()
|
||||
|
||||
try:
|
||||
return urlopen(url).read()
|
||||
except:
|
||||
raise MetadataServerConnectionError()
|
||||
|
||||
def retrieve(self, meta_str=None):
|
||||
"""
|
||||
Read the metadata from the given filename
|
||||
"""
|
||||
if meta_str:
|
||||
self._data = meta_str
|
||||
else:
|
||||
try:
|
||||
self._data = self.remote_metadata()
|
||||
except MetadataServerConnectionError:
|
||||
f = open("/var/lib/cloud/data/cfn-init-data")
|
||||
self._data = f.read()
|
||||
f.close()
|
||||
|
||||
if isinstance(self._data, str):
|
||||
self._metadata = json.loads(self._data)
|
||||
else:
|
||||
self._metadata = self._data
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(self._metadata)
|
||||
|
||||
def _is_valid_metadata(self):
|
||||
"""
|
||||
Should find the AWS::CloudFormation::Init json key
|
||||
"""
|
||||
is_valid = self._metadata and \
|
||||
self._init_key in self._metadata and \
|
||||
self._metadata[self._init_key]
|
||||
if is_valid:
|
||||
self._metadata = self._metadata[self._init_key]
|
||||
return is_valid
|
||||
|
||||
def _process_config(self):
|
||||
"""
|
||||
Parse and process a config section
|
||||
* packages
|
||||
* sources
|
||||
* users (not yet)
|
||||
* groups (not yet)
|
||||
* files
|
||||
* commands (not yet)
|
||||
* services
|
||||
"""
|
||||
|
||||
self._config = self._metadata["config"]
|
||||
PackagesHandler(self._config.get("packages")).apply_packages()
|
||||
#FIXME: handle sources
|
||||
SourcesHandler(self._config.get("sources")).apply_sources()
|
||||
#FIXME: handle users
|
||||
#FIXME: handle groups
|
||||
FilesHandler(self._config.get("files")).apply_files()
|
||||
#FIXME: handle commands
|
||||
ServicesHandler(self._config.get("services")).apply_services()
|
||||
|
||||
def cfn_init(self):
|
||||
"""
|
||||
Process the resource metadata
|
||||
"""
|
||||
# FIXME: when config sets are implemented, this should select the
|
||||
# correct config set from the metadata, and send each config in the
|
||||
# config set to process_config
|
||||
if not self._is_valid_metadata():
|
||||
raise Exception("invalid metadata")
|
||||
else:
|
||||
self._process_config()
|
||||
|
||||
def cfn_hup(self, hooks):
|
||||
"""
|
||||
Process the resource metadata
|
||||
"""
|
||||
if not self._is_valid_metadata():
|
||||
raise Exception("invalid metadata")
|
||||
else:
|
||||
if self._is_local_metadata:
|
||||
self._config = self._metadata["config"]
|
||||
s = self._config.get("services")
|
||||
sh = ServicesHandler(s, resource=self.resource, hooks=hooks)
|
||||
sh.monitor_services()
|
@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<name>F16-i386-cfntools-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>16</version>
|
||||
<arch>i386</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-16-i386-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 16</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
yum -y update --skip-broken;yum -y install yum-plugin-fastestmirror;yum -y update;/usr/sbin/useradd ec2-user;echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local;chmod +x /opt/aws/bin/cfn-*
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
<files>
|
||||
<file name='/opt/aws/bin/cfn-init' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-hup' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-signal' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
|
||||
</files>
|
||||
</template>
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<name>F16-i386-gold-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>16</version>
|
||||
<arch>i386</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-16-i386-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 16</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
rm -f /etc/yum.repos.d/fedora-updates*;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
while true; do
|
||||
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/Devices/0 | grep "State = 100"
|
||||
if [ \$? -eq 0 ]
|
||||
then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -d /root/.ssh ]; then
|
||||
mkdir -p /root/.ssh
|
||||
chmod 700 /root/.ssh
|
||||
fi
|
||||
|
||||
# Fetch public key using HTTP
|
||||
ATTEMPTS=10
|
||||
while [ ! -f /root/.ssh/authorized_keys ]; do
|
||||
curl -f http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /tmp/aws-key 2>/dev/null
|
||||
if [ \$? -eq 0 ]; then
|
||||
cat /tmp/aws-key >> /root/.ssh/authorized_keys
|
||||
chmod 0600 /root/.ssh/authorized_keys
|
||||
restorecon /root/.ssh/authorized_keys
|
||||
rm -f /tmp/aws-key
|
||||
echo "Successfully retrieved AWS public key from instance metadata"
|
||||
else
|
||||
FAILED=\$((\$FAILED + 1))
|
||||
if [ \$FAILED -ge \$ATTEMPTS ]; then
|
||||
echo "Failed to retrieve AWS public key after \$FAILED attempts, quitting"
|
||||
break
|
||||
fi
|
||||
echo "Could not retrieve AWS public key (attempt #\$FAILED/\$ATTEMPTS), retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
while [ ! -f /var/lib/cloud/instance/user-data.txt ]; do
|
||||
sleep 1
|
||||
done
|
||||
base64 -d /var/lib/cloud/instance/user-data.txt > /tmp/startup
|
||||
chmod +x /tmp/startup
|
||||
/tmp/startup
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
</template>
|
@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<name>F16-x86_64-cfntools-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>16</version>
|
||||
<arch>x86_64</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-16-x86_64-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 16</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
yum -y update --skip-broken;yum -y install yum-plugin-fastestmirror;yum -y update;/usr/sbin/useradd ec2-user;echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local;chmod +x /opt/aws/bin/cfn-*
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
<files>
|
||||
<file name='/opt/aws/bin/cfn-init' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-hup' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-signal' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
|
||||
</files>
|
||||
</template>
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<name>F16-x86_64-gold-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>16</version>
|
||||
<arch>x86_64</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-16-x86_64-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 16</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
rm -f /etc/yum.repos.d/fedora-updates*;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
while true; do
|
||||
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/Devices/0 | grep "State = 100"
|
||||
if [ \$? -eq 0 ]
|
||||
then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -d /root/.ssh ]; then
|
||||
mkdir -p /root/.ssh
|
||||
chmod 700 /root/.ssh
|
||||
fi
|
||||
|
||||
# Fetch public key using HTTP
|
||||
ATTEMPTS=10
|
||||
while [ ! -f /root/.ssh/authorized_keys ]; do
|
||||
curl -f http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /tmp/aws-key 2>/dev/null
|
||||
if [ \$? -eq 0 ]; then
|
||||
cat /tmp/aws-key >> /root/.ssh/authorized_keys
|
||||
chmod 0600 /root/.ssh/authorized_keys
|
||||
restorecon /root/.ssh/authorized_keys
|
||||
rm -f /tmp/aws-key
|
||||
echo "Successfully retrieved AWS public key from instance metadata"
|
||||
else
|
||||
FAILED=\$((\$FAILED + 1))
|
||||
if [ \$FAILED -ge \$ATTEMPTS ]; then
|
||||
echo "Failed to retrieve AWS public key after \$FAILED attempts, quitting"
|
||||
break
|
||||
fi
|
||||
echo "Could not retrieve AWS public key (attempt #\$FAILED/\$ATTEMPTS), retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
while [ ! -f /var/lib/cloud/instance/user-data.txt ]; do
|
||||
sleep 1
|
||||
done
|
||||
base64 -d /var/lib/cloud/instance/user-data.txt > /tmp/startup
|
||||
chmod +x /tmp/startup
|
||||
/tmp/startup
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
</template>
|
@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<name>F17-i386-cfntools-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>17</version>
|
||||
<arch>i386</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-17-i386-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 17</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
yum -y update --skip-broken;yum -y install yum-plugin-fastestmirror;yum -y update;/usr/sbin/useradd ec2-user;echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local;chmod +x /opt/aws/bin/cfn-*
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
<files>
|
||||
<file name='/opt/aws/bin/cfn-init' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-hup' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-signal' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
|
||||
</files>
|
||||
</template>
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<name>F17-i386-gold-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>17</version>
|
||||
<arch>i386</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-17-i386-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 17</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
rm -f /etc/yum.repos.d/fedora-updates*;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
while true; do
|
||||
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/Devices/0 | grep "State = 100"
|
||||
if [ \$? -eq 0 ]
|
||||
then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -d /root/.ssh ]; then
|
||||
mkdir -p /root/.ssh
|
||||
chmod 700 /root/.ssh
|
||||
fi
|
||||
|
||||
# Fetch public key using HTTP
|
||||
ATTEMPTS=10
|
||||
while [ ! -f /root/.ssh/authorized_keys ]; do
|
||||
curl -f http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /tmp/aws-key 2>/dev/null
|
||||
if [ \$? -eq 0 ]; then
|
||||
cat /tmp/aws-key >> /root/.ssh/authorized_keys
|
||||
chmod 0600 /root/.ssh/authorized_keys
|
||||
restorecon /root/.ssh/authorized_keys
|
||||
rm -f /tmp/aws-key
|
||||
echo "Successfully retrieved AWS public key from instance metadata"
|
||||
else
|
||||
FAILED=\$((\$FAILED + 1))
|
||||
if [ \$FAILED -ge \$ATTEMPTS ]; then
|
||||
echo "Failed to retrieve AWS public key after \$FAILED attempts, quitting"
|
||||
break
|
||||
fi
|
||||
echo "Could not retrieve AWS public key (attempt #\$FAILED/\$ATTEMPTS), retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
while [ ! -f /var/lib/cloud/instance/user-data.txt ]; do
|
||||
sleep 1
|
||||
done
|
||||
base64 -d /var/lib/cloud/instance/user-data.txt > /tmp/startup
|
||||
chmod +x /tmp/startup
|
||||
/tmp/startup
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
</template>
|
@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<name>F17-x86_64-cfntools-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>17</version>
|
||||
<arch>x86_64</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-17-x86_64-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 17</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
yum -y update --skip-broken;yum -y install yum-plugin-fastestmirror;yum -y update;/usr/sbin/useradd ec2-user;echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local;chmod +x /opt/aws/bin/cfn-*
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
<files>
|
||||
<file name='/opt/aws/bin/cfn-init' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-hup' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-signal' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
|
||||
</files>
|
||||
</template>
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<name>F17-x86_64-gold-jeos</name>
|
||||
<os>
|
||||
<name>Fedora</name>
|
||||
<version>17</version>
|
||||
<arch>x86_64</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/Fedora-17-x86_64-DVD.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Fedora 17</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
rm -f /etc/yum.repos.d/fedora-updates*;yum -y install cloud-init;cat >> /etc/rc.d/rc.local << EOF;chmod +x /etc/rc.d/rc.local
|
||||
#!/bin/bash
|
||||
setenforce 0
|
||||
while true; do
|
||||
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/Devices/0 | grep "State = 100"
|
||||
if [ \$? -eq 0 ]
|
||||
then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -d /root/.ssh ]; then
|
||||
mkdir -p /root/.ssh
|
||||
chmod 700 /root/.ssh
|
||||
fi
|
||||
|
||||
# Fetch public key using HTTP
|
||||
ATTEMPTS=10
|
||||
while [ ! -f /root/.ssh/authorized_keys ]; do
|
||||
curl -f http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /tmp/aws-key 2>/dev/null
|
||||
if [ \$? -eq 0 ]; then
|
||||
cat /tmp/aws-key >> /root/.ssh/authorized_keys
|
||||
chmod 0600 /root/.ssh/authorized_keys
|
||||
restorecon /root/.ssh/authorized_keys
|
||||
rm -f /tmp/aws-key
|
||||
echo "Successfully retrieved AWS public key from instance metadata"
|
||||
else
|
||||
FAILED=\$((\$FAILED + 1))
|
||||
if [ \$FAILED -ge \$ATTEMPTS ]; then
|
||||
echo "Failed to retrieve AWS public key after \$FAILED attempts, quitting"
|
||||
break
|
||||
fi
|
||||
echo "Could not retrieve AWS public key (attempt #\$FAILED/\$ATTEMPTS), retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
while [ ! -f /var/lib/cloud/instance/user-data.txt ]; do
|
||||
sleep 1
|
||||
done
|
||||
base64 -d /var/lib/cloud/instance/user-data.txt > /tmp/startup
|
||||
chmod +x /tmp/startup
|
||||
/tmp/startup
|
||||
EOF
|
||||
</command>
|
||||
</commands>
|
||||
</template>
|
@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<name>U10-x86_64-cfntools-jeos</name>
|
||||
<os>
|
||||
<name>Ubuntu</name>
|
||||
<version>10.04</version>
|
||||
<arch>x86_64</arch>
|
||||
<install type='iso'>
|
||||
<iso>file:/var/lib/libvirt/images/ubuntu-10.04.3-server-amd64.iso</iso>
|
||||
</install>
|
||||
</os>
|
||||
<description>Ubuntu 10.04</description>
|
||||
<commands>
|
||||
<command name='commands'>
|
||||
apt-get -y update;apt-get -y upgrade;apt-get -y install python-argparse;apt-get -y install chkconfig;apt-get -y install cloud-init;/usr/sbin/useradd -m ec2-user;echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers;chmod +x /opt/aws/bin/cfn-*
|
||||
</command>
|
||||
</commands>
|
||||
<files>
|
||||
<file name='/opt/aws/bin/cfn-init' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-hup' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-signal' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
|
||||
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
|
||||
</files>
|
||||
</template>
|
214
heat/utils.py
214
heat/utils.py
@ -59,220 +59,6 @@ def catch_error(action):
|
||||
return wrap
|
||||
|
||||
|
||||
@catch_error('jeos-create')
|
||||
def jeos_create(options, arguments, jeos_path, cfntools_path):
|
||||
'''
|
||||
Create a new JEOS (Just Enough Operating System) image.
|
||||
|
||||
Usage: heat jeos-create <distribution> <architecture> <image type>
|
||||
|
||||
Distribution: Distribution such as 'F16', 'F17', 'U10', 'D6'.
|
||||
Architecture: Architecture such as 'i386' 'i686' or 'x86_64'.
|
||||
Image Type: Image type such as 'gold' or 'cfntools'.
|
||||
'gold' is a basic gold JEOS.
|
||||
'cfntools' contains the cfntools helper scripts.
|
||||
|
||||
The command must be run as root in order for libvirt to have permissions
|
||||
to create virtual machines and read the raw DVDs.
|
||||
'''
|
||||
|
||||
# if not running as root, return EPERM to command line
|
||||
if os.geteuid() != 0:
|
||||
logging.error("jeos-create must be run as root")
|
||||
sys.exit(1)
|
||||
if len(arguments) < 3:
|
||||
print '\n Please provide the distro, arch, and instance type.'
|
||||
print ' Usage:'
|
||||
print ' heat jeos-create <distro> <arch> <instancetype>'
|
||||
print ' instance type can be:'
|
||||
print ' gold builds a base image where userdata is used to' \
|
||||
' initialize the instance'
|
||||
print ' cfntools builds a base image where AWS CloudFormation' \
|
||||
' tools are present'
|
||||
sys.exit(1)
|
||||
|
||||
distro = arguments.pop(0)
|
||||
arch = arguments.pop(0)
|
||||
instance_type = arguments.pop(0)
|
||||
images_dir = '/var/lib/libvirt/images'
|
||||
|
||||
arches = ('x86_64', 'i386', 'amd64')
|
||||
arches_str = " | ".join(arches)
|
||||
instance_types = ('gold', 'cfntools')
|
||||
instances_str = " | ".join(instance_types)
|
||||
|
||||
if not arch in arches:
|
||||
logging.error('arch %s not supported' % arch)
|
||||
logging.error('try: heat jeos-create %s [ %s ]' % (distro, arches_str))
|
||||
sys.exit(1)
|
||||
|
||||
if not instance_type in instance_types:
|
||||
logging.error('A JEOS instance type of %s not supported' %
|
||||
instance_type)
|
||||
logging.error('try: heat jeos-create %s %s [ %s ]' %
|
||||
(distro, arch, instances_str))
|
||||
sys.exit(1)
|
||||
|
||||
src_arch = 'i386'
|
||||
fedora_match = re.match('F(1[6-7])', distro)
|
||||
if fedora_match:
|
||||
if arch == 'x86_64':
|
||||
src_arch = 'x86_64'
|
||||
version = fedora_match.group(1)
|
||||
iso = '%s/Fedora-%s-%s-DVD.iso' % (images_dir, version, arch)
|
||||
elif distro == 'U10':
|
||||
if arch == 'amd64':
|
||||
src_arch = 'x86_64'
|
||||
iso = '%s/ubuntu-10.04.3-server-%s.iso' % (images_dir, arch)
|
||||
else:
|
||||
logging.error('distro %s not supported' % distro)
|
||||
logging.error('try: F16, F17 or U10')
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(iso, os.R_OK):
|
||||
logging.error('*** %s does not exist.' % (iso))
|
||||
sys.exit(1)
|
||||
|
||||
tdl_file = '%s-%s-%s-jeos.tdl' % (distro, arch, instance_type)
|
||||
tdl_path = os.path.join(jeos_path, tdl_file)
|
||||
if options.debug:
|
||||
print "Using tdl: %s" % tdl_path
|
||||
|
||||
# Load the cfntools into the cfntool image by encoding them in base64
|
||||
# and injecting them into the TDL at the appropriate place
|
||||
if instance_type == 'cfntools':
|
||||
tdl_xml = etree.parse(tdl_path)
|
||||
cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal',
|
||||
'cfn-get-metadata', 'cfn_helper.py', 'cfn-push-stats']
|
||||
for cfnname in cfn_tools:
|
||||
f = open('%s/%s' % (cfntools_path, cfnname), 'r')
|
||||
cfscript_e64 = base64.b64encode(f.read())
|
||||
f.close()
|
||||
cfnpath = "/template/files/file[@name='/opt/aws/bin/%s']" % cfnname
|
||||
tdl_xml.xpath(cfnpath)[0].text = cfscript_e64
|
||||
|
||||
# TODO(sdake) INSECURE
|
||||
tdl_xml.write('/tmp/tdl', xml_declaration=True)
|
||||
tdl_path = '/tmp/tdl'
|
||||
|
||||
dsk_filename = '%s/%s-%s-%s-jeos.dsk' % (images_dir, distro,
|
||||
src_arch, instance_type)
|
||||
qcow2_filename = '%s/%s-%s-%s-jeos.qcow2' % (images_dir, distro,
|
||||
arch, instance_type)
|
||||
image_name = '%s-%s-%s' % (distro, arch, instance_type)
|
||||
|
||||
if not os.access(tdl_path, os.R_OK):
|
||||
logging.error('The tdl for that disto/arch is not available')
|
||||
sys.exit(1)
|
||||
|
||||
creds = dict(username=options.username,
|
||||
password=options.password,
|
||||
tenant=options.tenant,
|
||||
auth_url=options.auth_url,
|
||||
strategy=options.auth_strategy)
|
||||
|
||||
client = glance_client.Client(host="0.0.0.0", port=9292,
|
||||
use_ssl=False, auth_tok=None, creds=creds)
|
||||
|
||||
parameters = {
|
||||
"filters": {},
|
||||
"limit": 10,
|
||||
}
|
||||
images = client.get_images(**parameters)
|
||||
|
||||
image_registered = False
|
||||
for image in images:
|
||||
if image['name'] == distro + '-' + arch + '-' + instance_type:
|
||||
image_registered = True
|
||||
|
||||
runoz = options.yes and 'y' or None
|
||||
if os.access(qcow2_filename, os.R_OK):
|
||||
while runoz not in ('y', 'n'):
|
||||
runoz = raw_input('An existing JEOS was found on disk.'
|
||||
' Do you want to build a fresh JEOS?'
|
||||
' (y/n) ').lower()
|
||||
if runoz == 'y':
|
||||
os.remove(qcow2_filename)
|
||||
os.remove(dsk_filename)
|
||||
if image_registered:
|
||||
client.delete_image(image['id'])
|
||||
elif runoz == 'n':
|
||||
answer = None
|
||||
while answer not in ('y', 'n'):
|
||||
answer = raw_input('Do you want to register your existing'
|
||||
' JEOS file with glance? (y/n) ').lower()
|
||||
if answer == 'n':
|
||||
logging.info('No action taken')
|
||||
sys.exit(0)
|
||||
elif answer == 'y' and image_registered:
|
||||
answer = None
|
||||
while answer not in ('y', 'n'):
|
||||
answer = raw_input('Do you want to delete the '
|
||||
'existing JEOS in glance?'
|
||||
' (y/n) ').lower()
|
||||
if answer == 'n':
|
||||
logging.info('No action taken')
|
||||
sys.exit(0)
|
||||
elif answer == 'y':
|
||||
client.delete_image(image['id'])
|
||||
|
||||
if runoz is None or runoz == 'y':
|
||||
logging.info('Creating JEOS image (%s) - '
|
||||
'this takes approximately 10 minutes.' % image_name)
|
||||
extra_opts = ' '
|
||||
if options.debug:
|
||||
extra_opts = ' -d 3 '
|
||||
|
||||
ozcmd = "oz-install %s -t 50000 -u %s -x /dev/null" % (extra_opts,
|
||||
tdl_path)
|
||||
logging.debug("Running : %s" % ozcmd)
|
||||
res = os.system(ozcmd)
|
||||
if res == 256:
|
||||
sys.exit(1)
|
||||
if not os.access(dsk_filename, os.R_OK):
|
||||
logging.error('oz-install did not create the image,'
|
||||
' check your oz installation.')
|
||||
sys.exit(1)
|
||||
|
||||
logging.info('Converting raw disk image to a qcow2 image.')
|
||||
os.system("qemu-img convert -c -O qcow2 %s %s" % (dsk_filename,
|
||||
qcow2_filename))
|
||||
|
||||
logging.info('Registering JEOS image (%s) '
|
||||
'with OpenStack Glance.' % image_name)
|
||||
|
||||
image_meta = {'name': image_name,
|
||||
'is_public': True,
|
||||
'disk_format': 'qcow2',
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
'owner': options.username,
|
||||
'container_format': 'bare'}
|
||||
|
||||
try:
|
||||
with open(qcow2_filename) as ifile:
|
||||
image_meta = client.add_image(image_meta, ifile)
|
||||
image_id = image_meta['id']
|
||||
logging.debug(" Added new image with ID: %s" % image_id)
|
||||
logging.debug(" Returned the following metadata for the new image:")
|
||||
for k, v in sorted(image_meta.items()):
|
||||
logging.debug(" %(k)30s => %(v)s" % locals())
|
||||
except exception.ClientConnectionError, e:
|
||||
logging.error((" Failed to connect to the Glance API server." +
|
||||
" Is the server running?" % locals()))
|
||||
pieces = unicode(e).split('\n')
|
||||
for piece in pieces:
|
||||
logging.error(piece)
|
||||
sys.exit(1)
|
||||
except Exception, e:
|
||||
logging.error(" Failed to add image. Got error:")
|
||||
pieces = unicode(e).split('\n')
|
||||
for piece in pieces:
|
||||
logging.error(piece)
|
||||
logging.warning(" Note: Your image metadata may still be in the " +
|
||||
"registry, but the image's status will likely be 'killed'.")
|
||||
|
||||
|
||||
class LazyPluggable(object):
|
||||
"""A pluggable backend loaded lazily based on some value."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user