Пример #1
0
def _do_download(task, session, opts):
    ''' Perform download of a single task. '''

    if opts.arches:
        print 'Downloading %s rpms from task %i: %s' \
            % (', '.join(opts.arches), task['id'], koji.taskLabel(task))
    else:
        print 'Downloading rpms from task %i: %s' \
            % (task['id'], koji.taskLabel(task))
    base_path = koji.pathinfo.taskrelpath(task['id'])
    output = session.listTaskOutput(task['id'])
    prog_meter = urlgrabber.progress.TextMeter()
    if output == []:
        print "This build is empty, no files to download"
        sys.exit(1)
    for filename in output:
        if opts.nologs and filename.endswith('log'):
            continue
        elif filename.endswith('.rpm'):
            if opts.arches:
                arch = filename.rsplit('.', 3)[2]
                if arch not in opts.arches:
                    continue
            if 'debuginfo' in filename and opts.nodebug:
                continue
        what = opts.baseurl + base_path + '/' + filename
        urlgrabber.grabber.urlgrab(what, progress_obj=prog_meter)
Пример #2
0
def _download_scratch_rpms(parser, task_ids, opts):
    ''' Given build id, tasks and CLI options download build results
    to current dir. '''

    if not os.access(os.getcwd(), os.R_OK | os.W_OK | os.X_OK):
        raise IOError("Insufficient permissons for current directory."
                      " Aborting download")
    session = koji.ClientSession(opts.huburl)
    for task_id in task_ids:
        task = session.getTaskInfo(task_id, request=True)
        if not task:
            parser.error('Invalid task ID: %i' % task_id)
        elif task['state'] in (koji.TASK_STATES['FREE'],
                               koji.TASK_STATES['OPEN']):
            parser.error('Task %i has not completed' % task['id'])
        elif task['state'] != koji.TASK_STATES['CLOSED']:
            parser.error('Task %i did not complete successfully' % task['id'])

        if task['method'] == 'build':
            print 'Getting rpms from children of task %i: %s' \
                % (task['id'], koji.taskLabel(task))
            task_opts = {'parent': task_id,
                         'method': 'buildArch',
                         'state': [koji.TASK_STATES['CLOSED']],
                         'decode': True}
            tasks = session.listTasks(opts=task_opts)
        elif task['method'] == 'buildArch':
            tasks = [task]
        else:
            parser.error('Task %i is not a build or buildArch task'
                         % task['id'])
        for task in tasks:
            _do_download(task, session, opts)
Пример #3
0
def get_build_info(build_id, nvr=False):
    """Get the dist-git commit and NVR when given a Koji build ID or NVR."""
    session = koji.ClientSession(HUB, KOJI_OPTIONS)
    if not session.krb_login(**KERBEROS_OPTIONS):
        raise Exception("Failed to log into Koji")


    if nvr is True:
        info = session.getBuild(build_id)
    else:
        info = session.getBuild(int(build_id))

    if info is None:
        raise ValueError("No such build: {}".format(build_id))

    task = None
    if info['task_id']:
        task = session.getTaskInfo(info['task_id'], request=True)

    nvrtag = info["nvr"]
    print(nvrtag)

    if task is None:
        return None

    tasklabel = koji.taskLabel(task)
    print(tasklabel)

    sha = urlparse(info["source"]).fragment
    print(sha)
    return (sha, nvrtag)
