Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support configuring ENTRYPOINT and CMD #271

Merged
merged 16 commits into from
Apr 9, 2023
Merged

Support configuring ENTRYPOINT and CMD #271

merged 16 commits into from
Apr 9, 2023

Conversation

vito
Copy link
Owner

@vito vito commented Apr 3, 2023

This change brings Bass closer to alignment with Dagger and Docker with how ENTRYPOINT/CMD (aka "default args") are handled.

Quick terminology note: "default args" is a modern spin on CMD since everyone should be using ENTRYPOINT these days and in that context CMD actually behaves as default args to the entrypoint. Dagger uses this new terminology, and so will Bass.

The tl;dr:

  • Thunks can now configure an entrypoint and default args. These are primarily useful for publishing containers, but they are also used within Bass in situations described below.
    (-> (from (linux/busybox)
          ($ sh -c "echo hello, world > /srv/index.html"))
        (with-entrypoint ["httpd" "-f" "-p" "8000"])
        (with-default-args ["-h" "/srv"])
        (with-port :http 8000))
  • ($ foo) does use the entrypoint. This is intentional, and mirrors RUN in Dockerfiles.
    (from (linux/alpine/git)                        ; has entrypoint ["git"]
      ($ git clone "https://github.com/vito/bass")) ; same as before
  • ($$) has been added for running a command using the entrypoint, replacing any default args.
    (from (linux/alpine/git)
      ($$ clone "https://github.com/vito/bass")) ; uses entrypoint ["git"]
  • When a thunk has no args and is forced to run, it runs the entrypoint + default args.
    (run (linux/hello-world))
  • (resolve) (and by extension (linux/foo)) now return a thunk instead of a nebulous "image type" that was really just a scope with certain fields.
    (assert thunk? (linux/alpine/git))
  • (docker-build) also now returns a thunk, and (oci-load) has been added to assist with turning OCI tarballs into thunks.
    (assert thunk? (docker-build *dir* {:os "linux"}))
    (assert thunk? (oci-load build/image.tar {:os "linux"}))
  • Internal refactor: thunks no longer have a "command" separate from "args" - just args.

Here are the tests, which probably tell the story more thoroughly:

(use (.strings)
     (*dir*/lib/oci.bass))

(defn entrypoint [thunk]
  (let [config (oci:config thunk)]
    (:Entrypoint config null)))

(defn read-all [thunk]
  (next (read thunk :raw)))

; entrypoint is not respected by ($)
(assert = "git version 2.36.3\n"
  (read-all
    (from (linux/alpine/git :2.36.3)
      ($ git --version))))

; entrypoint is respected by ($$)
(assert = "git version 2.36.3\n"
  (read-all
    (from (linux/alpine/git)
      ($$ --version))))

; entrypoint is preserved even after ($) ignores it
(assert = ["git"]
  (entrypoint
    (from (linux/alpine/git)
      ($ git --version))))

; entrypoint is used when running image directly
(assert null?
  (run (linux/hello-world)))

; entrypoint is used when reading output
(assert strings:includes?
  (read-all (linux/hello-world))
  "Hello from Docker")

; entrypoint is used when reading output path
(assert = "hello\n"
  (read-all
    (-> (linux/alpine)
        (with-entrypoint ["sh" "-c" "echo hello > index.html"])
        (subpath ./index.html))))

; entrypoint is removed when set to empty list
(assert null?
  (entrypoint
    (-> (linux/alpine/git :2.36.3)
        (with-entrypoint []))))

; entrypoint can be set by thunk
(def touch-and-run
  (-> (from (linux/busybox)
        ($ sh -c "echo hello from index.html > index.html"))
      (with-port :http 8000)
      (with-entrypoint ["httpd" "-f" "-p" "8000"])))

(assert = ["httpd" "-f" "-p" "8000"]
  (entrypoint touch-and-run))

; entrypoint is preserved from thunk to thunk
(assert = ["httpd" "-f" "-p" "8000"]
  (entrypoint
    (from touch-and-run
      ($ echo "Hello, world!"))))

; entrypoint is still used after running a command
(assert = "hello from index.html\n"
  (read-all
    (from (linux/alpine)
      ($ wget -O- (addr touch-and-run :http "http://$host:$port/")))))
  • Documentation
  • The implementation is, unfortunately, a bit confusing. But it works, and I think the surface-level semantics are intuitive. Probably ripe for a refactor/remodel someday.

@vito vito mentioned this pull request Apr 4, 2023
6 tasks
@vito vito changed the title Respect and allow configuring ENTRYPOINT and CMD Support configuring ENTRYPOINT and CMD Apr 7, 2023
vito added 7 commits April 8, 2023 00:09
along the way, remove Thunk.Cmd and instead just use args

NOTE: a few things will likely be broken on top of this.
<> are too subtle, {{foo}}/bar is much easier to read than <foo>/bar
Feels confusing in Bass thunk syntax. Dockerfiles don't respect them
either.

  (from (linux/alpine/git)
    ($ clone))

  (from (linux/alpine/git)
    ($ git clone)) ; much more recognizeable!

I could see Bass respecting them if/when it supports directly running an
image like (run (linux/redis)) though.
@vito vito force-pushed the entrypoints branch 3 times, most recently from f008961 to de08fb9 Compare April 9, 2023 05:33
vito added 7 commits April 9, 2023 18:55
this led to pretty confusing errors; it should only be used for decoding
Values, not general unmarshaling
this is a bit half-baked, just formalling marking the current decision

secret values are never encoded, because who knows where they'll end up.
in the long term there should be a way to fetch secrets on the runtime
side, but that's not implemented yet. for now, we'll just skip the
secret tests for the gRPC suite.
* (resolve), (docker-build), and the new (oci-load) return a thunk
  instead of an "image"

  "Image" was a sort of nebulous type; it was never its own type, it was
  really just a scope that had a certain set of fields. This was awkward
  because it meant you couldn't do the equivalent to this:

    FROM golang
    ENTRYPOINT ["go", "build"]

  But now you can, because it returns a thunk!

    (-> (linux/golang)
      (with-entrypoint ["go" "build"]))

  Same goes for (docker-build), and (oci-load) has been added and
  follows the same pattern.

  The returned thunk has no arguments set; it only has an image. More on
  this in the next point.

* If you tell a thunk to run, and the thunk has no arguments, it will
  inherit the ENTRYPOINT and CMD from its base image.

  This applies to (run), (read), thunk paths, and thunk addresses.

  It does NOT apply to (publish) or (export) as the intention is to
  publish the ENTRYPOINT/CMD, not run it.

* Now that (resolve) returns a thunk you'll also notice much smaller
  bass.lock files (thunks get their own marshaling, images marshaled as
  a scope, which is pretty verbose).

  On the other hand, you'll also have to remove your bass.lock files or
  bass --bump them since the old stored return value is no longer valid.

* Stop relying on nil vs. [] distinction for clearing ENTRYPOINT/CMD
  since it's not possible to represent with protobufs.

* Exposed ports are now included in published image config.
this was hard to figure out, but it works, and should be close to what
Dagger does. there is probably be a better way to model this, but I
think it achieves the desired semantics.
like ($), but uses the entrypoint.

it's *almost* sufficient to rely on 'default args' here instead, but the
two are for different things at the end of the day.
@vito vito marked this pull request as ready for review April 9, 2023 23:21
@vito vito merged commit 13d448c into main Apr 9, 2023
@vito vito deleted the entrypoints branch April 9, 2023 23:34
@vito vito added the enhancement New feature or request label Apr 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant