# Dockerfile<no value>
// <!-- Required for asciidoctor -->
:toc:
// Set toclevels to be at least your hugo [markup.tableOfContents.endLevel] config key
:toclevels: 4


[cols="1^,1^,1^",options=header]
|===
| source | condition | target
| &#10004; | &#10004; | &#10004;
|===

== Description

**source**

The Dockerfile "source" reads the value of a Dockerfile instruction and returns it, so one Dockerfile can drive updates elsewhere.

**condition**

The Dockerfile "condition" tests that a Dockerfile instruction is correctly set to a value

**target**

The Dockerfile "target" updates if needed the Dockerfile

=== Reading a value with a source

A source takes exactly one file. More than one entry in `files` fails with `the attributes 'spec.files' can't contain more than one element for sources`, and an empty spec with `no dockerfile specified`.

[source,yaml]
----
sources:
  goVersion:
    kind: dockerfile
    spec:
      file: Dockerfile.webapp
      instruction:
        keyword: "ARG"
        matcher: "GO_VERSION"
----

[source,shell]
----
$ updatecli diff --config source.yaml
✔ value "1.15.8" found for last stage in the dockerfile file "Dockerfile.webapp"
----

`stage` narrows the search to one named build stage; without it the **last** stage is used, which the report states explicitly:

[source,yaml]
----
spec:
  file: Dockerfile.webapp
  stage: build
  instruction:
    keyword: "FROM"
    matcher: "golang"
----

[source,shell]
----
$ updatecli diff --config source-stage.yaml
✔ value "golang:${GO_VERSION}-alpine" found for stage "build" in the dockerfile file "Dockerfile.webapp"
----

== Methods

There are 2 available methods for the resource "Dockerfile":

* <<By Text Matching>>
** Batch: Can match multiple instructions
** Keeper: Does not remove Dockerfile comments

* <<By Specific Coordinates>>
** Precise: only 1 instruction matched
** Full Support: all keywords are supported

== By Text Matching

Matches a set of Dockerfile instructions to manipulate,
by filtering per keyword and text matching on the keyword's arguments.

=== Usage

Given the following file:

[source,Dockerfile]
----
### File: Dockerfile.webapp
ARG GO_VERSION=1.15.8
FROM golang:"${GO_VERSION}-alpine" AS build
# Retrieve source code
WORKDIR /app
COPY . /app
# Build application
RUN go build -X "GoVersion=${GO_VERSION}" -o ./webapp

FROM ubuntu AS test
ARG GO_VERSION
ENV GO_VERSION=${GO_VERSION}
RUN ./run_tests.sh

FROM ubuntu:18.04 AS run
COPY --from=builder /app/webapp /usr/local/bin/webapp
----

And the following configuration:

[source,yaml]
----
title: "Bump golang version"
# Define custom source here
targets:
  updateGoVersion:
    name: "Update the value of ARG GO_VERSION in the Dockerfile"
    sourceID: getGolangVersion
    kind: dockerfile
    spec:
      file: Dockerfile.webapp
      instruction:
        keyword: "ARG"
        matcher: "GO_VERSION"
  updateUbuntuVersion:
    name: "Update Ubuntu image to latest LTS"
    sourceID: getLatestUbuntuLTSVersion
    kind: dockerfile
    spec:
      files:
        - Dockerfile.webapp
        - Dockerfile.api
      instruction:
        keyword: "FROM"
        matcher: "ubuntu"
----

With the following:

* `getGolangVersion`: `1.15.11`
* `getLatestUbuntuLTSVersion`: `20.04`


Then the result is:

[source,Dockerfile]
----
### File: Dockerfile.webapp
ARG GO_VERSION=1.15.11 <1>
FROM golang:"${GO_VERSION}-alpine" AS build
# Retrieve source code
WORKDIR /app
COPY . /app
# Build application
RUN go build -X "GoVersion=${GO_VERSION}" -o ./webapp

FROM ubuntu:20.04 AS test <2>
ARG GO_VERSION=1.15.11 <1>
ENV GO_VERSION=${GO_VERSION}
RUN ./run_tests.sh

FROM ubuntu:20.04 AS run <2>
COPY --from=builder /app/webapp /usr/local/bin/webapp
----

<1> Changed by the target `updateGoVersion`
<2> Changed by the target `updateUbuntuVersion`

Please look at <<Supported Keywords>> to see more specific examples.

=== Parameters

