Ejemplo n.º 1
0
def HEALTHCHECK(interval=None,
                timeout=None,
                start_period=None,
                retries=None,
                cmd=None):
    """Create a Dockerfile HEALTHCHECK instruction.

    Args:
        interval: Optional string containing the duration to wait both prior
            to the first heathcheck invocation after container start and the
            interval to wait after a successful healthcheck for applying the
            next healthcheck.
        timeout: Optional string containing the duration of time afterwhich
            a running healthcheck will expire and be considered as failed.
        start_period: Optional string containing a duration of time to consider
            as a start-up phase for a container, a phase during which failed
            healthchecks don't count towards the maximum retries.
        retries: Optional string containing the number of retries to attempt
            before consecutive healthcheck failures result in an 'unhealthy'
            container state.
        cmd: Optional. Either a single string containing one command or else a
            list or tuple containing an executable in the first element and
            optional parameters in the remaining elements.

    Returns:
        An instance of the HEALTHCHECK namedtuple.

    Raises:
        DockerphileError: raised when arguments are misspecified.

    """
    parameters = {
        'interval': interval,
        'timeout': timeout,
        'start_period': start_period,
        'retries': retries
    }
    for param, value in parameters.items():
        if not isinstance(value, (str, type(None))):
            raise DockerphileError(
                'HEALTHCHECK parameter %s requires string, not %s' %
                (param, type(value)))
    if not isinstance(cmd, (str, list, tuple, type(None))):
        raise DockerphileError(
            ('HEALTHCHECK "CMD" option must be a string or a list/tuple of '
             'strings, not %s') % type(cmd))
    if isinstance(cmd, (list, tuple)) and not cmd:
        raise DockerphileError(
            'HEALTHCHECK "CMD" exec format must have at least 1 executable.')
    return HEALTHCHECK_t(interval=interval,
                         timeout=timeout,
                         start_period=start_period,
                         retries=retries,
                         cmd=cmd)
Ejemplo n.º 2
0
def COPY(resources, from_=None):
    """Create a Dockerfile COPY instruction.

    Args:
        resources: A list or tuple of N strings (N >= 2) such that the
            first N - 1 are treated as COPY source URIs and the Nth is
            treated as the destination URI in the image being built. All
            URI strings will be enclosed with double-quote characters in
            the rendered Dockerfile.
        from_: Optional string naming a Docker image or index to specify a
            source image from which to copy when using multi-stage builds.

    Returns:
        An instance of the COPY namedtuple.

    Raises:
        DockerphileError: raised when `resources` or `from` arguments are not
            specified in a compatible way.

    """
    msg = ''
    if not isinstance(resources, (list, tuple)):
        msg = 'COPY instruction requires list or tuple, not %s' % (
            type(resources)
        )
    elif len(resources) < 2:
        msg = 'COPY instruction must have at least 1 src and 1 dest URI.'
    elif not isinstance(from_, (str, type(None))):
        msg = 'COPY --from option must be a string, not %s' % type(from_)
    if msg:
        raise DockerphileError(msg)
    return COPY_t(resources=resources, from_=from_)
Ejemplo n.º 3
0
 def run_block(self, form='shell'):
     """Syntactiv sugar for RUN block."""
     if form not in {'exec', 'shell'}:
         raise DockerphileError(
             'Invalid format specifier for RUN instruction %s' % form
         )
     return RunBlock(self, form=form)
Ejemplo n.º 4
0
def FROM(base_image, as_=None):
    """Create a Dockerfile FROM instruction.

    Args:
        base_image: A string naming a base Docker image. The string can contain
            references to pre-FROM ARG variables in accordance with Dockerfile
            support for ARG variables appearing in FROM instructions.
        as_: Optional string declaring a name for the image for later reference
            during a multi-stage build.

    Returns:
        An instance of the FROM namedtuple.

    Raises:
        DockerphileError: raised when `base_image` or `as_` arguments are
        misspecified.

    """
    msg = ''
    if not isinstance(base_image, str):
        msg = 'FROM parameter `base_image` requires string, not %s' % (
            type(base_image))
    elif not isinstance(as_, (str, type(None))):
        msg = 'FROM parameter `as_` requires string, not %s' % (type(as_))
    if msg:
        raise DockerphileError(msg)
    return FROM_t(base_image=base_image, as_=as_)
