├── .env ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── CLI_REFERENCE.md ├── DEV.md ├── LICENSE ├── README.md ├── apps ├── alpine │ ├── Dockerfile │ └── build.sh ├── ansible │ └── init.sh ├── aws │ └── README.md ├── az │ └── README.md ├── doctl │ ├── README.md │ └── build.sh ├── dolt │ └── build.sh ├── dotnet │ └── README.md ├── gh │ ├── README.md │ ├── build.sh │ └── init.sh ├── mkdocs │ └── README.md ├── mssql │ └── README.md ├── npm │ └── README.md ├── pip │ └── Dockerfile ├── postgres │ └── README.md ├── protoc │ └── Dockerfile ├── s3cmd │ ├── Dockerfile │ └── README.md └── scrapy │ └── Dockerfile ├── bin ├── dcx.bat ├── dockerized ├── dockerized.bat └── dockerized.ps1 ├── build └── .gitkeep ├── docker-compose.yml ├── dockerized-banner.png ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── pkg ├── dockerized.go ├── help │ └── help.go ├── options.go └── util │ └── util.go ├── release.config.js ├── terminalizer.gif └── terminalizer.yml /.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PATH_SEPARATOR=";" 2 | COMPOSE_FILE="${DOCKERIZED_ROOT}/docker-compose.yml" 3 | DEFAULT_ARCH=x86_64 4 | ALPINE_VERSION=3.14.2 5 | ANSIBLE_VERSION=2.12 6 | ANSIBLE_BASE=alpine-3.15 7 | AB_VERSION=latest 8 | AWS_VERSION=2.4.24 9 | AZ_VERSION=2.34.1 10 | COMPOSER_VERSION=2.3 11 | DEFAULT_BASE_IMAGE=alpine 12 | DEFAULT_BASE_IMAGE_VERSION=${ALPINE_VERSION} 13 | DEFAULT_BASE=${DEFAULT_BASE_IMAGE}:${DEFAULT_BASE_IMAGE_VERSION} 14 | DOCTL_VERSION=1.70.0 15 | DOLT_VERSION=0.37.9 16 | DOTNET_VERSION=6.0 17 | GIT_VERSION=2.32.0 18 | GH_VERSION=2.5.2 19 | GO_VERSION=1.17.8 20 | HASKELL_VERSION=9.2.2 21 | GHCI_VERSION=${HASKELL_VERSION} 22 | HELM_VERSION=3.8.1 23 | HTTPIE_VERSION=2.6.0 24 | HTTP_VERSION=${HTTPIE_VERSION} 25 | JAVA_VERSION=17.0.2 26 | LATEX_ALPINE_VERSION=${ALPINE_VERSION} 27 | LUA_IMAGE=nickblah/lua 28 | LUA_VERSION=5.4.3 29 | MKDOCS_VERSION=1.16.0 30 | MKDOCS_PACKAGES="mkdocs-material mkdocs-material-extensions" 31 | MSSQL_VERSION=2022-latest 32 | SA_PASSWORD=Dockerized1 33 | MYSQL_VERSION=8.0.28 34 | NODE_VERSION=17.7.2 35 | PERL_VERSION=5.34.1 36 | PHP_VERSION=8.1.3 37 | PROTOC_VERSION=3.9.1 38 | PROTOC_ARCH=${DEFAULT_ARCH} 39 | PROTOC_BASE=${DEFAULT_BASE} 40 | POSTGRES_VERSION=14.2 41 | PYTHON_VERSION=3.10 42 | PYTHON2_VERSION=2.7.18 43 | RUBY_VERSION=3.1.1 44 | RUST_VERSION=1.59.0 45 | RUSTC_VERSION=${RUST_VERSION} 46 | S3CMD_BASE=python:${PYTHON_VERSION}-${DEFAULT_BASE_IMAGE} 47 | S3CMD_VERSION=2.2.0 48 | SCRAPY_VERSION=2.6.1 49 | SWAGGER_CODEGEN_VERSION=3.0.29 50 | SWIPL_VERSION=8.5.5 51 | TYPESCRIPT_VERSION=4.6.2 52 | TSC_VERSION=${TYPESCRIPT_VERSION} 53 | VUE_VERSION=5.0.1 54 | YOUTUBE_DL_VERSION=2022.03.08.2 55 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | target: 12 | - { name: linux-x86_64, GOOS: linux, GOARCH: amd64 } 13 | - { name: mac-x86_64, GOOS: darwin, GOARCH: amd64 } 14 | - { name: mac-arm64, GOOS: darwin, GOARCH: arm64 } 15 | - { name: win64, GOOS: windows, GOARCH: amd64 } 16 | - { name: win32, GOOS: windows, GOARCH: 386 } 17 | steps: 18 | - uses: actions/checkout@v2 19 | - run: mkdir -p release/${{ matrix.target.name }} 20 | - name: Copy static assets 21 | run: cp -r apps/ .env *.md docker-compose.yml release/${{ matrix.target.name }} 22 | - name: Build 23 | env: 24 | GO_VERSION: 1.17.8 25 | GOOS: ${{ matrix.target.GOOS }} 26 | GOARCH: ${{ matrix.target.GOARCH }} 27 | run: | 28 | bin/dockerized go build -o release/${{ matrix.target.name }}/bin/ . 29 | ls -l release/${{ matrix.target.name }}/bin/dockerized* 30 | - name: Zip 31 | working-directory: release/${{ matrix.target.name }} 32 | run: | 33 | zip -r $GITHUB_WORKSPACE/release/${{ matrix.target.name }}.zip ./ 34 | - uses: actions/upload-artifact@v2 35 | with: 36 | name: release 37 | path: release/*.zip 38 | release: 39 | name: Release 40 | runs-on: ubuntu-latest 41 | needs: 42 | - build 43 | steps: 44 | - uses: actions/setup-node@v1 45 | with: 46 | node-version: 16 47 | - uses: actions/checkout@v2 48 | - uses: actions/download-artifact@v2 49 | with: 50 | name: release 51 | path: release 52 | - name: Release 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | run: npx -p @semantic-release/changelog -p @semantic-release/git -p semantic-release semantic-release -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Compile and Test 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | workflow_dispatch: {} 7 | jobs: 8 | ShellTest: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | command: 13 | - aws 14 | - dotnet 15 | - go 16 | - java 17 | - node 18 | - perl 19 | - php 20 | - python 21 | - ruby 22 | - rustc 23 | - zip 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout Dockerized 27 | uses: actions/checkout@v2 28 | - name: Compile 29 | run: bin/dockerized --compile 30 | - name: "dockerized --shell ${{matrix.command}}" 31 | run: bin/dockerized -v --shell ${{matrix.command}} -c env | tee ~/shell.log 32 | - name: "Assert" 33 | run: | 34 | echo "Test --shell" 35 | grep $(hostname) ~/shell.log 36 | IntegrationTest: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/setup-go@v2 40 | with: 41 | go-version: '1.17.8' 42 | - name: Checkout Dockerized 43 | uses: actions/checkout@v2 44 | - name: go test 45 | run: | 46 | DOCKERIZED_ROOT=$(pwd) go test -p 1 . 47 | CompileAndTest: 48 | runs-on: ${{ matrix.os }} 49 | strategy: 50 | matrix: 51 | os: 52 | - ubuntu-20.04 53 | # - windows-2022 54 | # - macos-10.15 55 | max-parallel: 1 56 | fail-fast: true 57 | steps: 58 | # region mac 59 | - name: Install Docker 60 | if: runner.os == 'macOS' 61 | uses: docker-practice/actions-setup-docker@master 62 | # endregion 63 | 64 | - name: Checkout Dockerized 65 | uses: actions/checkout@v2 66 | with: 67 | path: dockerized 68 | 69 | - run: dockerized/bin/dockerized --help 70 | shell: bash 71 | 72 | - name: "Test: dockerized returns inner exit code" 73 | run: | 74 | # Run a command that returns 100, check that dockerized returns 100 75 | EXITCODE=$(dockerized/bin/dockerized bash -c 'exit 100' &>/dev/null || echo $?) 76 | echo "Exit code: $EXITCODE" 77 | [ $EXITCODE -eq 100 ] 78 | shell: bash 79 | 80 | # region windows 81 | - if: runner.os == 'windows' 82 | name: "dockerized --compile (cmd)" 83 | shell: cmd 84 | run: bin/dockerized --compile 85 | - if: runner.os == 'windows' 86 | name: "dockerized --compile (powershell)" 87 | shell: cmd 88 | run: bin/dockerized --compile 89 | # endregion 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .cache/* 3 | .go/* 4 | test/* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.24.0](https://github.com/datastack-net/dockerized/compare/v2.23.0...v2.24.0) (2022-07-28) 2 | 3 | 4 | ### Features 5 | 6 | * **command:** Add mssql ([2a4962d](https://github.com/datastack-net/dockerized/commit/2a4962d22941f9fc48b41a7514ec577d354547bd)) 7 | 8 | # [2.23.0](https://github.com/datastack-net/dockerized/compare/v2.22.0...v2.23.0) (2022-05-22) 9 | 10 | 11 | ### Features 12 | 13 | * **command:** Add postgres - psql, pg_dump, pg_dumpall ([1df9aaa](https://github.com/datastack-net/dockerized/commit/1df9aaa0141645858f4ed8699c51435eca395fb0)) 14 | 15 | # [2.22.0](https://github.com/datastack-net/dockerized/compare/v2.21.0...v2.22.0) (2022-04-29) 16 | 17 | 18 | ### Features 19 | 20 | * **command:** Add mkdocs ([e84d96c](https://github.com/datastack-net/dockerized/commit/e84d96c86714728d15e186a49fc25fa6dd199b7d)) 21 | 22 | # [2.21.0](https://github.com/datastack-net/dockerized/compare/v2.20.0...v2.21.0) (2022-04-22) 23 | 24 | 25 | ### Features 26 | 27 | * **command:** add scrapy ([2277338](https://github.com/datastack-net/dockerized/commit/2277338082119d8424c9b583a2719fc60ab7e871)) 28 | 29 | # [2.20.0](https://github.com/datastack-net/dockerized/compare/v2.19.4...v2.20.0) (2022-04-22) 30 | 31 | 32 | ### Features 33 | 34 | * add option --entrypoint ([29697b6](https://github.com/datastack-net/dockerized/commit/29697b6c57973e4d99ab2a7c2a4526b3de48b4a7)) 35 | 36 | ## [2.19.4](https://github.com/datastack-net/dockerized/compare/v2.19.3...v2.19.4) (2022-04-22) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * Does not compile in docker under Apple Silicon ([94928c6](https://github.com/datastack-net/dockerized/commit/94928c61477afc08a8bca1d598b31e01f9d5e146)), closes [#33](https://github.com/datastack-net/dockerized/issues/33) 42 | 43 | ## [2.19.3](https://github.com/datastack-net/dockerized/compare/v2.19.2...v2.19.3) (2022-04-20) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * dockerized --compile on Apple Silicon ([c5bf3bf](https://github.com/datastack-net/dockerized/commit/c5bf3bfdb5efa4783e39071588d4082c5193e0da)), closes [#32](https://github.com/datastack-net/dockerized/issues/32) 49 | 50 | ## [2.19.2](https://github.com/datastack-net/dockerized/compare/v2.19.1...v2.19.2) (2022-04-20) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * --shell tests failing due to non-tty environment ([c2f13c4](https://github.com/datastack-net/dockerized/commit/c2f13c4abeae5526e68cae9d615f2410a687c771)), closes [#39](https://github.com/datastack-net/dockerized/issues/39) 56 | 57 | ## [2.19.1](https://github.com/datastack-net/dockerized/compare/v2.19.0...v2.19.1) (2022-04-11) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * run dockerized from dos ([107bea9](https://github.com/datastack-net/dockerized/commit/107bea9d5e269e467988f4557c5ba648b1da9efc)) 63 | 64 | # [2.19.0](https://github.com/datastack-net/dockerized/compare/v2.18.0...v2.19.0) (2022-04-03) 65 | 66 | 67 | ### Features 68 | 69 | * **dockerized:** add -p option to map a port ([#31](https://github.com/datastack-net/dockerized/issues/31)) ([8b2e37e](https://github.com/datastack-net/dockerized/commit/8b2e37eb873cd46e416d4c05d532af23decd5514)) 70 | 71 | # [2.18.0](https://github.com/datastack-net/dockerized/compare/v2.17.0...v2.18.0) (2022-04-03) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * version specification for dolt ([1bc2f79](https://github.com/datastack-net/dockerized/commit/1bc2f799b3b349b1f2eb92c828d2d18ba6c44d40)) 77 | 78 | 79 | ### Features 80 | 81 | * **command:** add mysql ([e1ad799](https://github.com/datastack-net/dockerized/commit/e1ad799983782303d3da742f5312ab7c7310bd96)) 82 | 83 | # [2.17.0](https://github.com/datastack-net/dockerized/compare/v2.16.0...v2.17.0) (2022-04-03) 84 | 85 | 86 | ### Features 87 | 88 | * **command:** add composer ([13bf11c](https://github.com/datastack-net/dockerized/commit/13bf11c3cba54a5aebf39901769b2c2f743891d5)) 89 | * **command:** add dolt ([5851ba2](https://github.com/datastack-net/dockerized/commit/5851ba296d9147c00bbceca7e47c7fc2f9fec182)) 90 | * **command:** add gofmt ([6a54469](https://github.com/datastack-net/dockerized/commit/6a54469c92b9b935d20b9e278bd719f6cc18b81a)) 91 | 92 | # [2.16.0](https://github.com/datastack-net/dockerized/compare/v2.15.3...v2.16.0) (2022-03-31) 93 | 94 | 95 | ### Features 96 | 97 | * Ansible can read ansible.cfg and ssh keys from host ([792f3f2](https://github.com/datastack-net/dockerized/commit/792f3f283814ea6a959b65c787ef97a652ec408a)) 98 | 99 | ## [2.15.3](https://github.com/datastack-net/dockerized/compare/v2.15.2...v2.15.3) (2022-03-30) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * Printing of errors and returning correct exit code ([#28](https://github.com/datastack-net/dockerized/issues/28)) ([47df2fe](https://github.com/datastack-net/dockerized/commit/47df2feabcc102954b6bd8924fce5485e3a9c569)), closes [#27](https://github.com/datastack-net/dockerized/issues/27) 105 | 106 | ## [2.15.2](https://github.com/datastack-net/dockerized/compare/v2.15.1...v2.15.2) (2022-03-28) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * Including Compose Files both globally and locally at the same time ([bcc2d94](https://github.com/datastack-net/dockerized/commit/bcc2d94c9764582086a2c7981ff80fcf1d832f89)) 112 | 113 | ## [2.15.1](https://github.com/datastack-net/dockerized/compare/v2.15.0...v2.15.1) (2022-03-28) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * Include binaries ([c3f1dda](https://github.com/datastack-net/dockerized/commit/c3f1ddac3fe772175b413637e7ca09effc1bdb29)) 119 | 120 | # [2.15.0](https://github.com/datastack-net/dockerized/compare/v2.14.0...v2.15.0) (2022-03-28) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * compile and run the correct binary when sharing source between windows and wsl ([862edb4](https://github.com/datastack-net/dockerized/commit/862edb496dbf3915b9e164d948861ed9634aad67)) 126 | 127 | 128 | ### Features 129 | 130 | * Ability to add or customize commands with additional compose files ([15f2d8b](https://github.com/datastack-net/dockerized/commit/15f2d8b24861c16f1cc60e0a197effd01c67aea9)) 131 | * Add your own commands, or customize the defaults ([e1d73e2](https://github.com/datastack-net/dockerized/commit/e1d73e2cd810abe20ebc7c5ced59d345e2d66943)) 132 | * Logo for Dockerized ([6895b71](https://github.com/datastack-net/dockerized/commit/6895b7154d2dede95b078e075b694b86cc8e0099)) 133 | 134 | # [2.14.0](https://github.com/datastack-net/dockerized/compare/v2.13.0...v2.14.0) (2022-03-25) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * Determine version variable from build.args Value, not Key ([4d5cf86](https://github.com/datastack-net/dockerized/commit/4d5cf86c4b457920b2d66e456679cae1beb76168)) 140 | * Listing versions of vue ([ddb526e](https://github.com/datastack-net/dockerized/commit/ddb526e56d3cb18245625a2056cc572a5f3477e8)) 141 | 142 | 143 | ### Features 144 | 145 | * **command:** Add latex ([04a7d42](https://github.com/datastack-net/dockerized/commit/04a7d420cc3379192f1904d8a65d66649c580daa)) 146 | 147 | # [2.13.0](https://github.com/datastack-net/dockerized/compare/v2.12.0...v2.13.0) (2022-03-23) 148 | 149 | 150 | ### Features 151 | 152 | * Listing versions with :? ([85653d8](https://github.com/datastack-net/dockerized/commit/85653d8dd3bde623249ea659f33aa390b8204563)) 153 | 154 | # [2.12.0](https://github.com/datastack-net/dockerized/compare/v2.11.0...v2.12.0) (2022-03-22) 155 | 156 | 157 | ### Features 158 | 159 | * **command:** Add azure cli ([6e210ef](https://github.com/datastack-net/dockerized/commit/6e210efec7c1afa0d94869653c9d23531b5addbf)) 160 | 161 | # [2.11.0](https://github.com/datastack-net/dockerized/compare/v2.10.0...v2.11.0) (2022-03-21) 162 | 163 | 164 | ### Features 165 | 166 | * Allow specifying the version with command:version [#5](https://github.com/datastack-net/dockerized/issues/5) ([fdb9e16](https://github.com/datastack-net/dockerized/commit/fdb9e16df4a1b54e286b7b365f49341b9412c4c5)) 167 | 168 | # [2.10.0](https://github.com/datastack-net/dockerized/compare/v2.9.0...v2.10.0) (2022-03-21) 169 | 170 | 171 | ### Bug Fixes 172 | 173 | * Strange mounting location ("host/\") when running from root of drive on Windows ([3734cbd](https://github.com/datastack-net/dockerized/commit/3734cbdc5db62c3d248a6b1b1873e87580bd0197)) 174 | 175 | 176 | ### Features 177 | 178 | * **command:** Add psql (postgres) ([7746ee7](https://github.com/datastack-net/dockerized/commit/7746ee7f105975f75216a86ab9e6aadac54526ba)) 179 | * **command:** Add telnet ([e68d188](https://github.com/datastack-net/dockerized/commit/e68d1882f64e850383255fd739124e60d20483e3)) 180 | 181 | # [2.9.0](https://github.com/datastack-net/dockerized/compare/v2.8.0...v2.9.0) (2022-03-21) 182 | 183 | 184 | ### Features 185 | 186 | * **command:** Add ansible, ansible-playbook ([b7e899e](https://github.com/datastack-net/dockerized/commit/b7e899e35bd741f9d9ef09f603574d41986aa1a1)) 187 | 188 | # [2.8.0](https://github.com/datastack-net/dockerized/compare/v2.7.0...v2.8.0) (2022-03-21) 189 | 190 | 191 | ### Features 192 | 193 | * **command:** Add haskell ghci ([eebb729](https://github.com/datastack-net/dockerized/commit/eebb7290eec0217d8f6798f12f8d784b4f241caa)) 194 | 195 | # [2.7.0](https://github.com/datastack-net/dockerized/compare/v2.6.0...v2.7.0) (2022-03-21) 196 | 197 | 198 | ### Features 199 | 200 | * **command:** Add gem ([7ebbfae](https://github.com/datastack-net/dockerized/commit/7ebbfaef74c2ce4d8c48a98bb4b5c14a4df59913)) 201 | * **command:** Add rake ([f7d317c](https://github.com/datastack-net/dockerized/commit/f7d317c4848d2a3dbbdc4f3a602c9a6a588e1888)) 202 | * **command:** Add swipl (Prolog) ([bc0b356](https://github.com/datastack-net/dockerized/commit/bc0b356f7610a60de0fbfebf2e32b095e1772b73)) 203 | 204 | # [2.6.0](https://github.com/datastack-net/dockerized/compare/v2.5.3...v2.6.0) (2022-03-21) 205 | 206 | 207 | ### Bug Fixes 208 | 209 | * fallback mechanism ([23afc0b](https://github.com/datastack-net/dockerized/commit/23afc0bc5d1b22f2a8f703f59929d83145577ac8)) 210 | 211 | 212 | ### Features 213 | 214 | * **command:** Add youtube-dl ([c3f60c0](https://github.com/datastack-net/dockerized/commit/c3f60c0619f60724d757dc5d7c399c4261106cac)) 215 | 216 | ## [2.5.3](https://github.com/datastack-net/dockerized/compare/v2.5.2...v2.5.3) (2022-03-21) 217 | 218 | 219 | ### Bug Fixes 220 | 221 | * tree ([f62a328](https://github.com/datastack-net/dockerized/commit/f62a328222d59641c28a91d1c44d384a276e62bb)) 222 | 223 | ## [2.5.2](https://github.com/datastack-net/dockerized/compare/v2.5.1...v2.5.2) (2022-03-21) 224 | 225 | 226 | ### Bug Fixes 227 | 228 | * running windows binary within wsl2 outside dockerized directory ([0bf888a](https://github.com/datastack-net/dockerized/commit/0bf888a133330b9e460ec35a77a82b8e1154b07c)) 229 | 230 | ## [2.5.1](https://github.com/datastack-net/dockerized/compare/v2.5.0...v2.5.1) (2022-03-21) 231 | 232 | 233 | ### Bug Fixes 234 | 235 | * building commands outside dockerized working directory ([6cd4701](https://github.com/datastack-net/dockerized/commit/6cd4701d37c53903f2827b0935163179bce2cf05)) 236 | 237 | # [2.5.0](https://github.com/datastack-net/dockerized/compare/v2.4.0...v2.5.0) (2022-03-20) 238 | 239 | 240 | ### Features 241 | 242 | * Downloads for Linux, Mac and Windows ([7a5d4a6](https://github.com/datastack-net/dockerized/commit/7a5d4a6ec1729b42c5e03e9bcd97e8a9def06294)) 243 | 244 | # [2.4.0](https://github.com/datastack-net/dockerized/compare/v2.3.0...v2.4.0) (2022-03-20) 245 | 246 | 247 | ### Bug Fixes 248 | 249 | * allow overriding GOARCH for 'go' when running from source ([fd34712](https://github.com/datastack-net/dockerized/commit/fd347121dd53ad6195c9655dcb32da64d35b72b6)) 250 | 251 | 252 | ### Features 253 | 254 | * pass GOARCH to go ([0934e53](https://github.com/datastack-net/dockerized/commit/0934e5355653e68f3ae6599cfb5d78705f5671b5)) 255 | 256 | # [2.3.0](https://github.com/datastack-net/dockerized/compare/v2.2.4...v2.3.0) (2022-03-20) 257 | 258 | 259 | ### Bug Fixes 260 | 261 | * --shell for alpine-based commands ([e3c3e86](https://github.com/datastack-net/dockerized/commit/e3c3e86ecaf4e5d4533ef3a36b556eaf7de0fc1f)) 262 | 263 | 264 | ### Features 265 | 266 | * add 'rust', 'zip' ([07af7d3](https://github.com/datastack-net/dockerized/commit/07af7d3856473e686e262be467008089be83e7b6)) 267 | 268 | ## [2.2.4](https://github.com/datastack-net/dockerized/compare/v2.2.3...v2.2.4) (2022-03-20) 269 | 270 | 271 | ### Bug Fixes 272 | 273 | * include .env file in windows build ([5f7c435](https://github.com/datastack-net/dockerized/commit/5f7c435ce2ed455e28ae983cef4db485a3346299)) 274 | 275 | ## [2.2.3](https://github.com/datastack-net/dockerized/compare/v2.2.2...v2.2.3) (2022-03-20) 276 | 277 | 278 | ### Bug Fixes 279 | 280 | * dockerized overrides GOOS env var, breaking windows build on fresh systems ([21703f6](https://github.com/datastack-net/dockerized/commit/21703f60f2ffe96131f1022884bfa58c59981c33)) 281 | 282 | ## [2.2.2](https://github.com/datastack-net/dockerized/compare/v2.2.1...v2.2.2) (2022-03-20) 283 | 284 | 285 | ### Bug Fixes 286 | 287 | * Windows build not executable on Windows ([528c7d8](https://github.com/datastack-net/dockerized/commit/528c7d83980bb88337245a2ff1dd684c9c3f7366)) 288 | 289 | ## [2.2.1](https://github.com/datastack-net/dockerized/compare/v2.2.0...v2.2.1) (2022-03-20) 290 | 291 | 292 | ### Bug Fixes 293 | 294 | * pre-compiled Windows binary could not run because of missing compose file ([e0fab23](https://github.com/datastack-net/dockerized/commit/e0fab23fe3c9d8f8baccd948bc172c88e15f3482)) 295 | * pre-compiled Windows binary could not run because of missing compose file ([48cbe6a](https://github.com/datastack-net/dockerized/commit/48cbe6a442b501078f0d1227344f86edb145aca3)) 296 | * remove accidentally added services that triggered warnings ([81dc609](https://github.com/datastack-net/dockerized/commit/81dc609d3f478994542470f42744ee0ee0eac655)) 297 | 298 | # [2.2.0](https://github.com/datastack-net/dockerized/compare/v2.1.0...v2.2.0) (2022-03-20) 299 | 300 | 301 | ### Features 302 | 303 | * add 'zip' ([82a753c](https://github.com/datastack-net/dockerized/commit/82a753cee8470bffe2c98707b3bb8f70240a5b39)) 304 | * automatically release windows binaries ([3c73176](https://github.com/datastack-net/dockerized/commit/3c73176840d9127c0d9d96e316040964dd6d7ad2)) 305 | * Pass GOOS env to 'go' command for cross-compilation ([416d14b](https://github.com/datastack-net/dockerized/commit/416d14b6baf8e57d3c80186738a8a74c5262cca5)) 306 | 307 | # [2.1.0](https://github.com/datastack-net/dockerized/compare/v2.0.0...v2.1.0) (2022-03-20) 308 | 309 | 310 | ### Bug Fixes 311 | 312 | * running --shell without arguments ([e9573ee](https://github.com/datastack-net/dockerized/commit/e9573eedc4ce20fb1da8c40f1969f0b81ad7b2ca)) 313 | 314 | 315 | ### Features 316 | 317 | * automatic releases ([ac2629d](https://github.com/datastack-net/dockerized/commit/ac2629da96a7197fdddc79320d75d7db120bae2e)) 318 | -------------------------------------------------------------------------------- /CLI_REFERENCE.md: -------------------------------------------------------------------------------- 1 | # Dockerized CLI Reference 2 | 3 | ```shell 4 | dockerized [options] [:version] [arguments] 5 | ``` 6 | 7 | When running dockerized from source, extra compilation options are available. See [Compilation options](#compilation-options). 8 | 9 | ## Options 10 | 11 | - `--build` — Rebuild the container before running it. 12 | - `--shell` — Start a shell inside the command container. Similar to `docker run --entrypoint=sh`. 13 | - `--entrypoint ` Override the default entrypoint of the command container. 14 | - `-p ` — Exposes given port to host, e.g. `-p 8080`. 15 | - `-p :` — Maps host port to container port, e.g. `-p 80:8080`. 16 | - `-v`, `--verbose` — Log what dockerized is doing. 17 | - `-h`, `--help` — Show this help. 18 | 19 | ## Version 20 | 21 | - `:` — The version of the command to run, e.g. `1`, `1.8`, `1.8.1`. 22 | - `:?`, `:` — List all available versions. E.g. `dockerized go:?` 23 | 24 | ## Arguments 25 | 26 | - All arguments after `` are passed to the command itself. 27 | 28 | ## Compilation options 29 | 30 | When running dockerized from source, there's an extra compilation option available. 31 | It should be the first argument. 32 | 33 | ```shell 34 | dockerized --compile[=docker|host] 35 | ``` 36 | 37 | - `--compile`, `--compile=docker` — Compile dockerized using docker. 38 | - `--compile=host` — Compile dockerized on the host, requires `go` 1.17+ to be installed. 39 | 40 | Example: 41 | 42 | ```shell 43 | # Re-compile dockerized with go and run python. 44 | dockerized --compile=host python 45 | ``` -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | # Adding commands 2 | 3 | To add your favorite command, simply add an entry to the [docker-compose.yml](docker-compose.yml) file. 4 | 5 | For example, this is how `go` is added: 6 | 7 | ```yaml 8 | services: 9 | go: 10 | image: golang:latest 11 | entrypoint: [ "go" ] 12 | ``` 13 | 14 | - `image: golang:latest` specifies the docker image to use. You can find these on [Docker Hub](https://hub.docker.com/). 15 | - `entrypoint` is the command to run when the service starts. 16 | 17 | That's it. Now you can run `dockerized go`. 18 | 19 | ## Configurable version 20 | 21 | Let's make sure users can choose the version of the command. 22 | 23 | Check the current version of the command. Often with ` --version` or ` version`. 24 | 25 | ```shell 26 | dockerized go version 27 | > go version go1.17.8 linux/amd64 28 | ``` 29 | 30 | Replace the version tag `latest` with `${GO_VERSION}`: 31 | 32 | ```yaml 33 | go: 34 | image: "golang:${GO_VERSION}" 35 | entrypoint: [ "go" ] 36 | ``` 37 | 38 | Set the global default version in [.env](.env): 39 | 40 | ```dotenv 41 | GO_VERSION=1.17.8 42 | ``` 43 | 44 | - Don't use `latest`, as there's no guarantee that newer versions will always work. 45 | 46 | ## Contribute your changes 47 | 48 | ```bash 49 | # Create a branch: 50 | dockerized git checkout -b fork 51 | 52 | # Commit your changes: 53 | dockerized git commit -am "Add go" 54 | 55 | # Authenticate to github: 56 | dockerized gh auth login 57 | ``` 58 | 59 | - Choose SSH, and Browser authentication 60 | - See [gh](apps/gh/README.md) for more information. 61 | 62 | ```bash 63 | # Create a PR: 64 | dockerized gh pr create 65 | ``` 66 | 67 | ## Dockerized Development 68 | 69 | When running from an IDE like Goland, or using the `go run` command, dockerized will have trouble finding the included `.env` file in the root of the project. 70 | To fix this, you can set up an environment variable `DOCKERIZED_ROOT` that points to the root of the project. 71 | 72 | > 🤔 If anyone knows how to 'embed' static files in a go project 73 | 74 | ```bash 75 | export DOCKERIZED_ROOT=/path/to/dockerized 76 | go run main.go --help 77 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DataStack.net 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Dockerized](dockerized-banner.png) 2 | 3 | # Dockerized [![Compile and Test](https://github.com/datastack-net/dockerized/actions/workflows/test.yml/badge.svg)](https://github.com/datastack-net/dockerized/actions/workflows/test.yml) 4 | 5 | Run popular commandline tools without installing them. 6 | 7 | ```shell 8 | dockerized 9 | ``` 10 | 11 | ![demo](terminalizer.gif) 12 | 13 | ## Supported commands 14 | 15 | > If your favorite command is not included, it can be added very easily. See [Customization](#customization). 16 | > Dockerized will also fall back to over 150 commands defined in [jessfraz/dockerfiles](https://github.com/jessfraz/dockerfiles). 17 | 18 | - Cloud 19 | - [aws](apps/aws/README.md) 20 | - [az](apps/az/README.md) (Azure CLI) 21 | - [doctl](apps/doctl/README.md) 22 | - [s3cmd](apps/s3cmd/README.md) 23 | - Database 24 | - dolt 25 | - [mssql](apps/mssql/README.md) 26 | - mysql 27 | - [postgres](apps/postgres/README.md) 28 | - psql 29 | - pg_dump 30 | - pg_dumpall 31 | - Dev-Ops & Docker 32 | - ansible 33 | - ansible-playbook 34 | - helm 35 | - Git 36 | - git 37 | - [gh](apps/gh/README.md) (Github) 38 | - Languages & SDKs 39 | - [dotnet](apps/dotnet/README.md) 40 | - go 41 | - gofmt 42 | - (haskell) 43 | - ghci 44 | - java 45 | - perl 46 | - php 47 | - composer 48 | - (prolog) 49 | - swipl (SWI-Prolog) 50 | - lua 51 | - node 52 | - [npm](apps/npm/README.md) 53 | - npx 54 | - vue 55 | - yarn 56 | - python 57 | - pip 58 | - python 59 | - python2 60 | - ruby 61 | - gem 62 | - rake 63 | - (rust) 64 | - rustc 65 | - typescript 66 | - tsc 67 | - Networking 68 | - http 69 | - telnet 70 | - wget 71 | - Unix 72 | - tree 73 | - zip 74 | - Other 75 | - jq 76 | - [mkdocs](apps/mkdocs/README.md) 77 | - (latex) 78 | - pdflatex 79 | - protoc 80 | - scrapy 81 | - swagger-codegen 82 | - youtube-dl (Youtube downloader) 83 | 84 | ## Installation 85 | 86 | - Make sure [Docker](https://docs.docker.com/get-docker/) is installed on your machine. 87 | - Download the latest zip from our [Release Page](https://github.com/datastack-net/dockerized/releases). 88 | - Extract it to a folder, for example your home directory. 89 | - Add the `dockerized/bin` directory to your `PATH` 90 |
91 | Instructions 92 | 93 | **Linux / MacOS:** 94 | 95 | ```bash 96 | export PATH="$PATH:$HOME/dockerized/bin" 97 | ``` 98 | **Windows** 99 | 100 | > See: [How to add a folder to `PATH` environment variable in Windows 10](https://stackoverflow.com/questions/44272416) 101 | 102 |
103 | 104 | ### Running from source 105 | 106 | You can run dockerized directly from source-code. 107 | 108 |
109 | Installation from Source 110 | 111 | 112 | - Requirements: 113 | - [Git](https://git-scm.com/downloads) 114 | - [Docker](https://docs.docker.com/get-docker/) 115 | - Steps 116 | - Clone the repository 117 | ```shell 118 | git clone https://github.com/datastack-net/dockerized.git 119 | ``` 120 | - Run `dockerized`: 121 | ```shell 122 | bin/dockerized --help 123 | ``` 124 | 125 | - The first time you run dockerized, it will compile itself. 126 | - Compilation is done within docker, no dependencies needed. 127 | - To re-compile after changing the source code, run: 128 | - `dockerized --compile` (runs within docker) 129 | - `dockerized --compile=host` (runs on your machine, requires Go 1.17+) 130 | - You do not need to re-compile to add commands. See [Add a command](DEV.md). 131 | 132 |
133 | 134 | 135 | ## Usage 136 | 137 | Run any supported command, but within Docker. 138 | 139 | ```shell 140 | dockerized 141 | ``` 142 | 143 | **Examples**: 144 | 145 | ```bash 146 | dockerized node --version # v16.13.0 147 | dockerized vue create new-project # create a project with vue cli 148 | dockerized tsc --init # initialize typescript for the current directory 149 | dockerized npm install # install packages.json 150 | ``` 151 | 152 | See [CLI Reference](CLI_REFERENCE.md) for all options. 153 | 154 | ## Use Cases 155 | 156 | - Quickly try out command line tools without the effort of downloading and installing them. 157 | - Installing multiple versions of node/python/typescript. 158 | - You need unix commands on Windows. 159 | - You don't want to pollute your system with a lot of tools you don't use. 160 | - Easily update your tools. 161 | - Ensure everyone on the team is using the same version of commandline tools. 162 | 163 | ## Design Goals 164 | 165 | - All commands work out of the box. 166 | - Dockerized commands behave the same as their native counterparts. 167 | - Files in the current directory are accessible using relative paths. 168 | - Cross-platform: Works on Linux, MacOS, and Windows (CMD, Powershell, Git Bash). 169 | - Suitable for ad-hoc usage (i.e. you quickly need to run a command, that is not on your system). 170 | - Configurability: for use within a project or CI/CD pipeline. 171 | 172 | ## Switching command versions 173 | 174 | ### Ad-hoc 175 | 176 | Add `:` to the end of the command to override the version. 177 | 178 | ```shell 179 | dockerized node:15 180 | ``` 181 | 182 | 183 | ### Listing versions 184 | 185 | To see which versions are available, run: 186 | 187 | ```shell 188 | dockerized node:? 189 | # or 190 | dockerized node: 191 | ``` 192 | 193 | ### Environment Variables 194 | 195 | Each command has a `_VERSION` environment variable which you can override. 196 | 197 | - python → `PYTHON_VERSION` 198 | - node → `NODE_VERSION` 199 | - tsc → `TSC_VERSION` 200 | - npm → `NODE_VERSION` ⚠ npm's version is determined by node. 201 | 202 | > See [.env](.env) for a list of configurable versions. 203 | 204 | 205 | **Global** 206 | 207 | - Create a `dockerized.env` file in your home directory for global configuration. 208 | 209 | ```shell 210 | # dockerized.env (example) 211 | NODE_VERSION=16.13.0 212 | PYTHON_VERSION=3.8.5 213 | TYPESCRIPT_VERSION=4.6.2 214 | ``` 215 | 216 | **Per project (directory)** 217 | 218 | You can also specify version and other settings per directory and its subdirectory. 219 | This allows you to "lock" your tools to specific versions for your project. 220 | 221 | - Create a `dockerized.env` file in the root of your project directory. 222 | - All commands executed within this directory will use the settings specified in this file. 223 | 224 | 225 | **Ad-hoc (Unix)** 226 | 227 | - Override the environment variable before the command, to specify the version for that command. 228 | 229 | ```shell 230 | NODE_VERSION=15.0.0 dockerized node 231 | ``` 232 | 233 | **Ad-hoc (Windows Command Prompt)** 234 | 235 | - Set the environment variable in the current session, before the command. 236 | 237 | ```cmd 238 | set NODE_VERSION=15.0.0 239 | dockerized node 240 | ``` 241 | 242 | ## Customization 243 | 244 | Dockerized uses [Docker Compose](https://docs.docker.com/compose/overview/) to run commands, which are defined in a Compose File. 245 | The default commands are listed in [docker-compose.yml](docker-compose.yml). You can add your own commands or customize the defaults, by loading a custom Compose File. 246 | 247 | The `COMPOSE_FILE` environment variable defines which files to load. This variable is set by the included [.env](.env) file, and your own `dockerized.env` files, as explained in [Environment Variables](#environment-variables). To load an additional Compose File, add the path to the file to the `COMPOSE_FILE` environment variable. 248 | 249 | 250 | ### Including an additional Compose File 251 | 252 | **Globally** 253 | 254 | To change global settings, create a file `dockerized.env` in your home directory, which loads an extra Compose File. 255 | In this example, the Compose File is also in the home directory, referenced relative to the `${HOME}` directory. The original variable `${COMPOSE_FILE}` is included to preserve the default commands. Omit it if you want to completely replace the default commands. 256 | 257 | ```bash 258 | # ~/dockerized.env 259 | COMPOSE_FILE="${COMPOSE_FILE};${HOME}/docker-compose.yml" 260 | ``` 261 | 262 | ```yaml 263 | # ~/docker-compose.yml 264 | ``` 265 | 266 | **Per Project** 267 | 268 | To change settings within a specific directory, create a file `dockerized.env` in the root of that directory, which loads the extra Compose File. `${DOCKERIZED_PROJECT_ROOT}` refers to the absolute path to the root of the project. 269 | 270 | ```shell 271 | # ./dockerized.env 272 | COMPOSE_FILE="${COMPOSE_FILE};${DOCKERIZED_PROJECT_ROOT}/docker-compose.yml" 273 | ``` 274 | 275 | ```yaml 276 | # ./docker-compose.yml 277 | ``` 278 | 279 | ### Adding custom commands 280 | 281 | After adding your custom Compose File to `COMPOSE_FILE`, you can add your own commands. 282 | 283 | ```yaml 284 | # docker-compose.yml 285 | version: "3" 286 | services: 287 | du: 288 | image: alpine 289 | entrypoint: ["du"] 290 | ``` 291 | 292 | > Now you can run `dockerized du` to see the size of the current directory. 293 | 294 | > To learn how to support versioning, see [Development Guide: Configurable Version](DEV.md#configurable-version). 295 | 296 | You can also mount a directory to the container: 297 | 298 | ```yaml 299 | # docker-compose.yml 300 | version: "3" 301 | services: 302 | du: 303 | image: alpine 304 | entrypoint: ["du"] 305 | volumes: 306 | - "${HOME}/.config:/root/.config" 307 | - "${DOCKERIZED_PROJECT_ROOT}/foobar:/foobar" 308 | ``` 309 | 310 | > Make sure host volumes are **absolute paths**. For paths relative to home and the project root, you can use `${HOME}` and `${DOCKERIZED_PROJECT_ROOT}`. 311 | 312 | > It is possible to use **relative paths** in the service definitions, but then your Compose File must be loaded **before** the default: `COMPOSE_FILE=${DOCKERIZED_PROJECT_ROOT}/docker-compose.yml;${COMPOSE_FILE}`. All paths are relative to the first Compose File. Compose Files are also merged in the order they are specified, with the last Compose File overriding earlier ones. Because of this, if you want to override the default Compose File, you must load it before _your_ file, and you can't use relative paths. 313 | 314 | > To keep **compatibility** with docker-compose, you can specify a default value for `DOCKERIZED_PROJECT_ROOT`, for example: `${DOCKERIZED_PROJECT_ROOT:-.}` sets the default to `.`, allowing you to run like this as well: `docker-compose --rm du -sh /foobar`. 315 | 316 | To customize existing commands, you can override or add properties to the `services` section of the Compose File, for the command you want to customize. For example, this is how to set an extra environment variable for `dockerized aws`: 317 | 318 | ```yaml 319 | # docker-compose.yml 320 | version: "3" 321 | services: 322 | aws: 323 | environment: 324 | AWS_DEFAULT_REGION: "us-east-1" 325 | ``` 326 | 327 | If you'd like to pass environment variables directly from your `dockerized.env` file, you can expose the variable as follows: 328 | 329 | ```bash 330 | # dockerized.env 331 | AWS_DEFAULT_REGION="us-east-1" 332 | ``` 333 | 334 | ```yaml 335 | # docker-compose.yml 336 | version: "3" 337 | services: 338 | aws: 339 | environment: 340 | AWS_DEFAULT_REGION: "${AWS_DEFAULT_REGION}" 341 | ``` 342 | 343 | 344 | 345 | For more information on extending Compose Files, see the Docker Compose documentation: [Multiple Compose Files](https://docs.docker.com/compose/extends/#multiple-compose-files). Note that the `extends` keyword is not supported in the Docker Compose version used by Dockerized. 346 | 347 | ## Localhost 348 | 349 | Dockerized applications run within an isolated network. To access services running on your machine, you need to use `host.docker.internal` instead of `localhost`. 350 | 351 | ```shell 352 | dockerized telnet host.docker.internal 8080 # instead of telnet localhost 8080 353 | ``` 354 | 355 | ## Limitations 356 | 357 | - It's not currently possible to access parent directories. (i.e. `dockerized tree ../dir` will not work) 358 | - Workaround: Execute the command from the parent directory. (i.e. `cd .. && dockerized tree dir`) 359 | - Commands will not persist changes outside the working directory, unless specifically supported by `dockerized`. 360 | -------------------------------------------------------------------------------- /apps/alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ALPINE_VERSION="" 2 | FROM alpine:$ALPINE_VERSION 3 | ARG ALPINE_PACKAGES="" 4 | ARG BUILD_SCRIPT_ARGS="" 5 | 6 | # Install packages 7 | RUN apk add --no-cache $ALPINE_PACKAGES 8 | 9 | ADD build.sh build.sh 10 | RUN chmod +x build.sh 11 | RUN ./build.sh $BUILD_SCRIPT_ARGS 12 | RUN rm build.sh 13 | -------------------------------------------------------------------------------- /apps/alpine/build.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastack-net/dockerized/79f19ae73f408e3170e85dd5fe2f540e6c554cdb/apps/alpine/build.sh -------------------------------------------------------------------------------- /apps/ansible/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cp -r /dockerized/host/home/.ssh /root/.ssh 6 | chmod -R go-rwx /root/.ssh 7 | 8 | "$@" 9 | -------------------------------------------------------------------------------- /apps/aws/README.md: -------------------------------------------------------------------------------- 1 | # dockerized aws (aws-cli) 2 | 3 | ```shell 4 | dockerized aws 5 | ``` 6 | 7 | ## Setup 8 | 9 | - Add your aws-credentials to a `dockerized.env` file, in the root of your project or in your HOME directory. 10 | ```ini 11 | AWS_ACCESS_KEY_ID=**** 12 | AWS_SECRET_ACCESS_KEY=**** 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```shell 18 | dockerized aws s3 ls 19 | dockerized aws s3 --endpoint-url=https://ams3.digitaloceanspaces.com ls 20 | ``` 21 | -------------------------------------------------------------------------------- /apps/az/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized az (Azure CLI) 2 | 3 | ```shell 4 | dockerized az 5 | ``` 6 | 7 | ## Examples 8 | 9 | ```shell 10 | dockerized az login # Authenticate with Azure 11 | dockerized az resource list # List resources 12 | ``` 13 | 14 | ## Volumes 15 | 16 | - `~/.dockerized/apps/az` → `/root/.azure` 17 | - This directory will contain your azure profile. 18 | - You can access the directory from your host machine. 19 | -------------------------------------------------------------------------------- /apps/doctl/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized doctl 2 | 3 | ```shell 4 | dockerized doctl 5 | ``` 6 | 7 | ## Examples 8 | 9 | ```shell 10 | dockerized doctl auth init # Set authentication token 11 | dockerized doctl projects list # List projects 12 | ``` 13 | 14 | ## Volumes 15 | 16 | - `~/.dockerized/apps/doctl` → `/root` 17 | - This directory will contain `.config/config.yaml`, containing your authentication token. 18 | - You can access the directory from your host machine. 19 | -------------------------------------------------------------------------------- /apps/doctl/build.sh: -------------------------------------------------------------------------------- 1 | DOCTL_VERSION=$1 2 | cd ~ 3 | wget https://github.com/digitalocean/doctl/releases/download/v${DOCTL_VERSION}/doctl-${DOCTL_VERSION}-linux-amd64.tar.gz 4 | tar xf ~/doctl-${DOCTL_VERSION}-linux-amd64.tar.gz 5 | mv ~/doctl /usr/local/bin 6 | -------------------------------------------------------------------------------- /apps/dolt/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DOLT_VERSION=$1 3 | 4 | set -e 5 | 6 | if [ -z "$DOLT_VERSION" ]; then 7 | echo "$DOLT_VERSION not set" 8 | exit 1 9 | fi 10 | 11 | if [ "$DOLT_VERSION" == "latest" ]; then 12 | curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash 13 | else 14 | curl -L https://github.com/dolthub/dolt/releases/download/v${DOLT_VERSION}/install.sh | bash 15 | fi -------------------------------------------------------------------------------- /apps/dotnet/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized dotnet 2 | 3 | ```shell 4 | dockerized dotnet 5 | ``` 6 | 7 | ## Example 8 | 9 | ```shell 10 | dockerized dotnet new console 11 | dockerized dotnet run 12 | # Hello, World! 13 | ``` 14 | -------------------------------------------------------------------------------- /apps/gh/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized gh (github client) 2 | 3 | ```shell 4 | dockerized gh 5 | ``` 6 | 7 | ## Authentication 8 | 9 | Dockerized gh supports SSH out of the box, using its own SSH key. 10 | 11 | ```shell 12 | dockerized gh auth login 13 | ``` 14 | - Protocol: SSH 15 | - SSH key: `/root/.ssh/id_rsa.pub` 16 | > This key is generated for you, you can view it at `~/.dockerized/apps/gh/.ssh/id_rsa.pub` 17 | - Authenticate using Web Browser 18 | > As `gh` cannot access your host machine's browser, please open the urls by (Ctrl-) clicking them or copy-pasting them into your browser. 19 | -------------------------------------------------------------------------------- /apps/gh/build.sh: -------------------------------------------------------------------------------- 1 | GH_VERSION=$1 2 | mkdir /gh 3 | cd /gh 4 | wget https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_386.tar.gz -O ghcli.tar.gz 5 | tar --strip-components=1 -xf ghcli.tar.gz 6 | ln -s /gh/bin/gh /usr/local/bin/gh 7 | -------------------------------------------------------------------------------- /apps/gh/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -f ~/.ssh/id_rsa ]; then 4 | ssh-keygen -t rsa -b 4096 -C "dockerized@${HOST_HOSTNAME}" -f ~/.ssh/id_rsa -N "" 5 | fi 6 | 7 | "$@" 8 | -------------------------------------------------------------------------------- /apps/mkdocs/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized mkdocs 2 | 3 | ```shell 4 | dockerized mkdocs 5 | ``` 6 | 7 | ## Getting started 8 | 9 | > See: https://www.mkdocs.org/getting-started/ 10 | 11 | **Installation** 12 | 13 | You can skip this step. 14 | 15 | **Creating a new Project** 16 | 17 | ```shell 18 | dockerized mkdocs new my-project 19 | ``` 20 | 21 | **Serving** 22 | 23 | The `mkdocs serve` command needs some adjustment to work with dockerized: 24 | 25 | ```shell 26 | dockerized -p 8000 mkdocs serve --dev-addr=0.0.0.0:8000 27 | ``` 28 | 29 | - `-p 8000` — Tells dockerized to forward port 8000 from the host to the container. 30 | - `--dev-addr=0.0.0.0:8000` — Tells mkdocs to listen on all interfaces on port 8000. 31 | 32 | You can now access the live site at http://localhost:8000. 33 | 34 | ## Mkdocs plugins 35 | 36 | > See: 37 | > - https://www.mkdocs.org/dev-guide/plugins/ 38 | > - https://github.com/mkdocs/mkdocs/wiki/MkDocs-Plugins 39 | 40 | By default, the following plugins are installed: 41 | 42 | - `mkdocs-material` 43 | - `mkdocs-material-extensions` 44 | 45 | To install more plugins: 46 | 47 | - Add their pip package name to the `MKDOCS_PACKAGES` environment variable. 48 | 49 | ```shell 50 | # dockerized.env 51 | MKDOCS_PACKAGES="${MKDOCS_PACKAGES} mdx-truly-sane-lists" 52 | ``` 53 | 54 | Rebuild mkdocs: 55 | 56 | ```shell 57 | dockerized --build mkdocs 58 | ``` 59 | - Follow the other instructions in the plugin's documentation. 60 | 61 | 62 | -------------------------------------------------------------------------------- /apps/mssql/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized mssql (Microsoft SQL Server) 2 | 3 | How to run Microsoft SQL Server within docker? 4 | 5 | ```shell 6 | dockerized mssql 7 | ``` 8 | 9 | - The default password is `Dockerized1` 10 | - Override the password with the environment variable `SA_PASSWORD` 11 | - `SA_PASSWORD= dockerized mssql` 12 | - Or change it in your `dockerized.env` file 13 | -------------------------------------------------------------------------------- /apps/npm/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized npm 2 | 3 | ```shell 4 | dockerized npm 5 | ``` 6 | 7 | ## Examples 8 | 9 | ```shell 10 | dockerized npm init # Initialize a new project 11 | dockerized npm install # Install package.json 12 | ``` 13 | 14 | ## Global installs 15 | 16 | Global installations are supported and are stored within a docker volume, which is shared with `npx`, `node` and `npm`. 17 | 18 | ```shell 19 | dockerized npx --package=@vue/cli vue 20 | ``` 21 | -------------------------------------------------------------------------------- /apps/pip/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VERSION 2 | FROM python:${PYTHON_VERSION} 3 | ARG PIP_PACKAGES 4 | RUN python -m pip install ${PIP_PACKAGES} -------------------------------------------------------------------------------- /apps/postgres/README.md: -------------------------------------------------------------------------------- 1 | # dockerized postgres 2 | 3 | You can use dockerized postgres to query your database, and manage backups, without locally installing postgres. 4 | 5 | The commands work as usual, except: 6 | 7 | - Use `host.docker.internal` instead of `localhost`. 8 | - You can only access files in the current working directory (e.g. when dumping). 9 | 10 | ## psql 11 | 12 | ```shell 13 | dockerized psql --host "host.docker.internal" --username 14 | ``` 15 | 16 | ## pg_dumpall 17 | 18 | ```shell 19 | dockerized pg_dumpall \ 20 | --host "host.docker.internal" \ 21 | --file "backup.sql" \ 22 | --username root \ 23 | --no-password \ 24 | --quote-all-identifiers \ 25 | --verbose 26 | ``` -------------------------------------------------------------------------------- /apps/protoc/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker image for protoc 2 | 3 | ARG PROTOC_BASE="" 4 | FROM $PROTOC_BASE 5 | ARG PROTOC_VERSION=""; 6 | ARG PROTOC_ARCH=""; 7 | 8 | RUN apk add gcompat 9 | ADD "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOC_VERSION/protoc-$PROTOC_VERSION-linux-$PROTOC_ARCH.zip" protoc.zip 10 | RUN mkdir /usr/local/lib/protoc && unzip protoc.zip -d /usr/local/lib/protoc && rm protoc.zip 11 | RUN ln -s /usr/local/lib/protoc/bin/protoc /usr/local/bin/protoc 12 | 13 | ENTRYPOINT ["/usr/local/bin/protoc"] 14 | CMD ["--version"] 15 | -------------------------------------------------------------------------------- /apps/s3cmd/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG S3CMD_BASE="" 2 | FROM $S3CMD_BASE 3 | ARG S3CMD_VERSION="" 4 | 5 | RUN pip install s3cmd==$S3CMD_VERSION 6 | 7 | ENTRYPOINT ["s3cmd"] 8 | -------------------------------------------------------------------------------- /apps/s3cmd/README.md: -------------------------------------------------------------------------------- 1 | # Dockerized s3cmd 2 | 3 | ```shell 4 | dockerized s3cmd 5 | ``` 6 | 7 | ## Setup 8 | 9 | - Add your aws-credentials to a `dockerized.env` file, in the root of your project or in your HOME directory. 10 | ```ini 11 | AWS_ACCESS_KEY_ID=**** 12 | AWS_SECRET_ACCESS_KEY=**** 13 | ``` 14 | - Configure s3cmd 15 | ```shell 16 | dockerized s3cmd --configure 17 | ``` 18 | - Configuration will be saved to `~/.dockerized/apps/s3cmd/.s3cfg` 19 | 20 | ## Usage 21 | 22 | ```shell 23 | dockerized s3cmd --help 24 | dockerized s3cmd ls 25 | ``` 26 | -------------------------------------------------------------------------------- /apps/scrapy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | ARG SCRAPY_VERSION="" 4 | 5 | ENV BUILD_DEPS gcc \ 6 | cargo \ 7 | musl-dev 8 | 9 | RUN apk -U add \ 10 | ${BUILD_DEPS} \ 11 | libffi-dev \ 12 | libxml2-dev \ 13 | libxslt-dev \ 14 | openssl-dev \ 15 | libressl-dev \ 16 | python3-dev \ 17 | py-pip \ 18 | curl \ 19 | ca-certificates \ 20 | && update-ca-certificates \ 21 | && pip install --upgrade pip \ 22 | && pip install scrapy==$SCRAPY_VERSION \ 23 | && apk -U del ${BUILD_DEPS} \ 24 | && rm -rf /var/cache/apk/* 25 | 26 | ENTRYPOINT ["/usr/bin/scrapy"] -------------------------------------------------------------------------------- /bin/dcx.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dockerized %* 3 | -------------------------------------------------------------------------------- /bin/dockerized: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | case "$OSTYPE" in 3 | msys | cygwin) 4 | PWD_ARGS="-W" 5 | ;; 6 | esac 7 | 8 | # CONSTANTS 9 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd $PWD_ARGS) 10 | DOCKERIZED_ROOT=$(dirname "$SCRIPT_DIR") 11 | DOCKERIZED_COMPOSE_FILE="${DOCKERIZED_ROOT}/docker-compose.yml" 12 | DOCKERIZED_BINARY="${DOCKERIZED_ROOT}/build/dockerized" 13 | 14 | # region COMPILE DOCKERIZED 15 | DOCKERIZED_COMPILE= 16 | if [ "$1" == "--compile" ] || [ "$1" == "--compile=docker" ]; then 17 | DOCKERIZED_COMPILE="docker" 18 | shift 19 | fi 20 | 21 | if [ "$1" == "--compile=host" ]; then 22 | DOCKERIZED_COMPILE="host" 23 | shift 24 | fi 25 | 26 | case "$OSTYPE" in 27 | msys | cygwin) 28 | DOCKERIZED_COMPILE_GOOS=windows 29 | DOCKERIZED_BINARY="${DOCKERIZED_BINARY}.exe" 30 | ;; 31 | darwin*) 32 | DOCKERIZED_COMPILE_GOOS=darwin 33 | ;; 34 | # Operating systems below not tested. Logic based on: 35 | # - https://github.com/dylanaraps/neofetch/issues/433 36 | # - https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 37 | openbsd*) 38 | DOCKERIZED_COMPILE_GOOS=openbsd 39 | ;; 40 | freebsd*) 41 | DOCKERIZED_COMPILE_GOOS=freebsd 42 | ;; 43 | netbsd*) 44 | DOCKERIZED_COMPILE_GOOS=netbsd 45 | ;; 46 | solaris*) 47 | DOCKERIZED_COMPILE_GOOS=solaris 48 | ;; 49 | # default 50 | *) 51 | DOCKERIZED_COMPILE_GOOS=linux 52 | ;; 53 | esac 54 | 55 | case "$(uname -m)" in 56 | x86_64) 57 | DOCKERIZED_COMPILE_GOARCH=amd64 58 | ;; 59 | i*86) 60 | DOCKERIZED_COMPILE_GOARCH=386 61 | ;; 62 | arm64 | aarch64) 63 | DOCKERIZED_COMPILE_GOARCH=arm64 64 | ;; 65 | esac 66 | 67 | if [ "$DOCKERIZED_COMPILE" ] || [ ! -f "$DOCKERIZED_BINARY" ]; then 68 | echo "Compiling dockerized..." >&2 69 | rm -f "$DOCKERIZED_BINARY" 70 | 71 | GIT_COMMIT=$(cd $DOCKERIZED_ROOT; git rev-list -1 HEAD) 72 | GO_BUILD_ARGS="-ldflags '-X main.Version=${GIT_COMMIT}'" 73 | GO_LDFLAGS="-X main.Version=${GIT_COMMIT}" 74 | if [ "${DOCKERIZED_COMPILE:-docker}" == "docker" ]; then 75 | # Create a volume for go pkg cache instead of local mount (#33) 76 | DOCKERIZED_COMPILE_VOLUME="dockerized_compile_${DOCKERIZED_COMPILE_GOOS}_${DOCKERIZED_COMPILE_GOARCH}" 77 | docker volume create --driver local "${DOCKERIZED_COMPILE_VOLUME}" 78 | 79 | CMD_BUILD="go build -ldflags \"$GO_LDFLAGS\" -o //build/ ." 80 | CMD_CHOWN="chown $(id -u):$(id -g) //build/$(basename $DOCKERIZED_BINARY)" # fix permissions (#33) 81 | CMD_CHMOD="chmod +x //build/$(basename $DOCKERIZED_BINARY)" # fix executable bit (#33) 82 | docker run \ 83 | --rm \ 84 | -e "GOOS=${DOCKERIZED_COMPILE_GOOS}" \ 85 | -e "GOARCH=${DOCKERIZED_COMPILE_GOARCH}" \ 86 | -v "${DOCKERIZED_ROOT}:/src" \ 87 | -v "${DOCKERIZED_ROOT}/build:/build" \ 88 | -v "${DOCKERIZED_COMPILE_VOLUME}:/go/pkg" \ 89 | -w //src \ 90 | "golang:1.17.8" \ 91 | bash -c "$CMD_BUILD && $CMD_CHOWN && $CMD_CHMOD" 92 | else 93 | ( 94 | cd "$DOCKERIZED_ROOT" 95 | go build -ldflags "$GO_LDFLAGS" -o build/ . 96 | ) 97 | fi 98 | 99 | if [ $? -ne 0 ]; then 100 | echo "Failed to compile dockerized" >&2 101 | exit 1 102 | fi 103 | 104 | if [ $# -eq 0 ]; then 105 | echo "Compiled dockerized" >&2 106 | exit 0 107 | fi 108 | fi 109 | # endregion 110 | 111 | # RUN DOCKERIZED: 112 | "$DOCKERIZED_BINARY" "$@" 113 | -------------------------------------------------------------------------------- /bin/dockerized.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | SET _DOCKERIZED_PS=%~dp0%dockerized.ps1 3 | PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '%_DOCKERIZED_PS%' %*" 4 | -------------------------------------------------------------------------------- /bin/dockerized.ps1: -------------------------------------------------------------------------------- 1 | # CONSTANTS 2 | $DOCKERIZED_ROOT = (get-item $PSScriptRoot).parent.FullName 3 | $DOCKERIZED_COMPOSE_FILE = "${DOCKERIZED_ROOT}\docker-compose.yml" 4 | $DOCKERIZED_BINARY = "${DOCKERIZED_ROOT}\build\dockerized.exe" 5 | 6 | function Write-StdErr { 7 | param ([PSObject] $InputObject) 8 | $outFunc = if ($Host.Name -eq 'ConsoleHost') { 9 | [Console]::Error.WriteLine 10 | } else { 11 | $host.ui.WriteErrorLine 12 | } 13 | if ($InputObject) { 14 | [void] $outFunc.Invoke($InputObject.ToString()) 15 | } else { 16 | [string[]] $lines = @() 17 | $Input | % { $lines += $_.ToString() } 18 | [void] $outFunc.Invoke($lines -join "`r`n") 19 | } 20 | } 21 | 22 | # region COMPILE DOCKERIZED 23 | $DOCKERIZED_COMPILE="" 24 | if ($args[0] -eq "--compile") { 25 | $DOCKERIZED_COMPILE = $true 26 | $_, $args = $args 27 | } 28 | 29 | if (($DOCKERIZED_COMPILE -eq $true) -Or !(Test-Path "$DOCKERIZED_BINARY")) 30 | { 31 | Write-StdErr "Compiling dockerized..." 32 | docker run ` 33 | --rm ` 34 | --entrypoint=go ` 35 | -e "GOOS=windows" ` 36 | -v "${DOCKERIZED_ROOT}:/src" ` 37 | -v "${DOCKERIZED_ROOT}\build:/build" ` 38 | -v "${DOCKERIZED_ROOT}\.cache:/go/pkg" ` 39 | -w /src ` 40 | "golang:1.17.8" ` 41 | build -o /build/ . 42 | 43 | if ($LASTEXITCODE -ne 0) { 44 | Write-StdErr "Failed to compile dockerized." 45 | exit $LASTEXITCODE 46 | } 47 | 48 | if ($args.Length -eq 0) 49 | { 50 | Write-StdErr "Compiled dockerized." 51 | exit 0 52 | } 53 | } 54 | # endregion 55 | 56 | # RUN DOCKERIZED: 57 | & $DOCKERIZED_BINARY $args 58 | -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastack-net/dockerized/79f19ae73f408e3170e85dd5fe2f540e6c554cdb/build/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | alpine: 5 | &alpine 6 | image: "alpine_${ALPINE_VERSION}" 7 | build: 8 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 9 | args: 10 | ALPINE_VERSION: "${ALPINE_VERSION}" 11 | ALPINE_PACKAGES: "tree" 12 | ansible: &ansible 13 | image: "willhallonline/ansible:${ANSIBLE_VERSION}-${ANSIBLE_BASE}" 14 | entrypoint: [ "/init.sh", "ansible" ] 15 | volumes: 16 | - "${HOME}/.ssh:/dockerized/host/home/.ssh" 17 | - ./apps/ansible/init.sh:/init.sh 18 | environment: 19 | - ANSIBLE_CONFIG=ansible.cfg 20 | ansible-playbook: 21 | <<: *ansible 22 | entrypoint: [ "/init.sh", "ansible-playbook" ] 23 | ab: 24 | <<: *alpine 25 | image: "ab" 26 | build: 27 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 28 | args: 29 | ALPINE_VERSION: "${ALPINE_VERSION}" 30 | ALPINE_PACKAGES: "apache2-ssl apache2-utils ca-certificates" 31 | entrypoint: [ "ab" ] 32 | aws: 33 | image: "amazon/aws-cli:${AWS_VERSION}" 34 | volumes: 35 | - "${HOME:-home}/.aws:/root/.aws" 36 | environment: 37 | AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID:-}" 38 | AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY:-}" 39 | az: 40 | image: "mcr.microsoft.com/azure-cli:${AZ_VERSION}" 41 | entrypoint: [ "az" ] 42 | volumes: 43 | - "${HOME:-home}/.ssh:/root/.ssh" 44 | - "${HOME:-home}/.dockerized/apps/az:/root/.azure" 45 | bash: 46 | image: "dockerized_bash" 47 | build: 48 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 49 | args: 50 | ALPINE_VERSION: "${ALPINE_VERSION}" 51 | ALPINE_PACKAGES: "bash" 52 | entrypoint: [ "/bin/bash" ] 53 | composer: 54 | image: "composer:${COMPOSER_VERSION}" 55 | doctl: 56 | image: "doctl:${DOCTL_VERSION}" 57 | build: 58 | context: "${DOCKERIZED_ROOT:-.}/apps/doctl" 59 | dockerfile: "${DOCKERIZED_ROOT:-.}/apps/alpine/Dockerfile" 60 | args: 61 | ALPINE_VERSION: "${ALPINE_VERSION}" 62 | BUILD_SCRIPT_ARGS: "${DOCTL_VERSION}" 63 | entrypoint: [ "doctl" ] 64 | volumes: 65 | - "${HOME:-home}/.dockerized/apps/doctl:/root" 66 | dolt: 67 | image: "dockerized_dolt:${DOLT_VERSION}" 68 | build: 69 | context: "${DOCKERIZED_ROOT:-.}/apps/dolt" 70 | dockerfile: "${DOCKERIZED_ROOT:-.}/apps/alpine/Dockerfile" 71 | args: 72 | ALPINE_VERSION: "${ALPINE_VERSION}" 73 | ALPINE_PACKAGES: "bash curl" 74 | BUILD_SCRIPT_ARGS: "${DOLT_VERSION}" 75 | volumes: 76 | - "${DOCKERIZED_ROOT:-.}/apps/dolt/init.sh:/init.sh" 77 | - "${HOME:-home}/.dockerized/apps/dolt:/root" 78 | entrypoint: [ "dolt" ] 79 | dotnet: 80 | image: "mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-alpine" 81 | entrypoint: [ "dotnet" ] 82 | gh: 83 | image: "gh:${GH_VERSION}" 84 | build: 85 | context: "${DOCKERIZED_ROOT:-.}/apps/gh" 86 | dockerfile: "${DOCKERIZED_ROOT:-.}/apps/alpine/Dockerfile" 87 | args: 88 | ALPINE_VERSION: "${ALPINE_VERSION}" 89 | ALPINE_PACKAGES: "git openssh-client" 90 | BUILD_SCRIPT_ARGS: "${GH_VERSION}" 91 | entrypoint: [ "/init.sh", "/gh/bin/gh" ] 92 | volumes: 93 | - "${HOME:-home}/.dockerized/apps/gh:/root" 94 | - "${DOCKERIZED_ROOT:-.}/apps/gh/init.sh:/init.sh" 95 | environment: 96 | BROWSER: "echo" 97 | git: 98 | image: "alpine/git:v${GIT_VERSION}" 99 | entrypoint: [ "git" ] 100 | ghci: 101 | image: "haskell:${GHCI_VERSION}" 102 | entrypoint: [ "ghci" ] 103 | go: &go 104 | image: "golang:${GO_VERSION}" 105 | entrypoint: [ "go" ] 106 | volumes: 107 | - "go_cache:/go/pkg" 108 | environment: 109 | GOOS: "${GOOS:-}" 110 | GOARCH: "${GOARCH:-}" 111 | gofmt: 112 | <<: *go 113 | entrypoint: [ "gofmt" ] 114 | helm: 115 | image: "alpine/helm:${HELM_VERSION}" 116 | http: 117 | image: "alpine/httpie:${HTTP_VERSION}" 118 | entrypoint: [ "http" ] 119 | java: 120 | image: "openjdk:${JAVA_VERSION}" 121 | entrypoint: [ "java" ] 122 | jq: 123 | image: stedolan/jq 124 | pdflatex: 125 | image: "pdflatex" 126 | build: 127 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 128 | dockerfile: "${DOCKERIZED_ROOT:-.}/apps/alpine/Dockerfile" 129 | args: 130 | ALPINE_VERSION: "${LATEX_ALPINE_VERSION}" 131 | ALPINE_PACKAGES: "texlive-full py-pygments gnuplot make git" 132 | entrypoint: [ "pdflatex" ] 133 | lua: 134 | image: "${LUA_IMAGE}:${LUA_VERSION}" 135 | entrypoint: [ "lua" ] 136 | mssql: 137 | image: "mcr.microsoft.com/mssql/server:${MSSQL_VERSION}" 138 | environment: 139 | SA_PASSWORD: "${SA_PASSWORD:-}" 140 | ACCEPT_EULA: "Y" 141 | mysql: 142 | image: "mysql:${MYSQL_VERSION}" 143 | entrypoint: [ "mysql" ] 144 | network_mode: "host" 145 | node: 146 | &node 147 | image: "node:${NODE_VERSION}" 148 | entrypoint: [ "node" ] 149 | volumes: 150 | - "node_modules:/usr/local/lib/node_modules" 151 | - "${HOME:-home}/.dockerized/apps/node:/root" 152 | npm: 153 | <<: *node 154 | entrypoint: [ "npm" ] 155 | npx: 156 | <<: *node 157 | entrypoint: [ "npx" ] 158 | perl: 159 | image: perl:${PERL_VERSION} 160 | entrypoint: [ "perl" ] 161 | php: 162 | image: "php:${PHP_VERSION}" 163 | psql: 164 | image: "postgres:${POSTGRES_VERSION}" 165 | entrypoint: [ "psql" ] 166 | pg_dump: 167 | image: "postgres:${POSTGRES_VERSION}" 168 | entrypoint: [ "pg_dump" ] 169 | pg_dumpall: 170 | image: "postgres:${POSTGRES_VERSION}" 171 | entrypoint: [ "pg_dumpall" ] 172 | protoc: 173 | image: "protoc:${PROTOC_VERSION}" 174 | build: 175 | context: "${DOCKERIZED_ROOT:-.}/apps/protoc" 176 | args: 177 | PROTOC_VERSION: "${PROTOC_VERSION}" 178 | PROTOC_BASE: "${PROTOC_BASE}" 179 | PROTOC_ARCH: "${PROTOC_ARCH}" 180 | python2: 181 | image: "python:${PYTHON2_VERSION}" 182 | python: 183 | &python 184 | image: "python:${PYTHON_VERSION}" 185 | entrypoint: [ "python" ] 186 | volumes: 187 | - "python_modules:/usr/local/lib/python${PYTHON_VERSION}/site-packages" 188 | - "${HOME:-home}/.dockerized/apps/python:/root" 189 | # region python 190 | pip: 191 | <<: *python 192 | entrypoint: [ "pip" ] 193 | mkdocs: 194 | image: "mkdocs:${MKDOCS_VERSION}" 195 | entrypoint: [ "python", "-m", "mkdocs" ] 196 | build: 197 | context: "${DOCKERIZED_ROOT:-.}/apps/pip" 198 | dockerfile: "${DOCKERIZED_ROOT:-.}/apps/pip/Dockerfile" 199 | args: 200 | PYTHON_VERSION: "${PYTHON_VERSION}" 201 | PIP_PACKAGES: "mkdocs ${MKDOCS_PACKAGES:-}" 202 | # endregion 203 | ruby: &ruby 204 | image: "ruby:${RUBY_VERSION}" 205 | entrypoint: [ "ruby" ] 206 | rake: 207 | <<: *ruby 208 | entrypoint: [ "rake" ] 209 | gem: 210 | <<: *ruby 211 | entrypoint: [ "gem" ] 212 | rustc: 213 | image: "rust:${RUSTC_VERSION}" 214 | entrypoint: [ "rustc" ] 215 | s3cmd: 216 | build: 217 | context: "${DOCKERIZED_ROOT:-.}/apps/s3cmd" 218 | args: 219 | S3CMD_VERSION: "${S3CMD_VERSION}" 220 | S3CMD_BASE: "${S3CMD_BASE}" 221 | image: "s3cmd:${S3CMD_VERSION}" 222 | environment: 223 | AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID:-}" 224 | AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY:-}" 225 | volumes: 226 | - "${HOME:-home}/.dockerized/apps/s3cmd:/root" 227 | scrapy: 228 | image: aciobanu/scrapy:${SCRAPY_VERSION} 229 | build: 230 | context: "${DOCKERIZED_ROOT:-.}/apps/scrapy" 231 | args: 232 | SCRAPY_VERSION: ${SCRAPY_VERSION} 233 | entrypoint: [ "scrapy" ] 234 | # semantic-release-cli: 235 | # <<: *node 236 | # entrypoint: [ "npx", "--package=semantic-release-cli@${SEMANTIC_RELEASE_VERSION}", "semantic-release-cli" ] 237 | # volumes: 238 | # - "${HOME:-home}/.dockerized/apps/gh:/root" 239 | swagger-codegen: 240 | image: "swaggerapi/swagger-codegen-cli-v3:${SWAGGER_CODEGEN_VERSION}" 241 | swipl: 242 | image: "swipl:${SWIPL_VERSION}" 243 | entrypoint: [ "swipl" ] 244 | telnet: 245 | <<: *alpine 246 | build: 247 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 248 | args: 249 | ALPINE_VERSION: "${ALPINE_VERSION}" 250 | ALPINE_PACKAGES: "busybox-extras" 251 | entrypoint: [ "telnet" ] 252 | tree: 253 | <<: *alpine 254 | build: 255 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 256 | args: 257 | ALPINE_VERSION: "${ALPINE_VERSION}" 258 | ALPINE_PACKAGES: "tree" 259 | entrypoint: [ "tree" ] 260 | tsc: 261 | <<: *node 262 | entrypoint: [ "npx", "--package=typescript@${TSC_VERSION}", "tsc" ] 263 | vue: 264 | <<: *node 265 | entrypoint: [ "npx", "--package=@vue/cli@${VUE_VERSION}", "vue" ] 266 | wget: 267 | image: "${DEFAULT_BASE}" 268 | entrypoint: [ "wget" ] 269 | yarn: 270 | <<: *node 271 | entrypoint: [ "yarn" ] 272 | youtube-dl: 273 | image: "mikenye/youtube-dl:${YOUTUBE_DL_VERSION}" 274 | zip: 275 | image: "zip" 276 | build: 277 | context: "${DOCKERIZED_ROOT:-.}/apps/alpine" 278 | args: 279 | ALPINE_VERSION: "${ALPINE_VERSION}" 280 | ALPINE_PACKAGES: "zip" 281 | entrypoint: [ "/usr/bin/zip" ] 282 | 283 | volumes: 284 | # Default for ${HOME}. Avoids creating directories on host machine when HOME isn't set. 285 | home: { } 286 | node_modules: { } 287 | python_modules: { } 288 | pip_cache: { } 289 | go_cache: { } 290 | -------------------------------------------------------------------------------- /dockerized-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastack-net/dockerized/79f19ae73f408e3170e85dd5fe2f540e6c554cdb/dockerized-banner.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/datastack-net/dockerized 2 | 3 | go 1.17 4 | 5 | require github.com/docker/compose/v2 v2.3.3 6 | 7 | replace ( 8 | github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible 9 | github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible 10 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939 11 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939 12 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939 13 | ) 14 | 15 | require ( 16 | github.com/compose-spec/compose-go v1.1.0 17 | github.com/docker/cli v20.10.12+incompatible 18 | github.com/docker/distribution v2.8.1+incompatible 19 | github.com/docker/hub-tool v0.4.4 20 | github.com/fatih/color v1.13.0 21 | github.com/hashicorp/go-version v1.3.0 22 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 23 | github.com/stretchr/testify v1.7.0 24 | k8s.io/apimachinery v0.22.5 25 | ) 26 | 27 | require ( 28 | github.com/AlecAivazis/survey/v2 v2.3.2 // indirect 29 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 30 | github.com/Microsoft/go-winio v0.5.1 // indirect 31 | github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect 32 | github.com/beorn7/perks v1.0.1 // indirect 33 | github.com/buger/goterm v1.0.4 // indirect 34 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 35 | github.com/containerd/console v1.0.3 // indirect 36 | github.com/containerd/containerd v1.6.1 // indirect 37 | github.com/containerd/continuity v0.2.2 // indirect 38 | github.com/containerd/typeurl v1.0.2 // indirect 39 | github.com/davecgh/go-spew v1.1.1 // indirect 40 | github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect 41 | github.com/docker/buildx v0.7.1 // indirect 42 | github.com/docker/docker v20.10.7+incompatible // indirect 43 | github.com/docker/docker-credential-helpers v0.6.4 // indirect 44 | github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect 45 | github.com/docker/go-connections v0.4.0 // indirect 46 | github.com/docker/go-metrics v0.0.1 // indirect 47 | github.com/docker/go-units v0.4.0 // indirect 48 | github.com/felixge/httpsnoop v1.0.2 // indirect 49 | github.com/fvbommel/sortorder v1.0.2 // indirect 50 | github.com/go-logr/logr v1.2.2 // indirect 51 | github.com/go-logr/stdr v1.2.2 // indirect 52 | github.com/gofrs/flock v0.8.0 // indirect 53 | github.com/gogo/googleapis v1.4.0 // indirect 54 | github.com/gogo/protobuf v1.3.2 // indirect 55 | github.com/golang/mock v1.6.0 // indirect 56 | github.com/golang/protobuf v1.5.2 // indirect 57 | github.com/google/go-cmp v0.5.6 // indirect 58 | github.com/google/gofuzz v1.2.0 // indirect 59 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 60 | github.com/google/uuid v1.2.0 // indirect 61 | github.com/gorilla/mux v1.8.0 // indirect 62 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect 63 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 64 | github.com/hashicorp/errwrap v1.1.0 // indirect 65 | github.com/hashicorp/go-multierror v1.1.1 // indirect 66 | github.com/imdario/mergo v0.3.12 // indirect 67 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 68 | github.com/json-iterator/go v1.1.12 // indirect 69 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 70 | github.com/klauspost/compress v1.13.5 // indirect 71 | github.com/mattn/go-colorable v0.1.12 // indirect 72 | github.com/mattn/go-isatty v0.0.14 // indirect 73 | github.com/mattn/go-shellwords v1.0.12 // indirect 74 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 75 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 76 | github.com/miekg/pkcs11 v1.0.3 // indirect 77 | github.com/mitchellh/mapstructure v1.4.3 // indirect 78 | github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da // indirect 79 | github.com/moby/locker v1.0.1 // indirect 80 | github.com/moby/sys/mount v0.2.0 // indirect 81 | github.com/moby/sys/mountinfo v0.5.0 // indirect 82 | github.com/moby/sys/signal v0.6.0 // indirect 83 | github.com/moby/sys/symlink v0.2.0 // indirect 84 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 85 | github.com/modern-go/reflect2 v1.0.2 // indirect 86 | github.com/morikuni/aec v1.0.0 // indirect 87 | github.com/opencontainers/go-digest v1.0.0 // indirect 88 | github.com/opencontainers/image-spec v1.0.2 // indirect 89 | github.com/opencontainers/runc v1.1.0 // indirect 90 | github.com/pelletier/go-toml v1.9.4 // indirect 91 | github.com/pkg/errors v0.9.1 // indirect 92 | github.com/pmezard/go-difflib v1.0.0 // indirect 93 | github.com/prometheus/client_golang v1.11.0 // indirect 94 | github.com/prometheus/client_model v0.2.0 // indirect 95 | github.com/prometheus/common v0.30.0 // indirect 96 | github.com/prometheus/procfs v0.7.3 // indirect 97 | github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b // indirect 98 | github.com/sirupsen/logrus v1.8.1 // indirect 99 | github.com/spf13/cobra v1.3.0 // indirect 100 | github.com/spf13/pflag v1.0.5 // indirect 101 | github.com/theupdateframework/notary v0.6.1 // indirect 102 | github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect 103 | github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect 104 | github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect 105 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 106 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 107 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 108 | go.opentelemetry.io/contrib v0.21.0 // indirect 109 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect 110 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect 111 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect 112 | go.opentelemetry.io/otel v1.3.0 // indirect 113 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect 114 | go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect 115 | go.opentelemetry.io/otel/metric v0.21.0 // indirect 116 | go.opentelemetry.io/otel/sdk v1.3.0 // indirect 117 | go.opentelemetry.io/otel/trace v1.3.0 // indirect 118 | go.opentelemetry.io/proto/otlp v0.11.0 // indirect 119 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 120 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect 121 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 122 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 123 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 124 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect 125 | golang.org/x/text v0.3.7 // indirect 126 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 127 | google.golang.org/appengine v1.6.7 // indirect 128 | google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect 129 | google.golang.org/grpc v1.45.0 // indirect 130 | google.golang.org/protobuf v1.27.1 // indirect 131 | gopkg.in/inf.v0 v0.9.1 // indirect 132 | gopkg.in/yaml.v2 v2.4.0 // indirect 133 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 134 | k8s.io/client-go v0.22.5 // indirect 135 | k8s.io/klog/v2 v2.30.0 // indirect 136 | k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect 137 | sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect 138 | sigs.k8s.io/yaml v1.2.0 // indirect 139 | ) 140 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/compose-spec/compose-go/types" 6 | . "github.com/datastack-net/dockerized/pkg" 7 | "github.com/datastack-net/dockerized/pkg/help" 8 | util "github.com/datastack-net/dockerized/pkg/util" 9 | "github.com/docker/compose/v2/pkg/api" 10 | "github.com/fatih/color" 11 | "github.com/moby/term" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | ) 16 | 17 | var Version string 18 | 19 | var contains = util.Contains 20 | var hasKey = util.HasKey 21 | 22 | func main() { 23 | err, exitCode := RunCli(os.Args[1:]) 24 | if err != nil { 25 | fmt.Printf("%s\n", err) 26 | } 27 | os.Exit(exitCode) 28 | } 29 | 30 | func RunCli(args []string) (err error, exitCode int) { 31 | dockerizedOptions, commandName, commandVersion, commandArgs := parseArguments(args) 32 | 33 | var optionHelp = hasKey(dockerizedOptions, OptionHelp) || hasKey(dockerizedOptions, ShortOptionHelp) 34 | var optionVerbose = hasKey(dockerizedOptions, OptionVerbose) || hasKey(dockerizedOptions, ShortOptionVerbose) 35 | var optionShell = hasKey(dockerizedOptions, OptionShell) 36 | var optionBuild = hasKey(dockerizedOptions, OptionBuild) 37 | var optionBuildPull = hasKey(dockerizedOptions, OptionBuildPull) 38 | var optionBuildNoCache = hasKey(dockerizedOptions, OptionBuildNoCache) 39 | var optionVersion = hasKey(dockerizedOptions, OptionVersion) 40 | var optionPort = hasKey(dockerizedOptions, ShortOptionPort) 41 | var optionEntrypoint = hasKey(dockerizedOptions, OptionEntrypoint) 42 | 43 | if !optionBuild { 44 | if optionBuildPull { 45 | return fmt.Errorf("%s option requires %s option", OptionBuildPull, OptionBuild), 1 46 | } 47 | if optionBuildNoCache { 48 | return fmt.Errorf("%s option requires %s option", OptionBuildNoCache, OptionBuild), 1 49 | } 50 | } 51 | 52 | dockerizedRoot := GetDockerizedRoot() 53 | NormalizeEnvironment(dockerizedRoot) 54 | 55 | if optionVerbose { 56 | fmt.Printf("Dockerized root: %s\n", dockerizedRoot) 57 | } 58 | 59 | if optionVersion { 60 | fmt.Printf("dockerized %s\n", Version) 61 | return nil, 0 62 | } 63 | 64 | hostCwd, _ := os.Getwd() 65 | err = LoadEnvFiles(hostCwd, optionVerbose) 66 | if err != nil { 67 | return err, 1 68 | } 69 | 70 | composeFilePaths := GetComposeFilePaths(dockerizedRoot) 71 | 72 | if optionVerbose { 73 | fmt.Printf("Compose files: %s\n", strings.Join(composeFilePaths, ", ")) 74 | } 75 | 76 | if commandName == "" || optionHelp { 77 | err := help.Help(composeFilePaths) 78 | if err != nil { 79 | return err, 1 80 | } 81 | if optionHelp { 82 | return nil, 0 83 | } else { 84 | return nil, 1 85 | } 86 | } 87 | 88 | if commandVersion != "" { 89 | if commandVersion == "?" { 90 | err = PrintCommandVersions(composeFilePaths, commandName, optionVerbose) 91 | if err != nil { 92 | return err, 1 93 | } else { 94 | return nil, 0 95 | } 96 | } else { 97 | SetCommandVersion(composeFilePaths, commandName, optionVerbose, commandVersion) 98 | } 99 | } 100 | 101 | project, err := GetProject(composeFilePaths) 102 | if err != nil { 103 | return err, 1 104 | } 105 | 106 | hostName, _ := os.Hostname() 107 | hostCwdDirName := filepath.Base(hostCwd) 108 | containerCwd := "/host" 109 | if hostCwdDirName != "\\" { 110 | containerCwd += "/" + hostCwdDirName 111 | } 112 | 113 | runOptions := api.RunOptions{ 114 | Service: commandName, 115 | Environment: []string{ 116 | "HOST_HOSTNAME=" + hostName, 117 | }, 118 | Command: commandArgs, 119 | AutoRemove: true, 120 | Tty: term.IsTerminal(os.Stdout.Fd()), 121 | WorkingDir: containerCwd, 122 | } 123 | 124 | var serviceOptions []func(config *types.ServiceConfig) error 125 | 126 | if optionPort { 127 | var port = dockerizedOptions["-p"] 128 | if port == "" { 129 | return fmt.Errorf("port option requires a port number"), 1 130 | } 131 | if optionVerbose { 132 | fmt.Printf("Mapping port: %s\n", port) 133 | } 134 | serviceOptions = append(serviceOptions, func(config *types.ServiceConfig) error { 135 | if !strings.ContainsRune(port, ':') { 136 | port = port + ":" + port 137 | } 138 | portConfig, err := types.ParsePortConfig(port) 139 | if err != nil { 140 | return err 141 | } 142 | config.Ports = portConfig 143 | return nil 144 | }) 145 | } 146 | 147 | volumes := []types.ServiceVolumeConfig{ 148 | { 149 | Type: "bind", 150 | Source: hostCwd, 151 | Target: containerCwd, 152 | }} 153 | 154 | if optionBuild { 155 | if optionVerbose { 156 | fmt.Printf("Building container image for %s...\n", commandName) 157 | } 158 | err := DockerComposeBuild(composeFilePaths, api.BuildOptions{ 159 | Services: []string{commandName}, 160 | Pull: optionBuildPull, 161 | NoCache: optionBuildNoCache, 162 | }) 163 | 164 | if err != nil { 165 | return err, 1 166 | } 167 | } 168 | 169 | if optionShell && optionEntrypoint { 170 | return fmt.Errorf("--shell and --entrypoint are mutually exclusive"), 1 171 | } 172 | 173 | if optionShell { 174 | if optionVerbose { 175 | fmt.Printf("Opening shell in container for %s...\n", commandName) 176 | 177 | if len(commandArgs) > 0 { 178 | fmt.Printf("Passing arguments to shell: %s\n", commandArgs) 179 | } 180 | } 181 | 182 | var ps1 = fmt.Sprintf( 183 | "%s %s:\\w \\$ ", 184 | color.BlueString("dockerized %s", commandName), 185 | color.New(color.FgHiBlue).Add(color.Bold).Sprintf("\\u@\\h"), 186 | ) 187 | var welcomeMessage = "Welcome to dockerized shell. Type 'exit' or press Ctrl+D to exit.\n" 188 | welcomeMessage += "Mounted volumes:\n" 189 | 190 | for _, volume := range volumes { 191 | welcomeMessage += fmt.Sprintf(" %s -> %s\n", volume.Source, volume.Target) 192 | } 193 | service, err := project.GetService(commandName) 194 | if err == nil { 195 | for _, volume := range service.Volumes { 196 | welcomeMessage += fmt.Sprintf(" %s -> %s\n", volume.Source, volume.Target) 197 | } 198 | } 199 | welcomeMessage = strings.ReplaceAll(welcomeMessage, "\\", "\\\\") 200 | 201 | shells := []string{ 202 | "bash", 203 | "zsh", 204 | "sh", 205 | } 206 | var shellDetectionCommands []string 207 | for _, shell := range shells { 208 | shellDetectionCommands = append(shellDetectionCommands, "command -v "+shell) 209 | } 210 | for _, shell := range shells { 211 | shellDetectionCommands = append(shellDetectionCommands, "which "+shell) 212 | } 213 | 214 | var cmdPrintWelcome = fmt.Sprintf("echo '%s'", color.YellowString(welcomeMessage)) 215 | var cmdLaunchShell = fmt.Sprintf("$(%s)", strings.Join(shellDetectionCommands, " || ")) 216 | 217 | runOptions.Environment = append(runOptions.Environment, "PS1="+ps1) 218 | runOptions.Entrypoint = []string{"/bin/sh"} 219 | 220 | if len(commandArgs) > 0 { 221 | runOptions.Command = []string{"-c", fmt.Sprintf("%s; %s \"%s\"", cmdPrintWelcome, cmdLaunchShell, strings.Join(commandArgs, "\" \""))} 222 | } else { 223 | runOptions.Command = []string{"-c", fmt.Sprintf("%s; %s", cmdPrintWelcome, cmdLaunchShell)} 224 | } 225 | } 226 | 227 | if optionEntrypoint { 228 | var entrypoint = dockerizedOptions["--entrypoint"] 229 | if optionVerbose { 230 | fmt.Printf("Setting entrypoint to %s\n", entrypoint) 231 | } 232 | runOptions.Entrypoint = strings.Split(entrypoint, " ") 233 | } 234 | 235 | if !contains(project.ServiceNames(), commandName) { 236 | image := "r.j3ss.co/" + commandName 237 | if optionVerbose { 238 | fmt.Printf("Service %s not found in compose file(s). Fallback to: %s.\n", commandName, image) 239 | fmt.Printf(" This command, if it exists, will not support version switching.\n") 240 | fmt.Printf(" See: https://github.com/jessfraz/dockerfiles\n") 241 | } 242 | return DockerRun(image, runOptions, volumes) 243 | } 244 | 245 | return DockerComposeRun(project, runOptions, volumes, serviceOptions...) 246 | } 247 | 248 | func parseArguments(args []string) (map[string]string, string, string, []string) { 249 | var options = []string{ 250 | OptionBuild, 251 | OptionBuildPull, 252 | OptionBuildNoCache, 253 | OptionHelp, 254 | ShortOptionHelp, 255 | ShortOptionPort, 256 | OptionShell, 257 | OptionEntrypoint, 258 | ShortOptionVerbose, 259 | OptionVerbose, 260 | OptionVersion, 261 | } 262 | 263 | var optionsWithParameters = []string{ 264 | "-p", 265 | "--entrypoint", 266 | } 267 | 268 | commandName := "" 269 | var commandArgs []string 270 | var dockerizedOptions []string 271 | var commandVersion string 272 | 273 | var optionMap = make(map[string]string) 274 | var optionBefore = "" 275 | 276 | for _, arg := range args { 277 | if arg[0] == '-' && commandName == "" { 278 | if util.Contains(options, arg) { 279 | var option = arg 280 | dockerizedOptions = append(dockerizedOptions, option) 281 | optionBefore = option 282 | optionMap[option] = "" 283 | } else { 284 | fmt.Println("Unknown option:", arg) 285 | os.Exit(1) 286 | } 287 | } else { 288 | if contains(optionsWithParameters, optionBefore) { 289 | optionMap[optionBefore] = arg 290 | } else if commandName == "" { 291 | commandName = arg 292 | } else { 293 | commandArgs = append(commandArgs, arg) 294 | } 295 | optionBefore = "" 296 | } 297 | } 298 | if strings.ContainsRune(commandName, ':') { 299 | commandSplit := strings.Split(commandName, ":") 300 | commandName = commandSplit[0] 301 | commandVersion = commandSplit[1] 302 | if commandVersion == "" { 303 | commandVersion = "?" 304 | } 305 | } 306 | return optionMap, commandName, commandVersion, commandArgs 307 | } 308 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | dockerized "github.com/datastack-net/dockerized/pkg" 6 | "github.com/stretchr/testify/assert" 7 | "io/ioutil" 8 | "k8s.io/apimachinery/pkg/util/rand" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | var initialEnv = os.Environ() 17 | 18 | type Context struct { 19 | homePath string 20 | before []func() 21 | after []func() 22 | envBefore []string 23 | cwdBefore string 24 | } 25 | 26 | func TestHelp(t *testing.T) { 27 | output := testDockerized(t, []string{"--help"}) 28 | assert.Contains(t, output, "Usage:") 29 | } 30 | 31 | func TestEntrypoint(t *testing.T) { 32 | var projectDir = dockerized.GetDockerizedRoot() + "/test/test_entrypoint" 33 | defer context(). 34 | WithDir(projectDir). 35 | WithCwd(projectDir). 36 | WithFile("foo.txt", "foo"). 37 | WithFile("bar.txt", "bar"). 38 | Restore() 39 | 40 | output := testDockerized(t, []string{"--entrypoint", "ls", "go"}) 41 | assert.Contains(t, output, "foo.txt") 42 | assert.Contains(t, output, "bar.txt") 43 | } 44 | 45 | func TestOverrideVersionWithEnvVar(t *testing.T) { 46 | defer context().WithEnv("PROTOC_VERSION", "3.6.0").Restore() 47 | var output = testDockerized(t, []string{"protoc", "--version"}) 48 | assert.Contains(t, output, "libprotoc 3.6.0") 49 | } 50 | 51 | func TestLocalEnvFileOverridesGlobalEnvFile(t *testing.T) { 52 | var projectPath = dockerized.GetDockerizedRoot() + "/test/project_override_global" 53 | defer context(). 54 | WithTempHome(). 55 | WithHomeEnvFile("PROTOC_VERSION=3.6.0"). 56 | WithDir(projectPath). 57 | WithCwd(projectPath). 58 | WithFile(projectPath+"/dockerized.env", "PROTOC_VERSION=3.8.0"). 59 | Restore() 60 | var output = testDockerized(t, []string{"-v", "protoc", "--version"}) 61 | assert.Contains(t, output, "libprotoc 3.8.0") 62 | } 63 | 64 | func TestRuntimeEnvOverridesLocalEnvFile(t *testing.T) { 65 | var projectPath = dockerized.GetDockerizedRoot() + "/test/project_override_global" 66 | defer context(). 67 | WithTempHome(). 68 | WithDir(projectPath). 69 | WithCwd(projectPath). 70 | WithFile(projectPath+"/dockerized.env", "PROTOC_VERSION=3.8.0"). 71 | WithEnv("PROTOC_VERSION", "3.16.1"). 72 | Restore() 73 | var output = testDockerized(t, []string{"protoc", "--version"}) 74 | assert.Contains(t, output, "libprotoc 3.16.1") 75 | } 76 | 77 | func TestCustomGlobalComposeFileAdditionalService(t *testing.T) { 78 | defer context(). 79 | WithTempHome(). 80 | WithHomeEnvFile(`COMPOSE_FILE="${COMPOSE_FILE};${HOME}/docker-compose.yml"`). 81 | WithHomeFile("docker-compose.yml", ` 82 | version: "3" 83 | services: 84 | test: 85 | image: alpine 86 | `). 87 | Restore() 88 | var output = testDockerized(t, []string{"test", "uname"}) 89 | assert.Contains(t, output, "Linux") 90 | } 91 | 92 | func TestUserCanGloballyCustomizeDockerizedCommands(t *testing.T) { 93 | defer context(). 94 | WithTempHome(). 95 | WithHomeEnvFile(`COMPOSE_FILE="${COMPOSE_FILE};${HOME}/docker-compose.yml"`). 96 | WithHomeFile("docker-compose.yml", ` 97 | version: "3" 98 | services: 99 | alpine: 100 | environment: 101 | CUSTOM: "CUSTOM_123456" 102 | `). 103 | Restore() 104 | var output = testDockerized(t, []string{"alpine", "env"}) 105 | assert.Contains(t, output, "CUSTOM_123456") 106 | } 107 | 108 | func TestUserCanLocallyCustomizeDockerizedCommands(t *testing.T) { 109 | projectPath := dockerized.GetDockerizedRoot() + "/test/project_with_customized_service" 110 | projectSubPath := projectPath + "/sub" 111 | 112 | defer context(). 113 | WithTempHome(). 114 | WithDir(projectPath). 115 | WithDir(projectSubPath). 116 | WithCwd(projectSubPath). 117 | WithFile(projectPath+"/dockerized.env", `COMPOSE_FILE="${COMPOSE_FILE};${DOCKERIZED_PROJECT_ROOT}/docker-compose.yml"`). 118 | WithFile(projectPath+"/docker-compose.yml", ` 119 | version: "3" 120 | services: 121 | alpine: 122 | environment: 123 | CUSTOM: "CUSTOM_123456" 124 | `). 125 | Restore() 126 | var output = testDockerized(t, []string{"-v", "alpine", "env"}) 127 | assert.Contains(t, output, "CUSTOM_123456") 128 | } 129 | 130 | func TestUserCanIncludeGlobalAndProjectComposeFile(t *testing.T) { 131 | projectPath := dockerized.GetDockerizedRoot() + "/test/project" + strconv.Itoa(rand.Int()) 132 | 133 | context := context(). 134 | WithTempHome(). 135 | WithHomeEnvFile(`COMPOSE_FILE="${COMPOSE_FILE};${HOME}/docker-compose.dockerized.yml"`). 136 | WithHomeFile("docker-compose.dockerized.yml", ` 137 | version: "3" 138 | services: 139 | home_cmd: 140 | image: "alpine" 141 | entrypoint: [ "echo", "HOME123" ] 142 | `). 143 | WithDir(projectPath). 144 | WithCwd(projectPath). 145 | WithFile(projectPath+"/dockerized.env", `COMPOSE_FILE="${COMPOSE_FILE};${DOCKERIZED_PROJECT_ROOT}/docker-compose.dockerized.yml"`). 146 | WithFile(projectPath+"/docker-compose.dockerized.yml", ` 147 | version: "3" 148 | services: 149 | project_cmd: 150 | image: "alpine" 151 | entrypoint: [ "echo", "PROJECT123" ] 152 | `) 153 | defer context.Restore() 154 | var outputProjectCmd = testDockerized(t, []string{"-v", "project_cmd"}) 155 | assert.Contains(t, outputProjectCmd, "PROJECT123") 156 | 157 | var outputHomeCmd = testDockerized(t, []string{"-v", "home_cmd"}) 158 | assert.Contains(t, outputHomeCmd, "HOME123") 159 | 160 | } 161 | 162 | func (c *Context) WithEnv(key string, value string) *Context { 163 | _ = os.Setenv(key, value) 164 | return c 165 | } 166 | 167 | func (c *Context) WithHome(path string) *Context { 168 | c.homePath = path 169 | c.WithDir(path) 170 | c.WithEnv("HOME", path) 171 | c.WithEnv("USERPROFILE", path) 172 | return c 173 | } 174 | 175 | func (c *Context) WithTempHome() *Context { 176 | var homePath = filepath.Join(dockerized.GetDockerizedRoot(), "test", "home"+strconv.Itoa(rand.Int())) 177 | c.WithHome(homePath) 178 | return c 179 | } 180 | 181 | func (c *Context) WithCwd(path string) *Context { 182 | c.cwdBefore, _ = os.Getwd() 183 | err := os.Chdir(path) 184 | if err != nil { 185 | panic(err) 186 | } 187 | c.after = append(c.after, func() { 188 | os.Chdir(c.cwdBefore) 189 | }) 190 | return c 191 | } 192 | 193 | func (c *Context) WithHomeEnvFile(content string) *Context { 194 | return c.WithHomeFile("dockerized.env", content) 195 | } 196 | 197 | func (c *Context) WithFile(path string, content string) *Context { 198 | _ = os.WriteFile(path, []byte(content), 0644) 199 | c.after = append(c.after, func() { 200 | _ = os.Remove(path) 201 | }) 202 | return c 203 | } 204 | 205 | func (c *Context) WithHomeFile(path string, content string) *Context { 206 | return c.WithFile(filepath.Join(c.homePath, path), content) 207 | } 208 | 209 | func (c *Context) WithDir(path string) *Context { 210 | _ = os.MkdirAll(path, os.ModePerm) 211 | c.after = append(c.after, func() { 212 | _ = os.RemoveAll(path) 213 | }) 214 | return c 215 | } 216 | 217 | func (c *Context) Execute(callback func()) { 218 | for _, before := range c.before { 219 | before() 220 | } 221 | defer func() { 222 | for _, after := range c.after { 223 | after() 224 | } 225 | }() 226 | callback() 227 | } 228 | 229 | func restoreInitialEnv() { 230 | os.Clearenv() 231 | for _, envEntry := range initialEnv { 232 | keyValue := strings.Split(envEntry, "=") 233 | os.Setenv(keyValue[0], keyValue[1]) 234 | } 235 | } 236 | 237 | func (c *Context) Restore() { 238 | defer restoreInitialEnv() 239 | for _, after := range c.after { 240 | //goland:noinspection GoDeferInLoop 241 | defer after() 242 | } 243 | } 244 | 245 | func context() *Context { 246 | restoreInitialEnv() 247 | return &Context{ 248 | envBefore: os.Environ(), 249 | } 250 | } 251 | 252 | func TestOverrideVersionWithGlobalEnvFile(t *testing.T) { 253 | defer context(). 254 | WithHome(dockerized.GetDockerizedRoot() + "/test/home"). 255 | WithHomeEnvFile("PROTOC_VERSION=3.8.0"). 256 | Restore() 257 | 258 | var output = testDockerized(t, []string{"protoc", "--version"}) 259 | 260 | assert.Contains(t, output, "3.8.0") 261 | } 262 | 263 | func capture(callback func()) string { 264 | read, write, _ := os.Pipe() 265 | os.Stdout = write 266 | 267 | callback() 268 | 269 | os.Stdout.Close() 270 | bytes, _ := ioutil.ReadAll(read) 271 | var output = string(bytes) 272 | return output 273 | } 274 | 275 | func testDockerized(t *testing.T, args []string) string { 276 | var err error 277 | var exitCode int 278 | var output = capture(func() { 279 | err, exitCode = RunCli(args) 280 | }) 281 | println(output) 282 | assert.Nil(t, err, fmt.Sprintf("error: %s", err)) 283 | assert.Equal(t, 0, exitCode) 284 | return output 285 | } 286 | -------------------------------------------------------------------------------- /pkg/dockerized.go: -------------------------------------------------------------------------------- 1 | package dockerized 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/compose-spec/compose-go/dotenv" 6 | "github.com/datastack-net/dockerized/pkg/util" 7 | "github.com/hashicorp/go-version" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "github.com/compose-spec/compose-go/cli" 16 | "github.com/compose-spec/compose-go/loader" 17 | "github.com/compose-spec/compose-go/types" 18 | "github.com/docker/cli/cli/command" 19 | "github.com/docker/cli/cli/flags" 20 | "github.com/docker/compose/v2/pkg/api" 21 | "github.com/docker/compose/v2/pkg/compose" 22 | "github.com/docker/distribution/reference" 23 | "github.com/docker/hub-tool/pkg/hub" 24 | "os" 25 | "os/signal" 26 | "path/filepath" 27 | "regexp" 28 | "sort" 29 | "strings" 30 | "syscall" 31 | ) 32 | 33 | // Determine which docker-compose file to use. Assumes .env files are already loaded. 34 | func GetComposeFilePaths(dockerizedRoot string) []string { 35 | var composeFilePaths []string 36 | composeFilePath := os.Getenv("COMPOSE_FILE") 37 | if composeFilePath == "" { 38 | composeFilePaths = append(composeFilePaths, filepath.Join(dockerizedRoot, "docker-compose.yml")) 39 | } else { 40 | composePathSeparator := os.Getenv("COMPOSE_PATH_SEPARATOR") 41 | if composePathSeparator == "" { 42 | composePathSeparator = ";" 43 | } 44 | composeFilePaths = strings.Split(composeFilePath, composePathSeparator) 45 | } 46 | return composeFilePaths 47 | } 48 | 49 | func getNpmPackageVersions(packageName string) ([]string, error) { 50 | var registryUrl = "https://registry.npmjs.org/" + packageName 51 | request, err := http.NewRequest(http.MethodGet, registryUrl, nil) 52 | if err != nil { 53 | return nil, err 54 | } 55 | request.Header.Add("Accept", "application/vnd.npm.install-v1+json") 56 | 57 | client := &http.Client{} 58 | response, err := client.Do(request) 59 | if err != nil { 60 | return nil, err 61 | } 62 | defer func(Body io.ReadCloser) { 63 | err := Body.Close() 64 | if err != nil { 65 | panic(err) 66 | } 67 | }(response.Body) 68 | 69 | // parse json 70 | var registryResponse map[string]interface{} 71 | err = json.NewDecoder(response.Body).Decode(®istryResponse) 72 | if err != nil { 73 | return nil, err 74 | } 75 | // read versions 76 | var versions = registryResponse["versions"].(map[string]interface{}) 77 | var versionKeys = make([]string, 0, len(versions)) 78 | for k := range versions { 79 | versionKeys = append(versionKeys, k) 80 | } 81 | sort.Strings(versionKeys) 82 | return versionKeys, nil 83 | } 84 | 85 | func PrintCommandVersions(composeFilePaths []string, commandName string, verbose bool) error { 86 | project, err := GetProject(composeFilePaths) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | service, err := project.GetService(commandName) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | var semanticVersions []string 97 | var rawVersions []string 98 | 99 | isNpmPackage := len(service.Entrypoint) > 0 && service.Entrypoint[0] == "npx" 100 | if isNpmPackage { 101 | var packagePattern = regexp.MustCompile(`--package=(@?[^@]+)@([^\s]+)`) 102 | packageArgument := service.Entrypoint[1] 103 | packageMatch := packagePattern.FindStringSubmatch(packageArgument) 104 | var packageName = packageMatch[1] 105 | rawVersions, err = getNpmPackageVersions(packageName) 106 | if err != nil { 107 | return err 108 | } 109 | } else { 110 | // isDockerHubImage 111 | if service.Build != nil { 112 | fmt.Printf("Cannot determine versions for command %s because it has a build step.\n", commandName) 113 | os.Exit(1) 114 | } 115 | 116 | ref, err := reference.ParseDockerRef(service.Image) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | refDomain := reference.Domain(ref) 122 | 123 | if refDomain != "docker.io" { 124 | fmt.Printf("Listing versions for commands is currently only supported for docker.io images.\n") 125 | os.Exit(1) 126 | } 127 | 128 | refPath := reference.Path(ref) 129 | 130 | hubClient, err := hub.NewClient(hub.WithAllElements()) 131 | 132 | if err != nil { 133 | return err 134 | } 135 | tags, _, err := hubClient.GetTags(refPath) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | for _, tag := range tags { 141 | var tagParts = strings.Split(tag.Name, ":") 142 | var tagVersion = tagParts[1] 143 | rawVersions = append(rawVersions, tagVersion) 144 | } 145 | } 146 | sort.Strings(rawVersions) 147 | rawVersions = unique(rawVersions) 148 | semanticVersions, err = getSemanticVersions(rawVersions) 149 | sortVersions(semanticVersions) 150 | semanticVersions = unique(semanticVersions) 151 | 152 | if verbose { 153 | fmt.Printf("\n") 154 | fmt.Printf("Raw versions:\n") 155 | for _, rawVersion := range rawVersions { 156 | fmt.Printf("%s\n", rawVersion) 157 | } 158 | fmt.Printf("\n") 159 | } 160 | 161 | if len(semanticVersions) == 0 { 162 | fmt.Printf("No parseable versions found for command %s.\n", commandName) 163 | if len(rawVersions) > 0 { 164 | fmt.Printf("Found: %s\n", strings.Join(rawVersions, ", ")) 165 | } 166 | os.Exit(1) 167 | } 168 | 169 | var versionGroups = make(map[string][]string) 170 | var versionGroupKeys []string 171 | for _, semanticVersion := range semanticVersions { 172 | var v, err = version.NewVersion(semanticVersion) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | var versionGroup string 178 | var segments = v.Segments() 179 | if len(segments) >= 2 { 180 | versionGroup = fmt.Sprintf("%d.%d", segments[0], segments[1]) 181 | } else { 182 | versionGroup = fmt.Sprintf("%d.0", segments[0]) 183 | } 184 | versionGroups[versionGroup] = append(versionGroups[versionGroup], semanticVersion) 185 | versionGroupKeys = append(versionGroupKeys, versionGroup) 186 | } 187 | versionGroupKeys = unique(versionGroupKeys) 188 | sortVersions(versionGroupKeys) 189 | 190 | for _, versionGroup := range versionGroupKeys { 191 | var versions = versionGroups[versionGroup] 192 | fmt.Printf("%s\n", strings.Join(versions, ", ")) 193 | } 194 | return nil 195 | } 196 | 197 | func getSemanticVersions(rawVersions []string) ([]string, error) { 198 | var semanticVersions []string 199 | for _, rawVersion := range rawVersions { 200 | var semanticVersion = regexp.MustCompile(`^v?(\d+(\.\d+)*$)`).FindStringSubmatch(rawVersion) 201 | if semanticVersion != nil { 202 | semanticVersions = append(semanticVersions, semanticVersion[1]) 203 | } 204 | } 205 | return semanticVersions, nil 206 | } 207 | 208 | func sortVersions(versions []string) { 209 | sort.Slice(versions, func(i, j int) bool { 210 | v1, e1 := version.NewVersion(versions[i]) 211 | v2, e2 := version.NewVersion(versions[j]) 212 | return e1 == nil && e2 == nil && v1.LessThan(v2) 213 | }) 214 | } 215 | 216 | func SetCommandVersion(composeFilePaths []string, commandName string, optionVerbose bool, commandVersion string) { 217 | rawProject, err := getRawProject(composeFilePaths) 218 | if err != nil { 219 | panic(err) 220 | } 221 | 222 | rawService, err := rawProject.GetService(commandName) 223 | 224 | var versionVariableExpected = strings.ReplaceAll(strings.ToUpper(commandName), "-", "_") + "_VERSION" 225 | var variablesUsed []string 226 | for _, variable := range ExtractVariables(rawService) { 227 | variablesUsed = append(variablesUsed, variable) 228 | } 229 | 230 | if len(variablesUsed) == 0 { 231 | fmt.Printf("Error: Version selection for %s is currently not supported.\n", commandName) 232 | os.Exit(1) 233 | } 234 | 235 | var versionVariablesUsed []string 236 | for _, variable := range variablesUsed { 237 | if strings.HasSuffix(variable, "_VERSION") { 238 | versionVariablesUsed = append(versionVariablesUsed, variable) 239 | } 240 | } 241 | versionKey := versionVariableExpected 242 | 243 | if !util.Contains(variablesUsed, versionVariableExpected) { 244 | if len(versionVariablesUsed) == 1 { 245 | fmt.Printf("Error: To specify the version of %s, please set %s.\n", 246 | commandName, 247 | versionVariablesUsed[0], 248 | ) 249 | os.Exit(1) 250 | } else if len(versionVariablesUsed) > 1 { 251 | fmt.Printf("Error: To specify the version of %s, please set one of:\n", commandName) 252 | for _, versionVariable := range versionVariablesUsed { 253 | fmt.Println(" " + versionVariable) 254 | } 255 | os.Exit(1) 256 | } 257 | } 258 | 259 | if optionVerbose { 260 | fmt.Printf("Setting %s to %s...\n", versionKey, commandVersion) 261 | } 262 | err = os.Setenv(versionKey, commandVersion) 263 | if err != nil { 264 | panic(err) 265 | } 266 | } 267 | 268 | func LoadEnvFiles(hostCwd string, optionVerbose bool) error { 269 | var envFiles []string 270 | 271 | // Default 272 | defaultEnvFile := filepath.Join(GetDockerizedRoot(), ".env") 273 | envFiles = append(envFiles, defaultEnvFile) 274 | 275 | // Global overrides 276 | homeDir, _ := os.UserHomeDir() 277 | globalUserEnvFile := filepath.Join(homeDir, dockerizedEnvFileName) 278 | if _, err := os.Stat(globalUserEnvFile); err == nil { 279 | envFiles = append(envFiles, globalUserEnvFile) 280 | } 281 | 282 | // Project overrides 283 | if projectEnvFile, err := findProjectEnvFile(hostCwd); err == nil { 284 | envFiles = append(envFiles, projectEnvFile) 285 | os.Setenv("DOCKERIZED_PROJECT_ROOT", filepath.Dir(projectEnvFile)) 286 | } 287 | 288 | envFiles = unique(envFiles) 289 | 290 | if optionVerbose { 291 | for _, envFile := range envFiles { 292 | fmt.Printf("Loading: '%s'\n", envFile) 293 | } 294 | } 295 | 296 | var envMap = make(map[string]string) 297 | err := func() error { 298 | for _, envFilePath := range envFiles { 299 | file, err := os.Open(envFilePath) 300 | if err != nil { 301 | return err 302 | } 303 | defer file.Close() 304 | 305 | envFileMap, err := dotenv.ParseWithLookup(file, func(key string) (string, bool) { 306 | // 1. Lookup in the environment 307 | var envValue = os.Getenv(key) 308 | if envValue != "" { 309 | return envValue, true 310 | } 311 | // 2. Lookup in previous env files 312 | if envMap[key] != "" { 313 | return envMap[key], true 314 | } 315 | return "", false 316 | }) 317 | if err != nil { 318 | return err 319 | } 320 | for key, value := range envFileMap { 321 | envMap[key] = value 322 | } 323 | } 324 | return nil 325 | }() 326 | 327 | if err != nil { 328 | return err 329 | } 330 | 331 | currentEnv := map[string]bool{} 332 | rawEnv := os.Environ() 333 | for _, rawEnvLine := range rawEnv { 334 | key := strings.Split(rawEnvLine, "=")[0] 335 | currentEnv[key] = true 336 | } 337 | 338 | for key, value := range envMap { 339 | if !currentEnv[key] { 340 | _ = os.Setenv(key, value) 341 | } 342 | } 343 | 344 | return nil 345 | } 346 | 347 | func dockerComposeRunAdHocService(service types.ServiceConfig, runOptions api.RunOptions) (error, int) { 348 | if service.Environment == nil { 349 | service.Environment = map[string]*string{} 350 | } 351 | return DockerComposeRun(&types.Project{ 352 | Name: "dockerized", 353 | Services: []types.ServiceConfig{ 354 | service, 355 | }, 356 | WorkingDir: GetDockerizedRoot(), 357 | }, runOptions, []types.ServiceVolumeConfig{}) 358 | } 359 | 360 | func DockerRun(image string, runOptions api.RunOptions, volumes []types.ServiceVolumeConfig) (error, int) { 361 | // Couldn't get 'docker run' to work, so instead define a Docker Compose Service and run that. 362 | // This coincidentally allows re-using the same code for both 'docker run' and 'docker-compose run' 363 | // - ContainerCreate is simple, but the logic to attach to it is very complex, and not exposed by the Docker SDK. 364 | // - Using [container.NewRunCommand] didn't work due to dependency compatibility issues. 365 | return dockerComposeRunAdHocService(types.ServiceConfig{ 366 | Name: runOptions.Service, 367 | Image: image, 368 | Volumes: volumes, 369 | }, runOptions) 370 | } 371 | 372 | var dockerizedEnvFileName = "dockerized.env" 373 | 374 | func GetDockerizedRoot() string { 375 | if os.Getenv("DOCKERIZED_ROOT") != "" { 376 | return os.Getenv("DOCKERIZED_ROOT") 377 | } 378 | executable, err := os.Executable() 379 | if err != nil { 380 | panic("Cannot detect dockerized root directory: " + err.Error()) 381 | } 382 | return filepath.Dir(filepath.Dir(executable)) 383 | } 384 | 385 | func findProjectEnvFile(path string) (string, error) { 386 | envFilePath := "" 387 | for i := 0; i < 10; i++ { 388 | envFilePath = filepath.Join(path, dockerizedEnvFileName) 389 | if _, err := os.Stat(envFilePath); err == nil { 390 | return envFilePath, nil 391 | } 392 | path = filepath.Dir(path) 393 | } 394 | return "", fmt.Errorf("no local %s found", dockerizedEnvFileName) 395 | } 396 | func NormalizeEnvironment(dockerizedRoot string) { 397 | _ = os.Setenv("DOCKERIZED_ROOT", dockerizedRoot) 398 | homeDir, _ := os.UserHomeDir() 399 | if os.Getenv("HOME") == "" { 400 | _ = os.Setenv("HOME", homeDir) 401 | } 402 | } 403 | 404 | func newSigContext() (context.Context, func()) { 405 | ctx, cancel := context.WithCancel(context.Background()) 406 | s := make(chan os.Signal, 1) 407 | signal.Notify(s, syscall.SIGTERM, syscall.SIGINT) 408 | go func() { 409 | <-s 410 | cancel() 411 | }() 412 | return ctx, cancel 413 | } 414 | 415 | func getRawProject(composeFilePaths []string) (*types.Project, error) { 416 | options, err := cli.NewProjectOptions(composeFilePaths, 417 | cli.WithInterpolation(false), 418 | cli.WithLoadOptions(func(l *loader.Options) { 419 | l.SkipValidation = true 420 | l.SkipConsistencyCheck = true 421 | l.SkipNormalization = true 422 | }), 423 | ) 424 | 425 | if err != nil { 426 | return nil, nil 427 | } 428 | 429 | return cli.ProjectFromOptions(options) 430 | } 431 | 432 | func GetProject(composeFilePaths []string) (*types.Project, error) { 433 | options, err := cli.NewProjectOptions([]string{}, 434 | cli.WithOsEnv, 435 | cli.WithConfigFileEnv, 436 | ) 437 | 438 | if err != nil { 439 | return nil, err 440 | } 441 | 442 | return cli.ProjectFromOptions(options) 443 | } 444 | 445 | func dockerComposeUpNetworkOnly(backend *api.ServiceProxy, ctx context.Context, project *types.Project) error { 446 | project.Services = []types.ServiceConfig{} 447 | upOptions := api.UpOptions{ 448 | Create: api.CreateOptions{ 449 | Services: []string{}, 450 | RemoveOrphans: true, 451 | Recreate: "always", 452 | }, 453 | } 454 | err := backend.Up(ctx, project, upOptions) 455 | 456 | // docker compose up will return error if there is no service to start, but the network will have been created. 457 | expectedErrorMessage := "no container found for project \"" + project.Name + "\": not found" 458 | if err == nil || api.IsNotFoundError(err) && err.Error() == expectedErrorMessage { 459 | return nil 460 | } 461 | return err 462 | } 463 | 464 | func getDockerCli() (*command.DockerCli, error) { 465 | dockerCli, err := command.NewDockerCli() 466 | if err != nil { 467 | return nil, err 468 | } 469 | dockerCliOpts := flags.NewClientOptions() 470 | err = dockerCli.Initialize(dockerCliOpts) 471 | if err != nil { 472 | return nil, err 473 | } 474 | return dockerCli, nil 475 | } 476 | 477 | func getBackend() (*api.ServiceProxy, error) { 478 | dockerCli, err := getDockerCli() 479 | if err != nil { 480 | return nil, err 481 | } 482 | 483 | var backend = api.NewServiceProxy() 484 | composeService := compose.NewComposeService(dockerCli) 485 | backend.WithService(composeService) 486 | return backend, nil 487 | } 488 | 489 | func DockerComposeBuild(composeFilePaths []string, buildOptions api.BuildOptions) error { 490 | project, err := GetProject(composeFilePaths) 491 | if err != nil { 492 | return err 493 | } 494 | err = os.Chdir(project.WorkingDir) 495 | if err != nil { 496 | return err 497 | } 498 | 499 | backend, err := getBackend() 500 | if err != nil { 501 | return err 502 | } 503 | ctx, _ := newSigContext() 504 | return backend.Build(ctx, project, buildOptions) 505 | } 506 | 507 | func DockerComposeRun(project *types.Project, runOptions api.RunOptions, volumes []types.ServiceVolumeConfig, serviceOptions ...func(config *types.ServiceConfig) error) (error, int) { 508 | err := os.Chdir(project.WorkingDir) 509 | if err != nil { 510 | return err, 1 511 | } 512 | ctx, _ := newSigContext() 513 | 514 | serviceName := runOptions.Service 515 | 516 | service, err := project.GetService(serviceName) 517 | if service.CustomLabels == nil { 518 | service.CustomLabels = map[string]string{} 519 | } 520 | 521 | stopGracePeriod := types.Duration(1) 522 | service.Volumes = append(service.Volumes, volumes...) 523 | 524 | for _, serviceOption := range serviceOptions { 525 | err = serviceOption(&service) 526 | if err != nil { 527 | return err, 1 528 | } 529 | } 530 | 531 | service.StopGracePeriod = &stopGracePeriod 532 | service.StdinOpen = true 533 | 534 | backend, err := getBackend() 535 | if err != nil { 536 | return err, 1 537 | } 538 | 539 | err = dockerComposeUpNetworkOnly(backend, ctx, project) 540 | if err != nil { 541 | return err, 1 542 | } 543 | 544 | project.Services = []types.ServiceConfig{service} 545 | 546 | exitCode, err := backend.RunOneOffContainer(ctx, project, runOptions) 547 | if err != nil { 548 | return err, exitCode 549 | } 550 | if exitCode != 0 { 551 | return fmt.Errorf("%s exited with code %d", serviceName, exitCode), exitCode 552 | } 553 | return nil, 0 554 | } 555 | 556 | func unique(s []string) []string { 557 | keys := make(map[string]bool) 558 | var list []string 559 | for _, entry := range s { 560 | if _, value := keys[entry]; !value { 561 | keys[entry] = true 562 | list = append(list, entry) 563 | } 564 | } 565 | return list 566 | } 567 | 568 | func ExtractVariables(rawService types.ServiceConfig) []string { 569 | var usedVariables []string 570 | for envKey := range rawService.Environment { 571 | usedVariables = append(usedVariables, envKey) 572 | } 573 | if rawService.Build != nil { 574 | for _, argValue := range rawService.Build.Args { 575 | for _, argValueVariable := range ExtractVariablesFromString(*argValue) { 576 | usedVariables = append(usedVariables, argValueVariable) 577 | } 578 | } 579 | } 580 | for _, imageVariable := range ExtractVariablesFromString(rawService.Image) { 581 | usedVariables = append(usedVariables, imageVariable) 582 | } 583 | 584 | for _, entryPointArgument := range rawService.Entrypoint { 585 | for _, entryPointVariable := range ExtractVariablesFromString(entryPointArgument) { 586 | usedVariables = append(usedVariables, entryPointVariable) 587 | } 588 | } 589 | 590 | usedVariables = unique(usedVariables) 591 | sort.Strings(usedVariables) 592 | 593 | return usedVariables 594 | } 595 | 596 | func ExtractVariablesFromString(value string) []string { 597 | var usedVariables []string 598 | pattern := regexp.MustCompile(`\${([^}]+)}`) 599 | for _, match := range pattern.FindAllStringSubmatch(value, -1) { 600 | usedVariables = append(usedVariables, match[1]) 601 | } 602 | return usedVariables 603 | } 604 | -------------------------------------------------------------------------------- /pkg/help/help.go: -------------------------------------------------------------------------------- 1 | package help 2 | 3 | import ( 4 | "fmt" 5 | dockerized "github.com/datastack-net/dockerized/pkg" 6 | "sort" 7 | ) 8 | 9 | func Help(composeFilePaths []string) error { 10 | project, err := dockerized.GetProject(composeFilePaths) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | fmt.Println("Usage: dockerized [options] [:version] [arguments]") 16 | fmt.Println("") 17 | fmt.Println("Examples:") 18 | fmt.Println(" dockerized go") 19 | fmt.Println(" dockerized go:1.8 build") 20 | fmt.Println(" dockerized --shell go") 21 | fmt.Println(" dockerized go:?") 22 | fmt.Println("") 23 | 24 | fmt.Println("Commands:") 25 | services := project.ServiceNames() 26 | sort.Strings(services) 27 | for _, service := range services { 28 | fmt.Printf(" %s\n", service) 29 | } 30 | fmt.Println() 31 | 32 | fmt.Println("Options:") 33 | fmt.Println(" --build Rebuild the container before running it.") 34 | fmt.Println(" --pull Pull the latest version of the container before building it (with --build).") 35 | fmt.Println(" --no-cache Do not use cache when building the container (with --build).") 36 | fmt.Println(" --shell Start a shell inside the command container. Similar to `docker run --entrypoint=sh`.") 37 | fmt.Println(" --entrypoint ") 38 | fmt.Println(" Override the default entrypoint of the command container.") 39 | fmt.Println(" -p Exposes given port to host, e.g. -p 8080") 40 | fmt.Println(" -p : Maps host port to container port, e.g. -p 80:8080") 41 | fmt.Println(" -v, --verbose Log what dockerized is doing.") 42 | fmt.Println(" -h, --help Show this help.") 43 | fmt.Println() 44 | 45 | fmt.Println("Version:") 46 | fmt.Println(" : The version of the command to run, e.g. 1, 1.8, 1.8.1.") 47 | fmt.Println(" :? List all available versions. E.g. `dockerized go:?`") 48 | fmt.Println(" : Same as ':?' .") 49 | fmt.Println() 50 | 51 | fmt.Println("Arguments:") 52 | fmt.Println(" All arguments after are passed to the command itself.") 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/options.go: -------------------------------------------------------------------------------- 1 | package dockerized 2 | 3 | const ( 4 | OptionBuild = "--build" 5 | OptionBuildPull = "--pull" 6 | OptionBuildNoCache = "--no-cache" 7 | OptionHelp = "--help" 8 | OptionShell = "--shell" 9 | OptionEntrypoint = "--entrypoint" 10 | OptionVerbose = "--verbose" 11 | OptionVersion = "--version" 12 | ) 13 | 14 | const ( 15 | ShortOptionHelp = "-h" 16 | ShortOptionPort = "-p" 17 | ShortOptionVerbose = "-v" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func Contains(s []string, str string) bool { 4 | for _, v := range s { 5 | if v == str { 6 | return true 7 | } 8 | } 9 | 10 | return false 11 | } 12 | 13 | func HasKey(m map[string]string, key string) bool { 14 | _, ok := m[key] 15 | return ok 16 | } 17 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: ['master'], 3 | plugins: [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | ["@semantic-release/github", { 7 | "assets": [ 8 | { 9 | "path": "release/win32.zip", 10 | "label": "Windows (32bit)", 11 | "name": "dockerized-${nextRelease.gitTag}-win32.zip" 12 | }, 13 | { 14 | "path": "release/win64.zip", 15 | "label": "Windows (64bit)", 16 | "name": "dockerized-${nextRelease.gitTag}-win64.zip" 17 | }, 18 | { 19 | "path": "release/linux-x86_64.zip", 20 | "label": "Linux (x86/64)", 21 | "name": "dockerized-${nextRelease.gitTag}-linux-x86_64.zip" 22 | }, 23 | { 24 | "path": "release/mac-x86_64.zip", 25 | "label": "MacOS (Intel)", 26 | "name": "dockerized-${nextRelease.gitTag}-mac-x86_64.zip" 27 | }, 28 | { 29 | "path": "release/mac-arm64.zip", 30 | "label": "MacOS (Apple Silicon)", 31 | "name": "dockerized-${nextRelease.gitTag}-mac-arm64.zip" 32 | }, 33 | ] 34 | }], 35 | '@semantic-release/changelog', 36 | '@semantic-release/git', 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /terminalizer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastack-net/dockerized/79f19ae73f408e3170e85dd5fe2f540e6c554cdb/terminalizer.gif -------------------------------------------------------------------------------- /terminalizer.yml: -------------------------------------------------------------------------------- 1 | # The configurations that used for the recording, feel free to edit them 2 | config: 3 | 4 | # Specify a command to be executed 5 | # like `/bin/bash -l`, `ls`, or any other commands 6 | # the default is bash for Linux 7 | # or powershell.exe for Windows 8 | command: bash -l 9 | 10 | # Specify the current working directory path 11 | # the default is the current working directory path 12 | cwd: /home/demo 13 | 14 | # Export additional ENV variables 15 | env: 16 | recording: true 17 | 18 | # Explicitly set the number of columns 19 | # or use `auto` to take the current 20 | # number of columns of your shell 21 | cols: 102 22 | 23 | # Explicitly set the number of rows 24 | # or use `auto` to take the current 25 | # number of rows of your shell 26 | rows: 12 27 | 28 | # Amount of times to repeat GIF 29 | # If value is -1, play once 30 | # If value is 0, loop indefinitely 31 | # If value is a positive number, loop n times 32 | repeat: 0 33 | 34 | # Quality 35 | # 1 - 100 36 | quality: 100 37 | 38 | # Delay between frames in ms 39 | # If the value is `auto` use the actual recording delays 40 | frameDelay: auto 41 | 42 | # Maximum delay between frames in ms 43 | # Ignored if the `frameDelay` isn't set to `auto` 44 | # Set to `auto` to prevent limiting the max idle time 45 | maxIdleTime: 2000 46 | 47 | # The surrounding frame box 48 | # The `type` can be null, window, floating, or solid` 49 | # To hide the title use the value null 50 | # Don't forget to add a backgroundColor style with a null as type 51 | frameBox: 52 | type: null 53 | title: null 54 | style: 55 | background: black 56 | padding: 8px 57 | 58 | # Add a watermark image to the rendered gif 59 | # You need to specify an absolute path for 60 | # the image on your machine or a URL, and you can also 61 | # add your own CSS styles 62 | watermark: 63 | imagePath: null 64 | style: 65 | position: absolute 66 | right: 15px 67 | bottom: 15px 68 | width: 100px 69 | opacity: 0.9 70 | 71 | # Cursor style can be one of 72 | # `block`, `underline`, or `bar` 73 | cursorStyle: block 74 | 75 | # Font family 76 | # You can use any font that is installed on your machine 77 | # in CSS-like syntax 78 | fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace" 79 | 80 | # The size of the font 81 | fontSize: 16 82 | 83 | # The height of lines 84 | lineHeight: 1.1 85 | 86 | # The spacing between letters 87 | letterSpacing: 0 88 | 89 | # Theme 90 | theme: 91 | background: "transparent" 92 | foreground: "#afafaf" 93 | cursor: "#c7c7c7" 94 | black: "#232628" 95 | red: "#fc4384" 96 | green: "#b3e33b" 97 | yellow: "#ffa727" 98 | blue: "#75dff2" 99 | magenta: "#ae89fe" 100 | cyan: "#708387" 101 | white: "#d5d5d0" 102 | brightBlack: "#626566" 103 | brightRed: "#ff7fac" 104 | brightGreen: "#c8ed71" 105 | brightYellow: "#ebdf86" 106 | brightBlue: "#75dff2" 107 | brightMagenta: "#ae89fe" 108 | brightCyan: "#b1c6ca" 109 | brightWhite: "#f9f9f4" 110 | 111 | # Records, feel free to edit them 112 | records: 113 | - delay: 445 114 | content: "\r\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~\e[0m$ " 115 | - delay: 2432 116 | content: w 117 | - delay: 90 118 | content: h 119 | - delay: 45 120 | content: i 121 | - delay: 120 122 | content: c 123 | - delay: 75 124 | content: h 125 | - delay: 75 126 | content: ' ' 127 | - delay: 75 128 | content: p 129 | - delay: 195 130 | content: 'y' 131 | - delay: 195 132 | content: t 133 | - delay: 195 134 | content: h 135 | - delay: 105 136 | content: o 137 | - delay: 195 138 | content: 'n' 139 | - delay: 255 140 | content: "\r\n\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~\e[0m$ " 141 | - delay: 1124 142 | content: '# ' 143 | - delay: 180 144 | content: p 145 | - delay: 180 146 | content: 'y' 147 | - delay: 92 148 | content: t 149 | - delay: 58 150 | content: h 151 | - delay: 75 152 | content: o 153 | - delay: 105 154 | content: 'n' 155 | - delay: 45 156 | content: ' ' 157 | - delay: 90 158 | content: i 159 | - delay: 106 160 | content: s 161 | - delay: 59 162 | content: ' ' 163 | - delay: 60 164 | content: 'n' 165 | - delay: 59 166 | content: o 167 | - delay: 91 168 | content: t 169 | - delay: 105 170 | content: ' ' 171 | - delay: 75 172 | content: i 173 | - delay: 75 174 | content: 'n' 175 | - delay: 75 176 | content: s 177 | - delay: 75 178 | content: t 179 | - delay: 60 180 | content: a 181 | - delay: 15 182 | content: l 183 | - delay: 120 184 | content: l 185 | - delay: 91 186 | content: e 187 | - delay: 59 188 | content: d 189 | - delay: 105 190 | content: "\r\n\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~\e[0m$ " 191 | - delay: 1590 192 | content: d 193 | - delay: 30 194 | content: o 195 | - delay: 105 196 | content: c 197 | - delay: 75 198 | content: k 199 | - delay: 90 200 | content: e 201 | - delay: 30 202 | content: r 203 | - delay: 150 204 | content: i 205 | - delay: 120 206 | content: z 207 | - delay: 30 208 | content: e 209 | - delay: 75 210 | content: d 211 | - delay: 151 212 | content: ' ' 213 | - delay: 134 214 | content: p 215 | - delay: 180 216 | content: 'y' 217 | - delay: 106 218 | content: t 219 | - delay: 60 220 | content: h 221 | - delay: 119 222 | content: o 223 | - delay: 75 224 | content: 'n' 225 | - delay: 60 226 | content: ' ' 227 | - delay: 150 228 | content: '-' 229 | - delay: 122 230 | content: '-' 231 | - delay: 88 232 | content: v 233 | - delay: 30 234 | content: e 235 | - delay: 76 236 | content: r 237 | - delay: 44 238 | content: s 239 | - delay: 61 240 | content: i 241 | - delay: 59 242 | content: o 243 | - delay: 30 244 | content: 'n' 245 | - delay: 345 246 | content: "\r\n" 247 | - delay: 321 248 | content: "Creating dockerized_python_run ... \r\r\n" 249 | - delay: 31 250 | content: "\e[1A\e[2K\rCreating dockerized_python_run ... \e[32mdone\e[0m\r\e[1B" 251 | - delay: 254 252 | content: "Python 3.10.2\r\n" 253 | - delay: 220 254 | content: "\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~\e[0m$ " 255 | - delay: 2189 256 | content: c 257 | - delay: 45 258 | content: d 259 | - delay: 90 260 | content: ' ' 261 | - delay: 75 262 | content: p 263 | - delay: 136 264 | content: r 265 | - delay: 89 266 | content: o 267 | - delay: 45 268 | content: j 269 | - delay: 45 270 | content: e 271 | - delay: 46 272 | content: c 273 | - delay: 179 274 | content: t 275 | - delay: 120 276 | content: "\r\n\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~/project\e[0m$ " 277 | - delay: 1125 278 | content: c 279 | - delay: 121 280 | content: a 281 | - delay: 74 282 | content: t 283 | - delay: 150 284 | content: ' ' 285 | - delay: 555 286 | content: d 287 | - delay: 90 288 | content: o 289 | - delay: 90 290 | content: c 291 | - delay: 106 292 | content: k 293 | - delay: 224 294 | content: e 295 | - delay: 60 296 | content: r 297 | - delay: 375 298 | content: i 299 | - delay: 105 300 | content: z 301 | - delay: 60 302 | content: e 303 | - delay: 105 304 | content: d 305 | - delay: 135 306 | content: . 307 | - delay: 225 308 | content: e 309 | - delay: 105 310 | content: 'n' 311 | - delay: 75 312 | content: v 313 | - delay: 211 314 | content: "\r\nPYTHON_VERSION=3.9.10\r\n\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~/project\e[0m$ " 315 | - delay: 1994 316 | content: d 317 | - delay: 105 318 | content: o 319 | - delay: 150 320 | content: c 321 | - delay: 91 322 | content: k 323 | - delay: 105 324 | content: e 325 | - delay: 45 326 | content: r 327 | - delay: 119 328 | content: i 329 | - delay: 120 330 | content: z 331 | - delay: 30 332 | content: e 333 | - delay: 90 334 | content: d 335 | - delay: 120 336 | content: ' ' 337 | - delay: 45 338 | content: p 339 | - delay: 226 340 | content: 'y' 341 | - delay: 149 342 | content: t 343 | - delay: 61 344 | content: h 345 | - delay: 104 346 | content: o 347 | - delay: 90 348 | content: 'n' 349 | - delay: 60 350 | content: ' ' 351 | - delay: 135 352 | content: '-' 353 | - delay: 120 354 | content: '-' 355 | - delay: 90 356 | content: v 357 | - delay: 45 358 | content: e 359 | - delay: 76 360 | content: r 361 | - delay: 61 362 | content: s 363 | - delay: 73 364 | content: i 365 | - delay: 30 366 | content: o 367 | - delay: 49 368 | content: 'n' 369 | - delay: 341 370 | content: "\r\n" 371 | - delay: 314 372 | content: "Creating dockerized_python_run ... \r\r\n" 373 | - delay: 34 374 | content: "\e[1A\e[2K\rCreating dockerized_python_run ... \e[32mdone\e[0m\r\e[1B" 375 | - delay: 242 376 | content: "Python 3.9.10\r\n" 377 | - delay: 203 378 | content: "\e[0;32mdemo@ubuntu\e[0m:\e[0;34m~/project\e[0m$ " 379 | - delay: 2283 380 | content: "logout\r\n" 381 | --------------------------------------------------------------------------------