Пример #4
0
    def get_koji_tasks(cls, task_id, dir_name):
        session = cls.session_maker(baseurl=cls.server)
        task_id = int(task_id)
        rpm_list = []
        log_list = []
        tasks = []
        task = session.getTaskInfo(task_id, request=True)
        if task['state'] in (koji.TASK_STATES['FREE'],
                             koji.TASK_STATES['OPEN']):
            return None, None
        elif task['state'] != koji.TASK_STATES['CLOSED']:
            logger.info('Task %i did not complete successfully' % task_id)

        if task['method'] == 'build':
            logger.info('Getting rpms for chilren of task %i: %s', task['id'],
                        koji.taskLabel(task))
            # getting rpms from children of task
            tasks = session.listTasks(
                opts={
                    'parent':
                    task_id,
                    'method':
                    'buildArch',
                    'state':
                    [koji.TASK_STATES['CLOSED'], koji.TASK_STATES['FAILED']],
                    'decode':
                    True
                })
        elif task['method'] == 'buildArch':
            tasks = [task]
        for task in tasks:
            base_path = koji.pathinfo.taskrelpath(task['id'])
            output = session.listTaskOutput(task['id'])
            if output is None:
                return None
            for filename in output:
                download = False
                full_path_name = os.path.join(dir_name, filename)
                if filename.endswith('.src.rpm'):
                    continue
                if filename.endswith('.rpm'):
                    if task['state'] != koji.TASK_STATES['CLOSED']:
                        continue
                    arch = filename.rsplit('.', 3)[2]
                    if full_path_name not in rpm_list:
                        download = arch in ['noarch', 'x86_64']
                        if download:
                            rpm_list.append(full_path_name)
                else:
                    if full_path_name not in log_list:
                        log_list.append(full_path_name)
                        download = True
                if download:
                    DownloadHelper.download_file(
                        cls.baseurl + base_path + '/' + filename,
                        full_path_name)
        return rpm_list, log_list
Пример #5
0
def print_task(task,depth=0):
    """Print a task"""
    task = task.copy()
    task['state'] = koji.TASK_STATES.get(task['state'],'BADSTATE')
    fmt = "%(id)-8s %(priority)-4s %(owner_name)-20s %(state)-8s %(arch)-10s "
    if depth:
        indent = "  "*(depth-1) + " +"
    else:
        indent = ''
    label = koji.taskLabel(task)
    print(''.join([fmt % task, indent, label]))
Пример #6
0
def print_task(task,depth=0):
    """Print a task"""
    task = task.copy()
    task['state'] = koji.TASK_STATES.get(task['state'],'BADSTATE')
    fmt = "%(id)-8s %(priority)-4s %(owner_name)-20s %(state)-8s %(arch)-10s "
    if depth:
        indent = "  "*(depth-1) + " +"
    else:
        indent = ''
    label = koji.taskLabel(task)
    print(''.join([fmt % task, indent, label]))
Пример #7
0
    def get_koji_tasks(cls, task_id, dir_name):
        session = cls.session_maker(baseurl=cls.server)
        task_id = int(task_id)
        rpm_list = []
        log_list = []
        tasks = []
        task = session.getTaskInfo(task_id, request=True)
        if task['state'] in (koji.TASK_STATES['FREE'], koji.TASK_STATES['OPEN']):
            return None, None
        elif task['state'] != koji.TASK_STATES['CLOSED']:
            logger.info('Task %i did not complete successfully' % task_id)

        if task['method'] == 'build':
            logger.info('Getting rpms for chilren of task %i: %s',
                        task['id'],
                        koji.taskLabel(task))
            # getting rpms from children of task
            tasks = session.listTasks(opts={'parent': task_id,
                                            'method': 'buildArch',
                                            'state': [koji.TASK_STATES['CLOSED'], koji.TASK_STATES['FAILED']],
                                            'decode': True})
        elif task['method'] == 'buildArch':
            tasks = [task]
        for task in tasks:
            base_path = koji.pathinfo.taskrelpath(task['id'])
            output = session.listTaskOutput(task['id'])
            if output is None:
                return None
            for filename in output:
                download = False
                full_path_name = os.path.join(dir_name, filename)
                if filename.endswith('.src.rpm'):
                    continue
                if filename.endswith('.rpm'):
                    if task['state'] != koji.TASK_STATES['CLOSED']:
                        continue
                    arch = filename.rsplit('.', 3)[2]
                    if full_path_name not in rpm_list:
                        download = arch in ['noarch', 'x86_64']
                        if download:
                            rpm_list.append(full_path_name)
                else:
                    if full_path_name not in log_list:
                        log_list.append(full_path_name)
                        download = True
                if download:
                    DownloadHelper.download_file(cls.baseurl + base_path + '/' + filename,
                                                 full_path_name)
        return rpm_list, log_list
Пример #8
0
 def str(self):
     if self.info:
         label = koji.taskLabel(self.info)
         return "%s%d %s" % ('  ' * self.level, self.id, label)
     else:
         return "%s%d" % ('  ' * self.level, self.id)