Ejemplo n.º 5
0
def ADD(resources):
    """Create a Dockerfile ADD instruction.

    Args:
        resources: A list or tuple of N strings (N >= 2) such that the
            first N - 1 are treated as ADD source URIs and the Nth is
            treated as the destination URI in the image being built. All
            URI strings will be enclosed with double-quote characters in
            the rendered Dockerfile.

    Returns:
        An instance of the ADD namedtuple.

    Raises:
        DockerphileError: raised when `resources` argument is not specified
            in a compatible way.

    """
    msg = ''
    if not isinstance(resources, (list, tuple)):
        msg = 'ADD instruction requires list or tuple, not %s' % type(
            resources)
    elif len(resources) < 2:
        msg = 'ADD instruction must have at least 1 src and 1 dest URI.'
    if msg:
        raise DockerphileError(msg)
    return ADD_t(resources=resources)
Ejemplo n.º 6
0
def VOLUME(volume_specs):
    """Create a Dockerfile VOLUME instruction.

    Args:
        volume_specs: A list or tuple of strings each containing a valid
            Dockerfile volume specifier. The volume specifiers will be
            rendered in the JSON array format in resulting Dockerfiles
            and enclosed in double quotes.

    Returns:
        An instance of the VOLUME namedtuple.

    Raises:
        DockerphileError: raised when `volume_specs` argument is misspecified.

    """
    msg = ''
    if not isinstance(volume_specs, (list, tuple)):
        msg = 'VOLUME instruction requires list or tuple, not %s' % (
            type(volume_specs))
    elif not volume_specs:
        msg = 'VOLUME instruction must have at least 1 volume specifier.'
    if msg:
        raise DockerphileError(msg)
    return VOLUME_t(volume_specs=volume_specs)
Ejemplo n.º 7
0
def ARG(key, default_value=None):
    """Create a Dockerfile ARG instruction.

    Args:
        key: string that names the Dockerfile build ARG.
        default_value: string supplying the ARG default value (e.g.
            `key=value`) in the Dockerfile.

    Returns:
        An instance of the ARG namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `key` or
        `default_value`.

    """
    msg = ''
    if not isinstance(key, str):
        msg = 'ARG instruction key requires string, not %s' % type(key)
    elif not isinstance(default_value, (str, type(None))):
        msg = 'ARG instruction default_value requires string, not %s' % (
            type(default_value))
    if msg:
        raise DockerphileError(msg)
    return ARG_t(key=key, default_value=default_value)
Ejemplo n.º 8
0
 def run(self, command, form='shell'):
     """run_t"""
     if form not in {'exec', 'shell'}:
         raise DockerphileError(
             'Invalid format specifier for RUN instruction %s' % form
         )
     self.sequence.append(
         structures.RUN(**{("%s_form" % form): command})
     )
Ejemplo n.º 9
0
 def entrypoint(self, entrypoint, form='exec'):
     """entrypoint_t"""
     if form not in {'exec', 'shell'}:
         raise DockerphileError(
             'Invalid format specifier for ENTRYPOINT instruction %s' % form
         )
     self.sequence.append(
         structures.ENTRYPOINT(**{("%s_form" % form): entrypoint})
     )
Ejemplo n.º 10
0
 def cmd(self, cmd, form='exec'):
     """cmd_t."""
     if form not in {'exec', 'default', 'shell'}:
         raise DockerphileError(
             'Invalid format specifier for CMD instruction %s' % form
         )
     self.sequence.append(
         structures.CMD(**{("%s_form" % form): cmd})
     )
Ejemplo n.º 11
0
def _check_format(instruction, arg, argname):
    """Helper to repeat argument validation for an instruction format."""
    msg = ''
    if not isinstance(arg, (list, tuple)):
        msg = '%s %s requires list or tuple of strings, not %s' % (
            instruction, argname, type(arg))
    if not len(arg) >= 1:
        msg = '%s %s requires at least one parameter.' % (instruction, argname)
    if msg:
        raise DockerphileError(msg)
