Esempio n. 1
0
def get_selection(available, selection, base='/scif/apps'):
    '''we compare the basename (the exp_id) of the selection and available, 
       regardless of parent directories'''

    if isinstance(selection, str):
        selection = selection.split(',')

    available = [os.path.basename(x) for x in available]
    selection = [os.path.basename(x) for x in selection]
    finalset = [x for x in selection if x in available]
    if len(finalset) == 0:
        bot.warning("No user experiments selected, providing all %s" %(len(available)))
        finalset = available
    return ["%s/%s" %(base,x) for x in finalset]
Esempio n. 2
0
def get_selection(available, selection, base="/scif/apps"):
    """we compare the basename (the exp_id) of the selection and available, 
       regardless of parent directories"""

    if isinstance(selection, str):
        selection = selection.split(",")

    available = [os.path.basename(x) for x in available]
    selection = [os.path.basename(x) for x in selection]
    finalset = [x for x in selection if x in available]
    if len(finalset) == 0:
        bot.warning("No user experiments selected, providing all %s" %
                    (len(available)))
        finalset = available
    return ["%s/%s" % (base, x) for x in finalset]
Esempio n. 3
0
def main(args, parser, subparser=None):
    """this is the main entrypoint for a container based web server, with
       most of the variables coming from the environment. See the Dockerfile
       template for how this function is executed.

    """

    # First priority to args.base
    base = args.base
    if base is None:
        base = os.environ.get("EXPFACTORY_BASE")

    # Does the base folder exist?
    if base is None:
        bot.error("You must set a base of experiments with --base" % base)
        sys.exit(1)

    if not os.path.exists(base):
        bot.error("Base folder %s does not exist." % base)
        sys.exit(1)

    # Export environment variables for the client
    experiments = args.experiments
    if experiments is None:
        experiments = " ".join(glob("%s/*" % base))

    os.environ["EXPFACTORY_EXPERIMENTS"] = experiments

    # If defined and file exists, set runtime variables
    if args.vars is not None:
        if os.path.exists(args.vars):
            os.environ["EXPFACTORY_RUNTIME_VARS"] = args.vars
            os.environ["EXPFACTORY_RUNTIME_DELIM"] = args.delim
        else:
            bot.warning("Variables file %s not found." % args.vars)

    subid = os.environ.get("EXPFACTORY_STUDY_ID")
    if args.subid is not None:
        subid = args.subid
        os.environ["EXPFACTORY_SUBID"] = subid

    os.environ["EXPFACTORY_RANDOM"] = str(args.disable_randomize)
    os.environ["EXPFACTORY_BASE"] = base

    from expfactory.server import start

    start(port=5000)
Esempio n. 4
0
def main(args,parser,subparser=None):
    '''this is the main entrypoint for a container based web server, with
       most of the variables coming from the environment. See the Dockerfile
       template for how this function is executed.

    '''

    # First priority to args.base
    base = args.base
    if base is None:
        base = os.environ.get('EXPFACTORY_BASE')

    # Does the base folder exist?
    if base is None:
        bot.error("You must set a base of experiments with --base" % base)
        sys.exit(1)

    if not os.path.exists(base):
        bot.error("Base folder %s does not exist." % base)
        sys.exit(1)

    # Export environment variables for the client
    experiments = args.experiments
    if experiments is None:
        experiments = " ".join(glob("%s/*" % base))

    os.environ['EXPFACTORY_EXPERIMENTS'] = experiments

    # If defined and file exists, set runtime variables
    if args.vars is not None:
        if os.path.exists(args.vars):
            os.environ['EXPFACTORY_RUNTIME_VARS'] = args.vars
            os.environ['EXPFACTORY_RUNTIME_DELIM'] = args.delim
        else:
            bot.warning('Variables file %s not found.' %args.vars)


    subid = os.environ.get('EXPFACTORY_STUDY_ID')
    if args.subid is not None:
        subid = args.subid 
        os.environ['EXPFACTORY_SUBID'] = subid

    os.environ['EXPFACTORY_RANDOM'] = str(args.disable_randomize)
    os.environ['EXPFACTORY_BASE'] = base
    
    from expfactory.server import start
    start(port=5000)