Пример #9
0
#!/usr/bin/python2

import sys
import koji

args = sys.argv[1:]
nvrs = []
ks = koji.ClientSession('https://koji.fedoraproject.org/kojihub')
ks.multicall = True
for build in args:
    ks.getBuild(build)
ret = ks.multiCall(strict=True)
ks.multicall = True
for i in range(len(args)):
    if ret[i][0] is not None:
        if ret[i][0]['task_id'] is not None:
            ks.getTaskInfo(ret[i][0]['task_id'], request=True)
            nvrs.append(args[i])
ret = ks.multiCall(strict=True)
for i in range(len(nvrs)):
    print nvrs[i], koji.taskLabel(ret[i][0])
Пример #10
0
 def str(self):
     if self.info:
         label = koji.taskLabel(self.info)
         return "%s%d %s" % ('  ' * self.level, self.id, label)
     else:
         return "%s%d" % ('  ' * self.level, self.id)
Пример #11
0
    def test_all(self):
        url = 'https+git://git.server/path/module#branch'
        module = '/path/module:branch'
        build = {'name': 'n', 'version': 'v', 'release': 'r', 'epoch': None}
        nvr = 'n-v-r'
        test_data = [
            ['randomdata', 'malformed task'],
            [{}, 'malformed task'],
            [None, 'malformed task'],
            [
                {'method': 'build', 'arch': 'x86_64',
                 'request': [url, 'target', 'opts'],
                }, 'build (target, %s)' % module
            ],
            [
                {'method': 'build', 'arch': 'x86_64',
                 'request': ['n-v-r.src.rpm', 'target', 'opts']
                }, 'build (target, n-v-r.src.rpm)'
            ],
            [
                {'method': 'maven', 'arch': 'x86_64',
                 'request': ['https+git://git.server/path/module#branch', 'target', 'opts'],
                }, 'maven (target, %s)' % module
            ],
            [
                {'method': 'maven', 'arch': 'x86_64',
                 'request': ['n-v-r.jar', 'target', 'opts'],
                }, 'maven (target, n-v-r.jar)'
            ],
            [
                {'method': 'indirectionimage', 'arch': 'x86_64',
                 'request': [build],
                }, 'indirectionimage (n, v, r)'
            ],
            [
                {'method': 'buildSRPMFromSCM', 'arch': 'x86_64',
                 'request': [url, 'build_tag', 'opts']
                }, 'buildSRPMFromSCM (%s)' % module
            ],
            [
                {'method': 'buildArch', 'arch': 'x86_64',
                 'request': ['pkg', 'root', 'arch', True, 'opts'],
                }, 'buildArch (pkg, arch)'
            ],
            [
                {'method': 'buildMaven', 'arch': 'x86_64',
                 'request': [url, {'name': 'build_tag', 'id': 123}, {}],
                }, 'buildMaven (build_tag)',
            ],
            [
                {'method': 'wrapperRPM', 'arch': 'x86_64',
                 'request': [url, {'name': 'target'}, build, 'task']
                }, 'wrapperRPM (target, n-v-r)',
            ],
            # winbuild, vmExec (not in legacy signatures)
            [
                {'method': 'buildNotification', 'arch': 'x86_64',
                 'request': ['rpts', build, 'target', 'weburl']
                }, 'buildNotification (n-v-r)'
            ],
            [
                {'method': 'newRepo', 'arch': 'x86_64',
                 'request': ['tag', 123, 'src']
                }, 'newRepo (tag)'
            ],
            [
                {'method': 'distRepo', 'arch': 'x86_64',
                 'request': ['tag', 123, 'keys', 'task_opts']
                }, 'distRepo (tag)'
            ],
            [
                {'method': 'tagBuild', 'arch': 'x86_64',
                 'request': ['tag', 123, True, 'from', True],
                }, 'tagBuild (x86_64)'
            ],
            [
                {'method': 'tagNotification', 'arch': 'x86_64',
                 'request': ['rcpts', True, 'tag', 'from', build, 'user'],
                }, 'tagNotification (x86_64)'
            ],
            [
                {'method': 'createrepo', 'arch': 'x86_64',
                 'request': ['repo_id', 'arch', 'oldrepo']
                }, 'createrepo (arch)'
            ],
            [
                {'method': 'createdistrepo', 'arch': 'x86_64',
                 'request': ['tag', 'repo_id', 'arch', 'keys', 'opts']
                }, 'createdistrepo (repo_id, arch)'
            ],
            [
                {'method': 'dependantTask', 'arch': 'x86_64',
                 'request': ['wait_list', [[1], [2]]],
                }, 'dependantTask (1, 2)'
            ],
            [
                {'method': 'chainbuild', 'arch': 'x86_64',
                 'request': ['srcs', 'target', 'opts'],
                }, 'chainbuild (target)'
            ],
            [
                {'method': 'chainmaven', 'arch': 'x86_64',
                 'request': ['srcs', 'target', 'opts'],
                }, 'chainmaven (target)'
            ],
            [
                {'method': 'waitrepo', 'arch': 'x86_64',
                 'request': ['tag', 'newer', ['nvr1', 'nvr2']]
                }, 'waitrepo (tag, nvr1, nvr2)'
            ],
            [
                {'method': 'appliance', 'arch': 'x86_64',
                 'request': ['name', 'version', 'arch', 'target', 'ksfile', 'opts'],
                }, 'appliance (arch, name-version, ksfile)',
            ],
            [
                {'method': 'livecd', 'arch': 'x86_64',
                 'request': ['name', 'version', 'arch', 'target', 'ksfile', 'opts'],
                }, 'livecd (arch, name-version, ksfile)',
            ],
            [
                {'method': 'image', 'arch': 'x86_64',
                 'request': ['name', 'version', 'arches', 'target', 'inst_tree', 'opts'],
                }, 'image (arches, name-version, inst_tree)',
            ],
            [
                {'method': 'livemedia', 'arch': 'x86_64',
                 'request': ['name', 'version', 'arches', 'target', 'ksfile', 'opts'],
                }, 'livemedia (arches, name-version, ksfile)',
            ],
            [
                {'method': 'createLiveCD', 'arch': 'x86_64',
                 'request': ['name', 'version', 'release', 'arch', {'name': 'target'}, 'build_tag',
                             'repo_info', 'ksfile', 'opts'],
                }, 'createLiveCD (target, name-version-release, ksfile, arch)',
            ],
            [
                {'method': 'restart', 'arch': 'noarch',
                 'request': [{'name': 'hostname'}],
                }, 'restart (hostname)'
            ],
            [
                {'method': 'restartVerify', 'arch': 'noarch',
                 'request': [123, {'name': 'hostname'}],
                }, 'restartVerify (hostname)'
            ],
            [
                {'method': 'vmExec', 'arch': 'x86_64',
                 'request': ['name', 'task_info', 'opts'],
                 }, 'vmExec (name)'
            ],
            [
                {'method': 'winbuild', 'arch': 'x86_64',
                 'request': ['name', 'source_url', 'target', 'opts'],
                 }, 'winbuild (target, :source_url)'
            ],

        ]

        for input, output in test_data:
            result = koji.taskLabel(input)
            self.assertEqual(result, output)
