Skip to content
Snippets Groups Projects
inau.py 71.6 KiB
Newer Older
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True).\
                    joinedload('facility', innerjoin=True)).\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    order_by(Installations.date.desc()).all()
        elif args['mode'] == 'diff':
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True).\
                    joinedload('facility', innerjoin=True)).\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    filter(Installations.type != int(InstallationType.GLOBAL)).\
                    order_by(Installations.date.desc()).all()
        else: # history
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True).\
                    joinedload('facility', innerjoin=True)).\
                order_by(Installations.date.desc()).all()
        retval = []
        for installation in installations:
            retval.append({ 'facility': installation.host.facility.name, 
                'host': installation.host.name,
                'repository': installation.build.repository.name,
                'tag': installation.build.tag, 'date': installation.date,
                'author': installation.user.name })
        return retval, 200 if len(retval) else 204
    @marshal_with(cs_installations_fields)
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('repository', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. repository=fake)')
        parser.add_argument('tag', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. tag=0.0.4)')
        args = parser.parse_args(strict=True)
        username = authenticate(AuthenticationType.USER, request)
        destinations = {}
        for repository in Repositories.query.\
                filter(Repositories.name == args['repository']).\
                all():
            for server in repository.platform.servers:
                for host in server.hosts:
                    try:
                        destinations[host.server].add(host)
                    except KeyError:
                        destinations[host.server] = {host}
        return install(username, args['repository'], args['tag'],
                destinations, InstallationType.GLOBAL)

facility_installations_fields = { 'host': fields.String(),
        'repository': fields.String(), 'tag': fields.String(),
        'date': fields.DateTime(), 'author': fields.String() }
class FacilityInstallationsHandler(Resource):
    @marshal_with(facility_installations_fields)
    def get(self, facilityname):
        facility = Facilities.query.\
                filter(Facilities.name == facilityname).\
                first_or_404()
        args = mode_parser.parse_args(strict=True)
        LatestInstallations = db.session.query(Installations)\
                .with_entities(Repositories.id, Installations.host_id,\
                    func.max(Installations.id).label('installation_id'))\
                .select_from(Installations)\
                .join(Builds).join(Repositories)\
                .group_by(Repositories.id, Installations.host_id)\
                .subquery()
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
        if args['mode'] == 'status':
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    join('host').\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    filter(Hosts.facility == facility).\
                    order_by(Installations.date.desc()).all()
        elif args['mode'] == 'diff':
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    join('host').\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    filter(Hosts.facility == facility,
                            Installations.type == int(InstallationType.HOST)).\
                    order_by(Installations.date.desc()).all()
        else: # history
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    join('host').\
                    filter(Hosts.facility == facility).\
                    order_by(Installations.date.desc()).all()
        retval = []
        for installation in installations:
            retval.append({ 'host': installation.host.name,
                'repository': installation.build.repository.name,
                'tag': installation.build.tag, 'date': installation.date,
                'author': installation.user.name })
        return retval, 200 if len(retval) else 204
    @marshal_with(facility_installations_fields)
    def post(self, facilityname):
        facility = Facilities.query.filter(Facilities.name == facilityname) \
                .first_or_404()
        parser = reqparse.RequestParser()
        parser.add_argument('repository', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. repository=fake)')
        parser.add_argument('tag', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. tag=0.0.4)')
        args = parser.parse_args(strict=True)
        username = authenticate(AuthenticationType.USER, request)
        destinations = {}
        for repository in Repositories.query.\
                filter(Repositories.name == args['repository']).\
                all():
                    for server in repository.platform.servers:
                        for host in server.hosts:
                            if host.facility_id != facility.id:
                                continue
                            try:
                                destinations[host.server].add(host)
                            except KeyError:
                                destinations[host.server] = {host}
        return install(username, args['repository'], args['tag'], 
                destinations, InstallationType.FACILITY)

host_installations_fields = { 'repository': fields.String(),
        'tag': fields.String(), 'date': fields.DateTime(),
        'author': fields.String() }