Ejemplo n.º 12
0
def CMD(exec_form=None, default_form=None, shell_form=None):
    """Create a Dockerfile CMD instruction.

    Docker CMD instructions have multiple forms that are rendered in
    different ways in Dockerfiles. This instruction can accept inputs that
    are compatible with each of the different CMD forms. Exception handling
    is performed to ensure that only one CMD form is specified in a single
    call to this CMD instruction, and that required properties of the input
    format are satisfied. When using either the exec or default forms of CMD
    inputs, the result is rendered as a JSON array of strings in any
    Dockerfile. For the shell form, the result is rendered as a series of
    unquoted literals directly following the CMD instruction.

    Args:
        exec_form: The exec form of input to the Dockerfile CMD instruction.
            This input must be a list or tuple of strings. The first entry
            specifies an executable and the remaining entries serve as the
            parameters to that executable.
        default_form: The form of CMD instruction input that provides default
            parameters to the ENTRYPOINT of the Docker image. This form must
            also be a list or tuple or strings, but all entries are expected
            to be parameters (not executable) that are implicitly passed to
            the Docker image's ENTRYPOINT executable.
        shell_form: The form of input to the CMD instruction that implicitly
            uses a shell context (not the Docker context) to execute an
            instruction with parameters. This argument must be a list or tuple
            of strings. The first entry must name an executable and the
            remaining entries serve as parameters.

    Returns:
        An instance of the CMD namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying the CMD form
            arguments.

    """
    kwargs = {
        "exec_form": exec_form,
        "default_form": default_form,
        "shell_form": shell_form
    }
    if sum(v is not None for k, v in kwargs.items()) != 1:
        raise DockerphileError('Exactly 1 CMD argument format must be used.')
    for arg_name, arg_value in kwargs.items():
        if arg_value is not None:
            _check_format("CMD", arg_value, arg_name)
    return CMD_t(**kwargs)
Ejemplo n.º 13
0
def STOPSIGNAL(signal):
    """Create a Dockerfile STOPSIGNAL instruction.

    Args:
        signal: a string naming a valid signal identifier according to the
            Dockerfile STOPSIGNAL instruction specification.

    Returns:
        An instance of the STOPSIGNAL namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `signal`.

    """
    msg = ''
    if not isinstance(signal, str):
        msg = 'STOPSIGNAL instruction signal requires string, not %s' % (
            type(signal))
    if msg:
        raise DockerphileError(msg)
    return STOPSIGNAL_t(signal=signal)
Ejemplo n.º 14
0
def WORKDIR(workdir):
    """Create a Dockerfile WORKDIR instruction.

    Args:
        workdir: a string naming a valid path identifier according to the
            Dockerfile WORKDIR instruction specification.

    Returns:
        An instance of the WORKDIR namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `workdir`.

    """
    msg = ''
    if not isinstance(workdir, str):
        msg = 'WORKDIR parameter `workdir` requires string, not %s' % (
            type(workdir))
    if msg:
        raise DockerphileError(msg)
    return WORKDIR_t(workdir=workdir)
Ejemplo n.º 15
0
def RUN(shell_form=None, exec_form=None):
    """Create a Dockerfile RUN instruction.

    Docker RUN instructions have multiple forms that are rendered in
    different ways in Dockerfiles. This instruction can accept inputs that
    are compatible with each of the different RUN forms. Exception
    handling is performed to ensure that only one RUN format is specified
    in a single call to this RUN instruction, and that required properties
    of the input format are satisfied. When using the exec form of RUN inputs,
    the result is rendered as a JSON array of strings in any Dockerfile. For
    the shell form, the result is rendered as a series of unquoted literals
    directly following the RUN instruction.

    Args:
        shell_form: The form of input to the RUN instruction that implicitly
            uses a shell context (not the Docker context) to execute an
            instruction with parameters. This argument must be a list or
            tuple of strings.
        exec_form: The exec form of input to the Dockerfile RUN instruction.
            This input must be a list or tuple of strings. The first entry
            specifies an executable and the remaining entries serve as the
            parameters to that executable.

    Returns:
        An instance of the RUN namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying the RUN form
            arguments.

    """
    kwargs = {"exec_form": exec_form, "shell_form": shell_form}
    if sum(v is not None for k, v in kwargs.items()) != 1:
        raise DockerphileError('Exactly 1 RUN argument format must be used.')
    for arg_name, arg_value in kwargs.items():
        if arg_value is not None:
            _check_format("RUN", arg_value, arg_name)
    return RUN_t(**kwargs)
