Beispiel #1
0
 def test_change_error(self):
     change = pebble.Change(
         id=pebble.ChangeID('1234'),
         kind='start',
         summary='Start service "foo"',
         status='Done',
         tasks=[],
         ready=True,
         err=None,
         spawn_time=datetime.datetime.now(),
         ready_time=datetime.datetime.now(),
     )
     error = pebble.ChangeError('Some error', change)
     self.assertIsInstance(error, pebble.Error)
     self.assertEqual(error.err, 'Some error')
     self.assertEqual(error.change, change)
     self.assertEqual(str(error), 'Some error')
Beispiel #2
0
 def test_change_init(self):
     change = pebble.Change(
         id=pebble.ChangeID('70'),
         kind='autostart',
         err='SILLY',
         ready=True,
         ready_time=datetime_nzdt(2021, 1, 28, 14, 37, 4, 291517),
         spawn_time=datetime_nzdt(2021, 1, 28, 14, 37, 2, 247202),
         status='Done',
         summary='Autostart service "svc"',
         tasks=[],
     )
     self.assertEqual(change.id, '70')
     self.assertEqual(change.kind, 'autostart')
     self.assertEqual(change.err, 'SILLY')
     self.assertEqual(change.ready, True)
     self.assertEqual(change.ready_time, datetime_nzdt(2021, 1, 28, 14, 37, 4, 291517))
     self.assertEqual(change.spawn_time, datetime_nzdt(2021, 1, 28, 14, 37, 2, 247202))
     self.assertEqual(change.status, 'Done')
     self.assertEqual(change.summary, 'Autostart service "svc"')
     self.assertEqual(change.tasks, [])
Beispiel #3
0
 def test_change_id(self):
     change_id = pebble.ChangeID('1234')
     self.assertEqual(change_id, '1234')
