1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
#!/usr/bin/python3
import os
import logging
import shutil
import subprocess
import tempfile
import time
import yaml
from argparse import ArgumentParser
CHARM_SRC = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class Deployer(object):
"""Simple wrapper around juju-deployer.
It creates a temporary directory that will act as local charm repository
for juju-deployer, so it can deploy the current charm branch as local
charm.
It also overrides the configuration in the stock bundles, accounting for
these files:
- config/repo-file
- config/license-file
- config/ssl-cert
- config/ssl-key
that can be used to provide custom parameters.
"""
def __init__(self, args):
self._args = args
def _stage_deployer_dir(self, deployer_dir, series):
"""Stage the directory for calling deployer."""
series_dest = os.path.join(deployer_dir, series)
os.mkdir(series_dest)
charm_dest = os.path.join(series_dest, "landscape-server")
os.symlink(CHARM_SRC, charm_dest)
def _create_local_yaml(self, tmpdir, target):
"""
Create a local yaml file to adjust settings in the bundle. Return the
created file name to the caller.
@param tmpdir: directory where we can write a yaml file.
@param target: what bundle target to override.
"""
# Will be appended to end of 'config_files' list. This will in turn
# be specified last on the juju-deployer command line, and will be able
# to overwrite charm settings. For instance we can use it to add a
# custom license-file to the deployment.
local_yaml_file = os.path.join(tmpdir, "99-local.yaml")
local_yaml = {}
# overridden options in landscape-charm, with the filename in the
# config dir that we read.
override_options = {"source": "repo-file",
"license-file": "license-file",
"ssl-cert": "ssl-cert",
"ssl-key": "ssl-key"}
# Base data structure for the landscape-charm that we will fill out
# with options.
landscape_service = {"charm": "landscape-server"}
options = {}
for option, filename in override_options.items():
filepath = os.path.join(CHARM_SRC, "config", filename)
if os.path.exists(filepath):
options[option] = "include-file://%s" % filepath
# Can't include a blank options section, deployer will choke
if options:
landscape_service["options"] = options
# target name == filename in our bundles branches.
local_yaml[target] = {"services": {}}
for service in ["landscape-server"]:
local_yaml[target]["services"][service] = landscape_service
with open(local_yaml_file, "w") as f:
f.write(yaml.dump(local_yaml, default_flow_style=False))
return local_yaml_file
def deploy(self, timeout=900):
"""
Use juju-deployer to deploy the target deployment type on current
`juju env`.
@param timeout: timeout in seconds (int or string is OK)
"""
target = self._args.target
start = time.time()
target = "landscape-" + target
deployer_dir = None
config_files = [os.path.join(CHARM_SRC, "bundles", target + ".yaml")]
try:
deployer_dir = tempfile.mkdtemp()
# Stage deployer directory for all supported series, even though
# typically in a deploy attempt only one series is used. Since
# it's determined by a bundle, we have to be ready for whatever.
for series in ["precise", "trusty"]:
self._stage_deployer_dir(deployer_dir, series)
config_files.append(
self._create_local_yaml(deployer_dir, target))
# The -w 30 is there to allow things to settle a bit before
# issuing a 'juju run' to wait for all the relations to settle.
args = ["juju-deployer", "-vdWL", "-w 30"]
for config_file in config_files:
args.extend(["-c", config_file])
args.append(target)
if timeout is not None:
args.extend(["--timeout", str(timeout)])
logging.info("(cwd=%s) RUN: %s" % (deployer_dir, args))
subprocess.check_call(args, cwd=deployer_dir)
logging.info("Waiting for relations to settle")
subprocess.check_output(["juju", "run", "--all", "/bin/true"])
deploy_time = int(round(time.time() - start))
logging.info(
"Deployment usable after {} seconds".format(deploy_time))
haproxy_ip = subprocess.check_output(
["juju-deployer", "-f", "haproxy"])
haproxy_ip = haproxy_ip.decode("utf-8").strip()
logging.info("Landscape URL: https://{}/".format(haproxy_ip))
finally:
if deployer_dir is not None:
shutil.rmtree(deployer_dir)
self._enable_flags()
def _enable_flags(self):
"""Enable feature flags."""
command = "sudo /opt/canonical/landscape/featureflags enable {}"
for flag in self._args.flags:
subprocess.check_output(
["juju", "run", "--unit", "landscape-server/0",
command.format(flag)])
def get_parser():
parser = ArgumentParser(description="Simple wrapper around juju-deployer")
parser.add_argument(
"target", choices=("scalable", "dense", "dense-maas"),
help="target deployment type")
parser.add_argument(
"--flags", nargs="+", default=[], help="feature flags to enable")
return parser
if __name__ == "__main__":
args = get_parser().parse_args()
logging.basicConfig(level=logging.INFO)
deployer = Deployer(args)
deployer.deploy()
|