1
from base64 import b64encode
8
from .charm import Charm
9
from .service import Service
10
from .relation import Endpoint
11
from .utils import path_join, yaml_dump, ErrorExit, resolve_include
14
class Deployment(object):
16
log = logging.getLogger("deployer.deploy")
18
def __init__(self, name, data, include_dirs, repo_path=""):
21
self.include_dirs = include_dirs
22
self.repo_path = repo_path
26
# Series could use a little help, charm series should be inferred
27
# directly from a store url
28
return self.data.get('series', 'precise')
31
def series_path(self):
32
return path_join(self.repo_path, self.series)
34
def pretty_print(self):
35
pprint.pprint(self.data)
37
def get_service(self, name):
38
if not name in self.data['services']:
40
return Service(name, self.data['services'][name])
42
def get_services(self):
43
for name, svc_data in self.data.get('services', {}).items():
44
yield Service(name, svc_data)
46
def get_relations(self):
47
if 'relations' not in self.data:
50
# Strip duplicate rels
54
k = tuple(sorted([a, b]))
56
#self.log.warning(" Skipping duplicate relation %r" % (k,))
61
# Support an ordered list of [endpoints]
62
if isinstance(self.data['relations'], list):
63
for end_a, end_b in self.data['relations']:
64
# Allow shorthand of [end_a, [end_b, end_c]]
65
if isinstance(end_b, list):
70
if check(end_a, end_b):
74
# Legacy format (dictionary of dictionaries with weights)
76
for k, v in self.data['relations'].items():
78
for c in v['consumes']:
79
expanded.append((k, c))
80
by_weight = rels.setdefault(v.get('weight', 0), [])
81
by_weight.extend(expanded)
82
for k in sorted(rels, reverse=True):
87
# "Found relations %s\n %s" % (" ".join(map(str, seen))))
90
for k, v in self.data.get('services', {}).items():
91
yield Charm.from_service(k, self.series_path, v)
93
def get_charm_for(self, svc_name):
94
svc_data = self.data['services'][svc_name]
95
return Charm.from_service(svc_name, self.series_path, svc_data)
97
def fetch_charms(self, update=False, no_local_mods=False):
98
if not os.path.exists(self.series_path):
99
os.mkdir(self.series_path)
100
for charm in self.get_charms():
104
if charm.is_modified():
106
"Charm %r has local modifications",
110
charm.update(build=True)
114
def resolve(self, cli_overides=()):
115
# Once we have charms definitions available, we can do verification
117
self.load_overrides(cli_overides)
118
self.resolve_config()
119
self.validate_relations()
121
def load_overrides(self, cli_overrides=()):
122
"""Load overrides."""
124
overrides.update(self.data.get('overrides', {}))
126
for o in cli_overrides:
127
key, value = o.split('=', 1)
128
overrides[key] = value
130
for k, v in overrides.iteritems():
132
for svc_name, svc_data in self.data['services'].items():
133
charm = self.get_charm_for(svc_name)
134
if k in charm.config:
135
if 'options' not in svc_data:
136
svc_data['options'] = {}
137
svc_data['options'][k] = v
141
"Override %s does not match any charms", k)
143
def resolve_config(self):
144
"""Load any lazy config values (includes), and verify config options.
146
self.log.debug("Resolving configuration")
147
# XXX TODO, rename resolve, validate relations
148
# against defined services
149
for svc_name, svc_data in self.data.get('services', {}).items():
150
if not 'options' in svc_data:
152
charm = self.get_charm_for(svc_name)
153
config = charm.config
156
for k, v in svc_data['options'].items():
159
"Invalid config charm %s %s=%s", charm.name, k, v)
161
iv = self._resolve_include(svc_name, k, v)
165
svc_data['options'] = options
167
def _resolve_include(self, svc_name, k, v):
168
for include_type in ["file", "base64"]:
169
if (not isinstance(v, basestring)
171
"include-%s://" % include_type)):
173
include, fname = v.split("://", 1)
174
ip = resolve_include(fname, self.include_dirs)
177
"Invalid config %s.%s include not found %s",
182
if include_type == "base64":
186
def validate_relations(self):
187
# Could extend to do interface matching against charms.
188
services = dict([(s.name, s) for s in self.get_services()])
189
for e_a, e_b in self.get_relations():
190
for ep in [Endpoint(e_a), Endpoint(e_b)]:
191
if not ep.service in services:
193
("Invalid relation in config,"
194
" service %s not found, rel %s"),
195
ep.service, "%s <-> %s" % (e_a, e_b))
198
def save(self, path):
199
with open(path, "w") as fh:
200
fh.write(yaml_dump(self.data))
203
def to_yaml(dumper, deployment):
204
return dumper.represent_dict(deployment.data)
206
yaml.add_representer(Deployment, Deployment.to_yaml)