1
"""Charm Helpers saltstack - declare the state of your machines.
3
This helper enables you to declare your machine state, rather than
4
program it procedurally (and have to test each change to your procedures).
5
Your install hook can be as simple as:
8
from charmhelpers.contrib.saltstack import (
15
install_salt_support()
16
update_machine_state('machine_states/dependencies.yaml')
17
update_machine_state('machine_states/installed.yaml')
20
and won't need to change (nor will its tests) when you change the machine
23
It's using a python package called salt-minion which allows various formats for
24
specifying resources, such as:
48
The docs for all the different state definitions are at:
49
http://docs.saltstack.com/ref/states/all/
53
* Add test helpers which will ensure that machine state definitions
54
are functionally (but not necessarily logically) correct (ie. getting
55
salt to parse all state defs.
56
* Add a link to a public bootstrap charm example / blogpost.
57
* Find a way to obviate the need to use the grains['charm_dir'] syntax
60
# Copyright 2013 Canonical Ltd.
63
# Charm Helpers Developers <juju@lists.ubuntu.com>
68
import charmhelpers.core.host
69
import charmhelpers.core.hookenv
72
charm_dir = os.environ.get('CHARM_DIR', '')
73
salt_grains_path = '/etc/salt/grains'
76
def install_salt_support(from_ppa=True):
77
"""Installs the salt-minion helper for machine state.
79
By default the salt-minion package is installed from
80
the saltstack PPA. If from_ppa is False you must ensure
81
that the salt-minion package is available in the apt cache.
84
subprocess.check_call([
85
'/usr/bin/add-apt-repository',
89
subprocess.check_call(['/usr/bin/apt-get', 'update'])
90
# We install salt-common as salt-minion would run the salt-minion
92
charmhelpers.fetch.apt_install('salt-common')
95
def update_machine_state(state_path):
96
"""Update the machine state using the provided state declaration."""
97
juju_state_to_yaml(salt_grains_path)
98
subprocess.check_call([
106
def juju_state_to_yaml(yaml_path, namespace_separator=':'):
107
"""Update the juju config and state in a yaml file.
109
This includes any current relation-get data, and the charm
112
config = charmhelpers.core.hookenv.config()
114
# Add the charm_dir which we will need to refer to charm
115
# file resources etc.
116
config['charm_dir'] = charm_dir
117
config['local_unit'] = charmhelpers.core.hookenv.local_unit()
119
# Add any relation data prefixed with the relation type.
120
relation_type = charmhelpers.core.hookenv.relation_type()
121
if relation_type is not None:
122
relation_data = charmhelpers.core.hookenv.relation_get()
123
relation_data = dict(
124
("{relation_type}{namespace_separator}{key}".format(
125
relation_type=relation_type.replace('-', '_'),
127
namespace_separator=namespace_separator), val)
128
for key, val in relation_data.items())
129
config.update(relation_data)
131
# Don't use non-standard tags for unicode which will not
132
# work when salt uses yaml.load_safe.
133
yaml.add_representer(unicode, lambda dumper,
134
value: dumper.represent_scalar(
135
u'tag:yaml.org,2002:str', value))
137
yaml_dir = os.path.dirname(yaml_path)
138
if not os.path.exists(yaml_dir):
139
os.makedirs(yaml_dir)
141
if os.path.exists(yaml_path):
142
with open(yaml_path, "r") as existing_vars_file:
143
existing_vars = yaml.load(existing_vars_file.read())
147
existing_vars.update(config)
148
with open(yaml_path, "w+") as fp:
149
fp.write(yaml.dump(existing_vars))