├── docs ├── themes │ └── metio │ │ ├── layouts │ │ ├── robots.txt │ │ ├── partials │ │ │ ├── body │ │ │ │ ├── post-tag-link.html │ │ │ │ ├── site-navigation.html │ │ │ │ ├── site-footer.html │ │ │ │ ├── sidebar.html │ │ │ │ └── site-header.html │ │ │ ├── i18n │ │ │ │ └── translations.html │ │ │ └── head │ │ │ │ ├── metadata.html │ │ │ │ ├── links.html │ │ │ │ └── styles.html │ │ ├── _default │ │ │ ├── home.html │ │ │ ├── home.humans.txt │ │ │ ├── list.html │ │ │ ├── baseof.html │ │ │ ├── home.foaf.rdf │ │ │ └── single.html │ │ ├── taxonomy │ │ │ ├── taxonomy.html │ │ │ └── terms.html │ │ ├── categories │ │ │ ├── taxonomy.html │ │ │ └── terms.html │ │ ├── 404.html │ │ └── sitemaps │ │ │ └── list.html │ │ ├── i18n │ │ ├── de.toml │ │ ├── en.toml │ │ └── pl.toml │ │ ├── static │ │ └── images │ │ │ ├── cc0.png │ │ │ ├── pencil.svg │ │ │ ├── sitemap.svg │ │ │ ├── rss.svg │ │ │ ├── history.svg │ │ │ ├── email.svg │ │ │ ├── github.svg │ │ │ ├── mastodon.svg │ │ │ ├── calendar.svg │ │ │ └── matrix.svg │ │ ├── archetypes │ │ └── default.md │ │ ├── assets │ │ └── css │ │ │ ├── navigation.css │ │ │ ├── layout-tablet.css │ │ │ ├── layout-mobile.css │ │ │ ├── footer.css │ │ │ ├── navigation-desktop.css │ │ │ ├── navigation-tablet.css │ │ │ ├── navigation-mobile.css │ │ │ ├── layout-desktop.css │ │ │ ├── darkmode.css │ │ │ ├── font.css │ │ │ ├── header.css │ │ │ ├── layout.css │ │ │ └── normalize.css │ │ ├── theme.toml │ │ └── config.toml ├── content │ ├── community │ │ ├── _index.md │ │ └── help.md │ ├── sitemap │ │ └── _index.md │ ├── contributors │ │ ├── _index.md │ │ ├── release.md │ │ ├── git-mirrors.md │ │ ├── building.md │ │ └── first-timer.md │ ├── usage │ │ ├── _index.md │ │ ├── install.md │ │ ├── autocomplete.md │ │ ├── build-envs.md │ │ └── argument-files.md │ ├── shell │ │ ├── runtimes.md │ │ ├── examples.md │ │ └── _index.md │ └── _index.md ├── data │ └── contributors │ │ └── sebhoss.yaml ├── htmltest.yml ├── README.md └── config.toml ├── src ├── test │ ├── resources │ │ └── wtf │ │ │ └── metio │ │ │ └── ilo │ │ │ ├── Ilo │ │ │ ├── root │ │ │ │ └── .ilo.rc │ │ │ └── empty │ │ │ │ └── .gitkeep │ │ │ └── cli │ │ │ └── RunCommands │ │ │ ├── multiple │ │ │ ├── .ilo.rc │ │ │ └── .ilo │ │ │ │ └── ilo.rc │ │ │ ├── nested │ │ │ └── .ilo │ │ │ │ └── ilo.rc │ │ │ ├── root │ │ │ └── .ilo.rc │ │ │ ├── different │ │ │ ├── .ilo.rc │ │ │ ├── another.rc │ │ │ └── some-name.rc │ │ │ ├── empty │ │ │ └── .gitkeep │ │ │ └── directory │ │ │ └── .ilo.rc │ │ │ └── .gitkeep │ └── java │ │ └── wtf │ │ └── metio │ │ └── ilo │ │ ├── shell │ │ ├── TestShellExecutor.java │ │ ├── DockerTest.java │ │ ├── PodmanTest.java │ │ ├── NerdctlTest.java │ │ ├── ShellRuntimeConverterTest.java │ │ ├── ShellExecutorTest.java │ │ ├── ShellOptionsTest.java │ │ └── ShellVolumeBehaviorTest.java │ │ ├── version │ │ ├── VersionTest.java │ │ └── VersionProviderTest.java │ │ ├── test │ │ ├── TestResources.java │ │ ├── TestMethodSources.java │ │ ├── ClassTests.java │ │ ├── TestCliExecutor.java │ │ ├── CliToolTCK.java │ │ └── ArchUnitTests.java │ │ ├── architecture │ │ ├── StructureRules.java │ │ ├── CodingRules.java │ │ ├── LayerRules.java │ │ └── ArchitectureTest.java │ │ ├── model │ │ ├── RuntimeTest.java │ │ └── CliExecutorTest.java │ │ ├── acceptance │ │ ├── VersionAcceptanceTest.java │ │ ├── CLI_TCK.java │ │ ├── HelpAcceptanceTest.java │ │ └── IloAcceptanceTest.java │ │ ├── errors │ │ ├── RuntimeIOExceptionTest.java │ │ ├── CommandListIsEmptyExceptionTest.java │ │ ├── SecurityManagerDeniesAccessExceptionTest.java │ │ ├── NoMatchingRuntimeExceptionTest.java │ │ ├── OperatingSystemNotSupportedExceptionTest.java │ │ ├── UnexpectedInterruptionExceptionTest.java │ │ ├── CommandListContainsNullExceptionTest.java │ │ ├── ExitCodesTest.java │ │ └── PrintingExceptionHandlerTest.java │ │ ├── os │ │ ├── NoOpExpansionTest.java │ │ ├── ShellTokenizerTest.java │ │ └── PowerShellTest.java │ │ ├── IloTest.java │ │ ├── utils │ │ ├── StringsTest.java │ │ └── StreamsTest.java │ │ ├── cli │ │ └── ExecutablesTest.java │ │ └── PicocliBooleanTest.java ├── assembly │ ├── licenseHeaderFile │ ├── jvm.xml │ ├── mac.xml │ ├── linux.xml │ └── windows.xml └── main │ ├── java │ ├── wtf │ │ └── metio │ │ │ └── ilo │ │ │ ├── model │ │ │ ├── Options.java │ │ │ ├── CliExecutor.java │ │ │ ├── Runtime.java │ │ │ └── CliTool.java │ │ │ ├── shell │ │ │ ├── Docker.java │ │ │ ├── Podman.java │ │ │ ├── Nerdctl.java │ │ │ ├── ShellCLI.java │ │ │ ├── ShellRuntimeConverter.java │ │ │ ├── ShellExecutor.java │ │ │ ├── ShellCommand.java │ │ │ ├── ShellRuntime.java │ │ │ ├── ShellVolumeBehavior.java │ │ │ ├── DockerLike.java │ │ │ └── ShellOptions.java │ │ │ ├── errors │ │ │ ├── RuntimeIOException.java │ │ │ ├── CommandListIsEmptyException.java │ │ │ ├── SecurityManagerDeniesAccessException.java │ │ │ ├── NoMatchingRuntimeException.java │ │ │ ├── OperatingSystemNotSupportedException.java │ │ │ ├── UnexpectedInterruptionException.java │ │ │ ├── ExitCodes.java │ │ │ ├── CommandListContainsNullException.java │ │ │ ├── LocalDirectoryDoesNotExistException.java │ │ │ ├── BusinessException.java │ │ │ └── PrintingExceptionHandler.java │ │ │ ├── os │ │ │ ├── NoOpExpansion.java │ │ │ ├── ParameterExpansion.java │ │ │ ├── OSSupport.java │ │ │ ├── PowerShell.java │ │ │ └── PosixShell.java │ │ │ ├── utils │ │ │ ├── Strings.java │ │ │ └── Streams.java │ │ │ ├── version │ │ │ └── VersionProvider.java │ │ │ ├── cli │ │ │ ├── EnvironmentVariables.java │ │ │ ├── CommandLifecycle.java │ │ │ ├── RunCommands.java │ │ │ └── Executables.java │ │ │ └── Ilo.java │ └── module-info.java │ └── java-templates │ └── wtf │ └── metio │ └── ilo │ └── version │ └── Version.java ├── .github ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── SUPPORT.md ├── dependabot.yml ├── workflows │ ├── reuse.yml │ ├── dependabot-automerge.yml │ ├── verify.yml │ ├── codeql-analysis.yml │ ├── website.yml │ └── update-parent.yml ├── SECURITY.md └── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ └── feature_request.md ├── .idea ├── copyright │ ├── profiles_settings.xml │ └── ilo.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml └── inspectionProfiles │ └── Project_Default.xml ├── dev ├── env ├── build ├── website ├── native └── serve ├── .mailmap ├── .gitignore ├── README.md ├── huber.yaml ├── .reuse └── dep5 ├── LICENSE ├── LICENSES └── 0BSD.txt ├── .editorconfig ├── .gitattributes ├── CONTRIBUTING.md └── Makefile /docs/themes/metio/layouts/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/Ilo/root/.ilo.rc: -------------------------------------------------------------------------------- 1 | shell maven:latest -------------------------------------------------------------------------------- /docs/themes/metio/i18n/de.toml: -------------------------------------------------------------------------------- 1 | [translations] 2 | other = "Translations" 3 | -------------------------------------------------------------------------------- /docs/themes/metio/i18n/en.toml: -------------------------------------------------------------------------------- 1 | [translations] 2 | other = "Translations" 3 | -------------------------------------------------------------------------------- /docs/themes/metio/i18n/pl.toml: -------------------------------------------------------------------------------- 1 | [translations] 2 | other = "Translations" 3 | -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/Ilo/empty/.gitkeep: -------------------------------------------------------------------------------- 1 | # folder is used by test -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/multiple/.ilo.rc: -------------------------------------------------------------------------------- 1 | maven:latest -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/multiple/.ilo/ilo.rc: -------------------------------------------------------------------------------- 1 | shell -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/nested/.ilo/ilo.rc: -------------------------------------------------------------------------------- 1 | shell 2 | -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/root/.ilo.rc: -------------------------------------------------------------------------------- 1 | shell maven:latest -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/different/.ilo.rc: -------------------------------------------------------------------------------- 1 | shell maven:latest -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/different/another.rc: -------------------------------------------------------------------------------- 1 | shell maven:latest -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/empty/.gitkeep: -------------------------------------------------------------------------------- 1 | # folder is used by test -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/different/some-name.rc: -------------------------------------------------------------------------------- 1 | shell maven:latest -------------------------------------------------------------------------------- /src/test/resources/wtf/metio/ilo/cli/RunCommands/directory/.ilo.rc/.gitkeep: -------------------------------------------------------------------------------- 1 | # folder is used by test -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - [ ] all checks are passing 2 | - [ ] review ok 3 | -------------------------------------------------------------------------------- /src/assembly/licenseHeaderFile: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: The ilo Authors 2 | SPDX-License-Identifier: 0BSD 3 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/cc0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metio/ilo/HEAD/docs/themes/metio/static/images/cc0.png -------------------------------------------------------------------------------- /dev/env: -------------------------------------------------------------------------------- 1 | shell 2 | --pull 3 | --volume ${XDG_CACHE_HOME}/maven:/root/.m2:z 4 | docker.io/metio/devcontainers-graalvm:latest 5 | bash 6 | -------------------------------------------------------------------------------- /docs/themes/metio/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/body/post-tag-link.html: -------------------------------------------------------------------------------- 1 | {{ . }} 2 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/navigation.css: -------------------------------------------------------------------------------- 1 | #navigation { 2 | border: dotted gray; 3 | } 4 | 5 | a.active, li.active { 6 | font-weight: bold; 7 | } 8 | -------------------------------------------------------------------------------- /dev/build: -------------------------------------------------------------------------------- 1 | shell 2 | --pull 3 | --no-interactive 4 | --volume ${XDG_CACHE_HOME}/maven:/root/.m2:z 5 | docker.io/metio/devcontainers-graalvm:latest 6 | mvn verify 7 | -------------------------------------------------------------------------------- /dev/website: -------------------------------------------------------------------------------- 1 | shell 2 | --pull 3 | --no-interactive 4 | docker.io/klakegg/hugo:latest 5 | --minify --printI18nWarnings --printPathWarnings --printUnusedTemplates --source docs 6 | -------------------------------------------------------------------------------- /docs/content/community/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Community 3 | date: 2020-04-13 4 | menu: main 5 | --- 6 | 7 | The following pages contain information about the community around `ilo`. 8 | 9 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/home.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 | {{ .Content }} 6 | {{ end }} 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /dev/native: -------------------------------------------------------------------------------- 1 | shell 2 | --pull 3 | --no-interactive 4 | --volume ${XDG_CACHE_HOME}/maven:/root/.m2:z 5 | docker.io/metio/devcontainers-graalvm:latest 6 | mvn verify --define skipNativeBuild=false 7 | -------------------------------------------------------------------------------- /docs/data/contributors/sebhoss.yaml: -------------------------------------------------------------------------------- 1 | id: 'sebhoss' 2 | title: 'Contributor' 3 | first_name: 'Sebastian' 4 | last_name: 'Hoß' 5 | email: 'seb@hoß.de' 6 | website: 'https://seb.people.metio.wtf' 7 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/home.humans.txt: -------------------------------------------------------------------------------- 1 | /* TEAM */ 2 | {{ range $.Site.Data.contributors }} 3 | {{ .title }}: {{ .first_name }} {{ .last_name }} 4 | Site: {{ .website }} 5 | {{ end }} 6 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/body/site-navigation.html: -------------------------------------------------------------------------------- 1 | {{ block "navigation" . }} 2 | 5 | {{ end }} 6 | -------------------------------------------------------------------------------- /dev/serve: -------------------------------------------------------------------------------- 1 | shell 2 | --pull 3 | --no-interactive 4 | --publish=1313:1313 5 | docker.io/klakegg/hugo:latest 6 | server --minify --printI18nWarnings --printPathWarnings --printUnusedTemplates --source docs 7 | -------------------------------------------------------------------------------- /docs/content/sitemap/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sitemap 3 | date: 2020-04-13 4 | type: sitemaps 5 | --- 6 | 7 | See all pages [by category](../categories) 8 | 9 | See all pages [by tag](../tags) 10 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | Sebastian Hoß 5 | Sebastian Hoß 6 | Sebastian Hoß 7 | -------------------------------------------------------------------------------- /docs/htmltest.yml: -------------------------------------------------------------------------------- 1 | DirectoryPath: docs/public 2 | IgnoreDirectoryMissingTrailingSlash: true 3 | EnforceHTML5: true 4 | EnforceHTTPS: true 5 | IgnoreExternalBrokenLinks: true 6 | IgnoreURLs: 7 | - https://www.microsoft.com/en-us/windows 8 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/layout-tablet.css: -------------------------------------------------------------------------------- 1 | #site { 2 | grid-template-rows: 100px auto auto 100px; 3 | grid-template-areas: "header" "content" "navigation" "footer"; 4 | } 5 | 6 | #header { 7 | visibility: visible; 8 | } 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ilo website 2 | 3 | This directory contains the sources for https://ilo.projects.metio.wtf/. It uses [hugo](https://gohugo.io/) as a static site generator. 4 | 5 | See https://ilo.projects.metio.wtf/contributors/building/ for more. 6 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/i18n/translations.html: -------------------------------------------------------------------------------- 1 | {{ if .IsTranslated }} 2 | 3 | {{ range .Translations }} 4 | {{ index $.Site.Data.i18n.Flags .Lang }} 5 | {{ end }} 6 | 7 | {{ end }} 8 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/model/Options.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | public interface Options { 9 | 10 | boolean debug(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Support 7 | 8 | Take a look at the [help section](https://ilo.projects.metio.wtf/community/help/) in case you run into any problems or have a new idea. 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: daily 10 | assignees: 11 | - sebhoss 12 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/layout-mobile.css: -------------------------------------------------------------------------------- 1 | #site { 2 | grid-template-rows: auto auto 100px auto; 3 | grid-template-columns: 1fr; 4 | grid-template-areas: "content" "navigation" "footer" "header"; 5 | } 6 | 7 | #header { 8 | visibility: hidden; 9 | display: none; 10 | } 11 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/footer.css: -------------------------------------------------------------------------------- 1 | #footer { 2 | border: dotted gray; 3 | border-width: 2px 0 0 0; 4 | font-size: small; 5 | margin-right: 15px; 6 | margin-left: 15px; 7 | text-align: center 8 | } 9 | 10 | #footer a { 11 | text-decoration: none; 12 | color: black 13 | } 14 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/navigation-desktop.css: -------------------------------------------------------------------------------- 1 | #navigation { 2 | border-width: 0 2px 0 0; 3 | margin-left: 0; 4 | margin-right: 0; 5 | margin-bottom: 15px; 6 | padding-right: 15px 7 | } 8 | 9 | .mobileonly,.mobile-and-tablet { 10 | display: none; 11 | visibility: hidden; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java-templates/wtf/metio/ilo/version/Version.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.version; 7 | 8 | public final class Version { 9 | 10 | public static final String VERSION = "${project.version}"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.idea/copyright/ilo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/head/metadata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/Docker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | public final class Docker extends DockerLike { 9 | 10 | @Override 11 | public String name() { 12 | return "docker"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/Podman.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | public final class Podman extends DockerLike { 9 | 10 | @Override 11 | public String name() { 12 | return "podman"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/Nerdctl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | public final class Nerdctl extends DockerLike { 9 | 10 | @Override 11 | public String name() { 12 | return "nerdctl"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellCLI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.model.CliTool; 9 | 10 | public interface ShellCLI extends CliTool { 11 | 12 | // Marker interface 13 | 14 | } 15 | -------------------------------------------------------------------------------- /docs/content/contributors/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contributors 3 | date: 2020-04-13 4 | menu: main 5 | --- 6 | 7 | You can find information on how to contribute to the project on the following pages. Thanks a lot for helping out! If there is anything missing or something that stops you from working on this project, don't hesitate to [reach out](../community/help). 8 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | module wtf.metio.ilo { 7 | 8 | requires info.picocli; 9 | 10 | opens wtf.metio.ilo to info.picocli; 11 | opens wtf.metio.ilo.shell to info.picocli; 12 | opens wtf.metio.ilo.version to info.picocli; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | .idea/* 5 | out/ 6 | !.idea/codeStyles 7 | !.idea/copyright 8 | !.idea/inspectionProfiles 9 | !.idea/runConfigurations 10 | .bash_history 11 | .jdk 12 | target 13 | .project 14 | .classpath 15 | docs/public 16 | docs/resources 17 | docs/.hugo_build.lock 18 | ilo.iml 19 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/navigation-tablet.css: -------------------------------------------------------------------------------- 1 | #navigation { 2 | border-width: 2px 0 0 0; 3 | margin-left: 15px; 4 | margin-right: 15px; 5 | } 6 | 7 | #navigation li { 8 | padding-top: 0.6rem; 9 | } 10 | 11 | .mobileonly { 12 | display: none; 13 | visibility: hidden; 14 | } 15 | 16 | .menutitle { 17 | margin-top: 0; 18 | margin-bottom: 0; 19 | } 20 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/navigation-mobile.css: -------------------------------------------------------------------------------- 1 | #navigation { 2 | border-width: 2px 0 0 0; 3 | margin-left: 15px; 4 | margin-right: 15px; 5 | } 6 | 7 | #navigation li { 8 | padding-top: 0.6rem; 9 | } 10 | 11 | .menudivider { 12 | margin-top: 16px; 13 | margin-left: -12px; 14 | } 15 | 16 | .menutitle { 17 | margin-top: 0; 18 | margin-bottom: 0; 19 | } 20 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/pencil.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/themes/metio/theme.toml: -------------------------------------------------------------------------------- 1 | name = "Metio Project Theme" 2 | license = "CC0-1.0" 3 | licenselink = "https://creativecommons.org/publicdomain/zero/1.0/" 4 | description = "Standard theme for all projects @ metio.wtf" 5 | homepage = "https://github.com/metio/metio-hugo-theme" 6 | tags = ["metio", "theme"] 7 | 8 | [author] 9 | name = "Sebastian Hoß" 10 | homepage = "https://seb.hoß.de" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # ilo [![Chat](https://img.shields.io/badge/matrix-%23talk.metio:matrix.org-brightgreen.svg?style=social&label=Matrix)](https://matrix.to/#/#talk.metio:matrix.org) 7 | 8 | Manage reproducible build environments. Take a look at the [website](https://ilo.projects.metio.wtf/) for detailed information. 9 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/layout-desktop.css: -------------------------------------------------------------------------------- 1 | #site { 2 | grid-template-rows: 100px auto 100px; 3 | grid-template-columns: 280px 1fr; 4 | grid-template-areas: "header header" "navigation content" "navigation footer"; 5 | } 6 | 7 | #header { 8 | visibility: visible; 9 | } 10 | 11 | #content img { 12 | max-width: calc(90vw - 280px); 13 | } 14 | 15 | #content pre { 16 | max-width: calc(90vw - 280px); 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: REUSE compliance 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | jobs: 11 | reuse: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v6 16 | - name: REUSE Compliance Check 17 | uses: fsfe/reuse-action@v6 18 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/RuntimeIOException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import java.io.IOException; 9 | 10 | public final class RuntimeIOException extends BusinessException { 11 | 12 | public RuntimeIOException(final IOException exception) { 13 | super(104, exception, "I/O error occurred."); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/darkmode.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | color: white; 4 | } 5 | 6 | #header a { 7 | color: white; 8 | } 9 | 10 | .svgimg { 11 | filter: invert(1); 12 | } 13 | 14 | a,a>span { 15 | color: #268bd2; 16 | } 17 | 18 | li::marker { 19 | color: white; 20 | } 21 | .post-titel,.post-titel > a,.post-titel > a:visited { 22 | color: white; 23 | } 24 | .post-tag { 25 | color: gray; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellRuntimeConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import picocli.CommandLine; 9 | 10 | public final class ShellRuntimeConverter implements CommandLine.ITypeConverter { 11 | 12 | @Override 13 | public ShellRuntime convert(final String value) { 14 | return ShellRuntime.fromAlias(value); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/TestShellExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.test.TestCliExecutor; 9 | 10 | class TestShellExecutor extends TestCliExecutor { 11 | 12 | @Override 13 | public ShellCLI selectRuntime(final ShellRuntime runtime) { 14 | return runtime.cli(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/CommandListIsEmptyException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | public final class CommandListIsEmptyException extends BusinessException { 9 | 10 | public CommandListIsEmptyException(final IndexOutOfBoundsException exception) { 11 | super(102, exception, "The generated command list is empty - this is a bug in ilo!"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.model.CliExecutor; 9 | 10 | final class ShellExecutor implements CliExecutor { 11 | 12 | @Override 13 | public ShellCLI selectRuntime(final ShellRuntime runtime) { 14 | return ShellRuntime.autoSelect(runtime); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Security Policy 7 | 8 | `ilo` is using a continuous delivery mode and therefore only supports the latest released version. 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Email security@metio.wtf with details about the vulnerability. 13 | 14 | Please include some way to reproduce your problem. We are going to provide a fix as soon as possible and include it in the next release. 15 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/SecurityManagerDeniesAccessException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | public final class SecurityManagerDeniesAccessException extends BusinessException { 9 | 10 | public SecurityManagerDeniesAccessException(final SecurityException exception) { 11 | super(105, exception, "A Java SecurityManager does not allow creating new processes."); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/taxonomy/taxonomy.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | Tag: {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 | {{ range .Pages }} 12 | 17 | {{ end }} 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/NoMatchingRuntimeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | public final class NoMatchingRuntimeException extends BusinessException { 9 | 10 | public NoMatchingRuntimeException() { 11 | super(107, null, "No matching runtime was found on your system. Select another runtime using '--runtime' or install your preferred runtime on your system."); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/categories/taxonomy.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | Category: {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 | {{ range .Pages }} 12 | 17 | {{ end }} 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 |

