github.com/GoogleContainerTools/skaffold/v2@v2.13.2/docs-v2/design_proposals/ko-builder.md (about)

     1  # ko builder
     2  
     3  * Author(s): Halvard Skogsrud (@halvards)
     4  * Design Shepherd: \<skaffold-core-team-member\>
     5  * Date: 2021-03-29
     6  * Status: Draft
     7  
     8  ## Objectives
     9  
    10  Fast, standardized, reproducible, configuration-less, Docker-less, and
    11  secure-by-default container image builds for Go apps.
    12  
    13  ## Background
    14  
    15  [ko](https://github.com/google/ko) is a container image builder for Go. It's
    16  [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko),
    17  doesn't use a `Dockerfile` or rely on the Docker daemon, and uses
    18  [distroless](https://github.com/GoogleContainerTools/distroless) base images by
    19  default. ko is to Go apps what
    20  [Jib](https://github.com/GoogleContainerTools/jib) is to JVM-based apps
    21  (approximately).
    22  
    23  The [Knative](https://knative.dev/) and [Tekton](https://tekton.dev/) open
    24  source projects use ko.
    25  
    26  ## Proposal
    27  
    28  This proposal adds a new `ko` builder to Skaffold, based on the `ko build`
    29  command (a.k.a. `ko publish`). The integration does _not_ include other ko
    30  functionality related to
    31  [rendering](https://github.com/google/ko#ko-resolve) manifests,
    32  [deploying](https://github.com/google/ko#ko-apply) to Kubernetes clusters, and
    33  [file watching](https://github.com/google/ko/blob/f7df8106196518df5c6c35432843421e33990329/pkg/commands/resolver.go#L240).
    34  
    35  Compared to ...
    36  
    37  - [the Cloud Native buildpacks builder](https://skaffold.dev/docs/builders/buildpacks/),
    38    the ko builder is
    39    [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko),
    40    doesn't require Docker, and uses a default base image that has a small attack
    41    surface ([distroless](https://github.com/GoogleContainerTools/distroless)).
    42  
    43  - [the Docker builder](https://skaffold.dev/docs/builders/docker/),
    44    the ko builder standardizes builds, avoiding artisanal
    45    [snowflake](https://martinfowler.com/bliki/SnowflakeServer.html)
    46    `Dockerfile`s. It also doesn't require the Docker daemon, so builds can
    47    run in security-constrained environments.
    48  
    49  - [the Kaniko builder](https://skaffold.dev/docs/builders/docker/#dockerfile-in-cluster-with-kaniko),
    50    the ko builder doesn't need a Kubernetes cluster, and avoids the
    51    previously-mentioned artisanal `Dockerfile`s.
    52  
    53  - [the Bazel builder](https://skaffold.dev/docs/builders/bazel/),
    54    the ko builder doesn't require users to adopt Bazel. However, users who
    55    already use Bazel for their Go app should use the Bazel builder.
    56  
    57  - [the custom builder](https://skaffold.dev/docs/builders/custom/),
    58    the ko builder is portable:
    59  
    60    1.  The Skaffold config can be shared with other developers and ops teams,
    61        and used in CI/CD pipelines, without requiring users to install
    62        additional tools such as Docker Engine or
    63        [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md)
    64        (or even ko, depending on how the builder is implemented). This eases the
    65        path to adoption of Skaffold and reduces friction for users, both for
    66        local development, and for anyone using Skaffold in CI/CD pipelines.
    67  
    68    2.  The ko builder doesn't require running custom shell scripts. This means
    69        more standardized builds, a desirable trait for enterprise users.
    70  
    71  The ko builder supports and enhances these Skaffold
    72  [features](https://skaffold.dev/docs/):
    73  
    74  - _fast local workflow_: building with ko is
    75    [fast](https://cloud.google.com/blog/topics/developers-practitioners/ship-your-go-applications-faster-cloud-run-ko).
    76  
    77  - _share with other developers_: no additional tools are required to
    78    `skaffold run` with the ko builder, not even Docker. Though if we don't embed
    79    ko in Skaffold, ko will be a tool that all developers in a team would have to
    80    install.
    81  
    82  - works great as a _CI/CD building block_: when using the ko builder, pipeline
    83    steps can run using the default Skaffold container image, without
    84    installing additional tools or keeping toolchain versions in sync across
    85    local development and CI/CD.
    86  
    87  ## Background: ko image names and Go import paths
    88  
    89  Ko uses Go import paths to build images. The
    90  [`ko build`](https://github.com/google/ko#build-an-image) command is similar
    91  to `go build` and takes a positional argument, which can be either a local
    92  file path or a Go  import path. If the argument is a local file path (as per
    93  [`go/build.IsLocalImport()`](https://pkg.go.dev/go/build#IsLocalImport))
    94  then, ko resolves the local file path to a Go import path (see
    95  [`github.com/google/ko/pkg/build`](https://github.com/google/ko/blob/ab4d264103bd4931c6721d52bfc9d1a2e79c81d1/pkg/build/gobuild.go#L261)).
    96  
    97  The import path must be of the package than contains the `main()` function.
    98  For instance, to build Skaffold using ko, from the repository root directory:
    99  
   100  ```sh
   101  ko build ./cmd/skaffold
   102  ```
   103  
   104  or
   105  
   106  ```sh
   107  ko build github.com/GoogleContainerTools/skaffold/cmd/skaffold
   108  ```
   109  
   110  When the ko CLI is used to
   111  [populate the image name in templated Kubernetes resource files](https://github.com/google/ko#kubernetes-integration),
   112  only the Go import path option can be used, and the import path must be
   113  prefixed by the `ko://` scheme, e.g.,
   114  `ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold`.
   115  
   116  Ko determines the image name from the container image registry (provided by the
   117  `KO_DOCKER_REPO` environment variable) and the Go import path. The Go import
   118  path is appended in one of these ways:
   119  
   120  - The last path segment (e.g., `skaffold`), followed by a hyphen and a MD5
   121    hash. This is the default behavior of the `ko build` command.
   122  
   123  - The last path segment (e.g., `skaffold`) only, if `ko build` is invoked
   124    with the `-B` or `--base-import-paths` flag.
   125  
   126  - The full import path, lowercased (e.g.,
   127    `github.com/googlecontainertools/skaffold/cmd/skaffold`), if `ko build` is
   128    invoked with the `-P` or `--preserve-import-paths` flag. This is the option
   129    used by projects such as Knative (see the
   130    [`release.sh` script](https://github.com/knative/serving/blob/v0.24.0/vendor/knative.dev/hack/release.sh#L98))
   131    and Tekton
   132    (see the pipeline in
   133    [publish.yaml](https://github.com/tektoncd/pipeline/blob/v0.25.0/tekton/publish.yaml#L137)).
   134  
   135  - No import path (just `KO_DOCKER_REPO`), if `ko build` is invoked with the
   136    `--bare` flag.
   137  
   138  ## Supporting existing Skaffold users
   139  
   140  The Skaffold ko builder follows the existing Skaffold image naming logic. This
   141  means that the image naming behavior doesn't change for existing Skaffold users
   142  who migrate from other builders to the ko builder.
   143  
   144  The ko builder achieves this by using ko's
   145  [`Bare`](https://github.com/google/ko/blob/ab4d264103bd4931c6721d52bfc9d1a2e79c81d1/pkg/commands/options/publish.go#L60)
   146  naming option.
   147  
   148  By using this option, the image name is not tied to the Go import path. If the
   149  Skaffold
   150  [default repo](https://skaffold.dev/docs/environment/image-registries/) value
   151  is `gcr.io/k8s-skaffold` and the value of the `image` field in `skaffold.yaml`
   152  is `skaffold`, the resulting image name will be `gcr.io/k8s-skaffold/skaffold`.
   153  
   154  It is still necessary to resolve the Go import path for the underlying ko
   155  implementation. To do so, the ko builder determines the import path based on
   156  the value of the `main` config field. The `main` config field refers to the
   157  location of a main package and corresponds to a `go build` pattern, e.g.,
   158  `go build ./cmd/skaffold`. Using the `main` field results in deterministic
   159  behavior even in cases where there are multiple main packages in different
   160  directories.
   161  
   162  If `main` is a relative path (and it will be most of the time), it is
   163  relative to the current
   164  [`context`](https://skaffold.dev/docs/references/yaml/#build-artifacts-context)
   165  (a.k.a.
   166  [`Workspace`](https://github.com/GoogleContainerTools/skaffold/blob/v1.27.0/pkg/skaffold/schema/latest/config.go#L832))
   167  directory.
   168  
   169  For example, to build Skaffold itself, with `package main` in the
   170  `./cmd/skaffold/` subdirectory, the config would be as follows:
   171  
   172  ```yaml
   173  apiVersion: skaffold/v2beta26
   174  kind: Config
   175  build:
   176    artifacts:
   177    - image: skaffold
   178      context: .
   179      ko:
   180        main: ./cmd/skaffold
   181  ```
   182  
   183  Users can specify a `main` value using a pattern with the `...` wildcard, such
   184  as `./...`. In this case, ko locates the main package. If there are multiple
   185  main packages,
   186  [ko fails](https://github.com/google/ko/blob/780c2812926cd706423e2ba65aeb1beb842c04af/pkg/build/gobuild.go#L270).
   187  
   188  Implementation note: The value of `main` will be the input when invoking
   189  [`QualifyImport()`](https://github.com/GoogleContainerTools/skaffold/blob/953594000be68fa8fad0ec4636ab03f8153a1c08/pkg/skaffold/build/ko/build.go#L93).
   190  
   191  If the Go sources and the `go.mod` file are in a subdirectory of the `context`
   192  directory, users can use the `dir` config field to specify the path where the
   193  ko builder runs the `go` tool.
   194  
   195  ## Supporting existing ko users
   196  
   197  To support existing ko users moving to Skaffold, the ko builder also supports
   198  `image` names in `skaffold.yaml` that use the Go import path, prefixed by the
   199  `ko://` scheme. Examples of such image references in Kubernetes manifest files
   200  can be seen in projects such as
   201  [Knative](https://github.com/knative/serving/blob/main/config/core/deployments/activator.yaml#L41)
   202  and
   203  [Tekton](https://github.com/tektoncd/pipeline/blob/v0.25.0/config/controller.yaml#L66).
   204  
   205  In the case of `ko://`-prefixed image names, the Skaffold ko builder
   206  constructs the image name by:
   207  
   208  1.  Removing the `ko://` scheme prefix.
   209  2.  Transforming the import path to a valid image name using the function
   210      [`SanitizeImageName()`](https://github.com/GoogleContainerTools/skaffold/blob/v1.27.0/pkg/skaffold/docker/reference.go#L83)
   211      (from the package
   212      `github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker`).
   213  3.  Combining the Skaffold default repo with the transformed import path as per
   214      existing Skaffold image naming logic.
   215  
   216  This will result in image names that match those produced by the `ko` CLI when
   217  using the `-P` or `--preserve-import-paths` flag. For example, if the Skaffold
   218  default repo is `gcr.io/k8s-skaffold` and the `image` name in `skaffold.yaml`
   219  is `ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold`, the resulting
   220  image name will be
   221  `gcr.io/k8s-skaffold/github.com/googlecontainertools/skaffold/cmd/skaffold`.
   222  
   223  Real-world examples of image names that follow this naming convention can be
   224  found in the Tekton and Knative release manifests. For instance, view the
   225  images in the Knative Serving release YAMLs:
   226  
   227  ```sh
   228  curl -sL https://github.com/knative/serving/releases/download/v0.24.0/serving-core.yaml | grep 'image: '
   229  ```
   230  
   231  If the `image` field in `skaffold.yaml` starts with the `ko://` scheme prefix,
   232  the Skaffold ko builder uses the Go import path that follows the prefix. For
   233  example, to build Skaffold itself, with `package main` in the `./cmd/skaffold/`
   234  subdirectory, the config would be as follows:
   235  
   236  ```yaml
   237  apiVersion: skaffold/v2beta26
   238  kind: Config
   239  build:
   240    artifacts:
   241    - image: ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold
   242      context: .
   243      ko: {}
   244  ```
   245  
   246  The `main` field is ignored if the `image` field starts with the `ko://`
   247  scheme prefix.
   248  
   249  ## Debugging ko images using Skaffold
   250  
   251  Ko provides the
   252  [`DisableOptimizations`](https://github.com/google/ko/blob/780c2812926cd706423e2ba65aeb1beb842c04af/pkg/commands/options/build.go#L34)
   253  build option to
   254  [set `gcflags` to disable optimizations and inlining](https://github.com/google/ko/blob/335c1ac8a6fdcc5eb0bb26579e4b44b4c62a9565/pkg/build/gobuild.go#L709-L712).
   255  The ko builder sets `DisableOptimizations` to `true` when the Skaffold
   256  `runMode` is `Debug`.
   257  
   258  Skaffold can
   259  [recognize Go-based container images](https://skaffold.dev/docs/workflows/debug/#go-runtime-go-protocols-dlv)
   260  built by ko by the presence of the
   261  [`KO_DATA_PATH` environment variable](https://github.com/google/ko/blob/9a256a4b1920ed5fd5fbf77d987fddbfb9733c40/pkg/build/gobuild.go#L803-L810).
   262  This allows Skaffold to transform Pod specifications to enable remote
   263  debugging.
   264  
   265  The ko builder implementation work will add `KO_DATA_PATH` to
   266  [the set of environment variables used to detect Go-based applications](https://github.com/GoogleContainerTools/skaffold/blob/c75c55133e709b2fee906eb158be13c7ccfa72cd/pkg/skaffold/debug/transform_go.go#L67-L72)
   267  and updating the associated unit tests and
   268  [documentation](https://github.com/GoogleContainerTools/skaffold/blob/01a833614efd780ddece99198aa7fdcf3f355706/docs-v1/content/en/docs/workflows/debug.md#L103).
   269  
   270  ## Design
   271  
   272  Adding the ko builder requires making config changes to the Skaffold schema.
   273  
   274  1.  Add a `KoArtifact` type:
   275  
   276      ```go
   277      // KoArtifact builds images using [ko](https://github.com/google/ko).
   278      type KoArtifact struct {
   279          // Asmflags are assembler flags passed to the builder.
   280          Asmflags []string `yaml:"asmflags,omitempty"`
   281  
   282          // BaseImage overrides the default ko base image.
   283          // Corresponds to, and overrides, the `defaultBaseImage` in `.ko.yaml`.
   284          BaseImage string `yaml:"fromImage,omitempty"`
   285  
   286          // Dependencies are the file dependencies that Skaffold should watch for both
   287          // rebuilding and file syncing for this artifact.
   288          Dependencies *KoDependencies `yaml:"dependencies,omitempty"`
   289  
   290          // Dir is the directory where the `go` tool will be run.
   291          // The value is a directory path relative to the `context` directory.
   292          // If empty, the `go` tool will run in the `context` directory.
   293          // Examples: `live-at-head`, `compat-go114`
   294          Dir string `yaml:"dir,omitempty"`
   295  
   296          // Env are environment variables, in the `key=value` form, passed to the build.
   297          // These environment variables are only used at build time.
   298          // They are _not_ set in the resulting container image.
   299          // For example: `["GOPRIVATE=source.developers.google.com", "GOCACHE=/workspace/.gocache"]`.
   300          Env []string `yaml:"env,omitempty"`
   301  
   302          // Flags are additional build flags passed to the builder.
   303          // For example: `["-trimpath", "-v"]`.
   304          Flags []string `yaml:"flags,omitempty"`
   305  
   306          // Gcflags are Go compiler flags passed to the builder.
   307          // For example: `["-m"]`.
   308          Gcflags []string `yaml:"gcflags,omitempty"`
   309  
   310          // Labels are key-value string pairs to add to the image config.
   311          // For example: `{"org.opencontainers.image.source":"https://github.com/GoogleContainerTools/skaffold"}`.
   312          Labels map[string]string `yaml:"labels,omitempty"`
   313  
   314          // Ldflags are linker flags passed to the builder.
   315          // For example: `["-buildid=", "-s", "-w"]`.
   316          Ldflags []string `yaml:"ldflags,omitempty"`
   317  
   318          // Main is the location of the main package. It is the pattern passed to `go build`.
   319          // If main is specified as a relative path, it is relative to the `context` directory.
   320          // If main is empty, the ko builder uses a default value of `.`.
   321          // If main is a pattern with wildcards, such as `./...`,
   322          // the expansion must contain only one main package, otherwise ko fails.
   323          // Main is ignored if the `ImageName` starts with `ko://`.
   324          // Example: `./cmd/foo`
   325          Main string `yaml:"main,omitempty"`
   326  
   327          // Platforms is the list of platforms to build images for. Each platform
   328          // is of the format `os[/arch[/variant]]`, e.g., `linux/amd64`.
   329          // By default, the ko builder builds for `all` platforms supported by the
   330          // base image.
   331          Platforms []string `yaml:"platforms,omitempty"`
   332  
   333          // SourceDateEpoch is the `created` time of the container image.
   334          // Specify as the number of seconds since January 1st 1970, 00:00 UTC.
   335          // You can override this value by setting the `SOURCE_DATE_EPOCH`
   336          // environment variable.
   337          SourceDateEpoch uint64 `yaml:"sourceDateEpoch,omitempty"`
   338      }
   339      ```
   340  
   341  2.  Add a `KoArtifact` field to the `ArtifactType` struct:
   342  
   343      ```go
   344      type ArtifactType struct {
   345        [...]
   346          // KoArtifact builds images using [ko](https://github.com/google/ko).
   347          KoArtifact *KoArtifact `yaml:"ko,omitempty" yamltags:"oneOf=artifact"`
   348      }
   349      ```
   350  
   351  3.  Define `KoDependencies`:
   352  
   353      ```go
   354      // KoDependencies is used to specify dependencies for an artifact built by ko.
   355      type KoDependencies struct {
   356          // Paths should be set to the file dependencies for this artifact,
   357          // so that the Skaffold file watcher knows when to rebuild and perform file synchronization.
   358          // Defaults to ["**/*.go"].
   359          Paths []string `yaml:"paths,omitempty" yamltags:"oneOf=dependency"`
   360  
   361          // Ignore specifies the paths that should be ignored by Skaffold's file watcher.
   362          // If a file exists in both `paths` and in `ignore`, it will be ignored,
   363          // and will be excluded from both rebuilds and file synchronization.
   364          Ignore []string `yaml:"ignore,omitempty"`
   365      }
   366      ```
   367  
   368  4.  Add `KO` to the `BuilderType` enum in `proto/enums/enums.proto`:
   369  
   370      ```proto
   371      enum BuilderType {
   372          // Could not determine builder type
   373          UNKNOWN_BUILDER_TYPE = 0;
   374          // JIB Builder
   375          JIB = 1;
   376          // Bazel Builder
   377          BAZEL = 2;
   378          // Buildpacks Builder
   379          BUILDPACKS = 3;
   380          // Custom Builder
   381          CUSTOM = 4;
   382          // Kaniko Builder
   383          KANIKO = 5;
   384          // Docker Builder
   385          DOCKER = 6;
   386          // Ko Builder
   387          KO = 7;
   388      }
   389      ```
   390  
   391  ### Builder config schema
   392  
   393  Example basic config, this will be sufficient for many users:
   394  
   395  ```yaml
   396  apiVersion: skaffold/v2beta26
   397  kind: Config
   398  build:
   399    artifacts:
   400    - image: skaffold-example-ko
   401      ko: {}
   402  ```
   403  
   404  The value of the `image` field is the Go import path of the app entry point,
   405  [prefixed by `ko://`](https://github.com/google/ko/pull/58).
   406  
   407  A more comprehensive example config:
   408  
   409  ```yaml
   410  apiVersion: skaffold/v2beta26
   411  kind: Config
   412  build:
   413    artifacts:
   414    - image: skaffold-example-ko-comprehensive
   415      ko:
   416        fromImage: gcr.io/distroless/base:nonroot
   417        dependencies:
   418          paths:
   419          - go.mod
   420          - "**.go"
   421        dir: '.'
   422        env:
   423        - GOPRIVATE=source.developers.google.com
   424        flags:
   425        - -trimpath
   426        - -v
   427        gcflags:
   428        - -m
   429        labels:
   430          foo: bar
   431          baz: frob
   432        ldflags:
   433        - -buildid=
   434        - -s
   435        - -w
   436        main: ./cmd/foo
   437        platforms:
   438        - linux/amd64
   439        - linux/arm64
   440  ```
   441  
   442  ko requires setting a
   443  [`KO_DOCKER_REPO`](https://github.com/google/ko#choose-destination)
   444  environment variable to specify where container images are pushed. The Skaffold
   445  [default repo](https://skaffold.dev/docs/environment/image-registries/)
   446  maps directly to this value.
   447  
   448  ### Resolved questions
   449  
   450  1.  Should Skaffold embed ko as a Go module, or shell out?
   451  
   452       __Resolved:__ Embed as a Go module
   453  
   454      Benefits of embedding:
   455  
   456      - Skaffold can pin the ko version it supports in its `go.mod` file. Users
   457        wouldn't raise bugs/issues for incompatible version pairings of Skaffold
   458        and ko.
   459  
   460      - Reduce toolchain maintenance toil for users. Skaffold users wouldn't need
   461        to synchronize ko versions used by different team members or in their CI
   462        build, since the Skaffold version determines the ko version.
   463  
   464      - Portability. Skaffold+ko users only need one tool for their container
   465        image building needs: the `skaffold` binary. (Plus the Go distribution,
   466        of course.) The current `gcr.io/k8s-skaffold/skaffold` container image
   467        could serve as a build and deploy image for CI/CD pipeline steps.
   468  
   469      Embedding ko would require some level of documented behavioural stability
   470      guarantees for the most ko interfaces that Skaffold would use, such as
   471      [`build.Interface`](https://github.com/google/ko/blob/82cabb40bae577ce3bc016e5939fd85889538e8b/pkg/build/build.go#L24)
   472      and
   473      [`publish.Interface`](https://github.com/google/ko/blob/82cabb40bae577ce3bc016e5939fd85889538e8b/pkg/publish/publish.go#L24),
   474      or others?
   475  
   476      Benefits of shelling out:
   477  
   478      - It's an established pattern used by other Skaffold builders.
   479  
   480      - It would allow Skaffold to support a range of ko versions. On the other
   481        hand, these versions would need to be tracked and documented.
   482  
   483      - No need to resolve dependency version differences between Skaffold and
   484        ko.
   485  
   486      - If a new ko version provided a significant bug fix, there would be no
   487        need to release a new version of Skaffold for this fix.
   488  
   489      Shelling out to ko would require some stability guarantees for the
   490      `ko build` subcommand.
   491  
   492      Suggest embedding as a Go module.
   493  
   494  2.  Should Skaffold use base image settings from
   495      [`.ko.yaml`](https://github.com/google/ko#configuration) if the ko builder
   496      definition in `skaffold.yaml` doesn't specify a base image?
   497  
   498      __Resolved:__ Yes, to simplify adoption of Skaffold for existing ko users.
   499  
   500  3.  If a config value is set both as an environment variable, and as a config
   501      value, which takes precedence? E.g., `ko.sourceDateEpoch` vs
   502      `SOURCE_DATE_EPOCH`.
   503  
   504      __Resolved:__ Follow existing Skaffold patterns.
   505  
   506  4.  Should the ko builder have a config option for
   507      [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/specs/source-date-epoch/),
   508      or should users specify the value via an environment variable?
   509  
   510      __Resolved__: Specify via the reproducible builds spec environment variable
   511      `SOURCE_DATE_EPOCH`, see
   512      <https://github.com/google/ko#why-are-my-images-all-created-in-1970> and
   513      <https://reproducible-builds.org/docs/source-date-epoch/>.
   514  
   515  5.  Should we default dependency paths to `["go.mod", "**.go"]` instead of
   516      `["."]`.?
   517  
   518      The former is a useful default for many (most?) Go apps, and it's used
   519      in the `custom` example. The latter is the default for some other builders.
   520  
   521      __Resolved__: Default to `["**/*.go"]`, see
   522      [#6617](https://github.com/GoogleContainerTools/skaffold/pull/6617#discussion_r719804744).
   523  
   524  6.  Add a Google Cloud Build (`gcb`) support for the ko builder?
   525  
   526      By embedding ko as a module, there is no need for a ko-specific Skaffold
   527      builder image.
   528  
   529      __Resolved__: Add remote builder support.
   530  
   531  7.  File sync support: Should we limit this to
   532      [ko static assets](https://github.com/google/ko#static-assets) only?
   533  
   534      This is the only way to include additional files in a container image
   535      built by ko.
   536  
   537      __Resolved__: Implement file sync (for the Beta stage).
   538  
   539  ### Open questions
   540  
   541  1.  Should the ko builder be the default for `skaffold init`, instead of
   542      buildpacks, for Go apps, when there's no Dockerfile and no Bazel workspace
   543      file?
   544  
   545      Suggest yes, to make Skaffold a compelling choice for Go developers.
   546  
   547      If no, we can still consider configuring the ko builder if `skaffold init`
   548      finds a `.ko.yaml` configuration file.
   549  
   550      __Not Yet Resolved__
   551  
   552  ## Approach
   553  
   554  Implement the ko builder as a series of small PRs that can be merged one by one.
   555  The PRs should not surface any new user-visible behavior until the feature is
   556  ready.
   557  
   558  This approach has a lower risk than implementing the entire feature on a
   559  separate branch before merging all at once.
   560  
   561  The steps roughly outlined:
   562  
   563  1.  Add dependency on the `github.com/google/ko` module.
   564  
   565  2.  Implement the core ko build and publish logic, including unit tests in the
   566      package `github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko`.
   567  
   568      Support this implementation with "dummy" schema definitions for types such
   569      as `KoArtifact` in a separate "temporary" package. This package only exists
   570      until the definitions are merged into the latest `v1` schema, and it allows
   571      for evolution of the types until they are committed to the schema.
   572  
   573  3.  Add integration test for the ko builder to the `integration` package, and
   574      an example app + config to a new `integration/examples/ko` directory.
   575  
   576      To avoid failures in schema unit tests from the new example in the
   577      integration directory, add an `if` statement to `TestParseExamples` in the
   578      package `github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema` that
   579      skips directories called `ko`.
   580  
   581  4.  Add the ko builder schema types (`KoArtifact` and `KoDependency`) to the
   582      latest unreleased schema.
   583  
   584      For the `ArtifactType` struct, add a `KoArtifact` field, but set its
   585      YAML flag key to `"-"`
   586      (full syntax `` `yaml:"-,omitempty" yamltags:"oneOf=artifact"` ``).
   587  
   588      Do _not_ yet add the `KO = 7;` entry to the `BuilderType` enum in
   589      `proto/enums/enums.proto`.
   590  
   591  5.  Plumb the code in the package `pkg/skaffold/build/ko` into the other parts
   592      of the Skaffold codebase that interacts with the config schema.
   593  
   594      E.g., a `case` statement for `a.KoArtifact` in the `newPerArtifactBuilder`
   595      function in `pkg/skaffold/build/local` (file `types.go`).
   596  
   597  6.  When we are ready to add the ko builder as an alpha feature to an upcoming
   598      Skaffold release, set the `KoArtifact` YAML flag key to `ko` and add `KO`
   599      to the `BuilderType` enum.
   600  
   601  ## Implementation plan
   602  
   603  1.  [Done] Define integration points in the ko codebase that allows ko to be
   604      used from Skaffold without duplicating existing ko CLI code.
   605  
   606      In the package `github.com/google/ko/pkg/commands`:
   607  
   608      [`resolver.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/resolver.go#L110)
   609      ```go
   610      // NewBuilder creates a ko builder
   611      func NewBuilder(ctx context.Context, bo *options.BuildOptions) (build.Interface, error)
   612      ```
   613  
   614      [`resolver.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/resolver.go#L146)
   615      ```go
   616      // NewPublisher creates a ko publisher
   617      func NewPublisher(po *options.PublishOptions) (publish.Interface, error)
   618      ```
   619  
   620      [`publisher.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/publisher.go#L28)
   621      ```go
   622      // PublishImages publishes images
   623      func PublishImages(ctx context.Context, importpaths []string, pub publish.Interface, b build.Interface) (map[string]name.Reference, error)
   624      ```
   625  
   626      Add build and publish options to support Skaffold config propagating to
   627      ko. In the package `github.com/google/ko/pkg/commands/options`:
   628  
   629      [`build.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/options/build.go#L25)
   630      ```go
   631      type BuildOptions struct {
   632            // BaseImage enables setting the default base image programmatically.
   633            // If non-empty, this takes precedence over the value in `.ko.yaml`.
   634            BaseImage string
   635  
   636            // WorkingDirectory allows for setting the working directory for invocations of the `go` tool.
   637            // Empty string means the current working directory.
   638            WorkingDirectory string
   639  
   640          // UserAgent enables overriding the default value of the `User-Agent` HTTP
   641            // request header used when retrieving the base image.
   642            UserAgent string
   643  
   644          [...]
   645      }
   646      ```
   647  
   648      [`publish.go`](https://github.com/google/ko/blob/ee23538378722e060a2f7c7800f226e0b82e09e7/pkg/commands/options/publish.go#L29)
   649      ```go
   650      type PublishOptions struct {
   651            // DockerRepo configures the destination image repository.
   652            // In normal ko usage, this is populated with the value of $KO_DOCKER_REPO.
   653            DockerRepo string
   654  
   655            // LocalDomain overrides the default domain for images loaded into the local Docker daemon.
   656            // Use with Local=true.
   657            LocalDomain string
   658  
   659            // UserAgent enables overriding the default value of the `User-Agent` HTTP
   660            // request header used when pushing the built image to an image registry.
   661            UserAgent string
   662  
   663          [...]
   664      }
   665      ```
   666  
   667  2.  Add ko builder with support for existing ko config options. Provide
   668      this as an Alpha feature in an upcoming Skaffold release.
   669  
   670      Config options supported, all are optional:
   671  
   672      -   `fromImage`, to override the default distroless base image
   673      -   `dependencies`, for Skaffold file watching
   674      -   `dir`, if Go sources are not in the `context` directory
   675      -   `env`, to support ko CLI users who currently set environment variables
   676          such as `GOFLAGS` when running ko.
   677      -   `labels`, e.g., to
   678          [link an image to a Git repository](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys)
   679      -   `platforms`
   680      -   `sourceDateEpoch`
   681      -   `flags`, e.g., `-v`, `-trimpath`
   682      -   `ldflags`, e.g., `-s`
   683  
   684      Example `skaffold.yaml` supported at this stage:
   685  
   686      ```yaml
   687      apiVersion: skaffold/v2beta26
   688      kind: Config
   689      build:
   690        artifacts:
   691        - image: skaffold-ko
   692          ko:
   693            fromImage: gcr.io/distroless/base:nonroot
   694            dependencies:
   695              paths:
   696              - go.mod
   697              - "**.go"
   698            dir: '.'
   699            env:
   700            - GOPRIVATE=source.developers.google.com
   701            labels:
   702              foo: bar
   703              baz: frob
   704            ldflags:
   705            - -s
   706            main: ./cmd/foo
   707            platforms:
   708            - linux/amd64
   709            - linux/arm64
   710      ```
   711  
   712  3.  Implement Skaffold config support for additional ko config options not
   713      currently supported by ko:
   714  
   715      -   `asmflags`
   716      -   `gcflags`
   717  
   718      Provide this as a feature in an upcoming Skaffold release.
   719  
   720  ## Release plan
   721  
   722  The ko builder will go through the release stages Alpha -> Beta -> Stable.
   723  
   724  The following features will be released at each stage:
   725  
   726  **Alpha**
   727  
   728  - Support for the Skaffold lifecycle subcommands that build images:
   729    - `build`
   730    - `debug`
   731    - `dev`
   732    - `run`
   733  - Images built using the ko builder are automatically detected as Go images
   734    for debugging support.
   735  - File watch support for `dev` mode.
   736  - Local builder only (no `gcb` or `cluster` builder at this stage).
   737  - Multi-platform images support, including support for
   738    [`all`](https://github.com/google/ko#multi-platform-images), which builds
   739    images for all platforms supported by the base image).
   740  - Image names following standard Skaffold naming, for existing Skaffold
   741    users.
   742  - Support for `ko://`-prefixed image names, for existing ko users.
   743  
   744  **Beta**
   745  
   746  - File sync support.
   747  - Remote builders support.
   748  - `skaffold init` support, behind a `--enableKoInit` flag.
   749  
   750  **Stable**
   751  
   752  - Cloud Code integration
   753  
   754  ## Integration test plan
   755  
   756  Please describe what new test cases you are going to consider.
   757  
   758  1.  Unit and integration tests for ko builder, similar to other builders.
   759  
   760      The integration tests should be written to catch situations such as where
   761      changes to ko interfaces break the Skaffold ko builder.
   762  
   763  2.  Test that the ko flag
   764      [`--disable-optimization`](https://github.com/google/ko/blob/f7df8106196518df5c6c35432843421e33990329/pkg/commands/options/build.go#L34)
   765      is added for debugging.
   766  
   767  3.  Add basic and comprehensive ko examples to the `integration/examples`
   768      directory.