Esempio n. 5
0
    def validate(self, folder):
        '''validate is the first entrypoint function for running an experiment
           or survey robot. It ensures that the content is valid,
           and then calls _validate (should be defined in subclass)'''
            
        validator = ExperimentValidator()
        valid = validator.validate(folder)

        if valid is True:

            # IF missing favicon, add
            self._check_favicon(folder)

            valid = self._validate(folder)
            bot.log("[done] stopping web server...")
            self.httpd.server_close()
        else:
            bot.warning('%s is not valid, skipping robot testing.' %folder)
Esempio n. 6
0
    def initdb(self):
        '''initdb will check for writability of the data folder, meaning
           that it is bound to the local machine. If the folder isn't bound,
           expfactory runs in demo mode (not saving data)
        '''

        self.database = EXPFACTORY_DATABASE
        bot.info("DATABASE: %s" %self.database)

        # Supported database options
        valid = ('sqlite', 'postgres', 'mysql', 'filesystem')
        if not self.database.startswith(valid):
            bot.warning('%s is not yet a supported type, saving to filesystem.' % self.database)
            self.database = 'filesystem'

        # Add functions specific to database type
        self.init_db() # uses url in self.database

        bot.log("Data base: %s" % self.database)
Esempio n. 7
0
    def initdb(self):
        '''initdb will check for writability of the data folder, meaning
           that it is bound to the local machine. If the folder isn't bound,
           expfactory runs in demo mode (not saving data)
        '''

        self.database = EXPFACTORY_DATABASE
        bot.info("DATABASE: %s" %self.database)

        # Supported database options
        valid = ('sqlite', 'postgres', 'mysql', 'filesystem')
        if not self.database.startswith(valid):
            bot.warning('%s is not yet a supported type, saving to filesystem.' % self.database)
            self.database = 'filesystem'

        # Add functions specific to database type
        self.init_db() # uses url in self.database

        bot.log("Data base: %s" % self.database)
Esempio n. 8
0
    def _validate_preview(self, url):

        bot.test("Experiment url: %s" % url)
        org, repo = url.split("/")[-2:]
        if repo.endswith(".git"):
            repo = repo.replace(".git", "")
        github_pages = "https://%s.github.io/%s" % (org, repo)
        bot.test("Github Pages url: %s" % github_pages)

        response = requests.get(github_pages)

        if response.status_code == 404:
            bot.error("""Preview not found at %s. You must publish a static 
                         preview from the master branch of your repository to
                         add to the library.""" % github_pages)
            return False

        index = response.text
        tmpdir = tempfile.mkdtemp()
        repo_master = clone(url, tmpdir)
        contenders = glob("%s/*" % repo_master)
        license = False
        found = False

        for test in contenders:
            if os.path.isdir(test):
                continue
            print("...%s" % test)
            if "LICENSE" in os.path.basename(test):
                license = True
            if os.path.basename(test) == "index.html":
                bot.test("Found index file in repository.")
                found = True
                break

        if license is False:
            bot.warning(
                "LICENSE file not found. This will be required for future experiments!"
            )

        self._print_valid(found)
        return found
Esempio n. 9
0
    def _validate_preview(self, url):

        bot.test('Experiment url: %s' %url)
        org,repo = url.split('/')[-2:]
        if repo.endswith('.git'):
            repo = repo.replace('.git','')
        github_pages =  "https://%s.github.io/%s" %(org,repo)
        bot.test('Github Pages url: %s' %github_pages)

        response = requests.get(github_pages)

        if response.status_code == 404:
            bot.error('''Preview not found at %s. You must publish a static 
                         preview from the master branch of your repository to
                         add to the library.''' % github_pages)
            return False 

        index = response.text
        tmpdir = tempfile.mkdtemp()
        repo_master = clone(url, tmpdir)
        contenders = glob('%s/*' %repo_master)
        license = False
        found = False

        for test in contenders:
            if os.path.isdir(test):
                continue
            print('...%s' %test)
            if "LICENSE" in os.path.basename(test):
                license = True
            if os.path.basename(test) == "index.html":
                bot.test('Found index file in repository.')
                found = True
                break

        if license is False:
            bot.warning("LICENSE file not found. This will be required for future experiments!")

        self._print_valid(found)
        return found