Pages

12 | {{ range .Pages }} 13 | 18 | {{ end }} 19 | {{ end }} 20 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/categories/terms.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 | {{ range (sort .Pages "Title" "asc") }} 12 | 17 | {{ end }} 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/taxonomy/terms.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 | {{ range (sort .Pages "Title" "asc") }} 12 | 17 | {{ end }} 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | blank_issues_enabled: true 5 | contact_links: 6 | - name: Chat Room 7 | url: https://matrix.to/#talk.metio:matrix.org 8 | about: Chat with the developers 9 | - name: Mailing List 10 | url: https://metio.groups.io/g/ilo/topics 11 | about: Ask questions on the mailing list 12 | - name: GitHub Discussion 13 | url: https://github.com/metio/ilo/discussions 14 | about: Start a discussion on GitHub 15 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/404.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main"}} 5 |

404 - Not Found

6 |

The page you are looking for does not exist or was moved to another location.

7 |

Either go back to the start page or search for something like site:ilo.projects.metio.wtf YOUR_SEARCH_TERM on google.

8 | {{ end }} 9 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/sitemaps/list.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | {{ .Title }} 8 | 9 |

10 | {{ .Content }} 11 |

Pages

12 | {{ range .Site.RegularPages }} 13 | 18 | {{ end }} 19 | {{ end }} 20 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/version/VersionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.version; 7 | 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class VersionTest { 12 | 13 | @Test 14 | void shouldDefineVersion() { 15 | Assertions.assertNotNull(Version.VERSION); 16 | } 17 | 18 | @Test 19 | void shouldCreateInstance() { 20 | Assertions.assertNotNull(new Version()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/OperatingSystemNotSupportedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | public final class OperatingSystemNotSupportedException extends BusinessException { 9 | 10 | public OperatingSystemNotSupportedException(final UnsupportedOperationException exception) { 11 | super(103, exception, "Your operating system does not support the creation of processes - sadly ilo won't work here."); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/UnexpectedInterruptionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | public final class UnexpectedInterruptionException extends BusinessException { 9 | 10 | public UnexpectedInterruptionException(final InterruptedException exception) { 11 | super(106, exception, "The process was unexpected interrupted. In case you can reproduce this, open a ticket at https://github.com/metio/ilo."); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /huber.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | --- 4 | - name: ilo 5 | description: Manage reproducible build environments 6 | source: 7 | Github: 8 | owner: metio 9 | repo: ilo 10 | targets: 11 | - LinuxAmd64: 12 | artifact_templates: 13 | - "ilo-{version}-linux.tar.gz" 14 | - MacOS: 15 | artifact_templates: 16 | - "ilo-{version}-mac.tar.gz" 17 | - Windows: 18 | artifact_templates: 19 | - "ilo-{version}-windows.tar.gz" 20 | detail: ~ 21 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/os/NoOpExpansion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | /** 9 | * Fallback implementation that does no parameter expansion. 10 | */ 11 | public class NoOpExpansion extends ParameterExpansion { 12 | 13 | @Override 14 | public String substituteCommands(final String value) { 15 | return value; 16 | } 17 | 18 | @Override 19 | public String expandParameters(final String value) { 20 | return value; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/utils/Strings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.utils; 7 | 8 | import java.util.Objects; 9 | 10 | public final class Strings { 11 | 12 | public static boolean isBlank(final String value) { 13 | return Objects.isNull(value) || value.isBlank(); 14 | } 15 | 16 | public static boolean isNotBlank(final String value) { 17 | return !isBlank(value); 18 | } 19 | 20 | private Strings() { 21 | // utility class 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ilo 3 | Upstream-Contact: Sebastian Hoß 4 | Source: https://github.com/metio/ilo 5 | 6 | Files: dev/* 7 | Copyright: The ilo Authors 8 | License: 0BSD 9 | 10 | Files: docs/* 11 | Copyright: The ilo Authors 12 | License: CC0-1.0 13 | 14 | Files: .github/* 15 | Copyright: The ilo Authors 16 | License: 0BSD 17 | 18 | Files: .idea/* 19 | Copyright: The ilo Authors 20 | License: 0BSD 21 | 22 | Files: src/test/resources/* 23 | Copyright: The ilo Authors 24 | License: 0BSD 25 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/ExitCodes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import picocli.CommandLine; 9 | 10 | public final class ExitCodes implements CommandLine.IExitCodeExceptionMapper { 11 | 12 | @Override 13 | public int getExitCode(final Throwable exception) { 14 | if (exception instanceof BusinessException) { 15 | return ((BusinessException) exception).getExitCode(); 16 | } 17 | return CommandLine.ExitCode.SOFTWARE; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: sebhoss 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/CommandListContainsNullException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | public final class CommandListContainsNullException extends BusinessException { 12 | 13 | public CommandListContainsNullException(final NullPointerException exception, final List args) { 14 | super(101, exception, args.stream().collect(Collectors.joining(", ", "[", "]"))); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/model/CliExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | import wtf.metio.ilo.cli.Executables; 9 | 10 | import java.util.List; 11 | 12 | public interface CliExecutor, CLI extends CliTool, OPTIONS extends Options> { 13 | 14 | CLI selectRuntime(RUNTIME runtime); 15 | 16 | default int execute(final List arguments, final boolean debug) { 17 | return Executables.runAndWaitForExit(arguments, debug); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/sitemap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/DockerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | 10 | @DisplayName("Docker") 11 | class DockerTest extends DockerLikeTCK { 12 | 13 | @Override 14 | public ShellCLI tool() { 15 | return new Docker(); 16 | } 17 | 18 | @Override 19 | protected ShellOptions options() { 20 | return new ShellOptions(); 21 | } 22 | 23 | @Override 24 | protected String name() { 25 | return "docker"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/PodmanTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | 10 | @DisplayName("Podman") 11 | class PodmanTest extends DockerLikeTCK { 12 | 13 | @Override 14 | public ShellCLI tool() { 15 | return new Podman(); 16 | } 17 | 18 | @Override 19 | protected ShellOptions options() { 20 | return new ShellOptions(); 21 | } 22 | 23 | @Override 24 | protected String name() { 25 | return "podman"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/version/VersionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.version; 7 | 8 | import picocli.CommandLine; 9 | 10 | public final class VersionProvider implements CommandLine.IVersionProvider { 11 | 12 | @Override 13 | public String[] getVersion() { 14 | return new String[]{ 15 | "ilo: " + Version.VERSION, 16 | "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})", 17 | "OS: ${os.name} ${os.version} ${os.arch}" 18 | }; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/NerdctlTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | 10 | @DisplayName("Nerdctl") 11 | class NerdctlTest extends DockerLikeTCK { 12 | 13 | @Override 14 | public ShellCLI tool() { 15 | return new Nerdctl(); 16 | } 17 | 18 | @Override 19 | protected ShellOptions options() { 20 | return new ShellOptions(); 21 | } 22 | 23 | @Override 24 | protected String name() { 25 | return "nerdctl"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /docs/content/usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | date: 2020-04-13 4 | menu: main 5 | weight: 100 6 | --- 7 | 8 | Make sure that `ilo` is [installed](./install) in your PATH or otherwise accessible from your terminal. The following subcommands are currently available: 9 | 10 | ## ilo shell 11 | 12 | The `ilo shell` command can be used to run a single container either in interactive mode (default) or non-interactive mode (e.g. for CI builds). 13 | 14 | ```console 15 | [you@hostname project-dir]$ ilo shell 16 | [root@container project-dir]# 17 | ``` 18 | 19 | Take a look at more detailed information [here](../shell). 20 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-automerge.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: Dependabot auto-merge 5 | on: pull_request 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | dependabot: 13 | runs-on: ubuntu-latest 14 | if: ${{ github.actor == 'dependabot[bot]' }} 15 | steps: 16 | - name: Enable auto-merge for Dependabot PRs 17 | run: gh pr merge --auto --rebase "$PR_URL" 18 | env: 19 | PR_URL: ${{ github.event.pull_request.html_url }} 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/rss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/TestResources.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | 11 | public final class TestResources { 12 | 13 | public static Path testResources() { 14 | return Paths.get("src/test/resources/"); 15 | } 16 | 17 | public static Path testResources(final Class clazz) { 18 | return testResources().resolve(clazz.getName().replace(".", "/")); 19 | } 20 | 21 | private TestResources() { 22 | // utility class 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission to use, copy, modify, and/or distribute this software for any 2 | purpose with or without fee is hereby granted. 3 | 4 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 5 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 6 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 7 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 8 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 9 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 10 | PERFORMANCE OF THIS SOFTWARE. 11 | -------------------------------------------------------------------------------- /LICENSES/0BSD.txt: -------------------------------------------------------------------------------- 1 | Permission to use, copy, modify, and/or distribute this software for any 2 | purpose with or without fee is hereby granted. 3 | 4 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 5 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 6 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 7 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 8 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 9 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 10 | PERFORMANCE OF THIS SOFTWARE. 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | # EditorConfig is awesome: https://EditorConfig.org 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # global rules for all fies 10 | [*] 11 | charset = utf-8 12 | indent_style = space 13 | trim_trailing_whitespace = true 14 | 15 | # Makefile use tabs by convention 16 | [Makefile] 17 | indent_style = tab 18 | 19 | # Java files use smaller indention because I like that 20 | [*.java] 21 | indent_size = 2 22 | insert_final_newline = true 23 | 24 | # XML files use larger indention 25 | [*.xml] 26 | indent_size = 4 27 | insert_final_newline = true 28 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | # Handle line endings automatically for files detected as text 5 | # and leave all files detected as binary untouched. 6 | * text=auto 7 | 8 | # 9 | # The above will handle all files NOT found below 10 | # 11 | # These files are text and should be normalized (Convert crlf => lf) 12 | *.css text 13 | *.spec text 14 | *.rb text 15 | *.java text 16 | *.js text 17 | *.md text 18 | *.properties text 19 | *.txt text 20 | *.yml text 21 | *.html text 22 | *.xml text 23 | *.rc text 24 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/head/links.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/LocalDirectoryDoesNotExistException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import java.nio.file.Path; 9 | 10 | /** 11 | * Signals that a local directory that was supposed to be mounted into a container does not exist. 12 | */ 13 | public final class LocalDirectoryDoesNotExistException extends BusinessException { 14 | 15 | public LocalDirectoryDoesNotExistException(final Path directory) { 16 | super(110, "The directory " + directory.toAbsolutePath() + " does not exist. Create it first before mounting it or set --missing-volumes to CREATE."); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/BusinessException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | abstract class BusinessException extends RuntimeException { 9 | 10 | private final int exitCode; 11 | 12 | public BusinessException(final int exitCode, final String message) { 13 | super(message); 14 | this.exitCode = exitCode; 15 | } 16 | 17 | public BusinessException(final int exitCode, final Throwable cause, final String message) { 18 | super(message, cause); 19 | this.exitCode = exitCode; 20 | } 21 | 22 | public final int getExitCode() { 23 | return exitCode; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: sebhoss 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/font.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'DejaVu Sans', Georgia, sans-serifs, serif; 3 | color: black; 4 | } 5 | 6 | .subtitle { 7 | font-size: small; 8 | color: gray 9 | } 10 | 11 | blockquote { 12 | font-style: italic; 13 | } 14 | 15 | blockquote > p::before, blockquote > p::after { 16 | content: '”'; 17 | } 18 | 19 | code { 20 | font-weight: bold; 21 | } 22 | 23 | .post-titel > a { 24 | text-decoration: none; 25 | } 26 | .post-titel > h1 { 27 | color: black; 28 | margin-bottom: 0; 29 | } 30 | .post-titel,.post-tag,.post-titel > a { 31 | color: black; 32 | text-decoration: none; 33 | } 34 | .post-tag { 35 | color: gray; 36 | } 37 | .icon { 38 | margin: 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/architecture/StructureRules.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.architecture; 7 | 8 | import com.tngtech.archunit.junit.ArchTest; 9 | import com.tngtech.archunit.lang.ArchRule; 10 | import org.junit.jupiter.api.DisplayName; 11 | 12 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; 13 | 14 | @DisplayName("Structural Rules") 15 | public final class StructureRules { 16 | 17 | @ArchTest 18 | public static final ArchRule iloDependsOnCommands = classes() 19 | .that().haveFullyQualifiedName("wtf.metio.ilo.Ilo") 20 | .should().dependOnClassesThat().resideInAnyPackage("..shell.."); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/errors/PrintingExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import picocli.CommandLine; 9 | 10 | public final class PrintingExceptionHandler implements CommandLine.IExecutionExceptionHandler { 11 | 12 | @Override 13 | public int handleExecutionException( 14 | final Exception exception, 15 | final CommandLine commandLine, 16 | final CommandLine.ParseResult parseResult) { 17 | commandLine.getErr().println(commandLine.getColorScheme().errorText(exception.getMessage())); 18 | final var mapper = commandLine.getExitCodeExceptionMapper(); 19 | return mapper.getExitCode(exception); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /docs/content/contributors/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Release 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: 'Contributors' 7 | --- 8 | 9 | The release process of `ilo` is highly automated, therefore you only have to: 10 | 11 | 1. Push changes into the `main` branch. 12 | 2. Wait until an [GitHub action](https://github.com/metio/ilo/blob/main/.github/workflows/release.yml) will perform a release automatically. Take a look at the [calendar](https://metio.groups.io/g/ilo/calendar) for the next scheduled release. Note that a release will only be performed if any changes to the source code were detected since the last release. 13 | 14 | Each new release will: 15 | 16 | - Publish a new GitHub [release](https://github.com/metio/ilo/releases) 17 | - Publish an [email](https://metio.groups.io/g/ilo/topics) 18 | -------------------------------------------------------------------------------- /docs/content/usage/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Install 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: usage 7 | identifier: usage_install 8 | weight: 100 9 | categories: 10 | - usage 11 | tags: 12 | - install 13 | --- 14 | 15 | Prebuilt binaries of `ilo` are available for each published release at: 16 | 17 | - https://github.com/metio/ilo/releases 18 | 19 | Download the package for your operating system and put the `ilo` binary in your `$PATH`. Use the JVM variant in case your operating system is not directly supported. 20 | 21 | ## Huber 22 | 23 | In case you are using [huber](https://github.com/innobead/huber), execute the following commands to install `ilo` on Linux/Mac/Windows: 24 | 25 | ```console 26 | $ huber repo add remote-repo --url https://github.com/metio/ilo 27 | $ huber install ilo 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/content/contributors/git-mirrors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Git Mirrors 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: 'Contributors' 7 | categories: 8 | - Contributors 9 | tags: 10 | - git 11 | - mirror 12 | --- 13 | 14 | `ilo` is using [GitHub](https://github.com/metio/ilo) as its primary source/issue/build environment, however its code is available in other git repositories as well in order to deal with [GitHub outages](https://www.githubstatus.com/). 15 | 16 | ## Available Mirrors 17 | 18 | Mirrors are updated automatically as explained in this [blog post](https://seb.people.metio.wtf/topic/gitlab-the-git-distributor/). 19 | 20 | - https://github.com/metio/ilo 21 | - https://gitlab.com/metio.wtf/ilo 22 | - https://codeberg.org/metio.wtf/ilo 23 | - https://bitbucket.org/metio-wtf/ilo 24 | - https://repo.or.cz/ilo.git 25 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/cli/EnvironmentVariables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.cli; 7 | 8 | /** 9 | * Enumeration of all known/supported environment variables. Their name on the host machine must match their 10 | * {@code name()} representation, e.g. 'ILO_RC.name()' is just 'ILO_RC'. 11 | */ 12 | public enum EnvironmentVariables { 13 | 14 | /** 15 | * Allows to specify the run command files to load during startup. 16 | * 17 | * @see RunCommands#locate(java.nio.file.Path) 18 | */ 19 | ILO_RC, 20 | 21 | /** 22 | * The runtime to use for 'ilo shell'. Can be overwritten with the '--runtime' flag. 23 | * 24 | * @see wtf.metio.ilo.shell.ShellOptions#runtime 25 | */ 26 | ILO_SHELL_RUNTIME, 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/model/Runtime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | import wtf.metio.ilo.errors.NoMatchingRuntimeException; 9 | 10 | import java.util.Arrays; 11 | 12 | public interface Runtime { 13 | 14 | static > RUNTIME firstMatching(final String alias, final RUNTIME[] runtimes) { 15 | return Arrays.stream(runtimes) 16 | .filter(runtime -> runtime.matches(alias)) 17 | .findFirst() 18 | .orElseThrow(NoMatchingRuntimeException::new); 19 | } 20 | 21 | default boolean matches(final String candidate) { 22 | return Arrays.stream(aliases()).anyMatch(candidate::equalsIgnoreCase); 23 | } 24 | 25 | String[] aliases(); 26 | 27 | CLI cli(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /docs/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "https://ilo.projects.metio.wtf/" 2 | languageCode = "en" 3 | theme = "metio" 4 | title = "ilo" 5 | 6 | pygmentsUseClasses = true 7 | sectionPagesMenu = "main" 8 | enableRobotsTXT = true 9 | defaultContentLanguage = "en" 10 | enableInlineShortcodes = true 11 | 12 | [outputs] 13 | home = [ "HTML", "RSS", "ATOM", "HUMANS", "FOAF"] 14 | section = ["HTML", "RSS", "ATOM"] 15 | term = ["HTML", "RSS", "ATOM"] 16 | 17 | [params] 18 | mainSections = ["community", "contributors", "integration", "metrics", "shell", "usage"] 19 | author = "metio.wtf" 20 | email = "https://metio.groups.io/g/main/topics" 21 | matrix = "#talk.metio:matrix.org" 22 | github = "metio/ilo" 23 | work = "ilo.projects.metio.wtf" 24 | description = "manage reproducible build environments" 25 | 26 | [security.funcs] 27 | getenv = ['^HUGO_', '^ILO_'] 28 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/baseof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ partial "head/metadata.html" . }} 5 | {{ template "_internal/opengraph.html" . }} 6 | {{ template "_internal/twitter_cards.html" . }} 7 | {{ template "_internal/schema.html" . }} 8 | {{ partial "head/links.html" . }} 9 | {{ partial "head/styles.html" . }} 10 | 11 | {{ block "title" . }} 12 | {{ .Site.Title }} 13 | {{ end }} 14 | 15 | 16 | 17 | {{ partial "body/site-header.html" . }} 18 | {{ partial "body/site-navigation.html" . }} 19 |
20 | {{ block "main" . }} 21 | 22 | {{ end }} 23 |
24 | {{ partial "body/site-footer.html" . }} 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/content/community/help.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Help 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: 'Community' 7 | categories: 8 | - Community 9 | tags: 10 | - help 11 | --- 12 | 13 | In case you need help, don’t panic - we’ve all been there! Try the following resources in order to get help: 14 | 15 | - [open a new bug report](https://github.com/metio/ilo/issues/new?assignees=sebhoss&labels=bug&template=bug_report.md&title=) 16 | - [create a new feature request](https://github.com/metio/ilo/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=) 17 | - [open a new discussion](https://github.com/metio/ilo/discussions) 18 | - [join the chat room](https://matrix.to/#/#ilo:matrix.org) 19 | - [send an email to the mailing list](https://metio.groups.io/g/ilo/topics) 20 | - Take a walk outside & come back to your issue with a fresh mind 21 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/model/RuntimeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | import wtf.metio.ilo.shell.ShellRuntime; 12 | import wtf.metio.ilo.test.TestMethodSources; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertNotNull; 15 | 16 | @DisplayName("Runtime") 17 | class RuntimeTest extends TestMethodSources { 18 | 19 | @ParameterizedTest 20 | @MethodSource("shellRuntimes") 21 | @DisplayName("finds first matching shell runtime") 22 | void findMatchingShellRuntime(final String runtime) { 23 | assertNotNull(Runtime.firstMatching(runtime, ShellRuntime.values())); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/TestMethodSources.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import wtf.metio.ilo.shell.ShellRuntime; 9 | 10 | import java.util.Arrays; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * Base class that provides various method sources for parameterized tests. 15 | */ 16 | public abstract class TestMethodSources { 17 | 18 | private static Stream shellRuntimes() { 19 | return Arrays.stream(ShellRuntime.values()) 20 | .flatMap(runtime -> Arrays.stream(runtime.aliases())); 21 | } 22 | 23 | private static Stream dockerLikeRuntimes() { 24 | return Stream.of(ShellRuntime.DOCKER, ShellRuntime.PODMAN, ShellRuntime.NERDCTL) 25 | .flatMap(runtime -> Arrays.stream(runtime.aliases())); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/acceptance/VersionAcceptanceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.acceptance; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | class VersionAcceptanceTest extends CLI_TCK { 15 | 16 | @ParameterizedTest 17 | @DisplayName("version info") 18 | @ValueSource(strings = {"-V", "--version"}) 19 | void shouldSupportVersionOption(final String flag) { 20 | final var exitCode = cmd.execute(flag); 21 | assertAll("version", 22 | () -> assertEquals(0, exitCode, "exitCode"), 23 | () -> assertTrue(output.toString().startsWith("ilo: "), "version")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/header.css: -------------------------------------------------------------------------------- 1 | #header { 2 | display: flex; 3 | align-items: center; 4 | align-self: center; 5 | border: dotted gray; 6 | border-width: 0 0 2px 0; 7 | margin-left: 15px; 8 | margin-right: 15px; 9 | max-width: 100vw 10 | } 11 | 12 | #header a { 13 | text-decoration: none; 14 | color: black 15 | } 16 | 17 | #projectname { 18 | flex-grow: 1; 19 | display: flex 20 | } 21 | 22 | #projectname #sitemaplink { 23 | margin-top: 4px; 24 | margin-left: 10px; 25 | mask: url(/images/sitemap.svg) no-repeat center; 26 | } 27 | 28 | #languages { 29 | font-size: 25px; 30 | border: dotted gray; 31 | border-width: 0 2px 0 0; 32 | } 33 | 34 | #languages a { 35 | margin-right: 15px 36 | } 37 | 38 | #actions a:first-child { 39 | margin-left: 15px 40 | } 41 | 42 | #actions a:not(:last-child) { 43 | margin-right: 15px 44 | } 45 | -------------------------------------------------------------------------------- /docs/themes/metio/config.toml: -------------------------------------------------------------------------------- 1 | [mediaTypes."application/atom+xml"] 2 | suffixes = ["xml"] 3 | [outputFormats.Atom] 4 | name = "Atom" 5 | mediaType = "application/atom+xml" 6 | baseName = "atom" 7 | isPlainText = false 8 | rel = "alternate" 9 | isHTML = false 10 | noUgly = true 11 | permalinkable = false 12 | 13 | [mediaTypes."application/rdf+xml"] 14 | suffixes = ["rdf"] 15 | [outputFormats.Foaf] 16 | name = "FOAF" 17 | mediaType = "application/rdf+xml" 18 | baseName = "foaf" 19 | isPlainText = false 20 | rel = "alternate" 21 | isHTML = false 22 | noUgly = true 23 | permalinkable = false 24 | 25 | [mediaTypes."text/plain"] 26 | suffixes = ["txt"] 27 | [outputFormats.Humans] 28 | name = "Humans" 29 | mediaType = "text/plain" 30 | baseName = "humans" 31 | isPlainText = true 32 | rel = "alternate" 33 | isHTML = false 34 | noUgly = true 35 | permalinkable = false 36 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/RuntimeIOExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertAll; 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | @DisplayName("RuntimeIOException") 17 | class RuntimeIOExceptionTest { 18 | 19 | @Test 20 | @DisplayName("has correct exit code and message") 21 | void exception() { 22 | final var exception = new RuntimeIOException(new IOException()); 23 | assertAll("exception", 24 | () -> assertEquals(104, exception.getExitCode(), "exitCode"), 25 | () -> assertEquals("I/O error occurred.", exception.getMessage(), "message")); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Contributor Guide 7 | 8 | Thank you so much for improving `ilo`! 9 | Without a healthy community, any open source project is doomed. 10 | 11 | ## How to become a contributor 12 | 13 | Take a look at the [first timer](https://ilo.projects.metio.wtf/contributors/first-timer/) documentation. 14 | 15 | ## Git Branching Model 16 | 17 | The `main` branch always contains the latest public stable release. 18 | 19 | ## Issue Management 20 | 21 | We are using the issue tracker over at [GitHub](https://github.com/metio/ilo/issues/) to track issues. 22 | 23 | ## Building the Project 24 | 25 | Take a look at the [build](https://ilo.projects.metio.wtf/contributors/building/) documentation. 26 | 27 | ## Release Process 28 | 29 | Take a look at the [release](https://ilo.projects.metio.wtf/contributors/release/) documentation. 30 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/os/NoOpExpansionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | class NoOpExpansionTest { 13 | 14 | private ParameterExpansion expansion; 15 | 16 | @BeforeEach 17 | void setUp() { 18 | expansion = new NoOpExpansion(); 19 | } 20 | 21 | @Test 22 | void shouldNotSubstituteCommand() { 23 | final var command = "$(git diff)"; 24 | final var result = expansion.substituteCommands(command); 25 | Assertions.assertEquals(command, result); 26 | } 27 | 28 | @Test 29 | void shouldNotExpandParameter() { 30 | final var command = "${HOME}"; 31 | final var result = expansion.expandParameters(command); 32 | Assertions.assertEquals(command, result); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/layout.css: -------------------------------------------------------------------------------- 1 | #site { 2 | display: grid; 3 | } 4 | 5 | #header { 6 | grid-area: header; 7 | } 8 | 9 | #navigation { 10 | grid-area: navigation; 11 | } 12 | 13 | #content { 14 | grid-area: content; 15 | margin-left: 15px; 16 | margin-right: 15px; 17 | } 18 | 19 | #footer { 20 | grid-area: footer; 21 | } 22 | 23 | #content pre { 24 | overflow-x: auto; 25 | max-width: 90vw; 26 | padding-left: 5px; 27 | padding-top: 5px; 28 | padding-bottom: 10px; 29 | } 30 | 31 | #post-content img { 32 | width: 80vw; 33 | margin-top: 24px; 34 | margin-bottom: 24px; 35 | } 36 | 37 | #home-divider { 38 | border: 0 dotted gray; 39 | border-top-width: 2px; 40 | margin-top: 24px; 41 | } 42 | 43 | #content > .post-titel { 44 | margin-bottom: 0; 45 | } 46 | 47 | th { 48 | border: 2px solid; 49 | padding: 0.5rem; 50 | } 51 | td { 52 | border: 2px solid; 53 | padding: 0.4rem; 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/CommandListIsEmptyExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("CommandListIsEmptyException") 15 | class CommandListIsEmptyExceptionTest { 16 | 17 | @Test 18 | @DisplayName("has correct exit code and message") 19 | void exception() { 20 | final var exception = new CommandListIsEmptyException(new IndexOutOfBoundsException()); 21 | assertAll("exception", 22 | () -> assertEquals(102, exception.getExitCode(), "exitCode"), 23 | () -> assertEquals("The generated command list is empty - this is a bug in ilo!", exception.getMessage(), "message")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/assembly/jvm.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | jvm 10 | true 11 | 12 | 13 | tar.gz 14 | 15 | 16 | 17 | 18 | ${project.build.directory}/appassembler 19 | 20 | 21 | 22 | ${project.basedir} 23 | 24 | 25 | LICENSE 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/SecurityManagerDeniesAccessExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("SecurityManagerDeniesAccessException") 15 | class SecurityManagerDeniesAccessExceptionTest { 16 | 17 | @Test 18 | @DisplayName("has correct exit code and message") 19 | void exception() { 20 | final var exception = new SecurityManagerDeniesAccessException(new SecurityException()); 21 | assertAll("exception", 22 | () -> assertEquals(105, exception.getExitCode(), "exitCode"), 23 | () -> assertEquals("A Java SecurityManager does not allow creating new processes.", exception.getMessage(), "message")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/architecture/CodingRules.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.architecture; 7 | 8 | import com.tngtech.archunit.junit.ArchTest; 9 | import com.tngtech.archunit.lang.ArchRule; 10 | import com.tngtech.archunit.library.GeneralCodingRules; 11 | import org.junit.jupiter.api.DisplayName; 12 | 13 | @DisplayName("Coding Rules") 14 | public final class CodingRules { 15 | 16 | @ArchTest 17 | public static final ArchRule noGenericExceptions = GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS; 18 | 19 | @ArchTest 20 | public static final ArchRule noFieldInjection = GeneralCodingRules.NO_CLASSES_SHOULD_USE_FIELD_INJECTION; 21 | 22 | @ArchTest 23 | public static final ArchRule noJavaLogging = GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING; 24 | 25 | @ArchTest 26 | public static final ArchRule noJodaTime = GeneralCodingRules.NO_CLASSES_SHOULD_USE_JODATIME; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/NoMatchingRuntimeExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("NoMatchingRuntimeException") 15 | class NoMatchingRuntimeExceptionTest { 16 | 17 | @Test 18 | @DisplayName("has correct exit code and message") 19 | void exception() { 20 | final var exception = new NoMatchingRuntimeException(); 21 | assertAll("exception", 22 | () -> assertEquals(107, exception.getExitCode(), "exitCode"), 23 | () -> assertEquals("No matching runtime was found on your system. Select another runtime using '--runtime' or install your preferred runtime on your system.", exception.getMessage(), "message")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/body/site-footer.html: -------------------------------------------------------------------------------- 1 | {{ block "footer" . }} 2 |
3 |

4 | 5 | CC0-1.0 6 |
7 | To the extent possible under law, 8 | 9 | {{ .Site.Params.author }} 10 | 11 | has waived all copyright and related or neighboring rights to 12 | {{ .Site.Params.work }}. 13 | This work is published from: 14 | 15 | Germany 16 | 17 |

18 |
19 | {{ end }} 20 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/OperatingSystemNotSupportedExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("OperatingSystemNotSupportedException") 15 | class OperatingSystemNotSupportedExceptionTest { 16 | 17 | @Test 18 | @DisplayName("has correct exit code and message") 19 | void exception() { 20 | final var exception = new OperatingSystemNotSupportedException(new UnsupportedOperationException()); 21 | assertAll("exception", 22 | () -> assertEquals(103, exception.getExitCode(), "exitCode"), 23 | () -> assertEquals("Your operating system does not support the creation of processes - sadly ilo won't work here.", exception.getMessage(), "message")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/UnexpectedInterruptionExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("UnexpectedInterruptionException") 15 | class UnexpectedInterruptionExceptionTest { 16 | 17 | @Test 18 | @DisplayName("has correct exit code and message") 19 | void exception() { 20 | final var exception = new UnexpectedInterruptionException(new InterruptedException()); 21 | assertAll("exception", 22 | () -> assertEquals(106, exception.getExitCode(), "exitCode"), 23 | () -> assertEquals("The process was unexpected interrupted. In case you can reproduce this, open a ticket at https://github.com/metio/ilo.", exception.getMessage(), "message")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/home.foaf.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ .Site.Title }} 9 | 10 | 11 | 12 | {{ range $.Site.Data.contributors }} 13 | 14 | {{ .first_name }} {{ .last_name }} 15 | {{ .title }} 16 | {{ .first_name }} 17 | {{ .last_name }} 18 | 19 | 20 | 21 | {{ end }} 22 | 23 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/acceptance/CLI_TCK.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.acceptance; 7 | 8 | import org.junit.jupiter.api.BeforeEach; 9 | import picocli.CommandLine; 10 | import wtf.metio.ilo.Ilo; 11 | import wtf.metio.ilo.shell.ShellCommand; 12 | import wtf.metio.ilo.test.TestMethodSources; 13 | 14 | import java.io.PrintWriter; 15 | import java.io.StringWriter; 16 | 17 | public abstract class CLI_TCK extends TestMethodSources { 18 | 19 | protected CommandLine cmd; 20 | protected StringWriter output; 21 | 22 | @BeforeEach 23 | final void initializeCLI() { 24 | cmd = Ilo.commandLine(); 25 | output = new StringWriter(); 26 | cmd.setOut(new PrintWriter(output)); 27 | } 28 | 29 | protected final ShellCommand parseShellCommand(final String... args) { 30 | final var parseResult = cmd.parseArgs(args); 31 | final var subcommand = parseResult.subcommand(); 32 | return (ShellCommand) subcommand.commandSpec().userObject(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/assembly/mac.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | mac 10 | true 11 | 12 | 13 | tar.gz 14 | 15 | 16 | 17 | 18 | ${project.build.directory} 19 | 20 | 21 | ilo 22 | 23 | 24 | 25 | ${project.basedir} 26 | 27 | 28 | LICENSE 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/version/VersionProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.version; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | @DisplayName("VersionProvider") 14 | class VersionProviderTest { 15 | 16 | @Test 17 | @DisplayName("provides ilo/jvm/os infos") 18 | void getVersion() { 19 | // given 20 | final var provider = new VersionProvider(); 21 | 22 | // when 23 | final var version = provider.getVersion(); 24 | 25 | // then 26 | assertAll("versions", 27 | () -> assertEquals(3, version.length, "length"), 28 | () -> assertTrue(version[0].contains("ilo:"), "ilo"), 29 | () -> assertTrue(version[1].contains("JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})"), "JVM"), 30 | () -> assertTrue(version[2].contains("OS: ${os.name} ${os.version} ${os.arch}"), "OS")); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/assembly/linux.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | linux 10 | true 11 | 12 | 13 | tar.gz 14 | 15 | 16 | 17 | 18 | ${project.build.directory} 19 | 20 | 21 | ilo 22 | 23 | 24 | 25 | ${project.basedir} 26 | 27 | 28 | LICENSE 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/CommandListContainsNullExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.ArrayList; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertAll; 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | @DisplayName("CommandListContainsNullException") 17 | class CommandListContainsNullExceptionTest { 18 | 19 | @Test 20 | @DisplayName("has correct exit code and message") 21 | void exception() { 22 | final var values = new ArrayList(); 23 | values.add("test"); 24 | values.add(null); 25 | final var exception = new CommandListContainsNullException(new NullPointerException(), values); 26 | assertAll("exception", 27 | () -> assertEquals(101, exception.getExitCode(), "exitCode"), 28 | () -> assertEquals("[test, null]", exception.getMessage(), "message")); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/assembly/windows.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | windows 10 | true 11 | 12 | 13 | tar.gz 14 | 15 | 16 | 17 | 18 | ${project.build.directory} 19 | 20 | 21 | ilo.exe 22 | 23 | 24 | 25 | ${project.basedir} 26 | 27 | 28 | LICENSE 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/os/ShellTokenizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import org.junit.jupiter.api.DynamicTest; 9 | import org.junit.jupiter.api.TestFactory; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Stream; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertIterableEquals; 16 | 17 | class ShellTokenizerTest { 18 | 19 | @TestFactory 20 | Stream tokenizeStrings() { 21 | return Map.ofEntries( 22 | Map.entry("echo", List.of("echo")), 23 | Map.entry("mysql -u root -p 'my database'", List.of("mysql", "-u", "root", "-p", "my database")), 24 | Map.entry("echo foo='bar'", List.of("echo", "foo=bar")) 25 | ) 26 | .entrySet() 27 | .stream() 28 | .map(entry -> DynamicTest.dynamicTest(entry.getKey(), () -> { 29 | final var tokens = ShellTokenizer.tokenize(entry.getKey()); 30 | assertIterableEquals(entry.getValue(), tokens); 31 | })); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/architecture/LayerRules.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.architecture; 7 | 8 | import com.tngtech.archunit.junit.ArchTest; 9 | import com.tngtech.archunit.lang.ArchRule; 10 | import org.junit.jupiter.api.DisplayName; 11 | 12 | import static com.tngtech.archunit.library.Architectures.layeredArchitecture; 13 | 14 | @DisplayName("Layer Rules") 15 | public final class LayerRules { 16 | 17 | @ArchTest 18 | public static final ArchRule layerAccessControl = layeredArchitecture() 19 | .consideringAllDependencies() 20 | .withOptionalLayers(true) 21 | .layer("CLI").definedBy("wtf.metio.ilo.cli..") 22 | .layer("Commands").definedBy("wtf.metio.ilo.shell..") 23 | .layer("Errors").definedBy("wtf.metio.ilo.errors..") 24 | .layer("Models").definedBy("wtf.metio.ilo.model..") 25 | .layer("Tools").definedBy("wtf.metio.ilo.tools..") 26 | .layer("Utils").definedBy("wtf.metio.ilo.utils..") 27 | .whereLayer("Models").mayOnlyBeAccessedByLayers("Commands", "CLI", "Tools"); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/ExitCodesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import picocli.CommandLine; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @DisplayName("ExitCodes") 15 | class ExitCodesTest { 16 | 17 | @Test 18 | @DisplayName("handles business exceptions") 19 | void businessException() { 20 | final var exitCodes = new ExitCodes(); 21 | final var exception = new NoMatchingRuntimeException(); 22 | 23 | final var exitCode = exitCodes.getExitCode(exception); 24 | 25 | assertEquals(exception.getExitCode(), exitCode); 26 | } 27 | 28 | @Test 29 | @DisplayName("handles generic exceptions") 30 | void genericException() { 31 | final var exitCodes = new ExitCodes(); 32 | final var exception = new RuntimeException(); 33 | 34 | final var exitCode = exitCodes.getExitCode(exception); 35 | 36 | assertEquals(CommandLine.ExitCode.SOFTWARE, exitCode); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/ShellRuntimeConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | @DisplayName("ShellRuntimeConverter") 15 | class ShellRuntimeConverterTest { 16 | 17 | @ParameterizedTest 18 | @DisplayName("converts String to ShellRuntime") 19 | @ValueSource(strings = { 20 | "podman", 21 | "docker", 22 | "p", 23 | "d", 24 | "DOCKER", 25 | "PODMAN", 26 | "dOCkeR", 27 | "podMAN", 28 | "NERDCTL", 29 | "nerdctl", 30 | "nerdCTL", 31 | "n" 32 | }) 33 | void shouldConvertStringToShellRuntime(final String input) { 34 | // given 35 | final var converter = new ShellRuntimeConverter(); 36 | 37 | // when 38 | final var runtime = converter.convert(input); 39 | 40 | // then 41 | assertNotNull(runtime, input); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/ClassTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import java.lang.reflect.Modifier; 9 | 10 | import static java.lang.reflect.Modifier.isPublic; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | public final class ClassTests { 15 | 16 | private ClassTests() { 17 | // helper class 18 | } 19 | 20 | public static void hasDefaultConstructor(final Class clazz) throws NoSuchMethodException { 21 | final var constructor = clazz.getDeclaredConstructor(); 22 | assertNotNull(constructor); 23 | assertTrue(constructor.trySetAccessible()); 24 | assertTrue(constructor.canAccess(null)); 25 | assertTrue(isPublic(constructor.getModifiers())); 26 | } 27 | 28 | public static void hasPrivateConstructor(final Class clazz) throws NoSuchMethodException { 29 | final var constructor = clazz.getDeclaredConstructor(); 30 | assertNotNull(constructor); 31 | assertTrue(Modifier.isPrivate(constructor.getModifiers())); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "title" }} 2 | {{ .Site.Title }} – {{ .Title }} 3 | {{ end }} 4 | {{ define "main" }} 5 |

6 | 7 | {{ .Title }} 8 | 9 | 10 | see history 11 | 12 | 13 | edit this page 14 | 15 |

16 | {{ with .Params.tags }} 17 |
18 | Talks about: 19 | {{ $sort := sort . }} 20 | {{ $links := apply $sort "partial" "body/post-tag-link" "." }} 21 | {{ $clean := apply $links "chomp" "." }} 22 | {{ delimit $clean ", " ", and " }} 23 |
24 | {{ end }} 25 |
26 | {{ .Content }} 27 |
28 | {{ end }} 29 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/acceptance/HelpAcceptanceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.acceptance; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | class HelpAcceptanceTest extends CLI_TCK { 15 | 16 | @DisplayName("usage help") 17 | @ParameterizedTest 18 | @ValueSource(strings = {"-h", "--help"}) 19 | void shouldHaveUsageHelp(final String flag) { 20 | verifyHelp(flag); 21 | } 22 | 23 | @DisplayName("shell help") 24 | @ParameterizedTest 25 | @ValueSource(strings = {"-h", "--help"}) 26 | void shouldHaveHelpForShell(final String flag) { 27 | verifyHelp("shell", flag); 28 | } 29 | 30 | private void verifyHelp(final String... flags) { 31 | final var exitCode = cmd.execute(flags); 32 | assertAll("help", 33 | () -> assertEquals(0, exitCode, "exitCode"), 34 | () -> assertTrue(output.toString().startsWith("Usage: ilo"), () -> output.toString())); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: Verify Commits 5 | on: 6 | pull_request: 7 | branches: [ main ] 8 | jobs: 9 | verify: 10 | name: Build on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - macos-latest 17 | - windows-latest 18 | steps: 19 | - id: checkout 20 | name: Clone Git Repository 21 | uses: actions/checkout@v6 22 | - id: graal 23 | name: Set up GraalVM 24 | uses: graalvm/setup-graalvm@v1 25 | with: 26 | version: latest 27 | java-version: 21 28 | github-token: ${{ secrets.GITHUB_TOKEN }} 29 | - id: cache 30 | name: Cache Maven Repository 31 | uses: actions/cache@v5 32 | with: 33 | path: ~/.m2/repository 34 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 35 | restore-keys: | 36 | ${{ runner.os }}-maven- 37 | - id: verify 38 | name: Verify Project 39 | run: mvn --batch-mode --define skipNativeBuild=false verify 40 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: CodeQL 5 | on: 6 | schedule: 7 | - cron: 37 13 * * 5 8 | jobs: 9 | codeql: 10 | name: Analyze 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | language: [ 'java' ] 16 | steps: 17 | - name: Clone Git Repository 18 | uses: actions/checkout@v6 19 | - id: graal 20 | name: Set up GraalVM 21 | uses: graalvm/setup-graalvm@v1 22 | with: 23 | version: latest 24 | java-version: 21 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | - id: cache 27 | name: Cache Maven Repository 28 | uses: actions/cache@v5 29 | with: 30 | path: ~/.m2/repository 31 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 32 | restore-keys: | 33 | ${{ runner.os }}-maven- 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v4 36 | with: 37 | languages: ${{ matrix.language }} 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v4 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v4 42 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/IloTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; 12 | import uk.org.webcompere.systemstubs.properties.SystemProperties; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static wtf.metio.ilo.test.TestResources.testResources; 16 | 17 | @DisplayName("Ilo") 18 | @ExtendWith(SystemStubsExtension.class) 19 | class IloTest { 20 | 21 | @Test 22 | @DisplayName("reads .rc files") 23 | void supportsRunCommands(final SystemProperties properties) { 24 | properties.set("user.dir", testResources(Ilo.class).resolve("root").toAbsolutePath().toString()); 25 | assertEquals(1, Ilo.runCommands(new String[]{}).count()); 26 | } 27 | 28 | @Test 29 | @DisplayName("does not need .rc files") 30 | void runsWithRunCommands(final SystemProperties properties) { 31 | properties.set("user.dir", testResources(Ilo.class).resolve("empty").toAbsolutePath().toString()); 32 | assertEquals(0, Ilo.runCommands(new String[]{}).count()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/os/ParameterExpansion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import java.util.function.Function; 9 | import java.util.regex.Pattern; 10 | 11 | abstract class ParameterExpansion { 12 | 13 | // visible for testing 14 | static final String MATCHER_GROUP_NAME = "expression"; 15 | 16 | abstract String substituteCommands(String value); 17 | 18 | abstract String expandParameters(String value); 19 | 20 | // visible for testing 21 | final String replace(final String value, final Function replacer, final Pattern... patterns) { 22 | var current = value; 23 | for (final var pattern : patterns) { 24 | current = replace(current, replacer, pattern); 25 | } 26 | return current; 27 | } 28 | 29 | private String replace(final String value, final Function replacer, final Pattern pattern) { 30 | final var builder = new StringBuilder(); 31 | final var matcher = pattern.matcher(value); 32 | while (matcher.find()) { 33 | matcher.appendReplacement(builder, replacer.apply(matcher.group(MATCHER_GROUP_NAME))); 34 | } 35 | matcher.appendTail(builder); 36 | return builder.toString(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /docs/content/usage/autocomplete.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Autocomplete 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: usage 7 | identifier: usage_autocomplete 8 | categories: 9 | - usage 10 | tags: 11 | - autocomplete 12 | --- 13 | 14 | The `ilo generate-completion` command generates autocompletion configuration for shells such as [bash](https://www.gnu.org/software/bash/) and [zsh](https://www.zsh.org/). 15 | 16 | Once enabled you can use the `` key to autocomplete ilo commands and their options: 17 | 18 | ```console 19 | # autocomplete commands 20 | $ ilo s 21 | $ ilo shell 22 | 23 | # autocomplete options 24 | $ ilo shell --re 25 | $ ilo shell --remove-image 26 | ``` 27 | 28 | ## bash 29 | 30 | In order to integrate autocompletion into [bash](https://www.gnu.org/software/bash/), follow these steps: 31 | 32 | 1. Create or edit `~/.bashrc`. 33 | 2. Add the following line 34 | ```shell 35 | source <(ilo generate-completion) 36 | ``` 37 | 3. Reload your shell (or create a new one) 38 | 39 | ## zsh 40 | 41 | In order to integrate autocompletion into [zsh](https://www.zsh.org/), follow these steps: 42 | 43 | 1. Create or edit `$ZDOTDIR/.zshrc`. 44 | 2. Add the following line 45 | ```shell 46 | source <(ilo generate-completion) 47 | ``` 48 | 3. Reload your shell (or create a new one) 49 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/errors/PrintingExceptionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.errors; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import wtf.metio.ilo.Ilo; 11 | 12 | import java.io.PrintWriter; 13 | import java.io.StringWriter; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertAll; 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | 18 | @DisplayName("PrintingExceptionHandler") 19 | class PrintingExceptionHandlerTest { 20 | 21 | @Test 22 | @DisplayName("prints exception message to error output") 23 | void printsExceptionMessage() { 24 | final var commandLine = Ilo.commandLine(); 25 | final var writer = new StringWriter(); 26 | final var output = new PrintWriter(writer); 27 | commandLine.setErr(output); 28 | final var handler = new PrintingExceptionHandler(); 29 | 30 | final var exception = new NoMatchingRuntimeException(); 31 | final var exitCode = handler.handleExecutionException(exception, commandLine, null); 32 | 33 | assertAll("exceptions", 34 | () -> assertEquals(exception.getExitCode(), exitCode), 35 | () -> assertEquals("No matching runtime was found on your system. Select another runtime using '--runtime' or install your preferred runtime on your system." + System.lineSeparator(), writer.toString())); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: Publish Website 5 | on: 6 | schedule: 7 | - cron: 45 4 * * MON 8 | push: 9 | branches: 10 | - main 11 | paths: 12 | - docs/** 13 | jobs: 14 | website: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - id: checkout 18 | name: Clone Git Repository 19 | uses: actions/checkout@v6 20 | with: 21 | fetch-depth: 0 22 | - id: hugo 23 | name: Setup Hugo 24 | uses: peaceiris/actions-hugo@v3 25 | with: 26 | hugo-version: latest 27 | - id: previous 28 | name: Get Last Release 29 | run: echo "::set-output name=version::$(git describe --abbrev=0 --tags)" 30 | - id: build 31 | name: Build Website 32 | run: hugo --minify --printI18nWarnings --printPathWarnings --printUnusedTemplates --source docs 33 | env: 34 | ILO_RELEASE: ${{ steps.previous.outputs.version }} 35 | - id: htmltest 36 | name: Run htmltest 37 | uses: wjdp/htmltest-action@master 38 | with: 39 | config: ./docs/htmltest.yml 40 | - id: deploy 41 | name: Deploy Website 42 | uses: peaceiris/actions-gh-pages@v4 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | publish_dir: ./docs/public 46 | force_orphan: true 47 | cname: ilo.projects.metio.wtf 48 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/architecture/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.architecture; 7 | 8 | import com.tngtech.archunit.core.domain.JavaClasses; 9 | import com.tngtech.archunit.core.importer.ClassFileImporter; 10 | import com.tngtech.archunit.core.importer.ImportOption; 11 | import org.junit.jupiter.api.*; 12 | import wtf.metio.ilo.Ilo; 13 | import wtf.metio.ilo.test.ArchUnitTests; 14 | 15 | import java.util.stream.Stream; 16 | 17 | @DisplayName("Architecture") 18 | public final class ArchitectureTest { 19 | 20 | private static JavaClasses classes; 21 | 22 | @BeforeAll 23 | static void importPackages() { 24 | classes = new ClassFileImporter() 25 | .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) 26 | .importPackagesOf(Ilo.class); 27 | } 28 | 29 | @TestFactory 30 | @DisplayName("Global Rules") 31 | Stream globalRules() { 32 | return Stream.of(CodingRules.class, StructureRules.class, LayerRules.class) 33 | .map(clazz -> ArchUnitTests.in(clazz, rule -> rule.check(classes))); 34 | } 35 | 36 | @TestFactory 37 | @DisplayName("Implementation Rules") 38 | @Disabled 39 | Stream implementationRules() { 40 | return Stream.of(); 41 | //return Stream.of(CliRules.class, ErrorsRules.class, ToolsRules.class) 42 | // .map(clazz -> ArchUnitTests.in(clazz, rule -> rule.check(classes))); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/TestCliExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import wtf.metio.ilo.model.CliExecutor; 9 | import wtf.metio.ilo.model.CliTool; 10 | import wtf.metio.ilo.model.Options; 11 | import wtf.metio.ilo.model.Runtime; 12 | 13 | import java.util.*; 14 | 15 | public abstract class TestCliExecutor, CLI extends CliTool, OPTIONS extends Options> 16 | implements CliExecutor { 17 | 18 | private final List> collectedArguments = new ArrayList<>(4); 19 | private final ArrayDeque exitCodes = new ArrayDeque<>(4); 20 | 21 | @Override 22 | public final int execute(final List arguments, final boolean debug) { 23 | collectedArguments.add(arguments); 24 | return Optional.ofNullable(exitCodes.pollFirst()).orElse(0); 25 | } 26 | 27 | public final List pullArguments() { 28 | return collectedArguments.get(0); 29 | } 30 | 31 | public final List buildArguments() { 32 | return collectedArguments.get(1); 33 | } 34 | 35 | public final List runArguments() { 36 | return collectedArguments.get(2); 37 | } 38 | 39 | public final List cleanupArguments() { 40 | return collectedArguments.get(3); 41 | } 42 | 43 | public final void exitCodes(final Integer... codes) { 44 | exitCodes.addAll(Arrays.asList(codes)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import picocli.CommandLine; 9 | import wtf.metio.ilo.cli.CommandLifecycle; 10 | import wtf.metio.ilo.model.CliExecutor; 11 | import wtf.metio.ilo.version.VersionProvider; 12 | 13 | import java.util.concurrent.Callable; 14 | 15 | @CommandLine.Command( 16 | name = "shell", 17 | description = "Opens an (interactive) shell for your build environment", 18 | versionProvider = VersionProvider.class, 19 | mixinStandardHelpOptions = true, 20 | showAtFileInUsageHelp = true, 21 | usageHelpAutoWidth = true, 22 | showDefaultValues = true, 23 | descriptionHeading = "%n", 24 | parameterListHeading = "%n" 25 | ) 26 | public final class ShellCommand implements Callable { 27 | 28 | @CommandLine.Mixin 29 | public ShellOptions options; 30 | 31 | private final CliExecutor executor; 32 | 33 | // default constructor for picocli 34 | public ShellCommand() { 35 | this(new ShellExecutor()); 36 | } 37 | 38 | // constructor for testing 39 | ShellCommand(final CliExecutor executor) { 40 | this.executor = executor; 41 | } 42 | 43 | @Override 44 | public Integer call() { 45 | final var tool = executor.selectRuntime(options.runtime); 46 | return CommandLifecycle.run(tool, options, executor::execute); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/model/CliTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | import wtf.metio.ilo.cli.Executables; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * CLI tools are used by 'ilo' in order to do most of its work. This interface represents such a tool. 14 | */ 15 | public interface CliTool { 16 | 17 | /** 18 | * @return The name of the CLI tool. 19 | */ 20 | String name(); 21 | 22 | /** 23 | * @return The CLI subcommand to use. 24 | */ 25 | default String command() { 26 | return ""; 27 | } 28 | 29 | /** 30 | * @return Whether this CLI tool is installed and executable. 31 | */ 32 | default boolean exists() { 33 | return Executables.of(name()).isPresent(); 34 | } 35 | 36 | /** 37 | * @param options The options to use. 38 | * @return The command line for the 'pull' step. 39 | */ 40 | List pullArguments(OPTIONS options); 41 | 42 | /** 43 | * @param options The options to use. 44 | * @return The command line for the 'build' step. 45 | */ 46 | List buildArguments(OPTIONS options); 47 | 48 | /** 49 | * @param options The options to use. 50 | * @return The command line for the 'run' step. 51 | */ 52 | List runArguments(OPTIONS options); 53 | 54 | /** 55 | * @param options The options to use. 56 | * @return The command line for the 'cleanup' step. 57 | */ 58 | List cleanupArguments(OPTIONS options); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /docs/content/shell/runtimes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Runtimes 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: shell 7 | identifier: shell_runtimes 8 | categories: 9 | - shell 10 | tags: 11 | - runtime 12 | - docker 13 | - podman 14 | - nerdctl 15 | --- 16 | 17 | `ilo shell` by default searches your local system for supported runtimes. In order to force the usage of a specific runtime, use the `--runtime` flag or set the `ILO_SHELL_RUNTIME` environment variable in your system. The `--runtime` flag overwrites the environment variable. 18 | 19 | ## Docker 20 | 21 | Force `ilo` to use [docker](https://www.docker.com/) like this: 22 | 23 | ```console 24 | $ ilo shell --runtime docker 25 | 26 | # use alias 27 | $ ilo shell --runtime d 28 | 29 | # use env variable 30 | $ ILO_SHELL_RUNTIME=docker ilo shell 31 | ``` 32 | 33 | ## nerdctl 34 | 35 | Force `ilo` to use [nerdctl](https://github.com/containerd/nerdctl) like this: 36 | 37 | ```console 38 | $ ilo shell --runtime nerdctl 39 | 40 | # use alias 41 | $ ilo shell --runtime n 42 | 43 | # use env variable 44 | $ ILO_SHELL_RUNTIME=nerdctl ilo shell 45 | ``` 46 | 47 | ## Podman 48 | 49 | Force `ilo` to use [podman](https://podman.io/) like this: 50 | 51 | ```console 52 | $ ilo shell --runtime podman 53 | 54 | # use alias 55 | $ ilo shell --runtime p 56 | 57 | # use env variable 58 | $ ILO_SHELL_RUNTIME=podman ilo shell 59 | ``` 60 | 61 | ## Auto Selection 62 | 63 | If not otherwise specified, `ilo` always picks runtimes in this order, depending on which are available on your system: 64 | 65 | 1. podman 66 | 2. nerdctl 67 | 3. docker 68 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/model/CliExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.model; 7 | 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.condition.EnabledOnOs; 11 | import org.junit.jupiter.api.condition.OS; 12 | import wtf.metio.ilo.errors.RuntimeIOException; 13 | 14 | import java.util.List; 15 | 16 | class CliExecutorTest { 17 | 18 | @Test 19 | void shouldExecuteMissingCommands() { 20 | final var executor = new CliExecutor<>() { 21 | 22 | @Override 23 | public CliTool selectRuntime(final Runtime runtime) { 24 | return null; 25 | } 26 | 27 | }; 28 | 29 | Assertions.assertThrows(RuntimeIOException.class, 30 | () -> executor.execute(List.of("some", "command"), false)); 31 | } 32 | 33 | @Test 34 | @EnabledOnOs({OS.LINUX, OS.MAC}) 35 | void shouldExecuteCommandOnUnix() { 36 | final var executor = new CliExecutor<>() { 37 | 38 | @Override 39 | public CliTool selectRuntime(final Runtime runtime) { 40 | return null; 41 | } 42 | 43 | }; 44 | 45 | Assertions.assertEquals(0, executor.execute(List.of("ls"), false)); 46 | } 47 | 48 | @Test 49 | @EnabledOnOs({OS.WINDOWS}) 50 | void shouldExecuteCommandOnWindows() { 51 | final var executor = new CliExecutor<>() { 52 | 53 | @Override 54 | public CliTool selectRuntime(final Runtime runtime) { 55 | return null; 56 | } 57 | 58 | }; 59 | 60 | Assertions.assertEquals(0, executor.execute(List.of("dir"), false)); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/body/sidebar.html: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/utils/Streams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.utils; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.stream.Stream; 13 | 14 | import static java.util.function.Function.identity; 15 | import static java.util.function.Predicate.not; 16 | import static java.util.stream.Stream.of; 17 | 18 | public final class Streams { 19 | 20 | public static Stream filter(final Stream stream) { 21 | return stream 22 | .filter(Objects::nonNull) 23 | .filter(not(String::isBlank)); 24 | } 25 | 26 | public static Stream fromList(final List list) { 27 | return Stream.ofNullable(list).flatMap(Collection::stream); 28 | } 29 | 30 | @SafeVarargs 31 | public static List flatten(final Stream... streams) { 32 | return filter(Arrays.stream(streams).flatMap(identity())).toList(); 33 | } 34 | 35 | public static Stream maybe(final boolean condition, final String... values) { 36 | return condition ? filter(Arrays.stream(values)) : Stream.empty(); 37 | } 38 | 39 | public static Stream optional(final String prefix, final String value) { 40 | return Objects.nonNull(value) && !value.isBlank() ? of(prefix, value) : Stream.empty(); 41 | } 42 | 43 | public static Stream withPrefix(final String prefix, final List values) { 44 | return filter(fromList(values)).flatMap(value -> of(prefix, value)); 45 | } 46 | 47 | private Streams() { 48 | // utility class 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/CliToolTCK.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import wtf.metio.ilo.model.CliTool; 11 | import wtf.metio.ilo.model.Options; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertNotNull; 15 | 16 | public abstract class CliToolTCK> { 17 | 18 | protected abstract SHELL tool(); 19 | 20 | protected abstract OPTIONS options(); 21 | 22 | protected abstract String name(); 23 | 24 | protected String command() { 25 | return ""; 26 | } 27 | 28 | @Test 29 | @DisplayName("has runtime name") 30 | void shouldHaveName() { 31 | assertEquals(name(), tool().name()); 32 | } 33 | 34 | @Test 35 | @DisplayName("has subcommand") 36 | void shouldHaveCommand() { 37 | assertEquals(command(), tool().command()); 38 | } 39 | 40 | @Test 41 | @DisplayName("non-null pull arguments") 42 | void pullArguments() { 43 | assertNotNull(tool().pullArguments(options())); 44 | } 45 | 46 | @Test 47 | @DisplayName("non-null build arguments") 48 | void buildArguments() { 49 | assertNotNull(tool().buildArguments(options())); 50 | } 51 | 52 | @Test 53 | @DisplayName("non-null run arguments") 54 | void runArguments() { 55 | assertNotNull(tool().runArguments(options())); 56 | } 57 | 58 | @Test 59 | @DisplayName("non-null cleanup arguments") 60 | void cleanupArguments() { 61 | assertNotNull(tool().cleanupArguments(options())); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/utils/StringsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.utils; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | import wtf.metio.ilo.test.ClassTests; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertFalse; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | @DisplayName("Strings") 18 | class StringsTest { 19 | 20 | @DisplayName("isBlank") 21 | @ParameterizedTest 22 | @ValueSource(strings = {"", " ", " "}) 23 | void shouldDetectBlankString(final String value) { 24 | assertTrue(Strings.isBlank(value)); 25 | } 26 | 27 | @DisplayName("isBlank") 28 | @ParameterizedTest 29 | @ValueSource(strings = {"a", " b ", " c "}) 30 | void shouldDetectBlankStringWithValues(final String value) { 31 | assertFalse(Strings.isBlank(value)); 32 | } 33 | 34 | @DisplayName("isBlank") 35 | @ParameterizedTest 36 | @ValueSource(strings = {"a", " b ", " c "}) 37 | void shouldDetectNonBlankString(final String value) { 38 | assertTrue(Strings.isNotBlank(value)); 39 | } 40 | 41 | @DisplayName("isBlank") 42 | @ParameterizedTest 43 | @ValueSource(strings = {"", " ", " "}) 44 | void shouldDetectNonBlankStringWithoutValues(final String value) { 45 | assertFalse(Strings.isNotBlank(value)); 46 | } 47 | 48 | @Test 49 | @DisplayName("has private constructor") 50 | void shouldHavePrivateConstructor() throws NoSuchMethodException { 51 | ClassTests.hasPrivateConstructor(Strings.class); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/ShellExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 14 | 15 | @DisplayName("ShellExecutor") 16 | class ShellExecutorTest { 17 | 18 | private ShellExecutor shellExecutor; 19 | 20 | @BeforeEach 21 | void setUp() { 22 | shellExecutor = new ShellExecutor(); 23 | } 24 | 25 | @Test 26 | @DisplayName("returns non-null values for auto-selection") 27 | void shouldReturnNonNullValueForAutoSelection() { 28 | assumeTrue(new Podman().exists() || new Docker().exists() || new Nerdctl().exists()); 29 | assertNotNull(shellExecutor.selectRuntime(null)); 30 | } 31 | 32 | @Test 33 | @DisplayName("returns non-null values for forced podman usage") 34 | void shouldReturnNonNullValueForPodman() { 35 | assumeTrue(new Podman().exists()); 36 | assertNotNull(shellExecutor.selectRuntime(ShellRuntime.PODMAN)); 37 | } 38 | 39 | @Test 40 | @DisplayName("returns non-null values for forced docker usage") 41 | void shouldReturnNonNullValueForDocker() { 42 | assumeTrue(new Docker().exists()); 43 | assertNotNull(shellExecutor.selectRuntime(ShellRuntime.DOCKER)); 44 | } 45 | 46 | @Test 47 | @DisplayName("returns non-null values for forced nerdctl usage") 48 | void shouldReturnNonNullValueForNerdctl() { 49 | assumeTrue(new Nerdctl().exists()); 50 | assertNotNull(shellExecutor.selectRuntime(ShellRuntime.NERDCTL)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/os/OSSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import wtf.metio.ilo.cli.Executables; 9 | 10 | import java.nio.file.Path; 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | import static java.util.stream.Collectors.toList; 15 | import static wtf.metio.ilo.utils.Streams.filter; 16 | import static wtf.metio.ilo.utils.Streams.fromList; 17 | 18 | public final class OSSupport { 19 | 20 | public static List expand(final List values) { 21 | return filter(fromList(values)) 22 | .map(OSSupport::expand) 23 | .collect(toList()); 24 | } 25 | 26 | public static String expand(final String value) { 27 | final var expansion = expansion(); 28 | return Optional.ofNullable(value) 29 | .map(expansion::expandParameters) 30 | .map(expansion::substituteCommands) 31 | .orElse(value); 32 | } 33 | 34 | static ParameterExpansion expansion() { 35 | return posixShell() 36 | .or(OSSupport::powerShell) 37 | .orElseGet(NoOpExpansion::new); 38 | } 39 | 40 | // visible for testing 41 | static Optional posixShell() { 42 | return Executables.of("bash") 43 | .or(() -> Executables.of("zsh")) 44 | .or(() -> Executables.of("sh")) 45 | .map(Path::toAbsolutePath) 46 | .map(PosixShell::new); 47 | } 48 | 49 | static Optional powerShell() { 50 | return Executables.of("pwsh.exe") 51 | .or(() -> Executables.of("powershell.exe")) 52 | .or(() -> Executables.of("pwsh")) 53 | .map(Path::toAbsolutePath) 54 | .map(PowerShell::new); 55 | } 56 | 57 | private OSSupport() { 58 | // utility class 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/os/PowerShell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import wtf.metio.ilo.cli.Executables; 9 | 10 | import java.nio.file.Path; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Support for Windows PowerShell 15 | * 16 | * @see PowerShell 17 | */ 18 | final class PowerShell extends ParameterExpansion { 19 | 20 | private static final String COMMAND_STYLE = String.format("\\$\\((?<%s>[^)]+)\\)", MATCHER_GROUP_NAME); 21 | private static final String PARAMETER_STYLE = String.format("(?<%s>\\$[a-zA-Z][a-zA-Z0-9_]*)", MATCHER_GROUP_NAME); 22 | 23 | // visible for testing 24 | static final Pattern COMMAND_PATTERN = Pattern.compile(COMMAND_STYLE); 25 | static final Pattern PARAMETER_PATTERN = Pattern.compile(PARAMETER_STYLE); 26 | 27 | private final Path shellBinary; 28 | 29 | PowerShell(final Path shellBinary) { 30 | this.shellBinary = shellBinary; 31 | } 32 | 33 | @Override 34 | public String substituteCommands(final String value) { 35 | return replace(value, 36 | command -> Executables.runAndReadOutput(shellBinary.toString(), "-OutputFormat", "Text", "-Command", command), 37 | COMMAND_PATTERN); 38 | } 39 | 40 | @Override 41 | public String expandParameters(final String value) { 42 | return replace(expandTilde(value), 43 | parameter -> Executables.runAndReadOutput(shellBinary.toString(), "-OutputFormat", "Text", "-Command", "'Write-Output \"" + parameter + "\"'"), 44 | PARAMETER_PATTERN); 45 | } 46 | 47 | private String expandTilde(final String value) { 48 | final var userHome = System.getProperty("user.home"); 49 | return value.replace("~", userHome); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /docs/content/usage/build-envs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Build Environments 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: usage 7 | identifier: usage_build_envs 8 | weight: 101 9 | categories: 10 | - usage 11 | tags: 12 | - build 13 | - env 14 | --- 15 | 16 | `ilo` allows you to define your build environment either in a [Containerfile/Dockerfile](https://docs.docker.com/engine/reference/builder/) or any other [OCI Image](https://github.com/opencontainers/image-spec/blob/master/spec.md) compliant way. In contrast to [toolbx](https://containertoolbx.org/), `ilo` relies on immutable containers which makes it easier to share those images across your team. `ilo` uses the same mechanism to define build environments that developers are already using to define their application run environments. Therefore, onboarding and adapting container based build environments should be easy for most teams. 17 | 18 | As an example, consider the following Containerfile that is based on the official [Maven image](https://hub.docker.com/_/maven) and extends that with another binary ([hugo](https://gohugo.io/) in this case). 19 | 20 | ```console 21 | # write some Containerfile 22 | $ cat your.containerfile 23 | FROM maven:3-openjdk-11-slim 24 | 25 | RUN apt-get update && apt-get install hugo -y 26 | ``` 27 | 28 | This image can be build just like any other image with your typical tooling, e.g. using [podman](https://podman.io/): 29 | 30 | ```console 31 | $ podman build --tag your.image:your.tag --file your.containerfile path/to/build/context 32 | ``` 33 | 34 | The idea behind `ilo` is that you use this image to start a container that mounts your project directory and is able to execute any command that you are using to build/test/package your project. 35 | 36 | Take a look at the detailed instructions for [ilo shell](../../shell) on how to use your created image. 37 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/utils/StreamsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.utils; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import wtf.metio.ilo.test.ClassTests; 11 | 12 | import java.util.List; 13 | import java.util.stream.Stream; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | @DisplayName("Streams") 18 | class StreamsTest { 19 | 20 | @Test 21 | void filterNonStrings() { 22 | assertEquals(1, Streams.filter(Stream.of("first", "", null)).count()); 23 | } 24 | 25 | @Test 26 | void listToStream() { 27 | assertEquals(2, Streams.fromList(List.of("first", "")).count()); 28 | } 29 | 30 | @Test 31 | void nullListToStream() { 32 | assertEquals(0, Streams.fromList(null).count()); 33 | } 34 | 35 | @Test 36 | void flattenStreams() { 37 | assertEquals(2, Streams.flatten(Stream.of("first"), Stream.of("second")).size()); 38 | } 39 | 40 | @Test 41 | void maybe() { 42 | assertEquals(2, Streams.maybe(true, "first", "second").count()); 43 | } 44 | 45 | @Test 46 | void maybeNot() { 47 | assertEquals(0, Streams.maybe(false, "first", "second").count()); 48 | } 49 | 50 | @Test 51 | void optional() { 52 | assertEquals(2, Streams.optional("--option", "value").count()); 53 | } 54 | 55 | @Test 56 | void optionalNot() { 57 | assertEquals(0, Streams.optional("--option", null).count()); 58 | } 59 | 60 | @Test 61 | void optionalEmpty() { 62 | assertEquals(0, Streams.optional("--option", "").count()); 63 | } 64 | 65 | @Test 66 | @DisplayName("has private constructor") 67 | void shouldHavePrivateConstructor() throws NoSuchMethodException { 68 | ClassTests.hasPrivateConstructor(Streams.class); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/test/ArchUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.test; 7 | 8 | import com.tngtech.archunit.junit.ArchTest; 9 | import com.tngtech.archunit.lang.ArchRule; 10 | import org.junit.jupiter.api.DisplayName; 11 | import org.junit.jupiter.api.DynamicNode; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.Arrays; 15 | import java.util.function.Consumer; 16 | import java.util.stream.Stream; 17 | 18 | import static java.lang.reflect.Modifier.*; 19 | import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; 20 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 21 | 22 | public final class ArchUnitTests { 23 | 24 | private ArchUnitTests() { 25 | // helper class 26 | } 27 | 28 | public static DynamicNode in(final Class clazz, final Consumer check) { 29 | final var displayName = clazz.getAnnotation(DisplayName.class); 30 | return dynamicContainer(displayName.value(), in(clazz) 31 | .map(rule -> dynamicTest(rule.getDescription(), () -> check.accept(rule)))); 32 | } 33 | 34 | public static Stream in(final Class clazz) { 35 | return Arrays.stream(clazz.getDeclaredFields()) 36 | .filter(field -> field.isAnnotationPresent(ArchTest.class)) 37 | .filter(field -> ArchRule.class.isAssignableFrom(field.getType())) 38 | .filter(field -> isPublic(field.getModifiers())) 39 | .filter(field -> isStatic(field.getModifiers())) 40 | .filter(field -> isFinal(field.getModifiers())) 41 | .map(ArchUnitTests::value); 42 | } 43 | 44 | static ArchRule value(final Field field) { 45 | try { 46 | return (ArchRule) field.get(null); 47 | } catch (final IllegalAccessException exception) { 48 | throw new RuntimeException(exception); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/body/site-header.html: -------------------------------------------------------------------------------- 1 | {{ block "header" . }} 2 | 36 | {{ end }} 37 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellRuntime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.cli.EnvironmentVariables; 9 | import wtf.metio.ilo.errors.NoMatchingRuntimeException; 10 | import wtf.metio.ilo.model.CliTool; 11 | import wtf.metio.ilo.model.Runtime; 12 | 13 | import java.util.Arrays; 14 | import java.util.Optional; 15 | 16 | public enum ShellRuntime implements Runtime { 17 | 18 | PODMAN(new Podman(), "podman", "p"), 19 | NERDCTL(new Nerdctl(), "nerdctl", "n"), 20 | DOCKER(new Docker(), "docker", "d"); 21 | 22 | private final ShellCLI cli; 23 | private final String[] aliases; 24 | 25 | ShellRuntime(final ShellCLI cli, final String... aliases) { 26 | this.cli = cli; 27 | this.aliases = aliases; 28 | } 29 | 30 | public static ShellRuntime fromAlias(final String alias) { 31 | return Runtime.firstMatching(alias, values()); 32 | } 33 | 34 | @Override 35 | public String[] aliases() { 36 | return aliases; 37 | } 38 | 39 | @Override 40 | public ShellCLI cli() { 41 | return cli; 42 | } 43 | 44 | /** 45 | * Select a runtime for 'ilo shell'. 46 | * 47 | * @param preferred The runtime to force, or null for auto-selection. 48 | * @return The selected shell runtime. 49 | */ 50 | public static ShellCLI autoSelect(final ShellRuntime preferred) { 51 | return Optional.ofNullable(preferred) 52 | .or(() -> Optional.ofNullable(System.getenv(EnvironmentVariables.ILO_SHELL_RUNTIME.name())) 53 | .map(ShellRuntime::fromAlias)) 54 | .map(ShellRuntime::cli) 55 | .or(() -> Arrays.stream(ShellRuntime.values()) 56 | .map(ShellRuntime::cli) 57 | .filter(CliTool::exists) 58 | .findFirst()) 59 | .filter(CliTool::exists) 60 | .orElseThrow(NoMatchingRuntimeException::new); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/update-parent.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | name: Update Parent 5 | on: 6 | schedule: 7 | - cron: 0 1 2 * * 8 | jobs: 9 | parent: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - id: checkout 13 | name: Clone Git Repository 14 | uses: actions/checkout@v6 15 | - id: graal 16 | name: Set up GraalVM 17 | uses: graalvm/setup-graalvm@v1 18 | with: 19 | version: latest 20 | java-version: 21 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | - id: cache 23 | uses: actions/cache@v5 24 | with: 25 | path: ~/.m2/repository 26 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 27 | restore-keys: | 28 | ${{ runner.os }}-maven- 29 | - id: parent 30 | name: Update parent 31 | run: mvn --batch-mode --define generateBackupPoms=false versions:update-parent 32 | - id: cpr 33 | name: Create Pull Request 34 | uses: peter-evans/create-pull-request@v8 35 | with: 36 | token: ${{ secrets.PAT }} 37 | commit-message: update parent to latest version 38 | committer: GitHub 39 | author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> 40 | title: update parent to latest version 41 | body: | 42 | Project updated with: `mvn versions:update-parent` 43 | labels: dependencies 44 | assignees: sebhoss 45 | draft: false 46 | base: main 47 | branch: update-parent 48 | delete-branch: true 49 | - name: Enable Pull Request Automerge 50 | if: steps.cpr.outputs.pull-request-operation == 'created' 51 | run: gh pr merge --auto --rebase "${{ steps.cpr.outputs.pull-request-number }}" 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.PAT }} 54 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/ShellOptionsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | import wtf.metio.ilo.test.ClassTests; 13 | 14 | import java.lang.reflect.Modifier; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | 19 | @DisplayName("ShellOptions") 20 | class ShellOptionsTest { 21 | 22 | @Test 23 | @DisplayName("has default constructor") 24 | void shouldHaveDefaultConstructor() throws NoSuchMethodException { 25 | ClassTests.hasDefaultConstructor(ShellOptions.class); 26 | } 27 | 28 | @ParameterizedTest 29 | @DisplayName("has public fields") 30 | @ValueSource(strings = { 31 | "runtime", 32 | "debug", 33 | "interactive", 34 | "pull", 35 | "containerfile", 36 | "hostname", 37 | "removeImage", 38 | "runtimeOptions", 39 | "runtimePullOptions", 40 | "runtimeBuildOptions", 41 | "runtimeRunOptions", 42 | "runtimeCleanupOptions", 43 | "volumes", 44 | "variables", 45 | "ports", 46 | "image", 47 | "mountProjectDir", 48 | "commands" 49 | }) 50 | void shouldHavePublicProperty(final String field) throws NoSuchFieldException { 51 | final var runtime = ShellOptions.class.getDeclaredField(field); 52 | assertTrue(Modifier.isPublic(runtime.getModifiers())); 53 | } 54 | 55 | @ParameterizedTest 56 | @DisplayName("returns debug value") 57 | @ValueSource(booleans = {true, false}) 58 | void shouldReturnDebugValue(final boolean value) { 59 | final var options = new ShellOptions(); 60 | options.debug = value; 61 | assertEquals(value, options.debug()); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: The ilo Authors 2 | # SPDX-License-Identifier: 0BSD 3 | 4 | MAKEFLAGS += --warn-undefined-variables 5 | SHELL = /bin/bash 6 | .SHELLFLAGS := -eu -o pipefail -c 7 | .DEFAULT_GOAL := all 8 | .DELETE_ON_ERROR: 9 | .SUFFIXES: 10 | 11 | TIMESTAMPED_VERSION := $(shell /bin/date "+%Y.%m.%d-%H%M%S") 12 | CURRENT_DATE := $(shell /bin/date "+%Y-%m-%d") 13 | USERNAME := $(shell id -u -n) 14 | USERID := $(shell id -u) 15 | GREEN := $(shell tput -Txterm setaf 2) 16 | WHITE := $(shell tput -Txterm setaf 7) 17 | YELLOW := $(shell tput -Txterm setaf 3) 18 | RESET := $(shell tput -Txterm sgr0) 19 | 20 | HELP_FUN = \ 21 | %help; \ 22 | while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z0-9\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \ 23 | print "usage: make [target]\n\n"; \ 24 | for (sort keys %help) { \ 25 | print "${WHITE}$$_:${RESET}\n"; \ 26 | for (@{$$help{$$_}}) { \ 27 | $$sep = " " x (32 - length $$_->[0]); \ 28 | print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \ 29 | }; \ 30 | print "\n"; } 31 | 32 | .PHONY: all 33 | all: help 34 | 35 | .PHONY: help 36 | help: ##@other Show this help 37 | @perl -e '$(HELP_FUN)' $(MAKEFILE_LIST) 38 | 39 | .PHONY: build 40 | build: ##@hacking Build everything 41 | mvn verify 42 | 43 | .PHONY: native-image 44 | native-image: ##@hacking Create a native image using GraalVM 45 | mvn verify --define skipNativeBuild=false 46 | 47 | .PHONY: clean 48 | clean: ##@hacking Clean build artifacts 49 | mvn clean 50 | 51 | .PHONY: ilo-build 52 | ilo-build: ##@hacking Build everything with ilo 53 | ilo @dev/build 54 | 55 | .PHONY: ilo-native 56 | ilo-native: ##@hacking Create a native image using GraalVM with ilo 57 | ilo @dev/native 58 | 59 | .PHONY: ilo-env 60 | ilo-env: ##@hacking Open a new development environment with ilo 61 | ilo @dev/env 62 | 63 | .PHONY: ilo-website 64 | ilo-website: ##@hacking Build the website with ilo 65 | ilo @dev/website 66 | 67 | .PHONY: ilo-serve 68 | ilo-serve: ##@hacking Serve the website locally with ilo 69 | ilo @dev/serve 70 | -------------------------------------------------------------------------------- /docs/themes/metio/layouts/partials/head/styles.html: -------------------------------------------------------------------------------- 1 | {{ $normalize := resources.Get "/css/normalize.css" }} 2 | {{ $font := resources.Get "/css/font.css" }} 3 | {{ $header := resources.Get "/css/header.css" }} 4 | {{ $footer := resources.Get "/css/footer.css" }} 5 | {{ $navigation := resources.Get "/css/navigation.css" }} 6 | {{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }} 7 | {{ $navigation_tablet := resources.Get "/css/navigation-tablet.css" }} 8 | {{ $navigation_desktop := resources.Get "/css/navigation-desktop.css" }} 9 | {{ $layout := resources.Get "/css/layout.css" }} 10 | {{ $layout_mobile := resources.Get "/css/layout-mobile.css" }} 11 | {{ $layout_tablet := resources.Get "/css/layout-tablet.css" }} 12 | {{ $layout_desktop := resources.Get "/css/layout-desktop.css" }} 13 | {{ $syntax := resources.Get "/css/syntax.css" }} 14 | {{ $darkmode := resources.Get "/css/darkmode.css" | minify | fingerprint "sha512" }} 15 | 16 | {{ $mobile := slice $normalize $font $header $footer $navigation $layout $syntax $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | minify | fingerprint "sha512" }} 17 | {{ $tablet := slice $normalize $font $header $footer $navigation $layout $syntax $navigation_tablet $layout_tablet | resources.Concat "css/tablet.css" | minify | fingerprint "sha512" }} 18 | {{ $desktop := slice $normalize $font $header $footer $navigation $layout $syntax $navigation_desktop $layout_desktop | resources.Concat "css/desktop.css" | minify | fingerprint "sha512" }} 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/content/contributors/building.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Building 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: 'Contributors' 7 | categories: 8 | - Contributors 9 | tags: 10 | - build 11 | - environment 12 | --- 13 | 14 | `ilo` requires a certain set of software installed on your system in order to be built. 15 | 16 | ## Prerequisites 17 | 18 | - [git](https://git-scm.com/) to fetch the [source code](../git-mirrors) of `ilo` 19 | 20 | ## ilo Setup 21 | 22 | You can use `ilo` to build `ilo`! Make sure your system has the following: 23 | 24 | - [ilo](../../usage/install) to open the reproducible build environment for `ilo` itself 25 | - One of the [runtimes](../../shell/runtimes) that `ilo shell` supports. 26 | 27 | ## Manual Setup 28 | 29 | In case you do not have `ilo` installed on your system, install the following manually: 30 | 31 | - [Java JDK](https://jdk.java.net/) to compile the code 32 | - [Maven](https://maven.apache.org/) to build the project 33 | - [hugo](https://gohugo.io/) in order to create the website 34 | - [GraalVM](https://www.graalvm.org/) to build a native executable 35 | 36 | ## Building 37 | 38 | ### Using ilo 39 | 40 | In case you have `ilo` installed, call this: 41 | 42 | ```console 43 | # open a shell with a pre-defined build environment 44 | $ ilo @dev/env 45 | 46 | # build the project 47 | $ ilo @dev/build 48 | 49 | # build native executable 50 | $ ilo @dev/native 51 | 52 | # build website 53 | $ ilo @dev/website 54 | 55 | # serve website 56 | $ ilo @dev/serve 57 | ``` 58 | 59 | ### Without ilo 60 | 61 | In order to build `ilo` without having `ilo` installed call: 62 | 63 | ```console 64 | # build the project 65 | $ mvn verify 66 | 67 | # build native executable 68 | $ mvn verify --define skipNativeBuild=false 69 | ``` 70 | 71 | In case you want to build or work on the website do this: 72 | 73 | ```console 74 | # build website 75 | $ hugo --minify --printI18nWarnings --printPathWarnings --printUnusedTemplates --source docs 76 | 77 | # serve website 78 | $ hugo server --minify --printI18nWarnings --printPathWarnings --printUnusedTemplates --source docs --watch 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/content/shell/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Examples 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: shell 7 | identifier: shell_examples 8 | categories: 9 | - shell 10 | tags: 11 | - examples 12 | --- 13 | 14 | The following examples show how `ilo shell` can be used. 15 | 16 | ## Cargo Projects 17 | 18 | [Cargo](https://doc.rust-lang.org/cargo/) caches all downloaded dependencies in your local `~/.cargo/registry` directory. 19 | 20 | In order to re-use already downloaded dependencies inside the container, specify a `--volumne` like this: 21 | 22 | ```console 23 | # Cargo project that mounts local .cargo folder 24 | $ ilo shell \ 25 | --volume ${HOME}/.cargo/registry:/usr/local/cargo/registry:z \ 26 | rust:latest 27 | ``` 28 | 29 | **Note**: The container path `/usr/local/cargo` is specified in the image used in this example (`rust:latest`). Adjust this value according to the image you are actually using in your project. 30 | 31 | ## Gradle Projects 32 | 33 | [Gradle](https://gradle.org/) caches all downloaded dependencies in your local `~/.gradle` directory. 34 | 35 | In order to re-use already downloaded dependencies inside the container, specify a `--volumne` like this: 36 | 37 | ```console 38 | # Gradle project that mounts local .gradle folder 39 | $ ilo shell \ 40 | --volume ${HOME}/.gradle:/home/gradle/.gradle:z \ 41 | gradle:latest 42 | ``` 43 | 44 | **Note**: The container path `/home/gradle/.gradle` is specified in the image used in this example (`gradle:latest`). Adjust this value according to the image you are actually using in your project. 45 | 46 | ## Maven Projects 47 | 48 | [Maven](https://maven.apache.org/) caches all downloaded dependencies in your local `~/.m2` directory. 49 | 50 | In order to re-use already downloaded dependencies inside the container, specify a `--volumne` like this: 51 | 52 | ```console 53 | # Maven project that mounts local m2 repo 54 | $ ilo shell \ 55 | --volume ${HOME}/.m2:/root/.m2:z \ 56 | maven:latest 57 | ``` 58 | 59 | **Note**: The container path `/root/.m2` is specified in the image used in this example (`maven:latest`). Adjust this value according to the image you are actually using in your project. 60 | -------------------------------------------------------------------------------- /docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: reproducible build environments 3 | date: 2020-04-13 4 | --- 5 | 6 | # ilo 7 | 8 | > manage reproducible build environments 9 | 10 | `ilo` is a [toolbx](https://containertoolbx.org/) inspired tool to create/manage environments for [reproducible builds](https://reproducible-builds.org/) based on [OCI](https://www.opencontainers.org/) container images. 11 | 12 | ## Features 13 | 14 | ### Reproducible Build Environments 15 | 16 | Thanks to containers, `ilo` can fully encapsulate the necessary tools required to build your project. Therefore, making it easy to reproduce the build output of any project. Custom tooling, a specific version of a compiler, or anything else required to build a project are no longer showstoppers, but rather implementation details. 17 | 18 | ### Per-Project Dependencies 19 | 20 | `ilo` recognizes that lots of projects have their own unique build requirements. Instead of forcing users to install all required tooling into their local system, `ilo` moves all project dependencies into a container. In case you want to clean up your computer, just remove the container image! `ilo` will automatically recreate a build environment for your project the next time you need it. 21 | 22 | ### Teamwork 23 | 24 | Onboarding new team members into big projects with complex build requirements can be a hassle. `ilo`'s container approach reduces the amount of work required to get new members up to speed - install `ilo`, clone your project, and you're good to go. `ilo` supports multiple ways to share immutable build environments with your team in order reproduce a project. 25 | 26 | ### Cross-Platform 27 | 28 | `ilo` is available for [Linux](https://www.kernel.org/), [MacOS](https://www.apple.com/macos/), [Windows](https://www.microsoft.com/en-us/windows), and others. It supports a wide range of runtimes which makes it easy to both add and remove `ilo` from your project. It plays nicely with tools already available on your local system - use your favorite IDE to write code! 29 | 30 | ## Users 31 | 32 | Want to try `ilo` for your project? Take a look at the [usage guide](./usage). 33 | 34 | ## Contributors 35 | 36 | Interested in contributing to `ilo`? Take a look at the [contributor guide](./contributors). 37 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/os/PosixShell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import wtf.metio.ilo.cli.Executables; 9 | 10 | import java.nio.file.Path; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Support for POSIX compatible shells. 15 | * 16 | * @see Shell Command Language 17 | */ 18 | final class PosixShell extends ParameterExpansion { 19 | 20 | private static final String NEW_COMMAND_STYLE = String.format("\\$\\((?<%s>[^)]+)\\)", MATCHER_GROUP_NAME); 21 | private static final String OLD_COMMAND_STYLE = String.format("`(?<%s>[^`]+)`", MATCHER_GROUP_NAME); 22 | private static final String PARAMETER_STYLE = String.format("(?<%s>\\$[a-zA-Z][a-zA-Z0-9_]*)", MATCHER_GROUP_NAME); 23 | private static final String PARAMETER_WITH_BRACES_STYLE = String.format("(?<%s>\\$\\{[a-zA-Z][a-zA-Z0-9_]*})", MATCHER_GROUP_NAME); 24 | 25 | // visible for testing 26 | static final Pattern NEW_COMMAND_PATTERN = Pattern.compile(NEW_COMMAND_STYLE); 27 | static final Pattern OLD_COMMAND_PATTERN = Pattern.compile(OLD_COMMAND_STYLE); 28 | static final Pattern PARAMETER_WITH_BRACES_PATTERN = Pattern.compile(PARAMETER_WITH_BRACES_STYLE); 29 | static final Pattern PARAMETER_PATTERN = Pattern.compile(PARAMETER_STYLE); 30 | 31 | private final Path shellBinary; 32 | 33 | PosixShell(final Path shellBinary) { 34 | this.shellBinary = shellBinary; 35 | } 36 | 37 | @Override 38 | public String substituteCommands(final String value) { 39 | return replace(value, 40 | command -> Executables.runAndReadOutput(shellBinary.toString(), "-c", command), 41 | NEW_COMMAND_PATTERN, OLD_COMMAND_PATTERN); 42 | } 43 | 44 | @Override 45 | public String expandParameters(final String value) { 46 | return replace(expandTilde(value), 47 | parameter -> Executables.runAndReadOutput("/usr/bin/env", shellBinary.toString(), "-c", "printf " + parameter), 48 | PARAMETER_WITH_BRACES_PATTERN, PARAMETER_PATTERN); 49 | } 50 | 51 | private String expandTilde(final String value) { 52 | final var userHome = System.getProperty("user.home"); 53 | return value.replace("~", userHome); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/cli/CommandLifecycle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.cli; 7 | 8 | import picocli.CommandLine; 9 | import wtf.metio.ilo.model.CliTool; 10 | import wtf.metio.ilo.model.Options; 11 | 12 | import java.util.List; 13 | import java.util.function.BiFunction; 14 | import java.util.stream.IntStream; 15 | 16 | /** 17 | * Utility class that encapsulates the common command lifecycle for 'ilo shell'. 18 | */ 19 | public final class CommandLifecycle { 20 | 21 | /** 22 | * The common command lifecycle executes: 23 | *
    24 | *
  1. Pull
  2. 25 | *
  3. Build
  4. 26 | *
  5. Run
  6. 27 | *
  7. Cleanup
  8. 28 | *
> 29 | * 30 | * @param tool The container tool to use, e.g. podman. 31 | * @param options The options to use for the entire command lifecycle. 32 | * @param executor The executor to use. 33 | * @param The type of the options supplied. 34 | * @param The type of the container tool supplied. 35 | * @return Stream of exit codes, one for each step in the lifecycle. 36 | */ 37 | public static > int run( 38 | final CLI tool, 39 | final OPTIONS options, 40 | final BiFunction, ? super Boolean, Integer> executor) { 41 | final var pullArguments = tool.pullArguments(options); 42 | final var pullExitCode = executor.apply(pullArguments, options.debug()); 43 | if (0 != pullExitCode) { 44 | return pullExitCode; 45 | } 46 | final var buildArguments = tool.buildArguments(options); 47 | final var buildExitCode = executor.apply(buildArguments, options.debug()); 48 | if (0 != buildExitCode) { 49 | return buildExitCode; 50 | } 51 | final var runArguments = tool.runArguments(options); 52 | final var runExitCode = executor.apply(runArguments, options.debug()); 53 | if (0 != runExitCode) { 54 | return runExitCode; 55 | } 56 | final var cleanupArguments = tool.cleanupArguments(options); 57 | final var cleanupExitCode = executor.apply(cleanupArguments, options.debug()); 58 | return IntStream.of(pullExitCode, buildExitCode, runExitCode, cleanupExitCode) 59 | .max().orElse(CommandLine.ExitCode.SOFTWARE); 60 | } 61 | 62 | private CommandLifecycle() { 63 | // utility class 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /docs/content/shell/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ilo shell 3 | date: 2020-04-13 4 | menu: main 5 | weight: 110 6 | --- 7 | 8 | The `ilo shell` command can be used to run a single container either interactively (default) or in non-interactive mode (e.g. for CI builds). It can build an image, mount directories automatically, stop containers, remove images, and customize the build environment according to the needs of your project. 9 | 10 | ```console 11 | # open shell for local builds in default image with default image command 12 | [you@hostname project-dir]$ ilo shell 13 | [root@container project-dir]# 14 | 15 | # use custom image 16 | [you@hostname project-dir]$ ilo shell maven:latest 17 | [root@container project-dir]# 18 | 19 | # use custom command 20 | [you@hostname project-dir]$ ilo shell openjdk:11 jshell 21 | [root@container project-dir]# 22 | 23 | # run command non-interactive 24 | [you@hostname project-dir]$ ilo shell --no-interactive openjdk:11 mvn verify 25 | [you@hostname project-dir]$ 26 | ``` 27 | 28 | `ilo shell` will delegate most of its work to one of the supported [runtimes](./runtimes). In order to override the default command of your image, specify the command you want to execute just after the image, like this: 29 | 30 | ```console 31 | [you@hostname project-dir]$ ilo shell openjdk:11 /bin/bash 32 | [root@container project-dir]# 33 | ``` 34 | 35 | In order to exit the container either use `exit` or hit `Ctrl + d`: 36 | 37 | ```console 38 | [root@container project-dir]# exit 39 | [you@hostname project-dir]$ 40 | ``` 41 | 42 | Once you have exited the container, `ilo` will automatically stop and remove it. In order to remove the image as well, specify the `--remove-image` flag: 43 | 44 | ```console 45 | [you@hostname project-dir]$ ilo shell --remove-image openjdk:11 46 | [root@container project-dir]# exit 47 | ``` 48 | 49 | In order to pull an image first before opening a new shell, use the `--pull` flag like this: 50 | 51 | ```console 52 | [you@hostname project-dir]$ ilo shell --pull openjdk:latest 53 | [root@container project-dir]# 54 | ``` 55 | 56 | In case you want to use a local `Containerfile`/`Dockerfile`, use the `--containerfile`/`--dockerfile` flag like this: 57 | 58 | ```console 59 | [you@hostname project-dir]$ ilo shell --containerfile your.containerfile your.image:latest 60 | [root@container project-dir]# 61 | ``` 62 | 63 | The resulting image name will be `your.image:latest`. Take a look at all available [options](./options) or use `ilo shell --help` to get a list of all options, and their default values. In order to simplify handling of long command line options, consider using [argument files](../usage/argument-files). 64 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/Ilo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo; 7 | 8 | import picocli.AutoComplete; 9 | import picocli.CommandLine; 10 | import wtf.metio.ilo.cli.RunCommands; 11 | import wtf.metio.ilo.errors.ExitCodes; 12 | import wtf.metio.ilo.errors.PrintingExceptionHandler; 13 | import wtf.metio.ilo.shell.ShellCommand; 14 | import wtf.metio.ilo.version.VersionProvider; 15 | 16 | import java.nio.file.Paths; 17 | import java.util.Arrays; 18 | import java.util.stream.Stream; 19 | 20 | /** 21 | * Main entry point for ilo - a little tool to manage reproducible build environments 22 | */ 23 | @CommandLine.Command( 24 | name = "ilo", 25 | description = "Manage reproducible build environments", 26 | versionProvider = VersionProvider.class, 27 | mixinStandardHelpOptions = true, 28 | showAtFileInUsageHelp = true, 29 | usageHelpAutoWidth = true, 30 | synopsisSubcommandLabel = "COMMAND", 31 | descriptionHeading = "%n", 32 | parameterListHeading = "%n", 33 | optionListHeading = "%nOptions:%n", 34 | commandListHeading = "%nCommands:%n", 35 | subcommands = { 36 | ShellCommand.class, 37 | AutoComplete.GenerateCompletion.class 38 | }, 39 | showDefaultValues = true 40 | ) 41 | public final class Ilo implements Runnable { 42 | 43 | @CommandLine.Spec 44 | CommandLine.Model.CommandSpec spec; 45 | 46 | public static void main(final String... userInput) { 47 | System.setProperty("picocli.disable.closures", "true"); 48 | System.exit(commandLine().execute(allArguments(userInput))); 49 | } 50 | 51 | // visible for testing 52 | static String[] allArguments(final String[] userInput) { 53 | return Stream.concat(runCommands(userInput), Arrays.stream(userInput)).toArray(String[]::new); 54 | } 55 | 56 | // visible for testing 57 | static Stream runCommands(final String[] userInput) { 58 | if (RunCommands.shouldAddRunCommands(userInput)) { 59 | final var currentDir = Paths.get(System.getProperty("user.dir")); 60 | return RunCommands.locate(currentDir); 61 | } 62 | return Stream.empty(); 63 | } 64 | 65 | // visible for testing 66 | public static CommandLine commandLine() { 67 | final var commandLine = new CommandLine(new Ilo()); 68 | commandLine.setStopAtPositional(true); 69 | commandLine.setCaseInsensitiveEnumValuesAllowed(true); 70 | commandLine.setExecutionExceptionHandler(new PrintingExceptionHandler()); 71 | commandLine.setExitCodeExceptionMapper(new ExitCodes()); 72 | return commandLine; 73 | } 74 | 75 | @Override 76 | public void run() { 77 | throw new CommandLine.ParameterException(spec.commandLine(), "ERROR: Missing required subcommand" + System.lineSeparator()); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/cli/RunCommands.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.cli; 7 | 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.Arrays; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * Support for so-called RC files. 15 | * 16 | * @see Run Commands 17 | * @see Picocli Argument Files 18 | */ 19 | public final class RunCommands { 20 | 21 | /** 22 | * Locate run commands on the host machine and prepares them for loading by picocli by prepending a '@' in front of 23 | * the path. This turns them into argument files which are natively supported by picocli. 24 | * 25 | * @param baseDirectory The base directory to use for relative paths. 26 | * @return Stream of run command paths, prepended with '@'. 27 | */ 28 | public static Stream locate(final Path baseDirectory) { 29 | if (System.getenv().containsKey(EnvironmentVariables.ILO_RC.name())) { 30 | final var rcFiles = System.getenv().get(EnvironmentVariables.ILO_RC.name()); 31 | final var files = rcFiles.split(","); 32 | return asArgumentFiles(Arrays.stream(files).map(String::trim).map(baseDirectory::resolve)); 33 | } 34 | return asArgumentFiles(Stream.of(".ilo/ilo.rc", ".ilo.rc").map(baseDirectory::resolve)); 35 | } 36 | 37 | private static Stream asArgumentFiles(final Stream locations) { 38 | return locations 39 | .filter(Files::isReadable) 40 | .filter(Files::isRegularFile) 41 | .map(Path::toAbsolutePath) 42 | .map(Path::toString) 43 | .map("@"::concat); 44 | } 45 | 46 | /** 47 | * Poor-mans guard to prohibit adding run command files in some 'special' cases, e.g. users wants to see 'help'. 48 | * 49 | * @param args The CLI arguments for ilo itself. 50 | * @return Whether to add run command files or not. 51 | */ 52 | public static boolean shouldAddRunCommands(final String[] args) { 53 | final var hasArguments = 0 < args.length; 54 | final var isVersion = (hasArguments && ("-V".equals(args[0]) || "--version".equals(args[0]))) 55 | || 1 < args.length && ("-V".equals(args[1]) || "--version".equals(args[1])); 56 | final var isHelp = (hasArguments && ("-h".equals(args[0]) || "--help".equals(args[0]))) 57 | || (1 < args.length && ("-h".equals(args[1]) || "--help".equals(args[1]))); 58 | final var isCompletion = hasArguments && "generate-completion".equals(args[0]); 59 | final var disableRunCommands = hasArguments && "--no-rc".equals(args[0]); 60 | 61 | return !isVersion && !isHelp && !isCompletion && !disableRunCommands; 62 | } 63 | 64 | private RunCommands() { 65 | // utility class 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellVolumeBehavior.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.errors.LocalDirectoryDoesNotExistException; 9 | 10 | import java.io.IOException; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.util.List; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | import static wtf.metio.ilo.utils.Streams.filter; 18 | import static wtf.metio.ilo.utils.Streams.fromList; 19 | 20 | /** 21 | * Controls how the 'ilo shell' command handles missing local mount directories. 22 | */ 23 | public enum ShellVolumeBehavior { 24 | 25 | /** 26 | * Automatically create local volume mount directories that do not exist. If local directory cannot be created, 27 | * behave like WARN and remove from --volume list. 28 | */ 29 | CREATE { 30 | @Override 31 | boolean handleMissingDirectory(final Path directory) { 32 | try { 33 | if (Files.notExists(directory)) { 34 | Files.createDirectories(directory); 35 | } 36 | return true; 37 | } catch (final IOException exception) { 38 | System.err.println("Could not create directory " + directory.toAbsolutePath() + " because of " + exception.getMessage()); 39 | return false; 40 | } 41 | } 42 | }, 43 | 44 | /** 45 | * Warn in case local mount directories do not exist remove them from --volume list. 46 | */ 47 | WARN { 48 | @Override 49 | boolean handleMissingDirectory(final Path directory) { 50 | if (Files.exists(directory)) { 51 | return true; 52 | } 53 | System.out.println("The local directory " + directory.toAbsolutePath() + " does not exist."); 54 | return false; 55 | } 56 | }, 57 | 58 | /** 59 | * Error in case local mount directories do not exist and stop execution. 60 | */ 61 | ERROR { 62 | @Override 63 | boolean handleMissingDirectory(final Path directory) { 64 | if (Files.notExists(directory)) { 65 | throw new LocalDirectoryDoesNotExistException(directory); 66 | } 67 | return true; 68 | } 69 | }; 70 | 71 | public List handleLocalDirectories(final List volumes) { 72 | return filter(fromList(volumes)) 73 | .filter(this::handleLocalDirectory) 74 | .collect(toList()); 75 | } 76 | 77 | private boolean handleLocalDirectory(final String volume) { 78 | final var localDirectory = extractLocalPart(volume); 79 | final var localPath = Paths.get(localDirectory); 80 | return handleMissingDirectory(localPath); 81 | } 82 | 83 | // visible for testing 84 | static String extractLocalPart(final String volume) { 85 | return volume.split(":")[0]; 86 | } 87 | 88 | abstract boolean handleMissingDirectory(Path directory); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /docs/content/contributors/first-timer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: First Timer 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: 'Contributors' 7 | categories: 8 | - Contributors 9 | tags: 10 | - help 11 | --- 12 | 13 | `ilo` is an open source product released under the [0BSD license](https://spdx.org/licenses/0BSD.html). In order to make sure that each contribution is correctly attributed and licensed, the Developer Certificate of Origin (DCO) **MUST** be signed by each contributor with their first commit. In order to do so, simply add a `Signed-off-by` statement at the end of your commit yourself or use `git commit -s` to do that automatically. The DCO can be seen below or at https://developercertificate.org/ 14 | 15 | ``` 16 | Developer's Certificate of Origin 1.1 17 | 18 | By making a contribution to this project, I certify that: 19 | 20 | (a) The contribution was created in whole or in part by me and I 21 | have the right to submit it under the open source license 22 | indicated in the file; or 23 | 24 | (b) The contribution is based upon previous work that, to the 25 | best of my knowledge, is covered under an appropriate open 26 | source license and I have the right under that license to 27 | submit that work with modifications, whether created in whole 28 | or in part by me, under the same open source license (unless 29 | I am permitted to submit under a different license), as 30 | Indicated in the file; or 31 | 32 | (c) The contribution was provided directly to me by some other 33 | person who certified (a), (b) or (c) and I have not modified 34 | it. 35 | 36 | (d) I understand and agree that this project and the contribution 37 | are public and that a record of the contribution (including 38 | all personal information I submit with it, including my 39 | sign-off) is maintained indefinitely and may be redistributed 40 | consistent with this project or the open source license(s) 41 | involved. 42 | ``` 43 | 44 | ## Metadata 45 | 46 | Every contributor **MAY** add/remove their metadata to the list of contributors at any time. Simply add a file called `.yaml` in the [contributors directory](https://github.com/metio/ilo/tree/main/docs/data/contributors) with the following properties: 47 | 48 | ```yaml 49 | id: '' # should match file name (required) 50 | title: '' # used by FOAF/humans.txt (optional) 51 | first_name: '' # used by FOAF/humans.txt (optional) 52 | last_name: '' # used by FOAF/humans.txt (optional) 53 | email: '' # used by FOAF (optional) 54 | website: '' # used by FOAF/humans.txt (optional) 55 | ``` 56 | 57 | Metadata is currently used in three places: 58 | 59 | 1. To generate a [humans.txt](https://humanstxt.org/) file of [all contributors](https://ilo.projects.metio.wtf/humans.txt). 60 | 2. To generate a [FOAF](http://xmlns.com/foaf/spec/) for the [entire project](https://ilo.projects.metio.wtf/foaf.rdf). 61 | -------------------------------------------------------------------------------- /docs/content/usage/argument-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Argument Files 3 | date: 2020-04-13 4 | menu: 5 | main: 6 | parent: usage 7 | identifier: usage_arg_files 8 | categories: 9 | - usage 10 | tags: 11 | - argument files 12 | --- 13 | 14 | In order to share the same options/commands across your team, `ilo` supports argument files which contain the options for your project, e.g. which image you are using. Argument files are just plain text files and both name and location can be chosen at will. In order to use an argument file, you have to add **@** in front of the file name: `ilo @file-name`. 15 | 16 | ```console 17 | # write argument file 18 | $ cat some/folder/your-arguments 19 | shell 20 | node:latest 21 | /bin/bash 22 | 23 | # use argument file 24 | $ ilo @some/folder/your-arguments 25 | ``` 26 | 27 | The argument file in the above example specified all commands and options on a new line, however you could write them all in a single line (or a mixture of both) as well: 28 | 29 | ```console 30 | # write argument file 31 | $ cat some/other/your-arguments 32 | shell node:latest /bin/bash 33 | 34 | # write argument file 35 | $ cat some/more/of/your-arguments 36 | shell 37 | node:latest /bin/bash 38 | 39 | # use argument file 40 | $ ilo @some/other/your-arguments 41 | $ ilo @some/more/of/your-arguments 42 | ``` 43 | 44 | **Important**: In case your option contains a whitespace, you have to either put the entire option with its value in single/double quotes or use a whitespace between option and value like this: 45 | 46 | ```console 47 | # quote the entire option 48 | "--runtime-option=some option here" 49 | 50 | # quote the value 51 | --runtime-option "some option here" 52 | 53 | # THIS WON'T WORK 54 | --runtime-option="some option here" 55 | ``` 56 | 57 | You can use multiple arguments files which are evaluated in-order, e.g like this: 58 | 59 | ```console 60 | $ ilo @first @second 61 | ``` 62 | 63 | You can mix argument files with regular CLI options as well: 64 | 65 | ```console 66 | $ ilo shell @default-shell openjdk:11 67 | ``` 68 | 69 | The argument file used by `ilo` developers can be seen [here](https://github.com/metio/ilo/blob/main/dev/env) and is used by calling `ilo @dev/env`. 70 | 71 | ## RC Files 72 | 73 | In order to simplify/automate its usage, `ilo` will look for [run command](https://en.wikipedia.org/wiki/Run_commands) files in the following locations: 74 | 75 | 1. `.ilo/ilo.rc` 76 | 2. `.ilo.rc` 77 | 78 | **Each** file found will be added in-order as an argument file to your invocation of `ilo` **before** any other options you specify in your terminal. You can change the locations to check by specifying the `ILO_RC` environment variable. Multiple locations can be given by separating them with a comma like this: 79 | 80 | ```console 81 | $ export ILO_RC=some-file.rc,another-one.rc 82 | $ ilo ... 83 | ``` 84 | 85 | In order to disable loading `.rc` files entirely, specify `--no-rc` in the command line before the actual `ilo` subcommand, like this: 86 | 87 | ```console 88 | # do not load .rc files 89 | $ ilo --no-rc shell ... 90 | $ ilo --no-rc @some-argument-file ... 91 | ``` 92 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/acceptance/IloAcceptanceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.acceptance; 7 | 8 | import org.junit.jupiter.api.Disabled; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.ValueSource; 13 | import wtf.metio.ilo.shell.ShellRuntime; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | @DisplayName("Ilo") 18 | class IloAcceptanceTest extends CLI_TCK { 19 | 20 | @Test 21 | @DisplayName("select runtime automatically by default") 22 | void shouldDefaultToAutoRuntimeSelection() { 23 | final var shell = parseShellCommand("shell"); 24 | assertNull(shell.options.runtime); 25 | } 26 | 27 | @DisplayName("allow to specify runtime") 28 | @ParameterizedTest 29 | @ValueSource(strings = {"podman", "docker", "p", "d"}) 30 | void shouldAllowToSpecifyRuntime(final String runtime) { 31 | final var shell = parseShellCommand("shell", "--runtime", runtime); 32 | assertEquals(ShellRuntime.fromAlias(runtime), shell.options.runtime); 33 | } 34 | 35 | @Test 36 | @DisplayName("debug is disabled by default") 37 | void shouldDisableDebugByDefault() { 38 | final var shell = parseShellCommand("shell"); 39 | assertFalse(shell.options.debug); 40 | } 41 | 42 | @Test 43 | @DisplayName("allow to enable debug") 44 | void shouldAllowToEnableDebug() { 45 | final var shell = parseShellCommand("shell", "--debug"); 46 | assertTrue(shell.options.debug); 47 | } 48 | 49 | @Test 50 | @DisplayName("allow to disable debug") 51 | void shouldAllowToDisableDebug() { 52 | final var shell = parseShellCommand("shell", "--debug=false"); 53 | assertFalse(shell.options.debug); 54 | } 55 | 56 | @Test 57 | @DisplayName("interactive mode is enabled by default") 58 | void shouldEnableInteractiveModeByDefault() { 59 | final var shell = parseShellCommand("shell"); 60 | assertTrue(shell.options.interactive); 61 | } 62 | 63 | @Test 64 | @Disabled("negate does not work from tests?") 65 | @DisplayName("interactive mode can be negated") 66 | void shouldAllowToNegateInteractiveMode() { 67 | final var shell = parseShellCommand("shell", "--no-interactive"); 68 | assertFalse(shell.options.interactive); 69 | } 70 | 71 | @Test 72 | @DisplayName("interactive mode can be disabled") 73 | void shouldAllowToDisableInteractiveMode() { 74 | final var shell = parseShellCommand("shell", "--interactive=false"); 75 | assertFalse(shell.options.interactive); 76 | } 77 | 78 | @Test 79 | @DisplayName("project directory should be mounted by default") 80 | void shouldMountProjectDirectoryByDefault() { 81 | final var shell = parseShellCommand("shell"); 82 | assertTrue(shell.options.mountProjectDir); 83 | } 84 | 85 | @Test 86 | @DisplayName("mounting the project directory can be disabled") 87 | void shouldAllowToDisableProjectDirectoryMounting() { 88 | final var shell = parseShellCommand("shell", "--mount-project-dir=false"); 89 | assertFalse(shell.options.mountProjectDir); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/DockerLike.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import wtf.metio.ilo.os.OSSupport; 9 | import wtf.metio.ilo.utils.Strings; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | import static java.util.stream.Stream.of; 15 | import static wtf.metio.ilo.utils.Streams.*; 16 | 17 | abstract class DockerLike implements ShellCLI { 18 | 19 | @Override 20 | public final List pullArguments(final ShellOptions options) { 21 | if (options.pull && Strings.isBlank(options.containerfile)) { 22 | return flatten( 23 | of(name()), 24 | fromList(OSSupport.expand(options.runtimeOptions)), 25 | of("pull"), 26 | fromList(OSSupport.expand(options.runtimePullOptions)), 27 | of(OSSupport.expand(options.image))); 28 | } 29 | return List.of(); 30 | } 31 | 32 | @Override 33 | public final List buildArguments(final ShellOptions options) { 34 | if (Strings.isNotBlank(options.containerfile)) { 35 | return flatten( 36 | of(name()), 37 | fromList(OSSupport.expand(options.runtimeOptions)), 38 | of("build", "--file", options.containerfile), 39 | fromList(OSSupport.expand(options.runtimeBuildOptions)), 40 | maybe(options.pull, "--pull"), 41 | of("--tag", OSSupport.expand(options.image)), 42 | of(OSSupport.expand(options.context))); 43 | } 44 | return List.of(); 45 | } 46 | 47 | @Override 48 | public final List runArguments(final ShellOptions options) { 49 | final var currentDir = System.getProperty("user.dir"); 50 | final var workingDir = Optional.ofNullable(options.workingDir) 51 | .filter(Strings::isNotBlank) 52 | .orElse(currentDir); 53 | final var projectDir = maybe(options.mountProjectDir, 54 | "--volume", currentDir + ":" + workingDir + ":z"); 55 | return flatten( 56 | of(name()), 57 | fromList(OSSupport.expand(options.runtimeOptions)), 58 | of("run", "--rm"), 59 | fromList(OSSupport.expand(options.runtimeRunOptions)), 60 | projectDir, 61 | of("--workdir", workingDir), 62 | maybe(options.interactive, "--interactive", "--tty"), 63 | of("--env", "ILO_CONTAINER=true"), 64 | withPrefix("--env", OSSupport.expand(options.variables)), 65 | optional("--hostname", OSSupport.expand(options.hostname)), 66 | withPrefix("--publish", OSSupport.expand(options.ports)), 67 | withPrefix("--volume", options.missingVolumes.handleLocalDirectories(OSSupport.expand(options.volumes))), 68 | of(OSSupport.expand(options.image)), 69 | fromList(OSSupport.expand(options.commands))); 70 | } 71 | 72 | @Override 73 | public final List cleanupArguments(final ShellOptions options) { 74 | if (options.removeImage) { 75 | return flatten( 76 | of(name()), 77 | fromList(OSSupport.expand(options.runtimeOptions)), 78 | of("rmi"), 79 | fromList(OSSupport.expand(options.runtimeCleanupOptions)), 80 | of(OSSupport.expand(options.image))); 81 | } 82 | return List.of(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /docs/themes/metio/static/images/matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 55 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /docs/themes/metio/assets/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | html { 3 | line-height: 1.15; /* 1 */ 4 | -webkit-text-size-adjust: 100%; /* 2 */ 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } 10 | 11 | main { 12 | display: block; 13 | } 14 | 15 | h1 { 16 | font-size: 2em; 17 | margin: 0.67em 0; 18 | } 19 | 20 | hr { 21 | box-sizing: content-box; /* 1 */ 22 | height: 0; /* 1 */ 23 | overflow: visible; /* 2 */ 24 | } 25 | 26 | pre { 27 | font-family: monospace, monospace; /* 1 */ 28 | font-size: 1em; /* 2 */ 29 | } 30 | 31 | a { 32 | background-color: transparent; 33 | } 34 | 35 | abbr[title] { 36 | border-bottom: none; /* 1 */ 37 | text-decoration: underline; /* 2 */ 38 | text-decoration: underline dotted; /* 2 */ 39 | } 40 | 41 | b, 42 | strong { 43 | font-weight: bolder; 44 | } 45 | 46 | code, 47 | kbd, 48 | samp { 49 | font-family: monospace, monospace; /* 1 */ 50 | font-size: 1em; /* 2 */ 51 | } 52 | 53 | small { 54 | font-size: 80%; 55 | } 56 | 57 | sub, 58 | sup { 59 | font-size: 75%; 60 | line-height: 0; 61 | position: relative; 62 | vertical-align: baseline; 63 | } 64 | 65 | sub { 66 | bottom: -0.25em; 67 | } 68 | 69 | sup { 70 | top: -0.5em; 71 | } 72 | 73 | img { 74 | border-style: none; 75 | } 76 | 77 | button, 78 | input, 79 | optgroup, 80 | select, 81 | textarea { 82 | font-family: inherit; /* 1 */ 83 | font-size: 100%; /* 1 */ 84 | line-height: 1.15; /* 1 */ 85 | margin: 0; /* 2 */ 86 | } 87 | 88 | button, 89 | input { /* 1 */ 90 | overflow: visible; 91 | } 92 | 93 | button, 94 | select { /* 1 */ 95 | text-transform: none; 96 | } 97 | 98 | button, 99 | [type="button"], 100 | [type="reset"], 101 | [type="submit"] { 102 | -webkit-appearance: button; 103 | } 104 | 105 | button::-moz-focus-inner, 106 | [type="button"]::-moz-focus-inner, 107 | [type="reset"]::-moz-focus-inner, 108 | [type="submit"]::-moz-focus-inner { 109 | border-style: none; 110 | padding: 0; 111 | } 112 | 113 | button:-moz-focusring, 114 | [type="button"]:-moz-focusring, 115 | [type="reset"]:-moz-focusring, 116 | [type="submit"]:-moz-focusring { 117 | outline: 1px dotted ButtonText; 118 | } 119 | 120 | fieldset { 121 | padding: 0.35em 0.75em 0.625em; 122 | } 123 | 124 | legend { 125 | box-sizing: border-box; /* 1 */ 126 | color: inherit; /* 2 */ 127 | display: table; /* 1 */ 128 | max-width: 100%; /* 1 */ 129 | padding: 0; /* 3 */ 130 | white-space: normal; /* 1 */ 131 | } 132 | 133 | progress { 134 | vertical-align: baseline; 135 | } 136 | 137 | textarea { 138 | overflow: auto; 139 | } 140 | 141 | [type="checkbox"], 142 | [type="radio"] { 143 | box-sizing: border-box; /* 1 */ 144 | padding: 0; /* 2 */ 145 | } 146 | 147 | [type="number"]::-webkit-inner-spin-button, 148 | [type="number"]::-webkit-outer-spin-button { 149 | height: auto; 150 | } 151 | 152 | [type="search"] { 153 | -webkit-appearance: textfield; /* 1 */ 154 | outline-offset: -2px; /* 2 */ 155 | } 156 | 157 | [type="search"]::-webkit-search-decoration { 158 | -webkit-appearance: none; 159 | } 160 | 161 | ::-webkit-file-upload-button { 162 | -webkit-appearance: button; /* 1 */ 163 | font: inherit; /* 2 */ 164 | } 165 | 166 | details { 167 | display: block; 168 | } 169 | 170 | summary { 171 | display: list-item; 172 | } 173 | 174 | template { 175 | display: none; 176 | } 177 | 178 | [hidden] { 179 | display: none; 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/cli/Executables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.cli; 7 | 8 | import wtf.metio.ilo.errors.*; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.regex.Pattern; 20 | import java.util.stream.Stream; 21 | 22 | /** 23 | * Utility class that interacts with executables found on the host machine. 24 | */ 25 | public final class Executables { 26 | 27 | /** 28 | * Resolves a tool by its name from the current $PATH. To match shell behavior, the first match will be returned. 29 | * Thus make sure to order your $PATH so that your preferred location will be picked first. 30 | * 31 | * @param tool The name of the tool to look up. 32 | * @return The path to the tool or an empty optional. 33 | */ 34 | public static Optional of(final String tool) { 35 | return allPaths().map(path -> path.resolve(tool)) 36 | .filter(Executables::canExecute) 37 | .findFirst(); 38 | } 39 | 40 | // visible for testing 41 | static Stream allPaths() { 42 | return Stream.of(System.getenv("PATH") 43 | .split(Pattern.quote(File.pathSeparator))) 44 | .map(Paths::get); 45 | } 46 | 47 | // visible for testing 48 | static boolean canExecute(final Path binary) { 49 | return Files.exists(binary) && Files.isExecutable(binary); 50 | } 51 | 52 | public static int runAndWaitForExit(final List arguments, final boolean debug) { 53 | if (null == arguments || arguments.isEmpty()) { 54 | return 0; 55 | } 56 | if (debug) { 57 | System.out.println("ilo executes: " + String.join(" ", arguments)); 58 | } 59 | try { 60 | return new ProcessBuilder(arguments).inheritIO().start().waitFor(); 61 | } catch (final InterruptedException exception) { 62 | throw new UnexpectedInterruptionException(exception); 63 | } catch (final UnsupportedOperationException exception) { 64 | throw new OperatingSystemNotSupportedException(exception); 65 | } catch (final NullPointerException exception) { 66 | throw new CommandListContainsNullException(exception, arguments); 67 | } catch (final IndexOutOfBoundsException exception) { 68 | throw new CommandListIsEmptyException(exception); 69 | } catch (final SecurityException exception) { 70 | throw new SecurityManagerDeniesAccessException(exception); 71 | } catch (final IOException exception) { 72 | throw new RuntimeIOException(exception); 73 | } 74 | } 75 | 76 | public static String runAndReadOutput(final String... arguments) { 77 | try { 78 | final var processBuilder = new ProcessBuilder(arguments); 79 | final var process = processBuilder.start(); 80 | try (final var reader = new InputStreamReader(process.getInputStream()); 81 | final var buffer = new BufferedReader(reader)) { 82 | final var builder = new StringBuilder(); 83 | String line; 84 | while (null != (line = buffer.readLine())) { 85 | builder.append(line); 86 | builder.append(System.lineSeparator()); 87 | } 88 | return builder.toString().strip(); 89 | } 90 | } catch (final IOException exception) { 91 | throw new RuntimeIOException(exception); 92 | } 93 | } 94 | 95 | private Executables() { 96 | // utility class 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/cli/ExecutablesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.cli; 7 | 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.condition.EnabledOnOs; 11 | import org.junit.jupiter.api.condition.OS; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; 14 | import uk.org.webcompere.systemstubs.stream.SystemOut; 15 | 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.util.List; 19 | 20 | import static org.junit.jupiter.api.Assertions.*; 21 | 22 | @ExtendWith(SystemStubsExtension.class) 23 | class ExecutablesTest { 24 | 25 | @Test 26 | @EnabledOnOs({OS.LINUX, OS.MAC}) 27 | @DisplayName("Should detect tool in PATH") 28 | void shouldDetectToolInPath() { 29 | // given 30 | final var tool = "ls"; 31 | 32 | // when 33 | final var path = Executables.of(tool); 34 | 35 | // then 36 | assertTrue(path.isPresent()); 37 | } 38 | 39 | @Test 40 | @DisplayName("Should detect missing tool in PATH") 41 | void shouldHandleMissingTool() { 42 | // given 43 | final var tool = "fgsdfgsdlgdjlgkjsdlfgjskdfgjsldfjgdflg"; 44 | 45 | // when 46 | final var path = Executables.of(tool); 47 | 48 | // then 49 | assertTrue(path.isEmpty()); 50 | } 51 | 52 | @Test 53 | @EnabledOnOs({OS.LINUX, OS.MAC}) 54 | void shouldBeAbleToExecuteLs() { 55 | // given 56 | final var tool = Executables.allPaths() 57 | .map(path -> path.resolve("ls")) 58 | .filter(Files::exists) 59 | .findFirst() 60 | .orElseThrow(); 61 | 62 | // when 63 | final var canExecute = Executables.canExecute(tool); 64 | 65 | // then 66 | assertTrue(canExecute); 67 | } 68 | 69 | @Test 70 | void shouldNotBeAbleToExecuteMissing() { 71 | // given 72 | final var tool = Paths.get("asdfasdfasadaggfksdjfgsdfglsdfglsfg"); 73 | 74 | // when 75 | final var canExecute = Executables.canExecute(tool); 76 | 77 | // then 78 | assertFalse(canExecute); 79 | } 80 | 81 | @Test 82 | @EnabledOnOs({OS.LINUX, OS.MAC}) 83 | void shouldNotBeAbleToExecuteTextFile() { 84 | // given 85 | final var tool = Paths.get("/etc/os-release"); 86 | 87 | // when 88 | final var canExecute = Executables.canExecute(tool); 89 | 90 | // then 91 | assertFalse(canExecute); 92 | } 93 | 94 | @Test 95 | @EnabledOnOs({OS.LINUX, OS.MAC}) 96 | @DisplayName("waits until tool exits") 97 | void shouldWaitForExit() { 98 | // given 99 | final var tool = "ls"; 100 | 101 | // when 102 | final var exitCode = Executables.runAndWaitForExit(List.of(tool), false); 103 | 104 | // then 105 | assertEquals(0, exitCode); 106 | } 107 | 108 | @Test 109 | @EnabledOnOs({OS.LINUX, OS.MAC}) 110 | @DisplayName("returns exit code on failures") 111 | void shouldReturnNonZeroExitCode() { 112 | // given 113 | final var arguments = List.of("ls", "--unknown"); 114 | 115 | // when 116 | final var exitCode = Executables.runAndWaitForExit(arguments, false); 117 | 118 | // then 119 | assertTrue(0 < exitCode); 120 | } 121 | 122 | @Test 123 | @EnabledOnOs({OS.LINUX, OS.MAC}) 124 | @DisplayName("writes debug message to system.out") 125 | void shouldWriteDebugMessageToSystemOut(final SystemOut systemOut) { 126 | // given 127 | final var tool = "ls"; 128 | 129 | // when 130 | Executables.runAndWaitForExit(List.of(tool), true); 131 | 132 | // then 133 | assertEquals("ilo executes: ls\n", systemOut.getText()); 134 | } 135 | 136 | @Test 137 | @DisplayName("ignores empty lists") 138 | void shouldNoExecuteEmptyList() { 139 | // given 140 | final List arguments = List.of(); 141 | 142 | // when 143 | final var exitCode = Executables.runAndWaitForExit(arguments, false); 144 | 145 | // then 146 | assertEquals(0, exitCode); 147 | } 148 | 149 | @Test 150 | @DisplayName("ignores null lists") 151 | void shouldNoExecuteNullList() { 152 | // given 153 | final List arguments = null; 154 | 155 | // when 156 | final var exitCode = Executables.runAndWaitForExit(arguments, false); 157 | 158 | // then 159 | assertEquals(0, exitCode); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/shell/ShellVolumeBehaviorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import com.google.common.jimfs.Configuration; 9 | import com.google.common.jimfs.Jimfs; 10 | import org.junit.jupiter.api.DisplayName; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.params.ParameterizedTest; 13 | import org.junit.jupiter.params.provider.ValueSource; 14 | import wtf.metio.ilo.errors.LocalDirectoryDoesNotExistException; 15 | 16 | import java.io.IOException; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | 20 | import static org.junit.jupiter.api.Assertions.*; 21 | 22 | @DisplayName("ShellVolumeBehavior") 23 | class ShellVolumeBehaviorTest { 24 | 25 | @ParameterizedTest 26 | @DisplayName("defines behavior") 27 | @ValueSource(strings = { 28 | "CREATE", 29 | "WARN", 30 | "ERROR" 31 | }) 32 | void shouldHaveBehavior(final String runtime) { 33 | assertNotNull(ShellVolumeBehavior.valueOf(runtime)); 34 | } 35 | 36 | @Test 37 | @DisplayName("CREATE: handle missing") 38 | void shouldCreateMissingDirectory() { 39 | final var directory = localDirectory("/missing"); 40 | final var ok = ShellVolumeBehavior.CREATE.handleMissingDirectory(directory); 41 | assertAll( 42 | () -> assertTrue(ok, "Missing directory could not be created"), 43 | () -> assertTrue(Files.exists(directory), "Directory was not actually created")); 44 | } 45 | 46 | @Test 47 | @DisplayName("CREATE: handle existing") 48 | void shouldIgnoreExistingDirectory() throws IOException { 49 | final var directory = localDirectory("/existing"); 50 | Files.createDirectory(directory); 51 | final var ok = ShellVolumeBehavior.CREATE.handleMissingDirectory(directory); 52 | assertTrue(ok, "Existing directory was not ignored"); 53 | } 54 | 55 | @Test 56 | @DisplayName("WARN: handle missing") 57 | void shouldWarnOnMissingDirectory() { 58 | final var directory = localDirectory("/missing"); 59 | final var ok = ShellVolumeBehavior.WARN.handleMissingDirectory(directory); 60 | assertAll( 61 | () -> assertFalse(ok, "No warning for missing directory"), 62 | () -> assertTrue(Files.notExists(directory), "Directory was actually created")); 63 | } 64 | 65 | @Test 66 | @DisplayName("WARN: handle existing") 67 | void shouldIgnoreExistingDirectoryWarn() throws IOException { 68 | final var directory = localDirectory("/existing"); 69 | Files.createDirectory(directory); 70 | final var ok = ShellVolumeBehavior.WARN.handleMissingDirectory(directory); 71 | assertTrue(ok, "Existing directory was not ignored"); 72 | } 73 | 74 | @Test 75 | @DisplayName("ERROR: handle missing") 76 | void shouldThrowOnMissingDirectory() { 77 | final var directory = localDirectory("/missing"); 78 | assertAll( 79 | () -> assertThrows(LocalDirectoryDoesNotExistException.class, 80 | () -> ShellVolumeBehavior.ERROR.handleMissingDirectory(directory)), 81 | () -> assertTrue(Files.notExists(directory), "Directory was actually created")); 82 | } 83 | 84 | @Test 85 | @DisplayName("ERROR: handle existing") 86 | void shouldIgnoreExistingDirectoryError() throws IOException { 87 | final var directory = localDirectory("/existing"); 88 | Files.createDirectory(directory); 89 | final var ok = ShellVolumeBehavior.ERROR.handleMissingDirectory(directory); 90 | assertTrue(ok, "Existing directory was not ignored"); 91 | } 92 | 93 | @Test 94 | @DisplayName("extract local directory") 95 | void shouldExtractLocalDirectory() { 96 | final var mount = "/local/directory:/container/directory"; 97 | final var localPart = ShellVolumeBehavior.extractLocalPart(mount); 98 | assertEquals("/local/directory", localPart); 99 | } 100 | 101 | @Test 102 | @DisplayName("extract local directory in a mount directive using a SEL label") 103 | void shouldExtractLocalDirectoryWithSelLabel() { 104 | final var mount = "/local/directory:/container/directory:Z"; 105 | final var localPart = ShellVolumeBehavior.extractLocalPart(mount); 106 | assertEquals("/local/directory", localPart); 107 | } 108 | 109 | @Test 110 | @DisplayName("extract local directory in a mount directive without a container path") 111 | void shouldExtractLocalDirectoryWithoutContainerPath() { 112 | final var mount = "/local/directory"; 113 | final var localPart = ShellVolumeBehavior.extractLocalPart(mount); 114 | assertEquals("/local/directory", localPart); 115 | } 116 | 117 | private Path localDirectory(final String directory) { 118 | final var fs = Jimfs.newFileSystem(Configuration.unix()); 119 | return fs.getPath(directory); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/os/PowerShellTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.os; 7 | 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Nested; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.condition.EnabledOnOs; 13 | import org.junit.jupiter.api.condition.OS; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | import static wtf.metio.ilo.os.ParameterExpansion.MATCHER_GROUP_NAME; 17 | 18 | @DisplayName("PowerShell") 19 | class PowerShellTest { 20 | 21 | @Nested 22 | @DisplayName("regex") 23 | class Regex { 24 | 25 | @Test 26 | @DisplayName("regex for command") 27 | void regexMatchesCommand() { 28 | final var matcher = PowerShell.COMMAND_PATTERN.matcher("$(some-command --with-option)"); 29 | assertAll("new style", 30 | () -> assertTrue(matcher.find(), "matches"), 31 | () -> assertEquals("some-command --with-option", matcher.group(MATCHER_GROUP_NAME), "extraction")); 32 | } 33 | 34 | @Test 35 | @DisplayName("regex for commands") 36 | void regexMatchesCommands() { 37 | final var matcher = PowerShell.COMMAND_PATTERN.matcher("$(some-command --with-option):$(other --option)"); 38 | assertAll("new style", 39 | () -> assertTrue(matcher.find(), "first match"), 40 | () -> assertEquals("some-command --with-option", matcher.group(MATCHER_GROUP_NAME), "first extraction"), 41 | () -> assertTrue(matcher.find(), "second match"), 42 | () -> assertEquals("other --option", matcher.group(MATCHER_GROUP_NAME), "second extraction")); 43 | } 44 | 45 | @Test 46 | @DisplayName("regex for parameter") 47 | void regexMatchesParameter() { 48 | final var matcher = PowerShell.PARAMETER_PATTERN.matcher("$HOME"); 49 | assertAll("new style", 50 | () -> assertTrue(matcher.find(), "matches"), 51 | () -> assertEquals("$HOME", matcher.group(MATCHER_GROUP_NAME), "extraction")); 52 | } 53 | 54 | @Test 55 | @DisplayName("regex for parameters") 56 | void regexMatchesParameters() { 57 | final var matcher = PowerShell.PARAMETER_PATTERN.matcher("$HOME:$OTHER"); 58 | assertAll("new style", 59 | () -> assertTrue(matcher.find(), "first matches"), 60 | () -> assertEquals("$HOME", matcher.group(MATCHER_GROUP_NAME), "first extraction"), 61 | () -> assertTrue(matcher.find(), "second matches"), 62 | () -> assertEquals("$OTHER", matcher.group(MATCHER_GROUP_NAME), "second extraction")); 63 | } 64 | 65 | } 66 | 67 | @Nested 68 | @DisplayName("expansion") 69 | @EnabledOnOs({OS.WINDOWS}) 70 | class Expansion { 71 | 72 | private ParameterExpansion powerShell; 73 | 74 | @BeforeEach 75 | void setUp() { 76 | powerShell = OSSupport.powerShell().orElseThrow(); 77 | } 78 | 79 | @Test 80 | @DisplayName("replaces parameter") 81 | void replacesParameter() { 82 | final var result = powerShell.replace("$HOME:abc", input -> "test", PowerShell.PARAMETER_PATTERN); 83 | assertEquals("test:abc", result); 84 | } 85 | 86 | @Test 87 | @DisplayName("replaces command") 88 | void replacesCommandWithNewStyle() { 89 | final var result = powerShell.replace("$(id -u):abc", input -> "test", PowerShell.COMMAND_PATTERN); 90 | assertEquals("test:abc", result); 91 | } 92 | 93 | @Test 94 | @DisplayName("replaces commands") 95 | void replacesCommandsWithNewStyle() { 96 | final var result = powerShell.replace("$(id -u):$(id -u)", input -> "test", PowerShell.COMMAND_PATTERN); 97 | assertEquals("test:test", result); 98 | } 99 | 100 | @Test 101 | @DisplayName("keeps constants as-is in parameters") 102 | void keepConstantsInParameters() { 103 | assertEquals("1000:1000", powerShell.expandParameters("1000:1000")); 104 | } 105 | 106 | @Test 107 | @DisplayName("keeps constants as-is in commands") 108 | void keepConstantsInCommands() { 109 | assertEquals("1000:1000", powerShell.substituteCommands("1000:1000")); 110 | } 111 | 112 | @Test 113 | @DisplayName("substitutes commands with their results") 114 | void substituteCommands() { 115 | assertEquals("hello:world", powerShell.substituteCommands("$(echo hello):$(echo world)")); 116 | } 117 | 118 | @Test 119 | @DisplayName("substitutes command with its result and keeps constant") 120 | void substituteCommandAndKeepConstant() { 121 | assertEquals("hello:1234", powerShell.substituteCommands("$(echo hello):1234")); 122 | } 123 | 124 | @Test 125 | @DisplayName("substitutes command with its result and keeps constant") 126 | void keepConstantAndSubstituteCommand() { 127 | assertEquals("1234:world", powerShell.substituteCommands("1234:$(echo world)")); 128 | } 129 | 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/wtf/metio/ilo/shell/ShellOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo.shell; 7 | 8 | import picocli.CommandLine; 9 | import wtf.metio.ilo.model.Options; 10 | 11 | import java.util.List; 12 | 13 | public final class ShellOptions implements Options { 14 | 15 | @CommandLine.Option( 16 | names = {"--runtime"}, 17 | description = "Specify the runtime to use. If none is specified, use auto-selection.", 18 | converter = ShellRuntimeConverter.class 19 | ) 20 | public ShellRuntime runtime; 21 | 22 | @CommandLine.Option( 23 | names = {"--debug"}, 24 | description = "Show additional debug information." 25 | ) 26 | public boolean debug; 27 | 28 | @CommandLine.Option( 29 | names = {"--pull"}, 30 | description = "Pull image before opening shell." 31 | ) 32 | public boolean pull; 33 | 34 | @CommandLine.Option( 35 | names = {"--interactive"}, 36 | description = "Open interactive shell or just run a single command.", 37 | defaultValue = "true", 38 | fallbackValue = "true", 39 | negatable = true 40 | ) 41 | public boolean interactive; 42 | 43 | @CommandLine.Option( 44 | names = {"--mount-project-dir"}, 45 | description = "Mount the project directory into the running container. Container path will be the same as --working-dir.", 46 | defaultValue = "true", 47 | fallbackValue = "true", 48 | negatable = true 49 | ) 50 | public boolean mountProjectDir; 51 | 52 | @CommandLine.Option( 53 | names = {"--working-dir"}, 54 | description = "The directory in the container to use. If not specified, defaults to the current directory." 55 | ) 56 | public String workingDir; 57 | 58 | @CommandLine.Option( 59 | names = {"--containerfile", "--dockerfile"}, 60 | description = "The Containerfile to use." 61 | ) 62 | public String containerfile; 63 | 64 | @CommandLine.Option( 65 | names = {"--context"}, 66 | description = "The context to use when building an image.", 67 | defaultValue = "." 68 | ) 69 | public String context; 70 | 71 | @CommandLine.Option( 72 | names = {"--hostname"}, 73 | description = "The hostname of the running container." 74 | ) 75 | public String hostname; 76 | 77 | @CommandLine.Option( 78 | names = {"--remove-image"}, 79 | description = "Remove image after closing the shell." 80 | ) 81 | public boolean removeImage; 82 | 83 | @CommandLine.Option( 84 | names = {"--runtime-option"}, 85 | description = "Options for the selected runtime itself." 86 | ) 87 | public List runtimeOptions; 88 | 89 | @CommandLine.Option( 90 | names = {"--runtime-pull-option"}, 91 | description = "Options for the pull command of the selected runtime." 92 | ) 93 | public List runtimePullOptions; 94 | 95 | @CommandLine.Option( 96 | names = {"--runtime-build-option"}, 97 | description = "Options for the build command of the selected runtime." 98 | ) 99 | public List runtimeBuildOptions; 100 | 101 | @CommandLine.Option( 102 | names = {"--runtime-run-option"}, 103 | description = "Options for the run command of the selected runtime." 104 | ) 105 | public List runtimeRunOptions; 106 | 107 | @CommandLine.Option( 108 | names = {"--runtime-cleanup-option"}, 109 | description = "Options for the cleanup command of the selected runtime." 110 | ) 111 | public List runtimeCleanupOptions; 112 | 113 | @CommandLine.Option( 114 | names = {"--volume"}, 115 | description = "Mount a volume into the container." 116 | ) 117 | public List volumes; 118 | 119 | @CommandLine.Option( 120 | names = {"--missing-volumes"}, 121 | description = "Specifies how missing local volume directories should be handles. Valid values: ${COMPLETION-CANDIDATES}", 122 | defaultValue = "CREATE" 123 | ) 124 | public ShellVolumeBehavior missingVolumes; 125 | 126 | @CommandLine.Option( 127 | names = {"--env"}, 128 | description = "Specify a environment variable for the container." 129 | ) 130 | public List variables; 131 | 132 | @CommandLine.Option( 133 | names = {"--publish"}, 134 | description = "Publish container ports to the host system." 135 | ) 136 | public List ports; 137 | 138 | @CommandLine.Parameters( 139 | index = "0", 140 | description = "The OCI image to use. In case --containerfile or --dockerfile is given as well, this defines the name of the resulting image.", 141 | defaultValue = "fedora:latest" 142 | ) 143 | public String image; 144 | 145 | @CommandLine.Parameters( 146 | index = "1..*", 147 | description = "Command and its option(s) to run inside the container. Overwrites the command specified in the image." 148 | ) 149 | public List commands; 150 | 151 | @Override 152 | public boolean debug() { 153 | return debug; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/test/java/wtf/metio/ilo/PicocliBooleanTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: The ilo Authors 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | package wtf.metio.ilo; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import picocli.CommandLine; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class PicocliBooleanTest { 14 | 15 | @CommandLine.Command 16 | static class TestCommand implements Runnable { 17 | 18 | @CommandLine.Option( 19 | names = {"--no-interactive"}, 20 | defaultValue = "true", 21 | negatable = true 22 | ) 23 | public boolean interactive; 24 | 25 | @CommandLine.Option( 26 | names = {"--no-autonomous"}, 27 | defaultValue = "false", 28 | negatable = true 29 | ) 30 | public boolean autonomous; 31 | 32 | @CommandLine.Option( 33 | names = {"--daemon"}, 34 | defaultValue = "true", 35 | negatable = true 36 | ) 37 | public boolean daemon; 38 | 39 | @CommandLine.Option( 40 | names = {"--human"}, 41 | defaultValue = "false", 42 | negatable = true 43 | ) 44 | public boolean human; 45 | 46 | @CommandLine.Option( 47 | names = {"--wanted"}, 48 | defaultValue = "true", 49 | fallbackValue = "true", 50 | negatable = true 51 | ) 52 | public boolean wanted; 53 | 54 | @Override 55 | public void run() { 56 | } 57 | 58 | } 59 | 60 | @Test 61 | public void optionNotSpecified() { 62 | final var command = new TestCommand(); 63 | new CommandLine(command).parseArgs(); 64 | assertAll( 65 | () -> assertTrue(command.interactive, "interactive"), 66 | () -> assertFalse(command.autonomous, "autonomous"), 67 | () -> assertTrue(command.daemon, "daemon"), 68 | () -> assertFalse(command.human, "human"), 69 | () -> assertTrue(command.wanted, "wanted") 70 | ); 71 | } 72 | 73 | @Test 74 | public void optionSpecifiedWithoutValue() { 75 | final var command = new TestCommand(); 76 | new CommandLine(command).parseArgs("--interactive", "--autonomous", "--daemon", "--human", "--wanted"); 77 | assertAll( 78 | () -> assertTrue(command.interactive, "interactive"), 79 | () -> assertFalse(command.autonomous, "autonomous"), 80 | () -> assertFalse(command.daemon, "daemon"), 81 | () -> assertTrue(command.human, "human"), 82 | () -> assertTrue(command.wanted, "wanted") 83 | ); 84 | } 85 | 86 | @Test 87 | public void optionSpecifiedWithBooleanTrue() { 88 | final var command = new TestCommand(); 89 | new CommandLine(command).parseArgs("--interactive=true", "--autonomous=true", "--daemon=true", "--human=true", "--wanted=true"); 90 | assertAll( 91 | () -> assertFalse(command.interactive, "interactive"), 92 | () -> assertFalse(command.autonomous, "autonomous"), 93 | () -> assertTrue(command.daemon, "daemon"), 94 | () -> assertTrue(command.human, "human"), 95 | () -> assertTrue(command.wanted, "wanted") 96 | ); 97 | } 98 | 99 | @Test 100 | public void optionSpecifiedWithBooleanFalse() { 101 | final var command = new TestCommand(); 102 | new CommandLine(command).parseArgs("--interactive=false", "--autonomous=false", "--daemon=false", "--human=false", "--wanted=false"); 103 | assertAll( 104 | () -> assertTrue(command.interactive, "interactive"), 105 | () -> assertTrue(command.autonomous, "autonomous"), 106 | () -> assertFalse(command.daemon, "daemon"), 107 | () -> assertFalse(command.human, "human"), 108 | () -> assertFalse(command.wanted, "wanted") 109 | ); 110 | } 111 | 112 | @Test 113 | public void optionSpecifiedWithNegatedForm() { 114 | final var command = new TestCommand(); 115 | new CommandLine(command).parseArgs("--no-interactive", "--no-autonomous", "--no-daemon", "--no-human", "--no-wanted"); 116 | assertAll( 117 | () -> assertFalse(command.interactive, "interactive"), 118 | () -> assertTrue(command.autonomous, "autonomous"), 119 | () -> assertTrue(command.daemon, "daemon"), 120 | () -> assertFalse(command.human, "human"), 121 | () -> assertFalse(command.wanted, "wanted") 122 | ); 123 | } 124 | 125 | @Test 126 | public void optionSpecifiedWithNegatedFormUsingBooleanTrue() { 127 | final var command = new TestCommand(); 128 | new CommandLine(command).parseArgs("--no-interactive=true", "--no-autonomous=true", "--no-daemon=true", "--no-human=true", "--no-wanted=true"); 129 | assertAll( 130 | () -> assertTrue(command.interactive, "interactive"), 131 | () -> assertTrue(command.autonomous, "autonomous"), 132 | () -> assertFalse(command.daemon, "daemon"), 133 | () -> assertFalse(command.human, "human"), 134 | () -> assertFalse(command.wanted, "wanted") 135 | ); 136 | } 137 | 138 | @Test 139 | public void optionSpecifiedWithNegatedFormUsingBooleanFalse() { 140 | final var command = new TestCommand(); 141 | new CommandLine(command).parseArgs("--no-interactive=false", "--no-autonomous=false", "--no-daemon=false", "--no-human=false", "--no-wanted=false"); 142 | assertAll( 143 | () -> assertFalse(command.interactive, "interactive"), 144 | () -> assertFalse(command.autonomous, "autonomous"), 145 | () -> assertTrue(command.daemon, "daemon"), 146 | () -> assertTrue(command.human, "human"), 147 | () -> assertTrue(command.wanted, "wanted") 148 | ); 149 | } 150 | 151 | } 152 | --------------------------------------------------------------------------------