class HostInstallationsHandler(Resource):
    @marshal_with(host_installations_fields)
    def get(self, facilityname, hostname):
        host = Hosts.query.join('facility').\
                filter(Facilities.name == facilityname,
                        Hosts.name == hostname).\
                first_or_404()
        args = mode_parser.parse_args(strict=True)
        LatestInstallations = db.session.query(Installations)\
                .with_entities(Repositories.id, Installations.host_id,\
                    func.max(Installations.id).label('installation_id'))\
                .select_from(Installations)\
                .join(Builds).join(Repositories)\
                .group_by(Repositories.id, Installations.host_id)\
                .subquery()
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
        if args['mode'] == 'status':
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    filter(Installations.host == host).\
                    order_by(Installations.date.desc()).all()
        elif args['mode'] == 'diff':
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    join(LatestInstallations, Installations.id == LatestInstallations.c.installation_id).\
                    filter(Installations.host == host,
                            Installations.type == int(InstallationType.HOST)).\
                    order_by(Installations.date.desc()).all()
        else: # history
            installations = Installations.query.options(
                    joinedload('user', innerjoin=True),\
                    joinedload('build', innerjoin=True).\
                    joinedload('repository', innerjoin=True),
                    joinedload('host', innerjoin=True)).\
                    filter(Installations.host == host).\
                    order_by(Installations.date.desc()).all()
        retval = []
        for installation in installations:
            retval.append({ 'repository': installation.build.repository.name,
                'tag': installation.build.tag, 'date': installation.date,
                'author': installation.user.name })
        return retval, 200 if len(retval) else 204
    @marshal_with(host_installations_fields)
    def post(self, facilityname, hostname):
        host = Hosts.query.options(joinedload('facility', innerjoin=True)) \
                .filter(Facilities.name == facilityname, Hosts.name == hostname) \
                .first_or_404()
        parser = reqparse.RequestParser()
        parser.add_argument('repository', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. repository=fake)')
        parser.add_argument('tag', required=True, trim=True, nullable=False,
                type=non_empty_string, help='{error_msg} (e.g. tag=0.0.4)')
        args = parser.parse_args(strict=True)
        username = authenticate(AuthenticationType.USER, request)
        destinations = {}
        destinations[host.server] = {host}
        return install(username, args['repository'], args['tag'], 
                destinations, InstallationType.HOST)
        return {}

def retrieveAnnotatedTags(gitrepo):
    atags = set()
    for atag in gitrepo.tags:
        if atag.tag != None:
            atags.add(atag)
    return atags

def updateRepo(repo):
    gitrepo = None
    atagsBefore = set()
    repoPath = app.config['GIT_TREES_DIR'] + repo.name
    if os.path.isdir(repoPath):
        gitrepo = git.Repo(repoPath)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
        gitrepo.git.clean("-fdx")
        gitrepo.git.reset('--hard')
        gitrepo.git.checkout("master")
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
        atagsBefore = retrieveAnnotatedTags(gitrepo)
        gitrepo.remotes.origin.fetch()
        gitrepo.submodule_update(recursive=True, init=False, force_reset=True)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
    else:
        gitrepo = git.Repo.clone_from(repo.provider.url + repo.name, to_path=repoPath)
        gitrepo.submodule_update(recursive=True, init=True, force_reset=False)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
    atagsAfter = set()
    atagsAfter = retrieveAnnotatedTags(gitrepo)
    return gitrepo, atagsAfter - atagsBefore