Esempio n. 10
0
def save_data(self, session, exp_id, content):
    '''save data will obtain the current subid from the session, and save it
       depending on the database type. Currently we just support flat files'''

    subid = session.get('subid')

    # We only attempt save if there is a subject id, set at start
    data_file = None
    if subid is not None:

        if os.path.exists(self.data_base):  # /scif/data
            data_base = "%s/%s" % (self.data_base, subid)
            # expfactory/00001
            if not os.path.exists(data_base):
                os.mkdir(data_base)
            data_file = "%s/%s/%s-results.json" % (self.data_base, subid,
                                                   exp_id)
            if os.path.exists(data_file):
                bot.warning('%s exists, and is being overwritten.' % data_file)
            write_json(content, data_file)

    return data_file
Esempio n. 11
0
def _validate_row(row, sep=',', required_length=None):
    '''validate_row will ensure that a row has the proper length, and is
       not empty and cleaned of extra spaces.
 
       Parameters
       ==========
       row: a single row, not yet parsed.

       Returns a valid row, or None if not valid

    '''
    if not isinstance(row, list):
        row = _parse_row(row, sep)

    if required_length:
        length = len(row)
        if length != required_length:
            bot.warning('Row should have length %s (not %s)' %(required_length,
                                                               length))
            bot.warning(row) 
            row = None

    return row
Esempio n. 12
0
def _validate_row(row, sep=",", required_length=None):
    """validate_row will ensure that a row has the proper length, and is
       not empty and cleaned of extra spaces.
 
       Parameters
       ==========
       row: a single row, not yet parsed.

       Returns a valid row, or None if not valid

    """
    if not isinstance(row, list):
        row = _parse_row(row, sep)

    if required_length:
        length = len(row)
        if length != required_length:
            bot.warning("Row should have length %s (not %s)" %
                        (required_length, length))
            bot.warning(row)
            row = None

    return row
Esempio n. 13
0
def main(args, parser, subparser):

    # List of experiments is required
    template = get_template('build/docker/Dockerfile.template')

    # For now, only one database provided
    database = args.database
    studyid = args.studyid
    experiments = args.experiments

    template = sub_template(template, "{{studyid}}", studyid)
    template = sub_template(template, "{{database}}", database)

    library = get_library(key='name')

    apps = "\n"

    # Add local experiments to library, first preference
    local_installs = 0
    for experiment in experiments:
        if os.path.exists(experiment):

            bot.info('local experiment %s found, validating...' % experiment)

            # Is the experiment valid?
            cli = ExperimentValidator()
            valid = cli.validate(experiment, cleanup=False)

            if valid is True:
                local_installs += 1
                config = load_experiment(experiment)
                exp_id = config['exp_id']

                # If we aren't building in the experiment directory, we need to copy there
                output_dir = "%s/%s" % (os.path.abspath(
                    os.path.dirname(args.output)), exp_id)
                experiment_dir = os.path.abspath(experiment)
                if output_dir != experiment_dir:
                    copy_directory(experiment_dir, output_dir)

                config['local'] = os.path.abspath(experiment)
                library[exp_id] = config

    # Warn the user that local installs are not reproducible (from recipe)
    if local_installs > 0:
        bot.warning(
            "%s local installs detected: build is not reproducible without experiment folders"
            % local_installs)

    # Build Image with Experiments
    for experiment in experiments:
        exp_id = os.path.basename(experiment)
        if exp_id in library:
            config = library[exp_id]

            app = "LABEL EXPERIMENT_%s /scif/apps/%s\n" % (exp_id, exp_id)

            # Here add custom build routine, should be list of lines
            if "install" in config:
                commands = "\n".join(
                    ["RUN %s " % s for x in config['install']]).strip('\n')
                app = "%s%s\n" % (app, commands)

            # The final installation step, either from Github (url) or local folder
            if "local" in config:
                app = "%sADD %s /scif/apps/%s\n" % (app, exp_id, exp_id)
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" % (
                    app, exp_id)
            else:
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" % (
                    app, config['github'])
            apps = "%s%s\n" % (apps, app)

        else:
            bot.warning('%s not in library, check spelling & punctuation.' %
                        exp_id)

    if apps == "\n":
        bot.error('No valid experiments found, cancelling recipe build.')
        sys.exit(1)

    template = sub_template(template, "{{experiments}}", apps)
    outfile = write_file(args.output, template)
    bot.log("Recipe written to %s" % outfile)