Beispiel #4
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--socket', help='pebble socket path, default $PEBBLE/.pebble.socket')
    subparsers = parser.add_subparsers(dest='command', metavar='command')

    p = subparsers.add_parser('abort', help='abort a change by ID')
    p.add_argument('change_id', help='ID of change to abort')

    p = subparsers.add_parser('ack',
                              help='acknowledge warnings up to given time')
    p.add_argument(
        '--timestamp',
        help='time to acknowledge up to (YYYY-mm-ddTHH:MM:SS.f+ZZ:zz'
        'format), default current time',
        type=pebble._parse_timestamp)

    p = subparsers.add_parser('autostart', help='autostart default service(s)')

    p = subparsers.add_parser('change', help='show a single change by ID')
    p.add_argument('change_id', help='ID of change to fetch')

    p = subparsers.add_parser('changes', help='show (filtered) changes')
    p.add_argument('--select',
                   help='change state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.ChangeState],
                   default='all')
    p.add_argument('--service', help='optional service name to filter on')

    p = subparsers.add_parser('start', help='start service(s)')
    p.add_argument('service',
                   help='name of service to start (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('stop', help='stop service(s)')
    p.add_argument('service',
                   help='name of service to stop (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('system-info',
                              help='show Pebble system information')

    p = subparsers.add_parser('warnings', help='show (filtered) warnings')
    p.add_argument('--select',
                   help='warning state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.WarningState],
                   default='all')

    args = parser.parse_args()

    if not args.command:
        parser.error('argument command: required')

    socket_path = args.socket
    if socket_path is None:
        pebble_env = os.getenv('PEBBLE')
        if not pebble_env:
            print(
                'cannot create Pebble client (set PEBBLE or specify --socket)',
                file=sys.stderr)
            sys.exit(1)
        socket_path = os.path.join(pebble_env, '.pebble.socket')

    client = pebble.Client(socket_path=socket_path)

    try:
        if args.command == 'abort':
            result = client.abort_change(pebble.ChangeID(args.change_id))
        elif args.command == 'ack':
            timestamp = args.timestamp or datetime.datetime.now(
                tz=datetime.timezone.utc)
            result = client.ack_warnings(timestamp)
        elif args.command == 'autostart':
            result = client.autostart_services()
        elif args.command == 'change':
            result = client.get_change(pebble.ChangeID(args.change_id))
        elif args.command == 'changes':
            result = client.get_changes(select=pebble.ChangeState(args.select),
                                        service=args.service)
        elif args.command == 'start':
            result = client.start_services(args.service)
        elif args.command == 'stop':
            result = client.stop_services(args.service)
        elif args.command == 'system-info':
            result = client.get_system_info()
        elif args.command == 'warnings':
            result = client.get_warnings(
                select=pebble.WarningState(args.select))
        else:
            raise AssertionError("shouldn't happen")
    except pebble.APIError as e:
        print('{} {}: {}'.format(e.code, e.status, e.message), file=sys.stderr)
        sys.exit(1)
    except pebble.ConnectionError as e:
        print('cannot connect to socket {!r}: {}'.format(socket_path, e),
              file=sys.stderr)
        sys.exit(1)
    except pebble.ChangeError as e:
        print('ChangeError:', e, file=sys.stderr)
        sys.exit(1)

    if isinstance(result, list):
        for x in result:
            print(x)
    else:
        print(result)
Beispiel #5
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--socket', help='pebble socket path, default $PEBBLE/.pebble.socket')
    subparsers = parser.add_subparsers(dest='command', metavar='command')

    p = subparsers.add_parser('abort', help='abort a change by ID')
    p.add_argument('change_id', help='ID of change to abort')

    p = subparsers.add_parser('ack',
                              help='acknowledge warnings up to given time')
    p.add_argument(
        '--timestamp',
        help='time to acknowledge up to (YYYY-mm-ddTHH:MM:SS.f+ZZ:zz'
        'format), default current time',
        type=pebble._parse_timestamp)

    p = subparsers.add_parser('add',
                              help='add a configuration layer dynamically')
    p.add_argument('--combine',
                   action='store_true',
                   help='combine layer instead of appending')
    p.add_argument('label', help='label for new layer')
    p.add_argument('layer_path', help='path of layer YAML file')

    p = subparsers.add_parser('autostart', help='autostart default service(s)')

    p = subparsers.add_parser('change', help='show a single change by ID')
    p.add_argument('change_id', help='ID of change to fetch')

    p = subparsers.add_parser('changes', help='show (filtered) changes')
    p.add_argument('--select',
                   help='change state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.ChangeState],
                   default='all')
    p.add_argument('--service', help='optional service name to filter on')

    p = subparsers.add_parser('ls', help='list files')
    p.add_argument('-d',
                   '--directory',
                   action='store_true',
                   help='list directories themselves, not their contents')
    p.add_argument('-p', '--pattern', help='glob pattern to filter results')
    p.add_argument('path', help='name of directory or file')

    p = subparsers.add_parser('mkdir', help='create directory')
    p.add_argument('-p',
                   '--parents',
                   action='store_true',
                   help='create parent directories if needed')
    p.add_argument('path', help='path to create')

    p = subparsers.add_parser('plan',
                              help='show configuration plan (combined layers)')

    p = subparsers.add_parser('pull', help='copy file from remote system')
    p.add_argument('remote_path', help='path of remote file')
    p.add_argument('local_path', help='path of local file to copy to')

    p = subparsers.add_parser('push', help='copy file to remote system')
    p.add_argument('-d',
                   '--dirs',
                   action='store_true',
                   help='create parent directories')
    p.add_argument('-m', '--mode', help='3-digit octal permissions')
    p.add_argument('-u', '--user', help='user to set')
    p.add_argument('-g', '--group', help='group to set')
    p.add_argument('local_path', help='path of local file')
    p.add_argument('remote_path', help='path of remote file to copy to')

    p = subparsers.add_parser('rm', help='remove path')
    p.add_argument('-r',
                   '--recursive',
                   action='store_true',
                   help='recursively delete directory contents')
    p.add_argument('path', help='path to remove')

    p = subparsers.add_parser('services', help='show service status')
    p.add_argument('service',
                   help='name of service (none means all; multiple ok)',
                   nargs='*')

    p = subparsers.add_parser('start', help='start service(s)')
    p.add_argument('service',
                   help='name of service to start (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('stop', help='stop service(s)')
    p.add_argument('service',
                   help='name of service to stop (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('system-info',
                              help='show Pebble system information')

    p = subparsers.add_parser('warnings', help='show (filtered) warnings')
    p.add_argument('--select',
                   help='warning state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.WarningState],
                   default='all')

    args = parser.parse_args()

    if not args.command:
        parser.error('argument command: required')

    socket_path = args.socket
    if socket_path is None:
        pebble_env = os.getenv('PEBBLE')
        if not pebble_env:
            print(
                'cannot create Pebble client (set PEBBLE or specify --socket)',
                file=sys.stderr)
            sys.exit(1)
        socket_path = os.path.join(pebble_env, '.pebble.socket')

    client = pebble.Client(socket_path=socket_path)

    try:
        if args.command == 'abort':
            result = client.abort_change(pebble.ChangeID(args.change_id))
        elif args.command == 'ack':
            timestamp = args.timestamp or datetime.datetime.now(
                tz=datetime.timezone.utc)
            result = client.ack_warnings(timestamp)
        elif args.command == 'add':
            try:
                with open(args.layer_path, encoding='utf-8') as f:
                    layer_yaml = f.read()
            except IOError as e:
                parser.error('cannot read layer YAML: {}'.format(e))
            client.add_layer(args.label,
                             layer_yaml,
                             combine=bool(args.combine))
            result = 'Layer {!r} added successfully from {!r}'.format(
                args.label, args.layer_path)
        elif args.command == 'autostart':
            result = client.autostart_services()
        elif args.command == 'change':
            result = client.get_change(pebble.ChangeID(args.change_id))
        elif args.command == 'changes':
            result = client.get_changes(select=pebble.ChangeState(args.select),
                                        service=args.service)
        elif args.command == 'ls':
            result = client.list_files(args.path,
                                       pattern=args.pattern,
                                       itself=args.directory)
        elif args.command == 'mkdir':
            client.make_dir(args.path, make_parents=bool(args.parents))
            result = 'created remote directory {}'.format(args.path)
        elif args.command == 'plan':
            result = client.get_plan().to_yaml()
        elif args.command == 'pull':
            content = client.pull(args.remote_path, encoding=None).read()
            if args.local_path != '-':
                with open(args.local_path, 'wb') as f:
                    f.write(content)
                result = 'wrote remote file {} to {}'.format(
                    args.remote_path, args.local_path)
            else:
                sys.stdout.buffer.write(content)
                return
        elif args.command == 'push':
            with open(args.local_path, 'rb') as f:
                client.push(args.remote_path,
                            f,
                            make_dirs=args.dirs,
                            permissions=int(args.mode, 8)
                            if args.mode is not None else None,
                            user=args.user,
                            group=args.group)
            result = 'wrote {} to remote file {}'.format(
                args.local_path, args.remote_path)
        elif args.command == 'rm':
            client.remove_path(args.path, recursive=bool(args.recursive))
            result = 'removed remote path {}'.format(args.path)
        elif args.command == 'services':
            result = client.get_services(args.service)
        elif args.command == 'start':
            result = client.start_services(args.service)
        elif args.command == 'stop':
            result = client.stop_services(args.service)
        elif args.command == 'system-info':
            result = client.get_system_info()
        elif args.command == 'warnings':
            result = client.get_warnings(
                select=pebble.WarningState(args.select))
        else:
            raise AssertionError("shouldn't happen")
    except pebble.APIError as e:
        print('{} {}: {}'.format(e.code, e.status, e.message), file=sys.stderr)
        sys.exit(1)
    except pebble.ConnectionError as e:
        print('cannot connect to socket {!r}: {}'.format(socket_path, e),
              file=sys.stderr)
        sys.exit(1)
    except pebble.ChangeError as e:
        print('ChangeError:', e, file=sys.stderr)
        sys.exit(1)

    if isinstance(result, list):
        for x in result:
            print(x)
    else:
        print(result)
Beispiel #6
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--socket', help='pebble socket path, default $PEBBLE/.pebble.socket')
    subparsers = parser.add_subparsers(dest='command', metavar='command')

    p = subparsers.add_parser('abort', help='abort a change by ID')
    p.add_argument('change_id', help='ID of change to abort')

    p = subparsers.add_parser('ack', help='acknowledge warnings up to given time')
    p.add_argument('--timestamp', help='time to acknowledge up to (YYYY-mm-ddTHH:MM:SS.f+ZZ:zz'
                                       'format), default current time',
                   type=pebble._parse_timestamp)

    p = subparsers.add_parser('add', help='add a configuration layer dynamically')
    p.add_argument('--combine', action='store_true', help='combine layer instead of appending')
    p.add_argument('label', help='label for new layer')
    p.add_argument('layer_path', help='path of layer YAML file')

    p = subparsers.add_parser('autostart', help='autostart default service(s)')

    p = subparsers.add_parser('change', help='show a single change by ID')
    p.add_argument('change_id', help='ID of change to fetch')

    p = subparsers.add_parser('changes', help='show (filtered) changes')
    p.add_argument('--select', help='change state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.ChangeState], default='all')
    p.add_argument('--service', help='optional service name to filter on')

    p = subparsers.add_parser('checks', help='show (filtered) checks')
    p.add_argument('--level', help='check level to filter on, default all levels',
                   choices=[c.value for c in pebble.CheckLevel], default='')
    p.add_argument('name', help='check name(s) to filter on', nargs='*')

    p = subparsers.add_parser('exec', help='execute a command')
    p.add_argument('--env', help='environment variables to set', action='append',
                   metavar='KEY=VALUE')
    p.add_argument('--working-dir', help='working directory to run command in')
    p.add_argument('--io-mode', help='input/output mode, default %(default)r',
                   choices=['passthrough', 'string'], default='passthrough')
    p.add_argument('-t', '--timeout', type=float, help='timeout in seconds')
    p.add_argument('-u', '--user', help='user to run as')
    p.add_argument('-g', '--group', help='group to run as')
    p.add_argument('--encoding', help="input/output encoding or 'none', default %(default)r",
                   default='utf-8')
    p.add_argument('--combine-stderr', help='combine stderr into stdout', action='store_true')
    p.add_argument('exec_command', help='command and arguments', nargs='+', metavar='command')

    p = subparsers.add_parser('ls', help='list files')
    p.add_argument('-d', '--directory', action='store_true',
                   help='list directories themselves, not their contents')
    p.add_argument('-p', '--pattern', help='glob pattern to filter results')
    p.add_argument('path', help='name of directory or file')

    p = subparsers.add_parser('mkdir', help='create directory')
    p.add_argument('-p', '--parents', action='store_true',
                   help='create parent directories if needed')
    p.add_argument('path', help='path to create')

    p = subparsers.add_parser('plan', help='show configuration plan (combined layers)')

    p = subparsers.add_parser('pull', help='copy file from remote system')
    p.add_argument('remote_path', help='path of remote file')
    p.add_argument('local_path', help='path of local file to copy to')

    p = subparsers.add_parser('push', help='copy file to remote system')
    p.add_argument('-d', '--dirs', action='store_true', help='create parent directories')
    p.add_argument('-m', '--mode', help='3-digit octal permissions')
    p.add_argument('-u', '--user', help='user to set')
    p.add_argument('-g', '--group', help='group to set')
    p.add_argument('local_path', help='path of local file')
    p.add_argument('remote_path', help='path of remote file to copy to')

    p = subparsers.add_parser('rm', help='remove path')
    p.add_argument('-r', '--recursive', action='store_true',
                   help='recursively delete directory contents')
    p.add_argument('path', help='path to remove')

    p = subparsers.add_parser('services', help='show service status')
    p.add_argument('service', help='name of service (none means all; multiple ok)', nargs='*')

    p = subparsers.add_parser('start', help='start service(s)')
    p.add_argument('service', help='name of service to start (can specify multiple)', nargs='+')

    p = subparsers.add_parser('stop', help='stop service(s)')
    p.add_argument('service', help='name of service to stop (can specify multiple)', nargs='+')

    p = subparsers.add_parser('system-info', help='show Pebble system information')

    p = subparsers.add_parser('wait', help='wait for a change by ID')
    p.add_argument('-t', '--timeout', type=float, help='timeout in seconds')
    p.add_argument('change_id', help='ID of change to wait for')

    p = subparsers.add_parser('warnings', help='show (filtered) warnings')
    p.add_argument('--select', help='warning state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.WarningState], default='all')

    args = parser.parse_args()

    if not args.command:
        parser.error('argument command: required')

    socket_path = args.socket
    if socket_path is None:
        pebble_env = os.getenv('PEBBLE')
        if not pebble_env:
            print('cannot create Pebble client (set PEBBLE or specify --socket)', file=sys.stderr)
            sys.exit(1)
        socket_path = os.path.join(pebble_env, '.pebble.socket')

    client = pebble.Client(socket_path=socket_path)

    try:
        if args.command == 'abort':
            result = client.abort_change(pebble.ChangeID(args.change_id))
        elif args.command == 'ack':
            timestamp = args.timestamp or datetime.datetime.now(tz=datetime.timezone.utc)
            result = client.ack_warnings(timestamp)
        elif args.command == 'add':
            try:
                with open(args.layer_path, encoding='utf-8') as f:
                    layer_yaml = f.read()
            except IOError as e:
                parser.error('cannot read layer YAML: {}'.format(e))
            client.add_layer(args.label, layer_yaml, combine=bool(args.combine))
            result = 'Layer {!r} added successfully from {!r}'.format(
                args.label, args.layer_path)
        elif args.command == 'autostart':
            result = client.autostart_services()
        elif args.command == 'change':
            result = client.get_change(pebble.ChangeID(args.change_id))
        elif args.command == 'changes':
            result = client.get_changes(select=pebble.ChangeState(args.select),
                                        service=args.service)
        elif args.command == 'checks':
            result = client.get_checks(level=pebble.CheckLevel(args.level), names=args.name)
        elif args.command == 'exec':
            environment = {}
            for env in args.env or []:
                key, _, value = env.partition('=')
                environment[key] = value

            encoding = args.encoding if args.encoding != 'none' else None
            if args.io_mode == 'passthrough':
                if encoding is not None:
                    stdin = sys.stdin
                    stdout = sys.stdout
                    stderr = sys.stderr if not args.combine_stderr else None
                else:
                    stdin = sys.stdin.buffer
                    stdout = sys.stdout.buffer
                    stderr = sys.stderr.buffer if not args.combine_stderr else None
            else:
                if sys.stdin.isatty():
                    if encoding is not None:
                        stdin = sys.stdin
                    else:
                        stdin = sys.stdin.buffer
                else:
                    if encoding is not None:
                        stdin = sys.stdin.read()
                    else:
                        stdin = sys.stdin.buffer.read()
                stdout = None
                stderr = None

            process = client.exec(
                args.exec_command,
                environment=environment,
                working_dir=args.working_dir,
                timeout=args.timeout,
                user=args.user,
                group=args.group,
                stdin=stdin,
                stdout=stdout,
                stderr=stderr,
                encoding=encoding,
                combine_stderr=args.combine_stderr,
            )

            try:
                if args.io_mode == 'passthrough':
                    process.wait()
                else:
                    stdout, stderr = process.wait_output()
                    print(repr(stdout))
                    if stderr:
                        print(repr(stderr), end='', file=sys.stderr)
                sys.exit(0)
            except pebble.ExecError as e:
                print('ExecError:', e, file=sys.stderr)
                sys.exit(e.exit_code)

        elif args.command == 'ls':
            result = client.list_files(args.path, pattern=args.pattern, itself=args.directory)
        elif args.command == 'mkdir':
            client.make_dir(args.path, make_parents=bool(args.parents))
            result = 'created remote directory {}'.format(args.path)
        elif args.command == 'plan':
            result = client.get_plan().to_yaml()
        elif args.command == 'pull':
            content = client.pull(args.remote_path, encoding=None).read()
            if args.local_path != '-':
                with open(args.local_path, 'wb') as f:
                    f.write(content)
                result = 'wrote remote file {} to {}'.format(args.remote_path, args.local_path)
            else:
                sys.stdout.buffer.write(content)
                return
        elif args.command == 'push':
            with open(args.local_path, 'rb') as f:
                client.push(
                    args.remote_path, f, make_dirs=args.dirs,
                    permissions=int(args.mode, 8) if args.mode is not None else None,
                    user=args.user, group=args.group)
            result = 'wrote {} to remote file {}'.format(args.local_path, args.remote_path)
        elif args.command == 'rm':
            client.remove_path(args.path, recursive=bool(args.recursive))
            result = 'removed remote path {}'.format(args.path)
        elif args.command == 'services':
            result = client.get_services(args.service)
        elif args.command == 'start':
            result = client.start_services(args.service)
        elif args.command == 'stop':
            result = client.stop_services(args.service)
        elif args.command == 'system-info':
            result = client.get_system_info()
        elif args.command == 'wait':
            result = client.wait_change(args.change_id, timeout=args.timeout)
        elif args.command == 'warnings':
            result = client.get_warnings(select=pebble.WarningState(args.select))
        else:
            raise AssertionError("shouldn't happen")
    except pebble.APIError as e:
        print('APIError: {} {}: {}'.format(e.code, e.status, e.message), file=sys.stderr)
        sys.exit(1)
    except pebble.ConnectionError as e:
        print('ConnectionError: cannot connect to socket {!r}: {}'.format(socket_path, e),
              file=sys.stderr)
        sys.exit(1)
    except pebble.ChangeError as e:
        print('ChangeError:', e, file=sys.stderr)
        sys.exit(1)

    if isinstance(result, list):
        for x in result:
            print(x)
    elif result is not None:
        print(result)
Beispiel #7
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--socket', help='pebble socket path, default $PEBBLE/.pebble.socket')
    subparsers = parser.add_subparsers(dest='command', metavar='command')

    p = subparsers.add_parser('abort', help='abort a change by ID')
    p.add_argument('change_id', help='ID of change to abort')

    p = subparsers.add_parser('ack',
                              help='acknowledge warnings up to given time')
    p.add_argument(
        '--timestamp',
        help='time to acknowledge up to (YYYY-mm-ddTHH:MM:SS.f+ZZ:zz'
        'format), default current time',
        type=pebble._parse_timestamp)

    p = subparsers.add_parser('add',
                              help='add a configuration layer dynamically')
    p.add_argument('--combine',
                   action='store_true',
                   help='combine layer instead of appending')
    p.add_argument('label', help='label for new layer')
    p.add_argument('layer_path', help='path of layer YAML file')

    p = subparsers.add_parser('autostart', help='autostart default service(s)')

    p = subparsers.add_parser('change', help='show a single change by ID')
    p.add_argument('change_id', help='ID of change to fetch')

    p = subparsers.add_parser('changes', help='show (filtered) changes')
    p.add_argument('--select',
                   help='change state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.ChangeState],
                   default='all')
    p.add_argument('--service', help='optional service name to filter on')

    p = subparsers.add_parser('plan',
                              help='show configuration plan (combined layers)')

    p = subparsers.add_parser('services', help='show service status')
    p.add_argument('service',
                   help='name of service (none means all; multiple ok)',
                   nargs='*')

    p = subparsers.add_parser('start', help='start service(s)')
    p.add_argument('service',
                   help='name of service to start (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('stop', help='stop service(s)')
    p.add_argument('service',
                   help='name of service to stop (can specify multiple)',
                   nargs='+')

    p = subparsers.add_parser('system-info',
                              help='show Pebble system information')

    p = subparsers.add_parser('warnings', help='show (filtered) warnings')
    p.add_argument('--select',
                   help='warning state to filter on, default %(default)s',
                   choices=[s.value for s in pebble.WarningState],
                   default='all')

    args = parser.parse_args()

    if not args.command:
        parser.error('argument command: required')

    socket_path = args.socket
    if socket_path is None:
        pebble_env = os.getenv('PEBBLE')
        if not pebble_env:
            print(
                'cannot create Pebble client (set PEBBLE or specify --socket)',
                file=sys.stderr)
            sys.exit(1)
        socket_path = os.path.join(pebble_env, '.pebble.socket')

    client = pebble.Client(socket_path=socket_path)

    try:
        if args.command == 'abort':
            result = client.abort_change(pebble.ChangeID(args.change_id))
        elif args.command == 'ack':
            timestamp = args.timestamp or datetime.datetime.now(
                tz=datetime.timezone.utc)
            result = client.ack_warnings(timestamp)
        elif args.command == 'add':
            try:
                with open(args.layer_path, encoding='utf-8') as f:
                    layer_yaml = f.read()
            except IOError as e:
                parser.error('cannot read layer YAML: {}'.format(e))
            client.add_layer(args.label,
                             layer_yaml,
                             combine=bool(args.combine))
            result = 'Layer {!r} added successfully from {!r}'.format(
                args.label, args.layer_path)
        elif args.command == 'autostart':
            result = client.autostart_services()
        elif args.command == 'change':
            result = client.get_change(pebble.ChangeID(args.change_id))
        elif args.command == 'changes':
            result = client.get_changes(select=pebble.ChangeState(args.select),
                                        service=args.service)
        elif args.command == 'plan':
            result = client.get_plan().to_yaml()
        elif args.command == 'services':
            result = client.get_services(args.service)
        elif args.command == 'start':
            result = client.start_services(args.service)
        elif args.command == 'stop':
            result = client.stop_services(args.service)
        elif args.command == 'system-info':
            result = client.get_system_info()
        elif args.command == 'warnings':
            result = client.get_warnings(
                select=pebble.WarningState(args.select))
        else:
            raise AssertionError("shouldn't happen")
    except pebble.APIError as e:
        print('{} {}: {}'.format(e.code, e.status, e.message), file=sys.stderr)
        sys.exit(1)
    except pebble.ConnectionError as e:
        print('cannot connect to socket {!r}: {}'.format(socket_path, e),
              file=sys.stderr)
        sys.exit(1)
    except pebble.ChangeError as e:
        print('ChangeError:', e, file=sys.stderr)
        sys.exit(1)

    if isinstance(result, list):
        for x in result:
            print(x)
    else:
        print(result)