def get_cloudbuild(raw_config, args):
    """Read and validate a cloudbuild recipe

    Args:
        raw_config (dict): deserialized cloudbuild.yaml
        args (argparse.Namespace): command line flags

    Returns:
        CloudBuild: valid configuration
    """
    if not isinstance(raw_config, dict):
        raise ValueError(
            'Expected {} contents to be of type "dict", but found type "{}"'.
            format(args.config, type(raw_config)))

    raw_steps = validation_utils.get_field_value(raw_config, 'steps', list)
    if not raw_steps:
        raise ValueError('No steps defined in {}'.format(args.config))

    steps = [get_step(raw_step) for raw_step in raw_steps]
    return CloudBuild(
        output_script=args.output_script,
        run=args.run,
        steps=steps,
        substitutions=args.substitutions,
    )
def get_step(raw_step):
    """Read and validate a single cloudbuild step

    Args:
        raw_step (dict): deserialized step

    Returns:
        Step: valid build step
    """
    if not isinstance(raw_step, dict):
        raise ValueError(
            'Expected step to be of type "dict", but found type "{}"'.
            format(type(raw_step)))
    raw_args = validation_utils.get_field_value(raw_step, 'args', list)
    args = [validation_utils.get_field_value(raw_args, index, str)
            for index in range(len(raw_args))]
    dir_ = validation_utils.get_field_value(raw_step, 'dir', str)
    raw_env = validation_utils.get_field_value(raw_step, 'env', list)
    env = [validation_utils.get_field_value(raw_env, index, str)
           for index in range(len(raw_env))]
    name = validation_utils.get_field_value(raw_step, 'name', str)
    return Step(
        args=args,
        dir_=dir_,
        env=env,
        name=name,
    )
def test_get_field_value_invalid(container, field_name, field_type):
    with pytest.raises(ValueError):
        validation_utils.get_field_value(container, field_name, field_type)
def test_get_field_value_valid(container, field_name, field_type, expected):
    assert validation_utils.get_field_value(container, field_name,
                                            field_type) == expected
Beispiel #5
0
def get_app_config(raw_config, base_image, config_file, source_dir):
    """Read and validate the application runtime configuration.

    We validate the user input for security and better error messages.

    Consider parsing a yaml file which has a string value where we
    expected a list.  Python will happily use the string as a sequence
    of individual characters, at least for a while, leading to
    confusing results when it finally fails.

    We also try to prevent Dockerfile and Bash injection attacks.  For
    example, specifying entrypoint as "true\\nADD /etc/passwd /pwned"
    would allow the user to inject arbitrary directives into the
    Dockerfile, which is a support problem if nothing else.

    Args:
        raw_config (dict): deserialized app.yaml
        base_image (str): Docker image name to build on top of
        config_file (str): Path to user's app.yaml (might be <service>.yaml)
        source_dir (str): Directory containing user's source code

    Returns:
        AppConfig: valid configuration
    """
    # Examine app.yaml
    if not isinstance(raw_config, collections.abc.Mapping):
        raise ValueError(
            'Expected {} contents to be a Mapping type, but found type "{}"'.
            format(config_file, type(raw_config)))

    entrypoint = validation_utils.get_field_value(raw_config, 'entrypoint',
                                                  str)
    if not PRINTABLE_REGEX.match(entrypoint):
        raise ValueError(
            'Invalid "entrypoint" value in app.yaml: {!r}'.format(entrypoint))

    # Mangle entrypoint in the same way as the Cloud SDK
    # (googlecloudsdk/third_party/appengine/api/validation.py)
    #
    # We could handle both string ("shell form") and list ("exec
    # form") but it appears that gcloud only handles string form.
    if entrypoint and not entrypoint.startswith('exec '):
        entrypoint = 'exec ' + entrypoint

    raw_runtime_config = validation_utils.get_field_value(
        raw_config, 'runtime_config', dict)
    python_version = validation_utils.get_field_value(raw_runtime_config,
                                                      'python_version', str)

    dockerfile_python_version = PYTHON_INTERPRETER_VERSION_MAP.get(
        python_version)
    if dockerfile_python_version is None:
        valid_versions = str(sorted(PYTHON_INTERPRETER_VERSION_MAP.keys()))
        raise ValueError(
            'Invalid "python_version" field in "runtime_config" section '
            'of app.yaml: {!r}.  Valid options are: {}'.format(
                python_version, valid_versions))

    # Examine user's files
    has_requirements_txt = os.path.isfile(
        os.path.join(source_dir, 'requirements.txt'))

    return AppConfig(base_image=base_image,
                     dockerfile_python_version=dockerfile_python_version,
                     entrypoint=entrypoint,
                     has_requirements_txt=has_requirements_txt)