Пример #12
0
for task_id in args:
    if not task_id.isdigit():
        parser.error('%s is not an integer task ID' % task_id)

    task_id = int(task_id)
    task = session.getTaskInfo(task_id, request=True)
    if not task:
        parser.error('Invalid task ID: %i' % task_id)
    elif task['state'] in (koji.TASK_STATES['FREE'], koji.TASK_STATES['OPEN']):
        parser.error('Task %i has not completed' % task['id'])
    elif task['state'] != koji.TASK_STATES['CLOSED']:
        parser.error('Task %i did not complete successfully' % task['id'])

    if task['method'] == 'build':
        print 'Getting rpms from children of task %i: %s' % (task['id'], koji.taskLabel(task))
        tasks = session.listTasks(opts={'parent': task_id, 'method': 'buildArch', 'state': [koji.TASK_STATES['CLOSED']],
                                        'decode': True})
    elif task['method'] == 'buildArch':
        tasks = [task]
    else:
        parser.error('Task %i is not a build or buildArch task' % task['id'])

    prog_meter = urlgrabber.progress.TextMeter()

    for task in tasks:
        if opts.arches:
            print 'Downloading %s rpms from task %i: %s' % (', '.join(opts.arches), task['id'], koji.taskLabel(task))
        else:
            print 'Downloading rpms from task %i: %s' % (task['id'], koji.taskLabel(task))
