summaryrefslogtreecommitdiffstats
path: root/scripts/lib/devtool/deploy.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/devtool/deploy.py')
-rw-r--r--scripts/lib/devtool/deploy.py387
1 files changed, 0 insertions, 387 deletions
diff --git a/scripts/lib/devtool/deploy.py b/scripts/lib/devtool/deploy.py
deleted file mode 100644
index a98b33c571..0000000000
--- a/scripts/lib/devtool/deploy.py
+++ /dev/null
@@ -1,387 +0,0 @@
1# Development tool - deploy/undeploy command plugin
2#
3# Copyright (C) 2014-2016 Intel Corporation
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7"""Devtool plugin containing the deploy subcommands"""
8
9import logging
10import os
11import shutil
12import subprocess
13import tempfile
14
15import bb.utils
16import argparse_oe
17import oe.types
18
19from devtool import exec_fakeroot_no_d, setup_tinfoil, check_workspace_recipe, DevtoolError
20
21logger = logging.getLogger('devtool')
22
23deploylist_dirname = '.devtool'
24
25def _prepare_remote_script(deploy, destdir='/', verbose=False, dryrun=False, undeployall=False, nopreserve=False, nocheckspace=False):
26 """
27 Prepare a shell script for running on the target to
28 deploy/undeploy files. We have to be careful what we put in this
29 script - only commands that are likely to be available on the
30 target are suitable (the target might be constrained, e.g. using
31 busybox rather than bash with coreutils).
32 """
33 lines = []
34 deploylist_path = os.path.join(destdir, deploylist_dirname)
35 lines.append('#!/bin/sh')
36 lines.append('set -e')
37 if undeployall:
38 # Yes, I know this is crude - but it does work
39 lines.append('for entry in %s/*.list; do' % deploylist_path)
40 lines.append('[ ! -f $entry ] && exit')
41 lines.append('set `basename $entry | sed "s/.list//"`')
42 if dryrun:
43 if not deploy:
44 lines.append('echo "Previously deployed files for $1:"')
45 lines.append('manifest="%s/$1.list"' % deploylist_path)
46 lines.append('preservedir="%s/$1.preserve"' % deploylist_path)
47 lines.append('if [ -f $manifest ] ; then')
48 # Read manifest in reverse and delete files / remove empty dirs
49 lines.append(' sed \'1!G;h;$!d\' $manifest | while read file')
50 lines.append(' do')
51 if dryrun:
52 lines.append(' if [ ! -d $file ] ; then')
53 lines.append(' echo $file')
54 lines.append(' fi')
55 else:
56 lines.append(' if [ -d $file ] ; then')
57 # Avoid deleting a preserved directory in case it has special perms
58 lines.append(' if [ ! -d $preservedir/$file ] ; then')
59 lines.append(' rmdir $file > /dev/null 2>&1 || true')
60 lines.append(' fi')
61 lines.append(' else')
62 lines.append(' rm -f $file')
63 lines.append(' fi')
64 lines.append(' done')
65 if not dryrun:
66 lines.append(' rm $manifest')
67 if not deploy and not dryrun:
68 # May as well remove all traces
69 lines.append(' rmdir `dirname $manifest` > /dev/null 2>&1 || true')
70 lines.append('fi')
71
72 if deploy:
73 if not nocheckspace:
74 # Check for available space
75 # FIXME This doesn't take into account files spread across multiple
76 # partitions, but doing that is non-trivial
77 # Find the part of the destination path that exists
78 lines.append('checkpath="$2"')
79 lines.append('while [ "$checkpath" != "/" ] && [ ! -e $checkpath ]')
80 lines.append('do')
81 lines.append(' checkpath=`dirname "$checkpath"`')
82 lines.append('done')
83 lines.append(r'freespace=$(df -P $checkpath | sed -nre "s/^(\S+\s+){3}([0-9]+).*/\2/p")')
84 # First line of the file is the total space
85 lines.append('total=`head -n1 $3`')
86 lines.append('if [ $total -gt $freespace ] ; then')
87 lines.append(' echo "ERROR: insufficient space on target (available ${freespace}, needed ${total})"')
88 lines.append(' exit 1')
89 lines.append('fi')
90 if not nopreserve:
91 # Preserve any files that exist. Note that this will add to the
92 # preserved list with successive deployments if the list of files
93 # deployed changes, but because we've deleted any previously
94 # deployed files at this point it will never preserve anything
95 # that was deployed, only files that existed prior to any deploying
96 # (which makes the most sense)
97 lines.append('cat $3 | sed "1d" | while read file fsize')
98 lines.append('do')
99 lines.append(' if [ -e $file ] ; then')
100 lines.append(' dest="$preservedir/$file"')
101 lines.append(' mkdir -p `dirname $dest`')
102 lines.append(' mv $file $dest')
103 lines.append(' fi')
104 lines.append('done')
105 lines.append('rm $3')
106 lines.append('mkdir -p `dirname $manifest`')
107 lines.append('mkdir -p $2')
108 if verbose:
109 lines.append(' tar xv -C $2 -f - | tee $manifest')
110 else:
111 lines.append(' tar xv -C $2 -f - > $manifest')
112 lines.append('sed -i "s!^./!$2!" $manifest')
113 elif not dryrun:
114 # Put any preserved files back
115 lines.append('if [ -d $preservedir ] ; then')
116 lines.append(' cd $preservedir')
117 # find from busybox might not have -exec, so we don't use that
118 lines.append(' find . -type f | while read file')
119 lines.append(' do')
120 lines.append(' mv $file /$file')
121 lines.append(' done')
122 lines.append(' cd /')
123 lines.append(' rm -rf $preservedir')
124 lines.append('fi')
125
126 if undeployall:
127 if not dryrun:
128 lines.append('echo "NOTE: Successfully undeployed $1"')
129 lines.append('done')
130
131 # Delete the script itself
132 lines.append('rm $0')
133 lines.append('')
134
135 return '\n'.join(lines)
136
137def deploy(args, config, basepath, workspace):
138 """Entry point for the devtool 'deploy' subcommand"""
139 import oe.utils
140
141 check_workspace_recipe(workspace, args.recipename, checksrc=False)
142
143 tinfoil = setup_tinfoil(basepath=basepath)
144 try:
145 try:
146 rd = tinfoil.parse_recipe(args.recipename)
147 except Exception as e:
148 raise DevtoolError('Exception parsing recipe %s: %s' %
149 (args.recipename, e))
150
151 srcdir = rd.getVar('D')
152 workdir = rd.getVar('WORKDIR')
153 path = rd.getVar('PATH')
154 strip_cmd = rd.getVar('STRIP')
155 libdir = rd.getVar('libdir')
156 base_libdir = rd.getVar('base_libdir')
157 max_process = oe.utils.get_bb_number_threads(rd)
158 fakerootcmd = rd.getVar('FAKEROOTCMD')
159 fakerootenv = rd.getVar('FAKEROOTENV')
160 finally:
161 tinfoil.shutdown()
162
163 return deploy_no_d(srcdir, workdir, path, strip_cmd, libdir, base_libdir, max_process, fakerootcmd, fakerootenv, args)
164
165def deploy_no_d(srcdir, workdir, path, strip_cmd, libdir, base_libdir, max_process, fakerootcmd, fakerootenv, args):
166 import math
167 import oe.package
168
169 try:
170 host, destdir = args.target.split(':')
171 except ValueError:
172 destdir = '/'
173 else:
174 args.target = host
175 if not destdir.endswith('/'):
176 destdir += '/'
177
178 recipe_outdir = srcdir
179 if not os.path.exists(recipe_outdir) or not os.listdir(recipe_outdir):
180 raise DevtoolError('No files to deploy - have you built the %s '
181 'recipe? If so, the install step has not installed '
182 'any files.' % args.recipename)
183
184 if args.strip and not args.dry_run:
185 # Fakeroot copy to new destination
186 srcdir = recipe_outdir
187 recipe_outdir = os.path.join(workdir, 'devtool-deploy-target-stripped')
188 if os.path.isdir(recipe_outdir):
189 exec_fakeroot_no_d(fakerootcmd, fakerootenv, "rm -rf %s" % recipe_outdir, shell=True)
190 exec_fakeroot_no_d(fakerootcmd, fakerootenv, "cp -af %s %s" % (os.path.join(srcdir, '.'), recipe_outdir), shell=True)
191 os.environ['PATH'] = ':'.join([os.environ['PATH'], path or ''])
192 oe.package.strip_execs(args.recipename, recipe_outdir, strip_cmd, libdir, base_libdir, max_process)
193
194 filelist = []
195 inodes = set({})
196 ftotalsize = 0
197 for root, _, files in os.walk(recipe_outdir):
198 for fn in files:
199 fstat = os.lstat(os.path.join(root, fn))
200 # Get the size in kiB (since we'll be comparing it to the output of du -k)
201 # MUST use lstat() here not stat() or getfilesize() since we don't want to
202 # dereference symlinks
203 if fstat.st_ino in inodes:
204 fsize = 0
205 else:
206 fsize = int(math.ceil(float(fstat.st_size)/1024))
207 inodes.add(fstat.st_ino)
208 ftotalsize += fsize
209 # The path as it would appear on the target
210 fpath = os.path.join(destdir, os.path.relpath(root, recipe_outdir), fn)
211 filelist.append((fpath, fsize))
212
213 if args.dry_run:
214 print('Files to be deployed for %s on target %s:' % (args.recipename, args.target))
215 for item, _ in filelist:
216 print(' %s' % item)
217 return 0
218
219 extraoptions = ''
220 if args.no_host_check:
221 extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
222 if not args.show_status:
223 extraoptions += ' -q'
224
225 scp_sshexec = ''
226 ssh_sshexec = 'ssh'
227 if args.ssh_exec:
228 scp_sshexec = "-S %s" % args.ssh_exec
229 ssh_sshexec = args.ssh_exec
230 scp_port = ''
231 ssh_port = ''
232 if args.port:
233 scp_port = "-P %s" % args.port
234 ssh_port = "-p %s" % args.port
235
236 if args.key:
237 extraoptions += ' -i %s' % args.key
238
239 # In order to delete previously deployed files and have the manifest file on
240 # the target, we write out a shell script and then copy it to the target
241 # so we can then run it (piping tar output to it).
242 # (We cannot use scp here, because it doesn't preserve symlinks.)
243 tmpdir = tempfile.mkdtemp(prefix='devtool')
244 try:
245 tmpscript = '/tmp/devtool_deploy.sh'
246 tmpfilelist = os.path.join(os.path.dirname(tmpscript), 'devtool_deploy.list')
247 shellscript = _prepare_remote_script(deploy=True,
248 destdir=destdir,
249 verbose=args.show_status,
250 nopreserve=args.no_preserve,
251 nocheckspace=args.no_check_space)
252 # Write out the script to a file
253 with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f:
254 f.write(shellscript)
255 # Write out the file list
256 with open(os.path.join(tmpdir, os.path.basename(tmpfilelist)), 'w') as f:
257 f.write('%d\n' % ftotalsize)
258 for fpath, fsize in filelist:
259 f.write('%s %d\n' % (fpath, fsize))
260 # Copy them to the target
261 ret = subprocess.call("scp %s %s %s %s/* %s:%s" % (scp_sshexec, scp_port, extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True)
262 if ret != 0:
263 raise DevtoolError('Failed to copy script to %s - rerun with -s to '
264 'get a complete error message' % args.target)
265 finally:
266 shutil.rmtree(tmpdir)
267
268 # Now run the script
269 ret = exec_fakeroot_no_d(fakerootcmd, fakerootenv, 'tar cf - . | %s %s %s %s \'sh %s %s %s %s\'' % (ssh_sshexec, ssh_port, extraoptions, args.target, tmpscript, args.recipename, destdir, tmpfilelist), cwd=recipe_outdir, shell=True)
270 if ret != 0:
271 raise DevtoolError('Deploy failed - rerun with -s to get a complete '
272 'error message')
273
274 logger.info('Successfully deployed %s' % recipe_outdir)
275
276 files_list = []
277 for root, _, files in os.walk(recipe_outdir):
278 for filename in files:
279 filename = os.path.relpath(os.path.join(root, filename), recipe_outdir)
280 files_list.append(os.path.join(destdir, filename))
281
282 return 0
283
284def undeploy(args, config, basepath, workspace):
285 """Entry point for the devtool 'undeploy' subcommand"""
286 if args.all and args.recipename:
287 raise argparse_oe.ArgumentUsageError('Cannot specify -a/--all with a recipe name', 'undeploy-target')
288 elif not args.recipename and not args.all:
289 raise argparse_oe.ArgumentUsageError('If you don\'t specify a recipe, you must specify -a/--all', 'undeploy-target')
290
291 extraoptions = ''
292 if args.no_host_check:
293 extraoptions += '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
294 if not args.show_status:
295 extraoptions += ' -q'
296
297 scp_sshexec = ''
298 ssh_sshexec = 'ssh'
299 if args.ssh_exec:
300 scp_sshexec = "-S %s" % args.ssh_exec
301 ssh_sshexec = args.ssh_exec
302 scp_port = ''
303 ssh_port = ''
304 if args.port:
305 scp_port = "-P %s" % args.port
306 ssh_port = "-p %s" % args.port
307
308 try:
309 host, destdir = args.target.split(':')
310 except ValueError:
311 destdir = '/'
312 else:
313 args.target = host
314 if not destdir.endswith('/'):
315 destdir += '/'
316
317 tmpdir = tempfile.mkdtemp(prefix='devtool')
318 try:
319 tmpscript = '/tmp/devtool_undeploy.sh'
320 shellscript = _prepare_remote_script(deploy=False, destdir=destdir, dryrun=args.dry_run, undeployall=args.all)
321 # Write out the script to a file
322 with open(os.path.join(tmpdir, os.path.basename(tmpscript)), 'w') as f:
323 f.write(shellscript)
324 # Copy it to the target
325 ret = subprocess.call("scp %s %s %s %s/* %s:%s" % (scp_sshexec, scp_port, extraoptions, tmpdir, args.target, os.path.dirname(tmpscript)), shell=True)
326 if ret != 0:
327 raise DevtoolError('Failed to copy script to %s - rerun with -s to '
328 'get a complete error message' % args.target)
329 finally:
330 shutil.rmtree(tmpdir)
331
332 # Now run the script
333 ret = subprocess.call('%s %s %s %s \'sh %s %s\'' % (ssh_sshexec, ssh_port, extraoptions, args.target, tmpscript, args.recipename), shell=True)
334 if ret != 0:
335 raise DevtoolError('Undeploy failed - rerun with -s to get a complete '
336 'error message')
337
338 if not args.all and not args.dry_run:
339 logger.info('Successfully undeployed %s' % args.recipename)
340 return 0
341
342
343def register_commands(subparsers, context):
344 """Register devtool subcommands from the deploy plugin"""
345
346 parser_deploy = subparsers.add_parser('deploy-target',
347 help='Deploy recipe output files to live target machine',
348 description='Deploys a recipe\'s build output (i.e. the output of the do_install task) to a live target machine over ssh. By default, any existing files will be preserved instead of being overwritten and will be restored if you run devtool undeploy-target. Note: this only deploys the recipe itself and not any runtime dependencies, so it is assumed that those have been installed on the target beforehand.',
349 group='testbuild')
350 parser_deploy.add_argument('recipename', help='Recipe to deploy')
351 parser_deploy.add_argument('target', help='Live target machine running an ssh server: user@hostname[:destdir]')
352 parser_deploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')
353 parser_deploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true')
354 parser_deploy.add_argument('-n', '--dry-run', help='List files to be deployed only', action='store_true')
355 parser_deploy.add_argument('-p', '--no-preserve', help='Do not preserve existing files', action='store_true')
356 parser_deploy.add_argument('--no-check-space', help='Do not check for available space before deploying', action='store_true')
357 parser_deploy.add_argument('-e', '--ssh-exec', help='Executable to use in place of ssh')
358 parser_deploy.add_argument('-P', '--port', help='Specify port to use for connection to the target')
359 parser_deploy.add_argument('-I', '--key',
360 help='Specify ssh private key for connection to the target')
361
362 strip_opts = parser_deploy.add_mutually_exclusive_group(required=False)
363 strip_opts.add_argument('-S', '--strip',
364 help='Strip executables prior to deploying (default: %(default)s). '
365 'The default value of this option can be controlled by setting the strip option in the [Deploy] section to True or False.',
366 default=oe.types.boolean(context.config.get('Deploy', 'strip', default='0')),
367 action='store_true')
368 strip_opts.add_argument('--no-strip', help='Do not strip executables prior to deploy', dest='strip', action='store_false')
369
370 parser_deploy.set_defaults(func=deploy)
371
372 parser_undeploy = subparsers.add_parser('undeploy-target',
373 help='Undeploy recipe output files in live target machine',
374 description='Un-deploys recipe output files previously deployed to a live target machine by devtool deploy-target.',
375 group='testbuild')
376 parser_undeploy.add_argument('recipename', help='Recipe to undeploy (if not using -a/--all)', nargs='?')
377 parser_undeploy.add_argument('target', help='Live target machine running an ssh server: user@hostname')
378 parser_undeploy.add_argument('-c', '--no-host-check', help='Disable ssh host key checking', action='store_true')
379 parser_undeploy.add_argument('-s', '--show-status', help='Show progress/status output', action='store_true')
380 parser_undeploy.add_argument('-a', '--all', help='Undeploy all recipes deployed on the target', action='store_true')
381 parser_undeploy.add_argument('-n', '--dry-run', help='List files to be undeployed only', action='store_true')
382 parser_undeploy.add_argument('-e', '--ssh-exec', help='Executable to use in place of ssh')
383 parser_undeploy.add_argument('-P', '--port', help='Specify port to use for connection to the target')
384 parser_undeploy.add_argument('-I', '--key',
385 help='Specify ssh private key for connection to the target')
386
387 parser_undeploy.set_defaults(func=undeploy)