Esempio n. 14
0
def generate_runtime_vars(variable_file=None, sep=","):
    """generate a lookup data structure from a 
       delimited file. We typically obtain the file name and delimiter from
       the environment by way of EXPFACTORY_RUNTIME_VARS, and
       EXPFACTORY_RUNTIME_DELIM, respectively, but the user can also parse
       from a custom variable file by way of specifying it to the function
       (preference is given here). The file should be csv, with the
       only required first header field as "token" and second as "exp_id" to
       distinguish the participant ID and experiment id. The subsequent
       columns should correspond to experiment variable names. No special parsing
       of either is done. 

       Parameters
       ==========
       variable_file: full path to the tabular file with token, exp_id, etc.
       sep: the default delimiter to use, if not set in enironment.

       Returns
       =======
       varset: a dictionary lookup by exp_id and then participant ID.

       { 'test-parse-url': {
                             '123': {
                                      'color': 'red',
                                      'globalname': 'globalvalue',
                                      'words': 'at the thing'
                                    },

                             '456': {'color': 'blue',
                                     'globalname': 'globalvalue',
                                     'words': 'omg tacos'}
                              }
       }

    """

    # First preference goes to runtime, then environment, then unset

    if variable_file is None:
        if EXPFACTORY_RUNTIME_VARS is not None:
            variable_file = EXPFACTORY_RUNTIME_VARS

    if variable_file is not None:
        if not os.path.exists(variable_file):
            bot.warning("%s is set, but not found" % variable_file)
            return variable_file

    # If still None, no file
    if variable_file is None:
        return variable_file

    # If we get here, we have a variable file that exists
    delim = sep
    if EXPFACTORY_RUNTIME_DELIM is not None:
        delim = EXPFACTORY_RUNTIME_DELIM
    bot.debug("Delim for variables file set to %s" % sep)

    # Read in the file, generate config

    varset = dict()
    rows = _read_runtime_vars(variable_file)

    if len(rows) > 0:

        # When we get here, we are sure to have
        # 'exp_id', 'var_name', 'var_value', 'token'

        for row in rows:

            exp_id = row[0].lower()  # exp-id must be lowercase
            var_name = row[1]
            var_value = row[2]
            token = row[3]

            # Level 1: Experiment ID
            if exp_id not in varset:
                varset[exp_id] = {}

            # Level 2: Participant ID
            if token not in varset[exp_id]:
                varset[exp_id][token] = {}

            # If found global setting, courtesy debug message
            if token == "*":
                bot.debug("Found global variable %s" % var_name)

            # Level 3: is the variable, issue warning if already defined
            if var_name in varset[exp_id][token]:
                bot.warning("%s defined twice %s:%s" %
                            (var_name, exp_id, token))
            varset[exp_id][token][var_name] = var_value

    return varset