Пример #13
0
def fetch_koji_build(build):
    """
    build ==> buildID or NVR
    """

    if build.isdigit():
        build = int(build)

    urls = []  # output

    pathinfo = koji.PathInfo(topdir=topurl)
    session = koji.ClientSession(server)
    info = session.getBuild(build)
    # print session.listArchives(build)
    # rpms = session.listRPMs(buildID=info['id'])
    # if not rpms:
    #    print ":-("
    # for rpm in rpms:
    #    fname = pathinfo.rpm(rpm)
    #    url = pathinfo.build(info) + '/' + fname
    #    print url

    if not info:
        return

    task_id = info["task_id"]
    nvr = info.get("nvr", str(task_id))
    package = info.get("name", str(task_id))

    task = session.getTaskInfo(task_id, request=True)
    if not task:
        return

    found = False
    for item in task["request"]:
        if not isinstance(item, str):
            continue
        if re.match(build_target, item):
            found = True
            break
    if not found:
        print "skipping", build, task["request"]
        return

    if not task:
        print('Invalid task ID: %i' % task_id)
    elif task['state'] in (koji.TASK_STATES['FREE'], koji.TASK_STATES['OPEN']):
        print('Task %i has not completed' % task['id'])
    elif task['state'] != koji.TASK_STATES['CLOSED']:
        print('Task %i did not complete successfully' % task['id'])

    if task['method'] == 'build':
        print 'Getting rpms from children of task %i: %s' % (
            task['id'], koji.taskLabel(task))
        tasks = session.listTasks(
            opts={
                'parent': task_id,
                'method': 'buildArch',
                'state': [koji.TASK_STATES['CLOSED']],
                'decode': True
            })
    elif task['method'] == 'buildArch':
        tasks = [task]
    else:
        print('Task %i is not a build or buildArch task' % task['id'])

    for task in tasks:
        print ">>>>", task, task['id']
        arch = task.get('arch', 'unkwown')
        output = session.listTaskOutput(task['id'])
        print ">>>>", arch, output
        # logs = [filename for filename in output if filename.endswith('.log')]
        for item in output:
            base_path = koji.pathinfo.taskrelpath(task['id'])
            file_url = "%s/%s/%s" % (work_url, base_path, item)
            urls.append((arch, file_url))
            # print file_url
            # urls.append(file_url)

    # rpms = session.listRPMs(buildID=info['id'])
    # pathinfo = koji.PathInfo(topdir=topurl)
    # for rpm in rpms:
    # fname = koji.pathinfo.rpm(rpm)
    # url = os.path.join(pathinfo.build(info), fname)
    # print url
    # skip SRPMs and 32-bit RPMs
    # if not url.endswith("src.rpm") and not url.endswith("686.rpm"):
    #    urls.append(url)

    print "Getting", urls

    if not urls:
        return

    download_url(package, nvr, urls)

    return package, nvr, urls