[cols="2,1,1,3",options=header]
|===
| Name | Required | Default |Description
| file  | &#10004; (if `files` is not set)  | - | Specifies the Dockerfile path to use and is incompatible with `files`
| files  | &#10004; (if `file` is not set) | - | Specifies the Dockerfile paths to use and is incompatible with `file`. A source accepts only one entry.
| instruction.keyword | &#10004; | - | Set the <<Supported Keywords>> to be matched.
| instruction.matcher | &#10004; | - | Set the text to be matched.
| stage | &#10007; | last stage | Restricts matching to one named build stage (the name after `AS`). Unset means the last stage.
|===

=== Supported Keywords

From the https://docs.docker.com/engine/reference/builder/[Dockerfile reference, window="_blank"] ,
the following keyword are currently supported:

* <<FROM>>
* <<ARG>>
* <<ENV>>

If you need an unsupported keyword, or an unsupported scenario:

* Consider using the method <<By Specific Coordinates>>
* Do not hesitate to add the keyword support by https://github.com/updatecli/updatecli/blob/main/doc/CONTRIBUTING.adoc[contributing to updatecli, window="_blank"]

==== FROM

Matches https://docs.docker.com/engine/reference/builder/#from[Dockerfile FROM, window="_blank"] instructions by image name to manipulate their image's tags.

* Matches *only* on the image name
** Matching is case sensitive
** Multi stages with an alias are supported, but the alias is not used for matching

* When used as a target, *only* the image tag is modified by Updatecli
** "Friends don't let friend use `latest`": if an instruction is matched and it has no tag,
  then Updatecli append the values as a tag.

With the following definition:

[source, yaml]
----
spec:
  file: Dockerfile
  instruction:
    keyword: "FROM"
    matcher: "alpine"
----

you get the following results:

[source, Dockerfile]
----
# Matches
FROM alpine:3.12
from alpine:3.13
FROM alpine:3.11 AS builder
FROM alpine
FROM alpine:latest

## Does NOT matches
FROM ubuntu:20.04
FROM debian:buster AS alpine
FROM mountain:alpine
FROM ALPINE:3.11
----

==== ARG

Matches https://docs.docker.com/engine/reference/builder/#arg[Dockerfile ARG, window="_blank"] instruction by key to manipulate their values.

* *Only* matches by key (left of the `=` when present)
** Matching is case sensitive

* When used as a target, *only* the value of the argument (right of the `=` when present)
** When no argument value is found (e.g. default value, no character `=` or empty value),
  then updatecli appends the `=` character followed by the value. It is possible to disable this behaviour by providing `ignoreUnsetValue: true` in the configuration.

With the following definition:

[source, yaml]
----
spec:
  file: Dockerfile
  instruction:
    keyword: "ARG"
    matcher: "UPDATECLI_VERSION"
----

you get the following results:

[source, Dockerfile]
----
# Matches
ARG UPDATECLI_VERSION
ARG UPDATECLI_VERSION=
ARG UPDATECLI_VERSION=0.1.0
arg UPDATECLI_VERSION=0.1.0

## Does NOT matches
ARG GOLANG_VERSION
ARG RUST_VERSION=UPDATECLI_VERSION
ARG updatecli_version
----

Using `ignoreUnsetValue: true`, with the following definition:

[source, yaml]
----
spec:
  file: Dockerfile
  instruction:
    keyword: "ARG"
    matcher: "UPDATECLI_VERSION"
    ignoreUnsetValue: true
----

you get the following results:

[source, Dockerfile]
----
# Matches
ARG UPDATECLI_VERSION=0.1.0
arg UPDATECLI_VERSION=0.1.0

## Does NOT matches
ARG UPDATECLI_VERSION
ARG UPDATECLI_VERSION=
ARG GOLANG_VERSION
ARG RUST_VERSION=UPDATECLI_VERSION
ARG updatecli_version
----

==== ENV

Matches https://docs.docker.com/engine/reference/builder/#env[Dockerfile ENV, window="_blank"] instruction by keys to manipulate their values.

* *Only* matches on the environment key (left of the `=` when present)
** Matching is case sensitive

* *Only* matches single and valid key/value pairs:
** `ENV foo=bar` is supported
** `ENV foo=bar toto=titi` is NOT supported
** `ENV foo` is NOT supported (invalid Dockerfile instruction as a value is mandatory)

* When used as a target, *only* the value of the environment (right of the `=` when present)
** When no environment value is found (e.g. default value, no character `=` or empty value),
  then updatecli appends the `=` character followed by the value. It is possible to disable this behaviour by providing `ignoreUnsetValue: true` in the configuration.

With the following definition:

[source, yaml]
----
spec:
  file: Dockerfile
  instruction:
    keyword: "ENV"
    matcher: "UPDATECLI_VERSION"