Ejemplo n.º 16
0
def EXPOSE(port_specs):
    """Create a Dockerfile EXPOSE instruction.

    Args:
        port_specs: A list or tuple of strings each containing a valid
            Dockerfile port/protocol specifier.

    Returns:
        An instance of the EXPOSE namedtuple.

    Raises:
        DockerphileError: raised when `port_specs` argument is misspecified.

    """
    msg = ''
    if not isinstance(port_specs, (list, tuple)):
        msg = 'EXPOSE instruction requires list or tuple, not %s' % (
            type(port_specs))
    elif not port_specs:
        msg = 'EXPOSE instruction must have at least 1 port specifier.'
    if msg:
        raise DockerphileError(msg)
    return EXPOSE_t(port_specs=port_specs)
Ejemplo n.º 17
0
def ESCAPE(character):
    """Create a Dockerfile `# escape` parser directive.

    Args:
        character: a single-character string naming a valid character to serve
            as the escape character according to the `escape` parser directive
            specification for Dockerfiles.

    Returns:
        An instance of the ESCAPE namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `character`.

    """
    msg = ''
    if not isinstance(character, str):
        msg = 'ESCAPE parameter must be a string, not %s' % (type(character))
    elif len(character) != 1:
        msg = 'ESCAPE parameter must contain exactly 1 character.'
    if msg:
        raise DockerphileError(msg)
    return ESCAPE_t(character=character)
Ejemplo n.º 18
0
def ENV(key, value):
    """Create a Dockerfile ENV instruction.

    Args:
        key: string that names Dockerfile environment variable.
        value: string containing the environment variable value to set.

    Returns:
        An instance of the ENV namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `key` or
        `value`.

    """
    msg = ''
    if not isinstance(key, str):
        msg = 'ENV instruction key requires string, not %s' % type(key)
    elif not isinstance(value, str):
        msg = 'ENV instruction value requires string, not %s' % type(value)
    if msg:
        raise DockerphileError(msg)
    return ENV_t(key=key, value=value)
Ejemplo n.º 19
0
def LABEL(key, value):
    """Create a Dockerfile LABEL instruction.

    Args:
        key: string that names Dockerfile label key.
        value: string containing the label value to set.

    Returns:
        An instance of the LABEL namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `key` or
        `value`.

    """
    msg = ''
    if not isinstance(key, str):
        msg = 'LABEL instruction key requires string, not %s' % type(key)
    elif not isinstance(value, str):
        msg = 'LABEL instruction value requires string, not %s' % type(value)
    if msg:
        raise DockerphileError(msg)
    return LABEL_t(key=key, value=value)
Ejemplo n.º 20
0
def USER(user, group=None):
    """Create a Dockerfile USER instruction.

    Args:
        user: string containing username or UID for Dockerfile USER
            instruction.
        group: Optional string containing a group name or ID for the user.

    Returns:
        An instance of the USER namedtuple.

    Raises:
        DockerphileError: raised for any errors when specifying `user` or
        `group`.

    """
    msg = ''
    if not isinstance(user, str):
        msg = 'USER parameter `user` requires string, not %s' % type(user)
    elif not isinstance(group, (str, type(None))):
        msg = 'USER parameter `group` requires string, not %s' % type(group)
    if msg:
        raise DockerphileError(msg)
    return USER_t(user=user, group=group)