def _populate_modulemd(module_ctx, module_exclusive_packages, api_rpms,
                       include_module_bootstrap):
    mod_md = modulemd.ModuleMetadata()
    mod_md.name = str(module_ctx.module)
    mod_md.description = str(module_ctx.module_desc)
    for lic in module_ctx.module_license:
        mod_md.add_module_license(str(lic))
    mod_md.community = str(module_ctx.community)
    mod_md.documentation = str(module_ctx.docs)
    mod_md.tracker = str(module_ctx.tracker)

    # All modules require base runtime
    mod_md.add_requires("base-runtime", "master")
    for req in module_ctx.module_deps:
        mod_md.add_requires(str(req), "master")

    # All modules build-require bootstrap
    mod_md.add_buildrequires("bootstrap", "master")
    for req in module_ctx.module_build_deps:
        mod_md.add_buildrequires(str(req), "master")
    if include_module_bootstrap:
        mod_md.add_buildrequires("%s-bootstrap" % str(module_ctx.module),
                                 "master")

    nvr_re = re.compile("\d+\:(.*).src$")
    nvr_values = list()
    for srpm_name, srpm_nvr in module_exclusive_packages.items():
        match = nvr_re.match(srpm_nvr)
        if match:
            nvr_values.append(match.group(1))
        else:
            raise IOError("NVR [%s] didnt parse" % srpm_nvr)

    ks = koji.ClientSession('https://koji.fedoraproject.org/kojihub')
    ks.multicall = True
    for srpm_nvr in nvr_values:
        ks.getBuild(srpm_nvr)
    ret = ks.multiCall(strict=True)
    ks.multicall = True
    task_nvrs = []
    for i in range(len(nvr_values)):
        if ret[i][0] is not None:
            if ret[i][0]['task_id'] is not None:
                ks.getTaskInfo(ret[i][0]['task_id'], request=True)
                task_nvrs.append(nvr_values[i])
            else:
                print("WARNING: no task ID for %s" % nvrs[i])
        else:
            print("WARNING: no task ID for %s" % nvrs[i])

    ret = ks.multiCall(strict=True)

    deps = dict()
    commit_re = re.compile("([^\/]+?):([a-f0-9]{40})")
    for i in range(len(task_nvrs)):
        match = commit_re.search(koji.taskLabel(ret[i][0]))
        if match:
            deps[match.group(1)] = match.group(2)
        else:
            raise IOError("Task [%s] didnt parse" %
                          (koji.taskLabel(ret[i][0])))

    for name in sorted(deps, key=deps.get):
        mod_md.components.add_rpm(name,
                                  "Automatically generated",
                                  ref=deps[name])

    for rpm in api_rpms:
        mod_md.api.add_rpm(rpm)

    return mod_md
Пример #15
0
def fetch_koji_build(build):
    """
    build ==> buildID or NVR
    """

    if build.isdigit():
        build = int(build)

    urls = []  # output

    pathinfo = koji.PathInfo(topdir=topurl)
    session = koji.ClientSession(server)
    info = session.getBuild(build)
    # print session.listArchives(build)
    # rpms = session.listRPMs(buildID=info['id'])
    # if not rpms:
    #    print ":-("
    # for rpm in rpms:
    #    fname = pathinfo.rpm(rpm)
    #    url = pathinfo.build(info) + '/' + fname
    #    print url

    if not info:
        return

    task_id = info["task_id"]
    nvr = info.get("nvr", str(task_id))
    package = info.get("name", str(task_id))

    task = session.getTaskInfo(task_id, request=True)
    if not task:
        return

    found = False
    for item in task["request"]:
        if not isinstance(item, str):
            continue
        if re.match(build_target, item):
            found = True
            break
    if not found:
        print "skipping", build, task["request"]
        return

    if not task:
        print ("Invalid task ID: %i" % task_id)
    elif task["state"] in (koji.TASK_STATES["FREE"], koji.TASK_STATES["OPEN"]):
        print ("Task %i has not completed" % task["id"])
    elif task["state"] != koji.TASK_STATES["CLOSED"]:
        print ("Task %i did not complete successfully" % task["id"])

    if task["method"] == "build":
        print "Getting rpms from children of task %i: %s" % (task["id"], koji.taskLabel(task))
        tasks = session.listTasks(
            opts={"parent": task_id, "method": "buildArch", "state": [koji.TASK_STATES["CLOSED"]], "decode": True}
        )
    elif task["method"] == "buildArch":
        tasks = [task]
    else:
        print ("Task %i is not a build or buildArch task" % task["id"])

    for task in tasks:
        print ">>>>", task, task["id"]
        arch = task.get("arch", "unkwown")
        output = session.listTaskOutput(task["id"])
        print ">>>>", arch, output
        # logs = [filename for filename in output if filename.endswith('.log')]
        for item in output:
            base_path = koji.pathinfo.taskrelpath(task["id"])
            file_url = "%s/%s/%s" % (work_url, base_path, item)
            urls.append((arch, file_url))
            # print file_url
            # urls.append(file_url)

    # rpms = session.listRPMs(buildID=info['id'])
    # pathinfo = koji.PathInfo(topdir=topurl)
    # for rpm in rpms:
    # fname = koji.pathinfo.rpm(rpm)
    # url = os.path.join(pathinfo.build(info), fname)
    # print url
    # skip SRPMs and 32-bit RPMs
    # if not url.endswith("src.rpm") and not url.endswith("686.rpm"):
    #    urls.append(url)

    print "Getting", urls

    if not urls:
        return

    download_url(package, nvr, urls)

    return package, nvr, urls