----

you get the following results:

[source, Dockerfile]
----
# Matches
ENV UPDATECLI_VERSION=0.1.0
env UPDATECLI_VERSION=0.1.0

## Does NOT matches
# Invalid
ENV GOLANG_VERSION
# No match
ENV RUST_VERSION=UPDATECLI_VERSION
# lower case: no match
ENV updatecli_version
# Multiple key/value pairs
ENV FOO=BAR UPDATECLI_VERSION=0.1.0
----

== By Specific Coordinates

=== Parameters

{{< resourceparameters "sources" "dockerfile" >}}

=== Syntax

Updatecli represents internally a Dockerfile as a two-dimensional array where the first dimension is a list of Dockerfile instruction like "FROM", "RUN", etc..., and the second dimension represents a list of arguments for each instruction.

In the following example "Dockerfile", the first dimension is ["FROM","LABEL","LABEL"]

.Dockerfile
```
FROM jenkins/jenkins:2.274
LABEL maintainer=olblak
LABEL version=2.274 \
      date = "2021/01/09"
```

And the second dimension is :
```
FROM    = ["jenkins/jenkins:2.274"]
LABEL0  = ["maintainer", "olblak"]
LABEL1  = ["version", "2.274", "date", "2021/01/09"]
```

Updatecli identifies a specific Dockerfile instruction through its coordinates,
by using the syntax `INSTRUCTION[x][y]` where:

* `INSTRUCTION` must be replaced by a valid Dockerfile instruction like `ARG`, `ENV`, `LABEL`, etc
* "x" references a specific instruction position where x is replaced by any integer starting from 0. So "0" means the first instruction of type `INSTRUCTION`, "1" means the second, etc
* "y" references a specific argument element for the `INSTRUCTION[x]` where "y" is replaced by any integer starting from 0. So "0" means the first argument, "1" means the second, etc

Based on the Dockerfile example, here is the list of instruction equivalent
* `LABEL[0][0]` equal `maintainer`
* `LABEL[0][1]` equal `olblak`
* `LABEL[1][0]` equal `version`
* `LABEL[1][4]` equal `2021/01/09`

NOTE: A shorter syntax is available where `INSTRUCTION` is an alias for `INSTRUCTION[0][0]`.

[IMPORTANT]
====
When used as a target (and writing to a file):

* "Specific Coordinates" method might not keep the initial Dockerfile syntax
* "Specific Coordinates" method drops comments from the initial Dockerfile
====

=== Examples

.updatecli.yaml
```
sources:
  lastHelmVersion:
    name: Get Latest helm release version
    kind: githubRelease
    spec:
      owner: "helm"
      repository: "helm"
      token: {{ requiredEnv .github.token }}
      username: olblak
      versionFilter:
        kind: latest
conditions:
  isENVSet:
    name: Is ENV HELM_VERSION set
    kind: dockerfile
    spec:
      file: docker/Dockerfile
      instruction: ENV[1][0]
      value: "HELM_VERSION"
    scm:
      github:
        user: "updatecli"
        email: "updatecli@olblak.com"
        owner: "olblak"
        repository: "charts"
        token: {{ requiredEnv "GITHUB_TOKEN" }}
        username: "olblak"
        branch: "master"
targets:
  updateENVHELMVERSION:
    name: Update HELM_VERSION
    kind: dockerfile
    spec:
      file: docker/Dockerfile
      instruction: ENV[1][1]
    scm:
      github:
        user: "updatecli"
        email: "updatecli@olblak.com"
        owner: "olblak"
        repository: "charts"
        token: {{ requiredEnv "GITHUB_TOKEN" }}
        username: "olblak"
        branch: "master"
```

What it says:

*Source*

Retrieve the helm version from its github release located on https://github.com/helm/helm
  => v3.4.2

*Conditions*

Then it will test one condition:
If the dockerfile 'docker/Dockerfile' is located on the git repository https://github.com/olblak/charts
has the instruction ENV[1][0] set to "HELM_VERSION". ENV[1][0] is a custom syntax to represent
a two-dimensional array where the first element represents a specific Dockerfile instruction identifier
starting from "0" at the beginning of the document, so we are looking for the second INSTRUCTION "ENV".
The second element represents an instruction argument position. In this case, we want to check that ENV key
is set to "HELM_VERSION"

*Targets*

If the condition is met, which is to be sure that the ENV key set to "HELM_VERSION" exist, then we'll
are going to update its value if needed based on the version retrieved from the source.
The syntax is the same for the condition excepted that this time we are looking for ENV[1][1]
which means that the second argument of the second ENV instruction.
