# Introduction `ambient-run` runs continuous integration build locally. It creates a virtual machine for each build. The VM has no network access, for safety, security, and control. While `ambient-run` is meant to be a usable tool on its own, it's also acts as a base component for a full CI system. However, the full CI system is outside the scope of this documents, which only considers `ambient-run` as a standalone tool. The build VM contains all the tools needed for the build, and gets all the source code and all the dependencies via virtual hard drives. The build writes any artifacts is produces to another virtual hard drives. Yet another virtual hard drive acts as a cache between builds, to allow for incremental builds. ~~~dot digraph "ambient-run" { src [label="source \n code" shape=folder] deps [label="dependencies" shape=folder] cache [label="cache" shape=folder] artifact [label="build \n artifacts" shape=folder] VM [label="build \n VM" shape=box3d] log [label="build log" shape=note] src -> VM deps -> VM cache -> VM VM -> cache VM -> artifact VM -> log } ~~~ ## Example FIXME: This section will have an example of how to use `ambient-run`. # Requirements and acceptance criteria FIXME: This chapter will enumerate all requirements for `ambient-run`, as acceptance criteria, using Subplot scenarios to document how to verify the software meets the requirements. ## Stakeholders The following people are explicitly named stakeholders for `ambient-run`: * Lars Wirzenius Additional stakeholders will be added as they're discovered. They may be specific people, or descriptions of groups. ## Configuration handling ### Show default per-user configuration file path name _Requirement:_ `ambient-run` can tell the user the path name of the default configuration file. _Justification:_ As a user I want to be sure where the default configuration file should be, whether it currently exists or not. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run when I run ambient-run config --default --filename then stdout matches regex ^/ then stdout contains "/.config/ambient-run/config.yaml" ~~~ ### List configuration file paths _Requirement:_ `ambient-run` can list the path name of the configuration file it read. _Justification:_ As a user I want to know what configuration files are actually used. _Stakeholder:_ Lars. Note that we want to know only the names of files that are actually used, not files that will be used if they exist. ~~~scenario given an installed ambient-run when I run ambient-run config --filename then stdout is exactly "" given file .config/ambient-run/config.yaml from default.yaml when I run ambient-run config --filename then stdout matches regex ^/ then stdout contains "/.config/ambient-run/config.yaml" then stdout doesn't contain "extra.yaml" given file extra.yaml when I run ambient-run --config extra.yaml config --filename then stdout contains "/.config/ambient-run/config.yaml" then stdout contains "/extra.yaml" ~~~ ### Show per-user configuration _Requirement:_ One can query `ambient-run` for its full per-user configuration. This means the union of the built-in default configuration, any configuration files, and command line arguments. _Justification:_ As a user I want to see the actual configuration without having to deduce it myself. _Stakeholder:_ Lars ~~~scenario given an installed ambient-run given file .config/ambient-run/config.yaml from default.yaml given file extra.yaml when I run ambient-run --config extra.yaml config then stdout, as YAML, matches file full-config.yaml ~~~ ~~~{#default.yaml .file .yaml} image: /my/image.qcow2 max_cpus: 4 ~~~ ~~~{#extra.yaml .file .yaml} image: /other/image.qcow2 max_cpus: 8 ~~~ ~~~{#full-config.yaml .file .yaml} image: /other/image.qcow2 max_cpus: 8 ~~~ ### Show per-project configuration _Requirement:_ One can query `ambient-run` for its full per-project configuration. This means the union of the built-in defaults and the configuration in the file. _Justification:_ As a user I want to see the actual configuration without having to deduce it myself. _Stakeholder:_ Lars ~~~scenario given an installed ambient-run given file .config/ambient-run/config.yaml from default.yaml given file project.yaml when I run ambient-run project project.yaml then stdout, as YAML, matches file full-project.yaml ~~~ ~~~{#project.yaml .file .yaml} shell: | cargo test ~~~ ~~~{#full-project.yaml .file .yaml} source: . shell: | cargo test image: /my/image.qcow2 artifact: null artifact_max_size: null dependencies: null cache: null ~~~ ### Show per-build configuration ## Building with ambient-run ### Smoke test build works _Requirement:_ I can do a simple build that just runs the shell command `echo hello, world` using `ambient-run`. _Justification:_ This is the simplest possible project build, and if it works, then at least the very fundamental parts of `ambient-run` work, and vice versa. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file smoke/project.yaml from smoke-project.yaml given image file image.qcow2 specified for test suite when I run ambient-run build smoke/project.yaml --log hello.log then file hello.log contains "hello, world" ~~~ ~~~{#smoke-project.yaml .file .yaml} source: . shell: | #!/bin/bash set -xeuo pipefail echo hello, world image: image.qcow2 ~~~ ### Build gets source code _Requirement:_ The build gets a copy of the specified source code. _Justification:_ This is the simplest possible project build, and if it works, then at least the very fundamental parts of `ambient-run` work, and vice versa. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file foo-project.yaml given file foo/README.md from foo-project.yaml given image file image.qcow2 specified for test suite when I run ambient-run build foo-project.yaml --log foo.log then file foo.log contains "README.md" ~~~ ~~~{#foo-project.yaml .file .yaml} source: foo shell: | #!/bin/bash ls -l image: image.qcow2 ~~~ ### Build produces an artifact _Requirement:_ The build can produce an artifact. _Justification:_ Without this a build can't produce something that can be used after the build has finished. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file artifact-project.yaml given file foo/README.md from foo-project.yaml given image file image.qcow2 specified for test suite when I run ambient-run build artifact-project.yaml --log foo.log then file foo.tar exists when I run tar tf foo.tar then stdout contains "README.md" ~~~ ~~~{#artifact-project.yaml .file .yaml} source: foo shell: | #!/bin/bash tar -cf /dev/vdc . image: image.qcow2 artifact: foo.tar ~~~ ### Fail build that tries to produce an unwanted artifact _Requirement:_ If an artifact output is not specified for the project, fail a build that tries to make one. _Justification:_ If a build tries to create an artifact, but one isn't wanted, the build should fail. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file no-artifact-project.yaml given file foo/README.md from foo-project.yaml given image file image.qcow2 specified for test suite when I try to run ambient-run build no-artifact-project.yaml --log foo.log then file foo.log contains "EXIT CODE: 2" then exit code is 1 ~~~ ~~~{#no-artifact-project.yaml .file .yaml} source: foo shell: | #!/bin/bash tar -cf /dev/vdc . image: image.qcow2 ~~~ ### Fail build that tries to produce an oversize artifact _Requirement:_ If a build produces an artifact that is too large, the build should fail. _Justification:_ Without a limit, a malicious build could fill all available storage space, which is a denial-of-service attack. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file small-artifact-project.yaml given file foo/README.md from foo-project.yaml given image file image.qcow2 specified for test suite when I try to run ambient-run build small-artifact-project.yaml --log foo.log then file foo.log contains "EXIT CODE: 2" then exit code is 1 ~~~ ~~~{#small-artifact-project.yaml .file .yaml} source: foo shell: | #!/bin/bash tar -cf /dev/vdc . image: image.qcow2 artifact: foo.tar artifact_max_size: 1 ~~~ ### Build is given dependencies _Requirement:_ The build is provided with its dependencies. _Justification:_ Without its dependencies, a project can't build. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file dep-project.yaml given file foo/README.md from dep-project.yaml given file foo-deps/library.file from dep-project.yaml given image file image.qcow2 specified for test suite when I run ambient-run build dep-project.yaml --log foo.log then file foo.log contains "/workspace/deps/library.file" ~~~ ~~~{#dep-project.yaml .file .yaml} source: foo shell: | #!/bin/bash find /workspace -type f image: image.qcow2 dependencies: foo-deps ~~~ ### Cache is persistent between builds _Requirement:_ The build can persist files between builds in a cache device. _Justification:_ This allows incremental builds and caching of build artifacts. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run given file cache-project.yaml given file foo/README.md from cache-project.yaml given a directory foo-cache given image file image.qcow2 specified for test suite then file foo-cache/cached.file does not exist when I run ambient-run build cache-project.yaml --log foo.log then file foo.log contains "creating cached file" then file foo-cache/cached.file exists when I run ambient-run build cache-project.yaml --log foo2.log then file foo2.log contains "cache has file" ~~~ ~~~{#cache-project.yaml .file .yaml} source: foo shell: | #!/bin/bash set -xeuo pipefail find /workspace -ls file="/workspace/cache/cached.file" if [ -e "$file" ]; then echo "cache has file" else echo "creating cached file" touch "$file" fi image: image.qcow2 cache: foo-cache ~~~ ### Build gets the resources is demands ## Utility functionality The `ambient-run` command has some subcommands that are meant to be useful for debugging and testing, but rarely used by normal users. This section is for verifying those. ### Create a virtual drive from directory _Requirement:_ I can create a virtual drive from the contents of a directory. _Justification:_ This is useful for, at least, verifying that the functionality dealing with virtual drives works, independently of the rest of the program. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run when I create directory src when I write "my project!" to file src/README.md when I run ambient-run vdrive create --root src my.drive then file my.drive exists when I run ambient-run vdrive list my.drive then stdout contains "README.md" when I run ambient-run vdrive extract my.drive --to extracted then file extracted/README.md contains "my project!" ~~~ ### Create an empty virtual drive _Requirement:_ I can create an virtual drive with a desired size. _Justification:_ This is useful for, at least, verifying that the functionality dealing with virtual drives works, independently of the rest of the program. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run when I run ambient-run vdrive create my.drive --size 1024 then file my.drive exists and is 1024 bytes long when I run ambient-run vdrive list my.drive then stdout is exactly "" ~~~ ### Create a virtual drive of wanted size _Requirement:_ I can create an virtual drive with a desired size with contents of a directory. _Justification:_ This is useful for, at least, verifying that the functionality dealing with virtual drives works, independently of the rest of the program. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run when I create directory src when I write "my project!" to file src/README.md when I run ambient-run vdrive create my.drive --size 10000 --root src then file my.drive exists and is 10000 bytes long when I run ambient-run vdrive list my.drive then stdout contains "README.md" when I run ambient-run vdrive extract my.drive --to extracted then file extracted/README.md contains "my project!" ~~~ ## Errors during build ### Bad per-user configuration _Requirement:_ `ambient-run` fails if the per-user configuration is bad. _Justification:_ As a user I want to get an error if the configuration is wrong. _Stakeholder:_ Lars ~~~scenario given an installed ambient-run given file garbage.yaml when I try to run ambient-run --config garbage.yaml config then command fails then stderr contains "garbage.yaml" ~~~ ~~~{#garbage.yaml .file .yaml} This is not a valid YAML file. ~~~ ### Bad per-project configuration _Requirement:_ `ambient-run` fails if the project configuration is bad. _Justification:_ As a user I want to get an error if the configuration is wrong. _Stakeholder:_ Lars ~~~scenario given an installed ambient-run given file garbage.yaml when I try to run ambient-run project garbage.yaml then command fails then stderr contains "garbage.yaml" ~~~ ### Build produces too large an artifact ### Build produces too large a cache ### Build demands too many CPUs ### Build demands too much RAM ### Build takes too long # Architecture of `ambient-run` ## Overview FIXME: This section gives the very high level view of how `ambient-run` works: the build VM, the inputs, the cache, the artifact output, and how the VM and the outside world interact. It also describes that there is per-user and per-project and per-build configuration. ## Per-user configuration FIXME: This section describes the configuration the user sets that applies to all use of `ambient-run`. ## Per-project configuration FIXME: This section describes the configuration that applies to all builds of a project. ## Per-build configuration FIXME: This section describes the configuration for one specific build, i.e., one run of `ambient-run`. ## Requirements for the build VM FIXME: This section describes what `ambient-run` expects of the VM to do, what inputs it gets, what outputs it produces, and how it gets the per-build instructions.