def build():
    try:
        with db.app.app_context():
            print("Checking makefiles repository for updates...") 
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
            updateRepo(Repositories.query.filter(Repositories.name == "makefiles").first())
            for distinctRepo in Repositories.query.with_entities(Repositories.name) \
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                    .filter(Repositories.name != "makefiles").filter(Repositories.provider_id != 11).distinct().all():
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                try:
                    print("Checking " + distinctRepo.name  + " repository for updates... ") 
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                    gitrepo, newAtags = updateRepo(Repositories.query \
                            .filter(Repositories.name == distinctRepo.name).first())
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                    for atag in newAtags:
                        for repo in Repositories.query \
                                .filter(Repositories.name == distinctRepo.name).all():
                            builder = Builders.query \
                                    .filter(Builders.platform_id == repo.platform.id).first()
                            if builder is None:
                                raise Exception("Missing builder")
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                            print("Checkout " + str(atag) + " of the reporitory " + repo.name + "...") 
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                            gitrepo.git.checkout(str(atag))
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                            try:
                                with paramiko.SSHClient() as sshClient:
                                    sshClient.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                                    sshClient.connect(hostname=builder.name, port=22, username="inau",
                                            key_filename="/home/inau/.ssh/id_rsa.pub")
                                    print("Start building " + str(atag) + " from " + repo.name 
                                            + " git repo on " + builder.name + "...")
                                    stdout, _, exitStatus = execSyncedCommand(sshClient,
                                            "source /etc/profile; cd " + app.config['GIT_TREES_DIR'] + repo.name + 
                                            "&& make clean; (test -f *.pro && qmake && cuuimake --plain-text-output); make 2>&1")
                                    print("Building " + str(atag) + " from " + repo.name 
                                            + " git repo on " + builder.name + " finished with code: " + str(exitStatus))
                                    build = Builds(repository_id=repo.id, platform_id=repo.platform_id, tag=str(atag), status=exitStatus, output=stdout) 
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                                    db.session.add(build)
                                    db.session.commit()
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                                    if exitStatus == 0:
                                        outcome = repo.name + " " + str(atag) + ": built successfully on " + builder.name

                                        dir = "/not-existing-directory-which-should-produce-an-error/"
                                        if repo.type == RepositoryType.cplusplus or repo.type == RepositoryType.python \
                                                or repo.type == RepositoryType.shellscript:
                                            dir = "/bin/"
                                        else: # repo.type == RepositoryType.configuration
                                            dir = "/etc/"

                                        print("Looking for file(s) in " + dir + "...")

                                        basedir = app.config['GIT_TREES_DIR'] + repo.name + dir
                                        for r, d, f in os.walk(basedir):
                                            dir = ""
                                            if r != basedir:
                                                dir = os.path.basename(r) + "/"
                                            for file in f:
                                                hashFile = ""
                                                with open(basedir + dir + file,"rb") as fd:
                                                    bytes = fd.read()
                                                    hashFile = hashlib.sha256(bytes).hexdigest();
                                                    if not os.path.isfile(app.config['FILES_STORE_DIR'] + hashFile):
                                                        print("Install " + basedir + dir + file + " in the file-store as " + hashFile + "...")
                                                        shutil.copyfile(basedir + dir + file, app.config['FILES_STORE_DIR'] + hashFile, follow_symlinks=False)
                                                artifact = Artifacts(build_id=build.id, hash=hashFile, filename=dir+file)
                                                db.session.add(artifact)
                                                db.session.commit()
                                    else:
                                        outcome = repo.name + " " + str(atag) + ": built failed on " + builder.name
                                    print("Send email to", atag.tag.tagger.email)
                                    sendEmail([atag.tag.tagger.email], outcome, stdout)
                                    if str([atag.tag.tagger.email]) != str([atag.tag.object.author.email]):
                                        print("Send email to", atag.tag.object.author.email)
                                        sendEmail([atag.tag.object.author.email], outcome, stdout)
                            except Exception as e:
                                print("Error on " + distinctRepo.name + " repository: ", e)
                                sendEmailAdmins("Error on " + distinctRepo.name + " repository", e)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
                except Exception as e:
                    print("Error on " + distinctRepo.name + " repository: ", e)
                    sendEmailAdmins("Error on " + distinctRepo.name + " repository", e)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
    except Exception as e:
        sendEmailAdmins("Error on makefiles repository", e)
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed


if __name__ == '__main__':
    # Configure Flask
    app.config['SQLALCHEMY_DATABASE_URI'] = args.db
    app.config['MAIL_SERVER'] = args.smtpserver
    app.config['MAIL_DOMAIN'] = args.smtpdomain
    app.config['MAIL_DEFAULT_SENDER'] = args.smtpsender + "@" + args.smtpdomain
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
    app.config['FILES_STORE_DIR'] = args.store
    app.config['GIT_TREES_DIR'] = args.repo
    app.config['LDAP_URL'] = args.ldap
    app.config['BUNDLE_ERRORS'] = True
    app.config['JOBS'] = [{'id': 'builder', 'func': build,
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    if args.port != "443":
        app.debug = True
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed
        app.config['SQLALCHEMY_ECHO'] = True

    # Configure SQLAclhemy
    db.app = app
    db.init_app(app)

    # Create and configure APScheduler
#    sched = APScheduler()
#    sched.init_app(app)
#    sched.start()
Alessio Igor Bogani's avatar
Alessio Igor Bogani committed

    # Create all DB tables if necessary
    db.create_all()

    # Creates REST endpoints
    v2.add_resource(CSHandler, '/cs', '/cs/')
    v2.add_resource(UsersHandler, '/cs/users', '/cs/users/')
    v2.add_resource(UserHandler, '/cs/users/<string:username>',
            '/cs/users/<string:username>/')
    v2.add_resource(ArchitecturesHandler, '/cs/architectures', '/cs/architectures/')
    v2.add_resource(ArchitectureHandler, '/cs/architectures/<string:archname>',
            '/cs/architectures/<string:archname>/')
    v2.add_resource(DistributionsHandler, '/cs/distributions', '/cs/distributions/')
    v2.add_resource(DistributionHandler, '/cs/distributions/<string:distroid>',
            '/cs/distributions/<string:distroid>/')
    v2.add_resource(PlatformsHandler, '/cs/platforms', '/cs/platforms/')
    v2.add_resource(PlatformHandler, '/cs/platforms/<int:platid>',
            '/cs/platforms/<int:platid>/')
    v2.add_resource(BuildersHandler,'/cs/builders', '/cs/builders/')
    v2.add_resource(BuilderHandler, '/cs/builders/<string:buildername>',
            '/cs/builders/<string:buildername>/')
    v2.add_resource(ServersHandler, '/cs/servers', '/cs/servers/')
    v2.add_resource(ServerHandler, '/cs/servers/<string:servername>',
            '/cs/servers/<string:servername>/')
    v2.add_resource(ProvidersHandler, '/cs/providers', '/cs/providers/')
    v2.add_resource(ProviderHandler, '/cs/servers/<int:providerid>',
            '/cs/providers/<int:providerid>/')
    v2.add_resource(RepositoriesHandler, '/cs/repositories', '/cs/repositories/')
    v2.add_resource(RepositoryHandler, '/cs/repositories/<int:repositoryid>',
            '/cs/repositories/<int:repositoryid>/')
    v2.add_resource(FacilitiesHandler, '/cs/facilities', '/cs/facilities/')
    v2.add_resource(FacilityHandler, '/cs/facilities/<string:facilityname>',
            '/cs/facilities/<string:facilityname>/')
    v2.add_resource(HostsHandler, '/cs/facilities/<string:facilityname>/hosts',
            '/cs/facilities/<string:facilityname>/hosts/')
    v2.add_resource(HostHandler, 
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>',
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/')
    v2.add_resource(FilesHandler, 
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/files',
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/files/')
    v2.add_resource(FileHandler,
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/files/<string:filename>',
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/files/<string:filename>/')

    v2.add_resource(CSInstallationsHandler, '/cs/installations',
            '/cs/installations/', '/cs/facilities/installations',
            '/cs/facilities/installations/')
    v2.add_resource(FacilityInstallationsHandler, 
            '/cs/facilities/<string:facilityname>/installations', 
            '/cs/facilities/<string:facilityname>/installations/', 
            '/cs/facilities/<string:facilityname>/hosts/installations',
            '/cs/facilities/<string:facilityname>/hosts/installations/')
    v2.add_resource(HostInstallationsHandler,
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/installations',
            '/cs/facilities/<string:facilityname>/hosts/<string:hostname>/installations/')

    # Start Flask (reloader is not compatible with APScheduler)
    if args.port != "443":
        app.run(host='0.0.0.0', port=args.port, threaded=True,
                use_reloader=False, use_debugger=False)
    else:
        app.run(host='0.0.0.0', port=args.port, threaded=True,
                ssl_context=('/etc/ssl/certs/inau_elettra_eu.pem',
                    '/etc/ssl/private/inau_elettra_eu.key'),
                use_reloader=False, use_debugger=False)