def test_get_field_value_invalid(container, field_name, field_type):
    with pytest.raises(ValueError):
        validation_utils.get_field_value(container, field_name, field_type)
def test_get_field_value_valid(container, field_name, field_type, expected):
    assert validation_utils.get_field_value(
        container, field_name, field_type) == expected
def get_app_config(raw_config, base_image, config_file, source_dir):
    """Read and validate the application runtime configuration.

    We validate the user input for security and better error messages.

    Consider parsing a yaml file which has a string value where we
    expected a list.  Python will happily use the string as a sequence
    of individual characters, at least for a while, leading to
    confusing results when it finally fails.

    We also try to prevent Dockerfile and Bash injection attacks.  For
    example, specifying entrypoint as "true\\nADD /etc/passwd /pwned"
    would allow the user to inject arbitrary directives into the
    Dockerfile, which is a support problem if nothing else.

    Args:
        raw_config (dict): deserialized app.yaml
        base_image (str): Docker image name to build on top of
        config_file (str): Path to user's app.yaml (might be <service>.yaml)
        source_dir (str): Directory containing user's source code

    Returns:
        AppConfig: valid configuration
    """
    # Examine app.yaml
    if not isinstance(raw_config, collections.abc.Mapping):
        raise ValueError(
            'Expected {} contents to be a Mapping type, but found type "{}"'.
            format(config_file, type(raw_config)))

    # Short circuit for python compat.
    if validation_utils.get_field_value(
        raw_config, 'runtime', str) == 'python-compat':
      return AppConfig(
          base_image=None,
          dockerfile_python_version=None,
          entrypoint=None,
          has_requirements_txt=None,
          is_python_compat=True)

    entrypoint = validation_utils.get_field_value(
        raw_config, 'entrypoint', str)
    if not PRINTABLE_REGEX.match(entrypoint):
        raise ValueError(
            'Invalid "entrypoint" value in app.yaml: {!r}'.format(entrypoint))

    # Mangle entrypoint in the same way as the Cloud SDK
    # (googlecloudsdk/third_party/appengine/api/validation.py)
    #
    # We could handle both string ("shell form") and list ("exec
    # form") but it appears that gcloud only handles string form.
    if entrypoint and not entrypoint.startswith('exec '):
        entrypoint = 'exec ' + entrypoint

    raw_runtime_config = validation_utils.get_field_value(
        raw_config, 'runtime_config', dict)
    python_version = validation_utils.get_field_value(
        raw_runtime_config, 'python_version', str)

    dockerfile_python_version = PYTHON_INTERPRETER_VERSION_MAP.get(
        python_version)
    if dockerfile_python_version is None:
        valid_versions = str(sorted(PYTHON_INTERPRETER_VERSION_MAP.keys()))
        raise ValueError(
            'Invalid "python_version" field in "runtime_config" section '
            'of app.yaml: {!r}.  Valid options are: {}'.
            format(python_version, valid_versions))

    # Examine user's files
    has_requirements_txt = os.path.isfile(
        os.path.join(source_dir, 'requirements.txt'))

    return AppConfig(
        base_image=base_image,
        dockerfile_python_version=dockerfile_python_version,
        entrypoint=entrypoint,
        has_requirements_txt=has_requirements_txt,
        is_python_compat=False)