Ejemplo n.º 21
0
def SHELL(shell_spec):
    """Create a Dockerfile SHELL instruction.

    Args:
        shell_spec: A list or tuple of strings together containing a
            JSON / exec formatted shell executable to be set via the
            Dockerfile SHELL instruction.

    Returns:
        An instance of the SHELL namedtuple.

    Raises:
        DockerphileError: raised when `shell_spec` argument is misspecified.

    """
    msg = ''
    if not isinstance(shell_spec, (list, tuple)):
        msg = 'SHELL instruction requires list or tuple, not %s' % (
            type(shell_spec))
    elif not shell_spec:
        msg = 'SHELL instruction parameter must have at least 1 entry.'
    if msg:
        raise DockerphileError(msg)
    return SHELL_t(shell_spec=shell_spec)
Ejemplo n.º 22
0
def ONBUILD(instruction):
    """Create a Dockerfile ONBUILD instruction.

    ONBUILD instructions must be instances of valid dockerphile types that are
    permitted to serve as an ONBUILD parameter in a Dockerfile. For example, if
    you seek to trigger a COPY instruction with an ONBUILD trigger, then you
    must create an instance of `dockerphile.structures.copy_t.COPY_t`, and pass
    this object as the instruction argument to ONBUILD. Note that FROM_t,
    COMMENT_t and ONBUILD_t are not valid parameters for ONBUILD, and these
    result in exceptions. Additionally, since ESCAPE maps to a parser
    directive, not a instruction, and it must appear in a Dockerfile prior to
    any instructions, ESCAPE is also considered invalid for ONBUILD.

    Args:
        instruction: An instance of a valid dockerphile instruction. FROM and
            ONBUILD are invalid to appear as the parameter for an ONBUILD
            instruction, but any other Dockerfile instruction can appear. Since
            COMMENT and ESCAPE do not refer to triggerable instructions, they
            are also not part of the valid instruction set for ONBUILD.

    Returns:
        An instance of the ONBUILD namedtuple.

    Raises:
        DockerphileError: raised if `instruction` is a dockerphile instruction
            type that is invalid for ONBUILD or if `instruction` is not an
            instance of a dockerphile instruction.

    """
    msg = ''
    if not isinstance(instruction, VALID_TYPES):
        msg = 'ONBUILD instruction parameter received disallowed type %s.' % (
            type(instruction))
    if msg:
        raise DockerphileError(msg)
    return ONBUILD_t(instruction=instruction)
Ejemplo n.º 23
0
def to_instruction(parsed_instruction):
    """Map a parsed `dockerfile.Command` to a `dockerphile.structures` type.

    `dockerfile.Command` contains the original parsed string (with line
    continuations and all comments removed) and also contains a `value`
    attribute storing the part of the Dockerfile instruction non-inclusive
    of Dockerfile keywords.

    Known limitations include: (1) no parsing of comment lines; (2) no parsing
    of the `escape` parser directive; and (2) no direct parsing of the optional
    arguments for the HEALTHCHECK or COPY commands.

    Args:
        parsed_instruction: An instance of `dockerfile.Command`.

    Returns:
        Either an instance of a `dockerphile.structures` namedtuple
        representing the parsed command using `dockerphile` types, or a list of
        such types. Return value is None for any underlying commands that are
        not supported by `dockerphile` even if parsed correctly, such as
        `MAINTAINER` (since `dockerphile` requires following Docker best
        practices to use `LABEL` for a maintainer label.

    Raises:
        DockerphileError: raised if incorrect instruction type is provided or
            if the parsed `dockerfile.Command` object contains invalid
            Dockerfile syntax.
        dockerfile.GoParseError: raised if underlying `dockerfile` library
            Go parser encounters an unhandled parser error.

    """
    cmd, original = parsed_instruction.cmd, parsed_instruction.original
    uses_json, value = parsed_instruction.json, parsed_instruction.value
    if cmd == 'add':
        return structures.ADD(value)
    if cmd == 'arg':
        arg_string = value[0]
        parsed_arg = re.match(arg_default_value, arg_string)
        if parsed_arg is not None:
            key, default_value = parsed_arg.groups()
            return structures.ARG(key, default_value=default_value)
        else:
            return structures.ARG(arg_string)
    if cmd == 'cmd':
        return structures.CMD(**{
            ('exec_form' if uses_json else 'shell_form'): value
        })
    if cmd == 'copy':
        parsed_from = re.match(copy_from, original)
        from_ = parsed_from.groups()[0] if parsed_from is not None else None
        return structures.COPY(value, from_=from_)
    if cmd == 'entrypoint':
        return structures.ENTRYPOINT(**{
            ('exec_form' if uses_json else 'shell_form'): value
        })
    if cmd == 'env':
        if not value:
            raise DockerphileError("ENV command has no key or value.")
        if len(value) == 2:
            return structures.ENV(value[0], value[1])
        try:
            result = [structures.ENV(value[k], value[k + 1])
                      for k in range(len(value))[::2]]
        except IndexError:
            raise DockerphileError("Unpaired index when parsing "
                                   "%s to list of ENV types." % value)
        return result
    if cmd == 'expose':
        return structures.EXPOSE(value)
    if cmd == 'from':
        parsed_from = re.match(from_as, original)
        if parsed_from is not None:
            image, as_ = parsed_from.groups()
            return structures.FROM(image, as_=as_)
        if not value:
            raise DockerphileError("FROM instruction requires nonempty base "
                                   "image.")
        return structures.FROM(value[0])
    if cmd == 'healthcheck':
        if value and value[0] != 'CMD':
            raise DockerphileError("Invalid CMD specifier for HEALTHCHECK: "
                                   "%s" % value[0])
        options = {
            'interval': re.match(healthcheck_interval, original),
            'timeout': re.match(healthcheck_timeout, original),
            'start_period': re.match(healthcheck_start_period, original),
            'retries': re.match(healthcheck_retries, original)
        }
        kwargs = {
            arg_name: (None if arg_match is None else arg_match.groups()[0])
            for arg_name, arg_match in options.items()
        }
        if len(value) == 2:
            kwargs['cmd'] = value[1]
        elif len(value) > 2:
            kwargs['cmd'] = value[1:]
        return structures.HEALTHCHECK(**kwargs)
    if cmd == 'label':
        if not value:
            raise DockerphileError("LABEL command has no key or value.")
        if len(value) == 2:
            return structures.LABEL(value[0], value[1])
        try:
            result = [structures.LABEL(value[k], value[k + 1])
                      for k in range(len(value))[::2]]
        except IndexError:
            msg = "Unpaired index when parsing %s to list of LABEL types."
            raise DockerphileError(msg % value)
        return result
    if cmd == 'maintainer':
        return None
    if cmd == 'onbuild':
        if parsed_instruction.sub_cmd == 'onbuild':
            raise DockerphileError("ONBUILD is not allowed as subcommand of "
                                   "ONBUILD.")
        sub_cmd_str = original.replace('ONBUILD', '')
        parsed_sub_cmd = parse_string(sub_cmd_str)[0]
        return structures.ONBUILD(to_instruction(parsed_sub_cmd))
    if cmd == 'run':
        return structures.RUN(**{
            ('exec_form' if uses_json else 'shell_form'): value
        })
    if cmd == "shell":
        if not uses_json:
            raise DockerphileError("SHELL instruction requires used of JSON "
                                   "array option format.")
        if not value:
            raise DockerphileError("SHELL instruction requires nonempty JSON "
                                   "array option format.")
        return structures.SHELL(value)
    if cmd == 'stopsignal':
        if not value:
            raise DockerphileError("STOPSIGNAL instruction requires nonempty "
                                   "value.")
        return structures.STOPSIGNAL(value[0])
    if cmd == 'user':
        if not value:
            raise DockerphileError("USER instruction requires nonempty value.")
        parsed_user_group = re.match(user_group, original)
        if parsed_user_group is not None:
            user, group = parsed_user_group.groups()
        else:
            user, group = value[0], None
        return structures.USER(user, group=group)
    if cmd == 'volume':
        return structures.VOLUME(value)
    if cmd == 'workdir':
        if not value:
            raise DockerphileError("WORKDIR instruction requires nonempty "
                                   "value.")
        return structures.WORKDIR(value[0])
    raise DockerphileError("Unrecognized dockerfile.Command parsed command "
                           "%s" % cmd)
