Repository navigation
v0.6.0
An environment is now something a library can hand you — configured, pinned, and with its command words attached.
Until now a command declaration took an image written with literal arguments, its words never left the module that wrote them, and #docker(...) could say nothing about a container beyond memory and cpus. A project could pin a toolchain, but not share one: the credentials, variables and mounts a tool needs had to be repeated at every call site, or wrapped in a module that re-implemented the tool's CLI as functions. v0.6.0 lets a command's environment be any expression, lets command words be imported, gives the container constructor the rest of its options — and makes the lock pin every registry image by digest, so what a library hands you is the same image on every machine.
Command words across modules
import commandbrings in the command words another module declares.import command { "python", "pip" } from "tools"makes a bare$ pip install ...dispatch to the environment thetoolsmodule declared for it.export command { "helm" } from "./lib/k8s.lask"imports and publishes again. A command declaration takesexport/internallike any other, and is exported by default.- No other import brings a command word in, and there is no renaming: the word is the program the environment runs. A named or namespace import changes nothing about where a command runs, so importing a module for its functions never reroutes your commands.
- A command's environment is any expression of type
Environment:command { "aws" } on tools.aws(profile = "dev"), a namespace member, a call with computed arguments. It is elaborated in the declaring module and evaluated when a command it selects runs. - It may not have an effect. No command, file,
stdin,logor random value, directly or through what it calls (E-TYPE-COMMAND-EFFECT);get_envis fine. This is what letslask cmdevaluate a declaration without running anything or consuming the program's stdin. - Dispatch compares where an environment comes from, not its value: the same literal, the same binding, or the same declaration. Two separate calls conflict, since only running them could show they agree — declare such words together.
- The words of a declaration are written in braces,
command { "go", "gofmt" } on e, for symmetry with the import form. With the braces the position ofonis lexical, so the editor marks it as a keyword there and leaves it an identifier elsewhere.
Container options
#docker(...)configures the container it launches. Beyondmemoryandcpus:memory_swapmemory_reservationcpu_sharescpuset_cpuscpuset_memspids_limitshm_sizeblkio_weightulimits;workdiruserenvplatformhostnameinit;read_onlytmpfscap_drop;networkdnsdns_searchadd_hostspublish;volumes; andbuild_argson a recipe.- Only options that stay inside the permission boundary of 10.7 are offered.
privileged,cap_add, devices and the host namespaces are left out: they would make that guarantee untrue rather than narrow it.publishandvolumeswiden it, and are written where the environment is. - An option given
nullis left out, as are thenullelements of a list or table, so a function that builds an environment can pass an optional parameter straight through.""is a value and is passed on —PAGER=""means something. A list or table already typedArray<String>orMap<String>is accepted as it is. - Environment values are keyed by argument name, not by the order the arguments were written in, so
#docker("a:1", memory = "1g", cpus = 2)and the same options reordered are one environment for dispatch. build_argscloses a gap 10.3 had already written down: the recipe hash covers the declared build arguments, and there was no way to declare any. Likedockerfileandcontext, it must be a literal, since it decides which imagelask env buildbuilds.- The environment in the command log is masked too. A
dockerenvironment can carry variables into the container, so a secret reaches the log through the environment value as readily as through the command string.
Images pinned by digest
- A run never pulls. Spec 10.3 and 10.4 always said so; none of it was implemented, and a run passed the tag to
docker run, which pulled whatever the tag named that day. Nowlask deps syncandlask env buildpull every registry image the program references, record its digest inlask.lock.json, andrun,eval,cmdand file access run<repository>@<digest>. - A fresh machine gets the very image the lock names, whatever the tag names upstream, and is checked to carry it (
E-IO-IMAGE-DIGEST). A missing image isE-IO-IMAGE-MISSING, naminglask env build. - A registry reference may omit its tag —
#alpinemeanslatest— since the lock pins what the bare name resolved to, and it cannot move under a run. deps syncmaterializes images after the modules, also in a project that declares no dependency, and--frozencovers images too.envs --checkchecks that each image is actually present, andenv listreports registry references and their pins, with JSON.- A reference computed at run time cannot be pinned: it runs as written if present. The REPL may still pull — it evaluates what is typed at it, which no lock records.
Language
- A secret binding may be
String | Null.--token!!: String | Null = nullsays "not given" the way every other optional parameter does, and a credential that may be unset can be read withfind_envand still be masked when present. Anullregisters nothing, so the text an absent value prints as is never masked elsewhere in a log.mark_secretbecomesT -> TforStringorString | Null. run(env, cmd)is the core command-execution function, formerlyrun_command(cmd, env): the environment comes first, as it does in$[env] cmd.- A re-exported name works everywhere its declaration does.
t.go()throughimport * as tfailed withinternal: missing declaration gowhentpublishedgobyexport { go } from ..., andlask runansweredno such functionfor it. Both now follow the re-export — keyword arguments,--helpbuilt from the declaring file under the published name, the function list, and shell completion for local paths — so a multi-file library that publishes its API from its entry module, as chapter 5 describes, has a CLI surface. - A recipe resolves inside the tree of the module that declares it.
#docker(dockerfile = "images/ansible/Dockerfile")written in a library read that path from the importing project and failed there. A library can now ship the recipes of its environments, built from the dependency cache.
In the editor
- Hovering a builtin documents it: the call with parameter names, the failure it raises, and the spec section it summarizes, condensed from chapter 15. Polymorphic builtins show their type parameters, as user declarations do, and a local that shadows a builtin is no longer mistaken for it.
Breaking changes
run_command(cmd, env)is nowrun(env, cmd). Rename the call and swap the arguments. A builtin call with keyword arguments is alwaysE-TYPE-KEYWORD.- A command declaration's words are written in braces.
command "go" on eis now a syntax error that says how to writecommand { "go" } on e. E-TYPE-COMMAND-DECLis replaced byE-TYPE-COMMAND-EFFECT. The literal-arguments rule it enforced is gone; what is refused now is an environment with an effect.- A project that runs containers needs
lask env build(orlask deps sync) before its first run. A registry image the lock does not pin, or one not on the daemon, isE-IO-IMAGE-MISSINGrather than a pull. Commit theimagessectionlask.lock.jsongains. build_argsmust be a literal, likedockerfileandcontext. Spec 10.2's claim that every container option had to be a literal is corrected: only these three are.
Lask is pre-1.0 and every feature is experimental until 1.0, so breaking changes remain possible; see compatibility.md.
Fixes and internals
- CI pins the image before running the containerized task in the colon-bearing-directory check, as the install test does, now that a run no longer pulls.
Examples
- example/ is reorganized into two groups, each numbered in reading order.
01-projectskeeps the end-to-end work:01-hello-world(was01-basic) and02-webapp-on-aws(was04-webapp). The Docker and Terraform projects are gone; what they showed is now in the language tour. - example/02-language is a tour of the whole language: fourteen topics, one runnable module each, covering the Quick Reference from values and types through to doc comments and the CLI. The commentary lives in the code beside the thing it describes, and because the comment above a task is also its help text,
lask run --helpin any of them is a second index into the same prose. - 02-webapp-on-aws takes its environments from lask-module-tools instead of wrapping Terraform and the AWS CLI through lask-terraform and lask-aws. The tools run as written —
$ terraform -chdir=infra apply ...,$ aws s3 sync ...— and their image tags, credentials and region are given once, in the environment each command word is declared on.
Documentation
- The Quick Reference, the specification and the READMEs follow every change above, and paragraphs are no longer hard-wrapped, so the docs pass cleanly through translation tools.
Full Changelog: v0.5.0...v0.6.0