Пример #16
0
    def update(self):
        # Get Lastest Info
        last_info = self.info

        self.info = self.session.getTaskInfo(self.id, request=True)
        self.info["label"] = koji.taskLabel(self.info)
        self.info["state_str"] = koji.TASK_STATES[self.info["state"]]

        # Write the new job state to the trace
        if last_info:
            if self.info["state"] != last_info["state"]:
                logging.getLogger(LOG_KOJI_TASK).info(
                        "Koji Task {old_state} => {new_state}"
                            .format(
                                task_id=self.id, 
                                old_state=last_info["state_str"], 
                                new_state=self.info["state_str"]),
                        extra={ "koji_task_id" : self.id, "gitlab_job_id" : self.job["id"] })

                if self.trace_callback is not None: 
                    self.trace_callback(self.session, None, "%(id)s %(state_str)s %(label)s\n" % self.info)
        else:
            if self.trace_callback is not None: 
                self.trace_callback(self.session, None, "%(id)s %(state_str)s %(label)s\n" % self.info)

        # Discover Child Tasks
        for child_task in self.session.getTaskChildren(self.id):
            child_task_id = child_task['id']

            if child_task_id not in self.child_tasks:
                logging.getLogger(LOG_KOJI_TASK).info(
                        "Discovered new child task {child_task_id}"
                            .format(child_task_id = child_task_id),
                            extra={ "koji_task_id" : self.id, "gitlab_job_id" : self.job["id"] })

                self.child_tasks[child_task_id] = \
                        KojiTaskWatcher(\
                            self.job, \
                            child_task_id, \
                            self.session, \
                            self.trace_callback, \
                            self.artifact_callback)

        # Update child tasks
        all_children_done = True
        for child_task_id in self.child_tasks:
            if self.child_tasks[child_task_id].update():
                all_children_done = False

        # Get log updates/output
        output_files = self.session.listTaskOutput(self.id)

        for file_name in output_files:
            full_file_name = os.path.join(str(self.id), file_name)

            while True:
                if file_name not in self.file_offsets:
                    self.file_offsets[file_name] = 0

                logging.getLogger(LOG_KOJI_TASK_OUTPUT).debug(
                        "Downloading {file_name} starting at offset {offset}"\
                            .format(file_name = full_file_name, offset = self.file_offsets[file_name]),
                            extra={ "koji_task_id" : self.id, "gitlab_job_id" : self.job["id"] })

                contents = self.session.downloadTaskOutput(self.id, file_name, self.file_offsets[file_name], 16384)

                logging.getLogger(LOG_KOJI_TASK_OUTPUT).debug(
                        "Downloaded {content_length} bytes of {file_name} starting at offset {offset}"
                            .format(file_name = full_file_name, offset = self.file_offsets[file_name], content_length = len(contents)),
                            extra={ "koji_task_id" : self.id, "gitlab_job_id" : self.job["id"] })

                if len(contents) > 0:
                    if file_name.endswith('.log') and self.trace_callback is not None: 
                        self.trace_callback(self.session, "Task:%d - %s" % (self.id, file_name,), contents)

                    if self.artifact_callback is not None: 
                        self.artifact_callback(self.session, full_file_name, contents, self.file_offsets[file_name])

                    self.file_offsets[file_name] += len(contents)
                else:
                    break

        logging.getLogger(LOG_KOJI_TASK).debug(
                "Koji Task - State: {state}, Done: {is_done}"
                    .format(
                        task_id=self.id, 
                        state=self.info["state_str"],
                        is_done=self.is_done()),
                extra={ "koji_task_id" : self.id, "gitlab_job_id" : self.job["id"] })

        # Return True until we and our childeren have finished
        return not (self.is_done() and all_children_done)