Ejemplo n.º 24
0
def render_instruction(instruction):
    """Render a string format of a dockerphile instruction.

    Args:
        instruction: A dockerphile instruction type from
            `dockerphile.structures`.

    Returns:
        A string containing the rendered output of the given instruction.

    Raises:
        DockerphileError: raised when the input is not a valid dockerphile
            instruction type.

    """
    if isinstance(instruction, structures.ADD_t):
        result = "ADD"
        for resource in instruction.resources:
            result = result + ' "%s"' % resource
        return result
    if isinstance(instruction, structures.ARG_t):
        result = "ARG %s" % instruction.key
        if instruction.default_value is not None:
            result = result + "=%s" % instruction.default_value
        return result
    if isinstance(instruction, structures.CMD_t):
        result = "CMD"
        if instruction.shell_form is not None:
            for c in instruction.shell_form:
                result = result + " %s" % c
            return result
        elif instruction.exec_form is not None:
            commands = json.dumps(instruction.exec_form)
        else:
            commands = json.dumps(instruction.default_form)
        return result + " " + commands
    if isinstance(instruction, structures.COMMENT_t):
        return "# %s" % instruction.comment
    if isinstance(instruction, structures.COPY_t):
        result = "COPY"
        if instruction.from_ is not None:
            result = result + " --from=%s" % instruction.from_
        for resource in instruction.resources:
            result = result + ' "%s"' % resource
        return result
    if isinstance(instruction, structures.ENTRYPOINT_t):
        result = "ENTRYPOINT"
        if instruction.shell_form is not None:
            for c in instruction.shell_form:
                result = result + " %s" % c
            return result
        else:
            commands = json.dumps(instruction.exec_form)
            return result + " " + commands
    if isinstance(instruction, structures.ENV_t):
        return "ENV %s %s" % (instruction.key, instruction.value)
    if isinstance(instruction, structures.ESCAPE_t):
        return "# escape=%s" % instruction.character
    if isinstance(instruction, structures.EXPOSE_t):
        result = "EXPOSE"
        for port_spec in instruction.port_specs:
            result = result + " %s" % port_spec
        return result
    if isinstance(instruction, structures.FROM_t):
        result = "FROM %s" % instruction.base_image
        if instruction.as_ is not None:
            result = result + " AS %s" % instruction.as_
        return result
    if isinstance(instruction, structures.HEALTHCHECK_t):
        result = "HEALTHCHECK"
        if instruction.interval is not None:
            result = result + " \\\n  --interval=%s" % instruction.interval
        if instruction.timeout is not None:
            result = result + " \\\n  --timeout=%s" % instruction.timeout
        if instruction.start_period is not None:
            result = result + " \\\n  --start-period=%s" % (
                instruction.start_period
            )
        if instruction.retries is not None:
            result = result + " \\\n  --retries=%s" % instruction.retries
        if instruction.cmd is not None:
            result = result + " \\\n  CMD"
            if isinstance(instruction.cmd, str):
                return result + " %s" % instruction.cmd
            else:
                for c in instruction.cmd:
                    result = result + " %s" % c
                return result
    if isinstance(instruction, structures.LABEL_t):
        return "LABEL %s=%s" % (instruction.key, instruction.value)
    if isinstance(instruction, structures.ONBUILD_t):
        return "ONBUILD %s" % render_instruction(instruction.instruction)
    if isinstance(instruction, structures.RUN_t):
        result = "RUN"
        if instruction.shell_form is not None:
            for c in instruction.shell_form:
                result = result + " %s" % c
            return result
        else:
            commands = json.dumps(instruction.exec_form)
            return result + " " + commands
    if isinstance(instruction, structures.SHELL_t):
        return "SHELL " + json.dumps(instruction.shell_spec)
    if isinstance(instruction, structures.STOPSIGNAL_t):
        return "STOPSIGNAL %s" % instruction.signal
    if isinstance(instruction, structures.USER_t):
        result = "USER %s" % instruction.user
        if instruction.group is not None:
            result = result + ":%s" % instruction.group
        return result
    if isinstance(instruction, structures.VOLUME_t):
        return "VOLUME " + json.dumps(instruction.volume_specs)
    if isinstance(instruction, structures.WORKDIR_t):
        return "WORKDIR %s" % instruction.workdir
    raise DockerphileError(
        "Unrecognized or invalid instruction type %s" % instruction
    )