Esempio n. 15
0
def generate_runtime_vars(variable_file=None, sep=','):
    '''generate a lookup data structure from a 
       delimited file. We typically obtain the file name and delimiter from
       the environment by way of EXPFACTORY_RUNTIME_VARS, and
       EXPFACTORY_RUNTIME_DELIM, respectively, but the user can also parse
       from a custom variable file by way of specifying it to the function
       (preference is given here). The file should be csv, with the
       only required first header field as "token" and second as "exp_id" to
       distinguish the participant ID and experiment id. The subsequent
       columns should correspond to experiment variable names. No special parsing
       of either is done. 

       Parameters
       ==========
       variable_file: full path to the tabular file with token, exp_id, etc.
       sep: the default delimiter to use, if not set in enironment.

       Returns
       =======
       varset: a dictionary lookup by exp_id and then participant ID.

       { 'test-parse-url': {
                             '123': {
                                      'color': 'red',
                                      'globalname': 'globalvalue',
                                      'words': 'at the thing'
                                    },

                             '456': {'color': 'blue',
                                     'globalname': 'globalvalue',
                                     'words': 'omg tacos'}
                              }
       }

    '''

    # First preference goes to runtime, then environment, then unset

    if variable_file is None:    
        if EXPFACTORY_RUNTIME_VARS is not None:
            variable_file = EXPFACTORY_RUNTIME_VARS

    if variable_file is not None:
        if not os.path.exists(variable_file):
            bot.warning('%s is set, but not found' %variable_file)
            return variable_file

    # If still None, no file
    if variable_file is None:
        return variable_file

    # If we get here, we have a variable file that exists
    delim = sep
    if EXPFACTORY_RUNTIME_DELIM is not None:
        delim = EXPFACTORY_RUNTIME_DELIM
    bot.debug('Delim for variables file set to %s' %sep)

    # Read in the file, generate config

    varset = dict()
    rows = _read_runtime_vars(variable_file)
    
    if len(rows) > 0:

        # When we get here, we are sure to have 
        # 'exp_id', 'var_name', 'var_value', 'token'

        for row in rows:

            exp_id = row[0].lower()   # exp-id must be lowercase
            var_name = row[1]
            var_value = row[2]
            token = row[3]

            # Level 1: Experiment ID
            if exp_id not in varset:
                varset[exp_id] = {}

            # Level 2: Participant ID
            if token not in varset[exp_id]:
                varset[exp_id][token] = {}

            # If found global setting, courtesy debug message
            if token == "*":
                bot.debug('Found global variable %s' %var_name)

            # Level 3: is the variable, issue warning if already defined
            if var_name in varset[exp_id][token]:
                bot.warning('%s defined twice %s:%s' %(var_name, exp_id, token))
            varset[exp_id][token][var_name] = var_value


    return varset
Esempio n. 16
0
def main(args, parser, subparser):

    template = "build/docker/Dockerfile.template"

    # Full path to template is required if provided via input
    if args.input is not None:
        template = args.input

    template = get_template(template)

    # For now, only one database provided
    database = args.database
    studyid = args.studyid
    experiments = args.experiments
    branch = "-b %s" % os.environ.get("EXPFACTORY_BRANCH", "master")

    headless = "false"
    if args.headless is True:
        headless = "true"

    template = sub_template(template, "{{studyid}}", studyid)
    template = sub_template(template, "{{database}}", database)
    template = sub_template(template, "{{headless}}", headless)
    template = sub_template(template, "{{branch}}", branch)

    if args.headless is True:
        bot.info(
            "Headless build detected, you will need to generate tokens for application entry with expfactory users --new"
        )

    library = get_library(key="name")

    apps = "\n"

    # Add local experiments to library, first preference
    local_installs = 0
    for experiment in experiments:
        if os.path.exists(experiment):

            bot.info("local experiment %s found, validating..." % experiment)

            # Is the experiment valid?
            cli = ExperimentValidator()
            valid = cli.validate(experiment, cleanup=False)

            if valid is True:
                local_installs += 1
                config = load_experiment(experiment)
                exp_id = config["exp_id"]

                # If we aren't building in the experiment directory, we need to copy there
                output_dir = "%s/%s" % (
                    os.path.abspath(os.path.dirname(args.output)),
                    exp_id,
                )
                experiment_dir = os.path.abspath(experiment)
                if output_dir != experiment_dir:
                    copy_directory(experiment_dir, output_dir)

                config["local"] = os.path.abspath(experiment)
                library[exp_id] = config

    # Warn the user that local installs are not reproducible (from recipe)
    if local_installs > 0:
        bot.warning(
            "%s local installs detected: build is not reproducible without experiment folders"
            % local_installs
        )

    # Build Image with Experiments
    for experiment in experiments:
        exp_id = os.path.basename(experiment)
        if exp_id in library:
            config = library[exp_id]

            app = "LABEL EXPERIMENT_%s /scif/apps/%s\n" % (exp_id, exp_id)

            # Here add custom build routine, should be list of lines
            if "install" in config:
                commands = "\n".join(["RUN %s " % s for x in config["install"]]).strip(
                    "\n"
                )
                app = "%s%s\n" % (app, commands)

            # The final installation step, either from Github (url) or local folder
            if "local" in config:
                app = "%sADD %s /scif/apps/%s\n" % (app, exp_id, exp_id)
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" % (
                    app,
                    exp_id,
                )
            else:
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" % (
                    app,
                    config["github"],
                )
            apps = "%s%s\n" % (apps, app)

        else:
            bot.warning("%s not in library, check spelling & punctuation." % exp_id)

    if apps == "\n":
        bot.error("No valid experiments found, cancelling recipe build.")
        sys.exit(1)

    template = sub_template(template, "{{experiments}}", apps)
    outfile = write_file(args.output, template)
    bot.log("Recipe written to %s" % outfile)
Esempio n. 17
0
def main(args,parser,subparser):

    template = 'build/docker/Dockerfile.template'

    # Full path to template is required if provided via input
    if args.input is not None:
        template = args.input

    template = get_template(template)
    
    # For now, only one database provided
    database = args.database
    studyid = args.studyid
    experiments = args.experiments
    branch = "-b %s" %os.environ.get('EXPFACTORY_BRANCH','master')

    headless = "false"
    if args.headless is True:
        headless = "true"

    template = sub_template(template,"{{studyid}}",studyid)
    template = sub_template(template,"{{database}}",database)
    template = sub_template(template,"{{headless}}",headless)
    template = sub_template(template,"{{branch}}",branch)

    if args.headless is True:
        bot.info("Headless build detected, you will need to generate tokens for application entry with expfactory users --new")

    library = get_library(key='name')

    apps = "\n"

    # Add local experiments to library, first preference
    local_installs = 0
    for experiment in experiments:
        if os.path.exists(experiment):

            bot.info('local experiment %s found, validating...' %experiment)

            # Is the experiment valid?
            cli = ExperimentValidator()
            valid = cli.validate(experiment, cleanup=False)

            if valid is True:
                local_installs +=1
                config = load_experiment(experiment)
                exp_id = config['exp_id']

                # If we aren't building in the experiment directory, we need to copy there
                output_dir = "%s/%s" %(os.path.abspath(os.path.dirname(args.output)), exp_id)
                experiment_dir = os.path.abspath(experiment)
                if output_dir != experiment_dir:
                    copy_directory(experiment_dir, output_dir)

                config['local'] = os.path.abspath(experiment)
                library[exp_id] = config


    # Warn the user that local installs are not reproducible (from recipe)
    if local_installs > 0:
        bot.warning("%s local installs detected: build is not reproducible without experiment folders" %local_installs)

    # Build Image with Experiments
    for experiment in experiments:
        exp_id = os.path.basename(experiment)
        if exp_id in library:
            config = library[exp_id]

            app = "LABEL EXPERIMENT_%s /scif/apps/%s\n" %(exp_id, exp_id)

            # Here add custom build routine, should be list of lines
            if "install" in config:
                commands = "\n".join(["RUN %s "%s for x in config['install']]).strip('\n')
                app = "%s%s\n" %(app, commands)

            # The final installation step, either from Github (url) or local folder
            if "local" in config:
                app = "%sADD %s /scif/apps/%s\n" %(app, exp_id, exp_id)
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" %(app, exp_id)
            else:
                app = "%sWORKDIR /scif/apps\nRUN expfactory install %s\n" %(app, config['github'])  
            apps = "%s%s\n" %(apps,app)


        else:
            bot.warning('%s not in library, check spelling & punctuation.' %exp_id)

    if apps == "\n":
        bot.error('No valid experiments found, cancelling recipe build.')
        sys.exit(1)

    template = sub_template(template,"{{experiments}}",apps)
    outfile = write_file(args.output,template)
    bot.log("Recipe written to %s" %outfile)