git.iko.soy/typeable/octopod
on master help sign in
Initial open source release
commit4939206d33dceded6edf965b96248a4352eb0009 authorDenis Redozubov <denis.redozubov@gmail.com> dateWed 9 Dec 2020, 16:20 (6 years ago)
416 files changed+33685
.gitattributes+3 .github/workflows/build_docker.yaml+63 .github/workflows/build_octo_cli.yaml+68 .github/workflows/check_documentation.yaml+23 .gitignore+9 .stylish-haskell.yaml+318 Caddyfile+1 Caddyfile1+42 Caddyfile2+27 ChangeLog.md+3 Development_guide.md+73 LICENSE+30 Makefile+39 README.md+53 Release_checklist.md+18 build.sh+66 cabal.project+6 charts/cert-control/.helmignore+22 charts/cert-control/Chart.yaml+5 charts/cert-control/templates/cert-control-clusterrole.yaml+8 charts/cert-control/templates/cert-control-rolebinding.yaml+13 charts/cert-control/values.yaml+3 charts/helm-access/.helmignore+22 charts/helm-access/Chart.yaml+5 charts/helm-access/templates/helm-clusterrole.yaml+11 charts/helm-access/templates/helm-clusterrolebinding.yaml+12 charts/helm-access/values.yaml+2 charts/kubedog-access/Chart.yaml+5 charts/kubedog-access/templates/kubedog-clusterrole.yaml+17 charts/kubedog-access/templates/kubedog-clusterrolebinding.yaml+12 charts/kubedog-access/values.yaml+2 charts/octopod-infra/.helmignore+22 charts/octopod-infra/Chart.yaml+5 charts/octopod-infra/templates/postgres-configmap.yaml+11 charts/octopod-infra/templates/postgres-service.yaml+14 charts/octopod-infra/templates/postgres-statefulset.yaml+51 charts/octopod-infra/values.yaml+20 charts/octopod/.helmignore+22 charts/octopod/Chart.yaml+5 charts/octopod/templates/_helpers.tpl+7 charts/octopod/templates/clean-archive-cronjob.yaml+47 charts/octopod/templates/octopod-app-ingress.yaml+32 charts/octopod/templates/octopod-configmap.yaml+19 charts/octopod/templates/octopod-deployment.yaml+150 charts/octopod/templates/octopod-ingress.yaml+34 charts/octopod/templates/octopod-issuer.yaml+18 charts/octopod/templates/octopod-migration-job.yaml+75 charts/octopod/templates/octopod-nginx-configmap.yaml+20 charts/octopod/templates/octopod-power-app-ingress.yaml+25 charts/octopod/templates/octopod-service.yaml+23 charts/octopod/templates/octopod-ws-ingress.yaml+26 charts/octopod/values.yaml+38 charts/pvc-control/.helmignore+22 charts/pvc-control/Chart.yaml+5 charts/pvc-control/templates/pvc-control-clusterrole.yaml+8 charts/pvc-control/templates/pvc-control-rolebinding.yaml+13 charts/pvc-control/values.yaml+3 default.nix+54 docs/Makefile+31 docs/README.md+15 docs/diagrams/images/app-architecture.png docs/diagrams/images/sample_architecture.png docs/diagrams/images/sample_deployment.png docs/diagrams/images/technical-architecture-archive-via-cli.png docs/diagrams/images/technical-architecture-archive-via-ui.png docs/diagrams/images/technical-architecture-cleanup-via-cli.png docs/diagrams/images/technical-architecture-cleanup-via-ui.png docs/diagrams/images/technical-architecture-create-via-cli.png docs/diagrams/images/technical-architecture-create-via-ui.png docs/diagrams/images/technical-architecture-deployment-states-fsm.png docs/diagrams/images/technical-architecture-restore-via-cli.png docs/diagrams/images/technical-architecture-restore-via-ui.png docs/diagrams/images/technical-architecture-update-via-cli.png docs/diagrams/images/technical-architecture-update-via-ui.png docs/diagrams/src/sample_architecture.puml+16 docs/diagrams/src/sample_deployment.puml+61 docs/diagrams/src/technical-architecture-archive-via-cli.mmd+37 docs/diagrams/src/technical-architecture-archive-via-ui.mmd+36 docs/diagrams/src/technical-architecture-cleanup-via-cli.mmd+20 docs/diagrams/src/technical-architecture-cleanup-via-ui.mmd+19 docs/diagrams/src/technical-architecture-create-via-cli.mmd+41 docs/diagrams/src/technical-architecture-create-via-ui.mmd+40 docs/diagrams/src/technical-architecture-deployment-states-fsm.mmd+16 docs/diagrams/src/technical-architecture-restore-via-cli.mmd+41 docs/diagrams/src/technical-architecture-restore-via-ui.mmd+40 docs/diagrams/src/technical-architecture-update-via-cli.mmd+41 docs/diagrams/src/technical-architecture-update-via-ui.mmd+40 docs/en/Control_scripts.md+294 docs/en/Helm-based_deployment_guide.md+177 docs/en/Integration.md+35 docs/en/Octo_user_guide.md+251 docs/en/Octopod_deployment_guide.md+399 docs/en/Overview.md+98 docs/en/PM_case_study.md+122 docs/en/README.md+15 docs/en/Security_model.md+114 docs/en/Tech_case_study.md+74 docs/en/Technical_architecture.md+292 docs/images/break1.png docs/images/break2.png docs/images/dev1.png docs/images/dev2.png docs/images/env1.png docs/images/env2.png docs/images/first.png docs/images/hello-octopod-1.png docs/images/hello-octopod-2.png docs/images/hello-octopod-3.png docs/images/second.png docs/ru/Control_scripts.md+209 docs/ru/Helm-based_deployment_guide.md+165 docs/ru/Octo_user_guide.md+173 docs/ru/Octopod_deployment_with_K8S.md+275 docs/ru/README.md+8 docs/ru/Security_model.md+120 docs/ru/Technical_architecture.md+275 docs/style.css+90 examples/helm-based-control-scripts/.gitignore+1 examples/helm-based-control-scripts/Cargo.lock+1244 examples/helm-based-control-scripts/Cargo.toml+46 examples/helm-based-control-scripts/README.md+9 examples/helm-based-control-scripts/docker/Dockerfile+29 examples/helm-based-control-scripts/src/bin/archive.rs+59 examples/helm-based-control-scripts/src/bin/archive_check.rs+59 examples/helm-based-control-scripts/src/bin/check.rs+78 examples/helm-based-control-scripts/src/bin/cleanup.rs+50 examples/helm-based-control-scripts/src/bin/create.rs+237 examples/helm-based-control-scripts/src/bin/info.rs+52 examples/helm-based-control-scripts/src/bin/init.rs+9 examples/helm-based-control-scripts/src/bin/tag_check.rs+91 examples/web-app-v2/.gitignore+1 examples/web-app-v2/Cargo.lock+1935 examples/web-app-v2/Cargo.toml+12 examples/web-app-v2/README.md+5 examples/web-app-v2/charts/web-app/.helmignore+22 examples/web-app-v2/charts/web-app/Chart.yaml+5 examples/web-app-v2/charts/web-app/templates/deployment.yaml+48 examples/web-app-v2/charts/web-app/templates/ingress.yaml+26 examples/web-app-v2/charts/web-app/templates/issuer.yaml+18 examples/web-app-v2/charts/web-app/templates/service.yaml+14 examples/web-app-v2/charts/web-app/values.yaml+8 examples/web-app-v2/docker/Dockerfile+22 examples/web-app-v2/src/main.rs+43 examples/web-app-v2/templates/index.html+16 examples/web-app/.gitignore+1 examples/web-app/Cargo.lock+1935 examples/web-app/Cargo.toml+12 examples/web-app/README.md+5 examples/web-app/charts/web-app/.helmignore+22 examples/web-app/charts/web-app/Chart.yaml+5 examples/web-app/charts/web-app/templates/deployment.yaml+48 examples/web-app/charts/web-app/templates/ingress.yaml+26 examples/web-app/charts/web-app/templates/issuer.yaml+18 examples/web-app/charts/web-app/templates/service.yaml+14 examples/web-app/charts/web-app/values.yaml+8 examples/web-app/docker/Dockerfile+22 examples/web-app/src/main.rs+43 examples/web-app/templates/index.html+14 hie.yaml+19 img/demo.gif img/typeable.png migrations/deploy/.gitkeep migrations/deploy/add_archived_status.sql+7 migrations/deploy/add_deployment_overrides_and_deployment_log_overrides.sql+83 migrations/deploy/add_duration_and_stdout_and_stderr_to_deployment_logs.sql+9 migrations/deploy/archived_flag.sql+17 migrations/deploy/create_deployment_metadata.sql+16 migrations/deploy/deployment_logs.sql+17 migrations/deploy/deployments.sql+16 migrations/deploy/rename-delete-to-archive.sql+8 migrations/deploy/rename_delete_to_archive_2.sql+7 migrations/deploy/rename_elements_of_scope_enum.sql+8 migrations/deploy/status_and_status_updated_at.sql+13 migrations/revert/.gitkeep migrations/revert/add_archived_status.sql+7 migrations/revert/add_deployment_overrides_and_deployment_log_overrides.sql+46 migrations/revert/add_duration_and_stdout_and_stderr_to_deployment_logs.sql+9 migrations/revert/archived_flag.sql+10 migrations/revert/create_deployment_metadata.sql+7 migrations/revert/deployment_logs.sql+9 migrations/revert/deployments.sql+7 migrations/revert/rename-delete-to-archive.sql+8 migrations/revert/rename_delete_to_archive_2.sql+7 migrations/revert/rename_elements_of_scope_enum.sql+8 migrations/revert/status_and_status_updated_at.sql+11 migrations/sqitch.conf+8 migrations/sqitch.plan+15 migrations/verify/.gitkeep migrations/verify/add_archived_status.sql+7 migrations/verify/add_deployment_overrides_and_deployment_log_overrides.sql+7 migrations/verify/add_duration_and_stdout_and_stderr_to_deployment_logs.sql+7 migrations/verify/archived_flag.sql+7 migrations/verify/create_deployment_metadata.sql+7 migrations/verify/deployment_logs.sql+7 migrations/verify/deployments.sql+7 migrations/verify/rename-delete-to-archive.sql+7 migrations/verify/rename_delete_to_archive_2.sql+7 migrations/verify/rename_elements_of_scope_enum.sql+7 migrations/verify/status_and_status_updated_at.sql+7 nix/default.nix+120 nix/octo.nix+40 nix/sources.json+86 nix/sources.nix+171 octo-cli/LICENSE+30 octo-cli/Setup.hs+2 octo-cli/octo-cli.cabal+63 octo-cli/src/Main.hs+6 octo-cli/src/Octopod/CLI.hs+311 octo-cli/src/Octopod/CLI/Args.hs+200 octo-cli/src/Octopod/CLI/TLS.hs+39 octo-cli/src/Text/Layout/Table/Extras.hs+27 octopod-api/LICENSE+30 octopod-api/Setup.hs+2 octopod-api/octopod-api.cabal+46 octopod-api/src/Octopod/API.hs+65 octopod-api/src/Octopod/API/WebSocket.hs+22 octopod-api/src/Octopod/PowerAPI.hs+71 octopod-backend/LICENSE+30 octopod-backend/Setup.hs+2 octopod-backend/app/Main.hs+6 octopod-backend/octopod-backend.cabal+189 octopod-backend/src/Octopod/Server.hs+1236 octopod-backend/src/Octopod/Server/Args.hs+72 octopod-backend/src/Octopod/Server/ControlScriptUtils.hs+31 octopod-backend/src/Octopod/Server/Logger.hs+34 octopod-backend/src/Octopod/Server/Posix.hs+25 octopod-backend/src/Orphans.hs+16 octopod-backend/src/TLS.hs+68 octopod-backend/src/Types.hs+83 octopod-backend/test/Spec.hs+2 octopod-common/CHANGELOG.md+5 octopod-common/LICENSE+30 octopod-common/Setup.hs+2 octopod-common/octopod-common.cabal+48 octopod-common/src/Common/Types.hs+272 octopod-common/src/Common/Utils.hs+41 octopod-common/src/Common/Validation.hs+20 octopod-css/.stylelintrc.json+98 octopod-css/development/images/dialog-delete.png octopod-css/development/images/dialog-delete@2x.png octopod-css/development/images/null-data.png octopod-css/development/images/null-data@2x.png octopod-css/development/images/null-search.png octopod-css/development/images/null-search@2x.png octopod-css/development/layouts/index.html+885 octopod-css/development/layouts/staging.html+710 octopod-css/development/layouts/standalone-loading.html+43 octopod-css/development/markups/_action.html+109 octopod-css/development/markups/_bar.html+58 octopod-css/development/markups/_break.html+82 octopod-css/development/markups/_button.html+134 octopod-css/development/markups/_dash.html+127 octopod-css/development/markups/_drop.html+109 octopod-css/development/markups/_expander.html+93 octopod-css/development/markups/_external.html+58 octopod-css/development/markups/_input.html+108 octopod-css/development/markups/_listing.html+97 octopod-css/development/markups/_loading.html+133 octopod-css/development/markups/_notification.html+85 octopod-css/development/markups/_null.html+78 octopod-css/development/markups/_spot.html+58 octopod-css/development/markups/_status.html+80 octopod-css/development/markups/_title.html+57 octopod-css/development/scripts/_classic-popup.js+92 octopod-css/development/scripts/_data.js+7 octopod-css/development/scripts/_drop.js+26 octopod-css/development/scripts/_expander.js+8 octopod-css/development/scripts/_popup.js+35 octopod-css/development/scripts/_sort.js+16 octopod-css/development/styles/_action.css+55 octopod-css/development/styles/_bar.css+24 octopod-css/development/styles/_body.css+5 octopod-css/development/styles/_break.css+3 octopod-css/development/styles/_browserupgrade.css+5 octopod-css/development/styles/_button.css+101 octopod-css/development/styles/_classic-popup.css+57 octopod-css/development/styles/_container.css+4 octopod-css/development/styles/_dash.css+57 octopod-css/development/styles/_data.css+19 octopod-css/development/styles/_deployment.css+59 octopod-css/development/styles/_dialog.css+47 octopod-css/development/styles/_drop.css+70 octopod-css/development/styles/_expander.css+90 octopod-css/development/styles/_external.css+26 octopod-css/development/styles/_header.css+31 octopod-css/development/styles/_html.css+4 octopod-css/development/styles/_input.css+91 octopod-css/development/styles/_listing.css+56 octopod-css/development/styles/_loading.css+82 octopod-css/development/styles/_log.css+10 octopod-css/development/styles/_nav.css+39 octopod-css/development/styles/_no-data.css+8 octopod-css/development/styles/_no-deployment.css+7 octopod-css/development/styles/_no-log.css+7 octopod-css/development/styles/_no-page.css+15 octopod-css/development/styles/_no-table.css+16 octopod-css/development/styles/_notification.css+54 octopod-css/development/styles/_null.css+66 octopod-css/development/styles/_overrides.css+28 octopod-css/development/styles/_page.css+71 octopod-css/development/styles/_popup.css+139 octopod-css/development/styles/_root.css+25 octopod-css/development/styles/_sort.css+73 octopod-css/development/styles/_spot.css+25 octopod-css/development/styles/_status.css+31 octopod-css/development/styles/_table.css+224 octopod-css/development/styles/_tag.css+3 octopod-css/development/styles/_title.css+5 octopod-css/development/styles/_universal.css+11 octopod-css/development/styles/_visuallyhidden.css+41 octopod-css/development/styles/style.css+42 octopod-css/development/vectors/button-add.svg+1 octopod-css/development/vectors/button-save.svg+1 octopod-css/development/vectors/classic-popup-close.svg+3 octopod-css/development/vectors/common-use-archive.svg+1 octopod-css/development/vectors/common-use-edit-white.svg+1 octopod-css/development/vectors/common-use-edit.svg+1 octopod-css/development/vectors/common-use-logs.svg+1 octopod-css/development/vectors/common-use-restore.svg+1 octopod-css/development/vectors/dash-add-active.svg+1 octopod-css/development/vectors/dash-add-hover.svg+1 octopod-css/development/vectors/dash-add.svg+1 octopod-css/development/vectors/dash-back-active.svg+1 octopod-css/development/vectors/dash-back-hover.svg+1 octopod-css/development/vectors/dash-back.svg+1 octopod-css/development/vectors/dash-next-active.svg+1 octopod-css/development/vectors/dash-next-hover.svg+1 octopod-css/development/vectors/dash-next.svg+1 octopod-css/development/vectors/drop-actions-active.svg+1 octopod-css/development/vectors/drop-actions-hover.svg+1 octopod-css/development/vectors/drop-actions.svg+1 octopod-css/development/vectors/expander-active.svg+1 octopod-css/development/vectors/expander-hover.svg+1 octopod-css/development/vectors/expander-larger-active.svg+1 octopod-css/development/vectors/expander-larger-hover.svg+1 octopod-css/development/vectors/expander-larger.svg+1 octopod-css/development/vectors/expander.svg+1 octopod-css/development/vectors/external-hover-active.svg+1 octopod-css/development/vectors/external.svg+1 octopod-css/development/vectors/input-search.svg+1 octopod-css/development/vectors/loading-alternate.svg+1 octopod-css/development/vectors/loading-enlarged-alternate.svg+1 octopod-css/development/vectors/loading-enlarged.svg+1 octopod-css/development/vectors/loading.svg+1 octopod-css/development/vectors/notification-close-danger-active.svg+3 octopod-css/development/vectors/notification-close-danger-hover.svg+3 octopod-css/development/vectors/notification-close-danger.svg+3 octopod-css/development/vectors/notification-close-success-active.svg+3 octopod-css/development/vectors/notification-close-success-hover.svg+3 octopod-css/development/vectors/notification-close-success.svg+3 octopod-css/development/vectors/popup-close-active.svg+3 octopod-css/development/vectors/popup-close-hover.svg+3 octopod-css/development/vectors/popup-close.svg+3 octopod-css/development/vectors/spot.svg+1 octopod-css/development/vectors/status-archived.svg+1 octopod-css/development/vectors/status-failure.svg+1 octopod-css/development/vectors/status-pending.svg+1 octopod-css/development/vectors/status-success.svg+1 octopod-css/development/vendors/jquery/jquery-3.2.1.min.js+4 octopod-css/development/vendors/normalize/normalize.css+349 octopod-css/development/vendors/outline/index.html+67 octopod-css/development/vendors/outline/outline.js+37 octopod-css/gulpfile.js+308 octopod-css/package-lock.json+6159 octopod-css/package.json+19 octopod-css/production/images/dialog-delete.png octopod-css/production/images/dialog-delete@2x.png octopod-css/production/images/null-data.png octopod-css/production/images/null-data@2x.png octopod-css/production/images/null-search.png octopod-css/production/images/null-search@2x.png octopod-css/production/layouts/index.html+888 octopod-css/production/layouts/staging.html+713 octopod-css/production/layouts/standalone-loading.html+46 octopod-css/production/markups/_action.html+112 octopod-css/production/markups/_bar.html+61 octopod-css/production/markups/_break.html+85 octopod-css/production/markups/_button.html+137 octopod-css/production/markups/_dash.html+130 octopod-css/production/markups/_drop.html+112 octopod-css/production/markups/_expander.html+96 octopod-css/production/markups/_external.html+61 octopod-css/production/markups/_input.html+111 octopod-css/production/markups/_listing.html+100 octopod-css/production/markups/_loading.html+136 octopod-css/production/markups/_notification.html+88 octopod-css/production/markups/_null.html+81 octopod-css/production/markups/_spot.html+61 octopod-css/production/markups/_status.html+83 octopod-css/production/markups/_title.html+60 octopod-css/production/scripts/_classic-popup.js+92 octopod-css/production/scripts/_data.js+7 octopod-css/production/scripts/_drop.js+26 octopod-css/production/scripts/_expander.js+8 octopod-css/production/scripts/_popup.js+35 octopod-css/production/scripts/_sort.js+16 octopod-css/production/styles/style.css+1 octopod-css/production/vendors/jquery/jquery-3.2.1.min.js+4 octopod-css/production/vendors/outline/index.html+67 octopod-css/production/vendors/outline/outline.js+37 octopod-frontend/CHANGELOG.md+5 octopod-frontend/LICENSE+30 octopod-frontend/Setup.hs+2 octopod-frontend/octopod-frontend.cabal+82 octopod-frontend/src/Frontend/API.hs+143 octopod-frontend/src/Frontend/GHCJS.hs+89 octopod-frontend/src/Frontend/Route.hs+48 octopod-frontend/src/Frontend/Utils.hs+470 octopod-frontend/src/Main.hs+117 octopod-frontend/src/Page/ClassicPopup.hs+66 octopod-frontend/src/Page/Deployment.hs+365 octopod-frontend/src/Page/Deployments.hs+496 octopod-frontend/src/Page/Popup/EditDeployment.hs+172 octopod-frontend/src/Page/Popup/NewDeployment.hs+156 release.sh+20 stack.yaml+14
.gitattributesnew file+3
@@ -0,0 +1,3 @@
1+* linguist-vendored
2+*.hs linguist-vendored=false
3+*.rs linguist-vendored=false
.github/workflows/build_docker.yamlnew file+63
@@ -0,0 +1,63 @@
1+name: Octopod Docker Image
2+on:
3+ push:
4+ branches:
5+ - master
6+ pull_request:
7+ branches:
8+ - master
9+ - develop
10+
11+jobs:
12+ build:
13+ name: build-docker
14+ runs-on: ubuntu-latest
15+ timeout-minutes: 600
16+ steps:
17+ - uses: actions/checkout@v2
18+ with:
19+ repo: Aviora/dm
20+
21+ - name: Install Nix
22+ uses: cachix/install-nix-action@v12
23+
24+ - name: Login to Cachix
25+ uses: cachix/cachix-action@v8
26+ with:
27+ name: octopod
28+ signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
29+
30+ - name: Build Docker Images
31+ run: |
32+ # enable required features (see https://github.com/cachix/install-nix-action/issues/19)
33+ mkdir -p ~/.config/nix
34+ echo "system-features = kvm" >> ~/.config/nix/nix.conf
35+
36+ # build docker images
37+ ./build.sh build
38+
39+ - name: Login to DockerHub
40+ id: login-docker-hub
41+ if: github.ref == 'refs/heads/master'
42+ uses: docker/login-action@v1
43+ with:
44+ username: ${{ secrets.DOCKERHUB_USERNAME }}
45+ password: ${{ secrets.DOCKERHUB_TOKEN }}
46+
47+ - name: Push Docker Images to DockerHub
48+ if: github.ref == 'refs/heads/master'
49+ run: |
50+ # push docker images to DockerHub
51+ image_name=`cat octo-docker | docker load | awk '{print $3}'`
52+ docker tag $image_name typeable/octo:latest
53+ docker push typeable/octo:latest
54+
55+ image_name=`cat octopod-server-docker | docker load | awk '{print $3}'`
56+ docker tag $image_name typeable/octopod:latest
57+ docker push typeable/octopod:latest
58+
59+ - name: Clean up
60+ if: always()
61+ continue-on-error: true
62+ run: |
63+ docker logout ${{ steps.login-docker-hub.outputs.registry }}
.github/workflows/build_octo_cli.yamlnew file+68
@@ -0,0 +1,68 @@
1+name: octo CLI
2+on:
3+ push:
4+ branches:
5+ - master
6+ pull_request:
7+ branches:
8+ - master
9+ - develop
10+
11+jobs:
12+ macOS:
13+ name: Create macOS octo CLI Release (Stack)
14+ runs-on: macos-latest
15+ steps:
16+ - uses: actions/checkout@v1
17+ - name: Cache stack dependencies
18+ uses: actions/cache@v2
19+ with:
20+ path: ~/.stack
21+ key: octo-cli-stack-${{ runner.os }}
22+ # TODO: Remove this step once https://github.com/actions/cache/issues/445 is resolved.
23+ - name: Fix macOS cache bug
24+ run: rm -rf ~/.stack/setup-exe-cache
25+ - name: Build
26+ run: stack build octo-cli --local-bin-path out --copy-bins
27+ - uses: actions/upload-artifact@v2
28+ with:
29+ name: octo-cli-macos
30+ path: out/octo
31+ linux:
32+ name: Create Linux octo CLI Release (Nix)
33+ runs-on: ubuntu-latest
34+ timeout-minutes: 600
35+ steps:
36+ - uses: actions/checkout@v1
37+ - uses: cachix/install-nix-action@v12
38+ - uses: cachix/cachix-action@v8
39+ with:
40+ name: octopod
41+ signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
42+ - name: Build
43+ run: nix-build nix/octo.nix
44+ - uses: actions/upload-artifact@v2
45+ with:
46+ name: octo-cli-linux
47+ path: result/bin/octo
48+ release:
49+ name: "Release"
50+ if: github.ref == 'refs/heads/master'
51+ runs-on: "ubuntu-latest"
52+ needs: [macOS, linux]
53+ steps:
54+ - uses: actions/download-artifact@v2
55+ - name: Zip
56+ run: |
57+ chmod +x octo-cli-macos/octo
58+ zip octo-cli-macos octo-cli-macos/octo
59+ chmod +x octo-cli-linux/octo
60+ zip octo-cli-linux octo-cli-linux/octo
61+ - uses: "marvinpinto/action-automatic-releases@latest"
62+ with:
63+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
64+ automatic_release_tag: "latest"
65+ prerelease: true
66+ title: "Pre-Release"
67+ files: |
68+ *.zip
.github/workflows/check_documentation.yamlnew file+23
@@ -0,0 +1,23 @@
1+name: Documentation
2+on:
3+ push:
4+ branches:
5+ - master
6+ pull_request:
7+ schedule:
8+ - cron: "0 0 * * *"
9+
10+jobs:
11+ build:
12+ name: Check Markdown Documentation
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v1
16+
17+ - name: Set up linter
18+ run: |
19+ yarn add remark-cli remark-lint-mdash-style https://github.com/typeable/remark-validate-links#anchors remark-preset-lint-recommended remark-lint-no-dead-urls
20+
21+ - name: Run linter
22+ run: |
23+ yarn run remark -f -u validate-links -u remark-lint-mdash-style -u remark-lint-final-newline -u remark-lint-list-item-bullet-indent -u remark-lint-no-auto-link-without-protocol -u remark-lint-no-blockquote-without-marker -u remark-lint-ordered-list-marker-style -u remark-lint-no-literal-urls -u remark-lint-hard-break-spaces -u remark-lint-no-duplicate-definitions -u remark-lint-no-heading-content-indent -u remark-lint-no-inline-padding -u remark-lint-no-shortcut-reference-image -u remark-lint-no-shortcut-reference-link -u remark-lint-no-undefined-references -u remark-lint-no-unused-definitions -u remark-lint-no-dead-urls docs README.md
.gitignorenew file+9
@@ -0,0 +1,9 @@
1+.stack-work/
2+package.yaml
3+result
4+dms-docker
5+*~
6+dist-newstyle
7+octopod-config.json
8+frontend-result
9+octopod-css/node_modules
.stylish-haskell.yamlnew file+318
@@ -0,0 +1,318 @@
1+# stylish-haskell configuration file
2+# ==================================
3+
4+# The stylish-haskell tool is mainly configured by specifying steps. These steps
5+# are a list, so they have an order, and one specific step may appear more than
6+# once (if needed). Each file is processed by these steps in the given order.
7+steps:
8+ # Format record definitions. This is disabled by default.
9+ #
10+ # You can control the layout of record fields. The only rules that can't be configured
11+ # are these:
12+ #
13+ # - "|" is always aligned with "="
14+ # - "," in fields is always aligned with "{"
15+ # - "}" is likewise always aligned with "{"
16+ #
17+ # - records:
18+ # # How to format equals sign between type constructor and data constructor.
19+ # # Possible values:
20+ # # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor.
21+ # # - "indent N" -- insert a new line and N spaces from the beginning of the next line.
22+ # equals: "same_line"
23+
24+ # # How to format first field of each record constructor.
25+ # # Possible values:
26+ # # - "same_line" -- "{" and first field goes on the same line as the data constructor.
27+ # # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor
28+ # first_field: "indent 2"
29+
30+ # # How many spaces to insert between the column with "," and the beginning of the comment in the next line.
31+ # field_comment: 2
32+
33+ # # # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines.
34+ # deriving: 2
35+
36+ # Align the right hand side of some elements. This is quite conservative
37+ # and only applies to statements where each element occupies a single
38+ # line. All default to true.
39+ - simple_align:
40+ cases: false
41+ top_level_patterns: false
42+ records: false
43+
44+ # Import cleanup
45+ - imports:
46+ # There are different ways we can align names and lists.
47+ #
48+ # - global: Align the import names and import list throughout the entire
49+ # file.
50+ #
51+ # - file: Like global, but don't add padding when there are no qualified
52+ # imports in the file.
53+ #
54+ # - group: Only align the imports per group (a group is formed by adjacent
55+ # import lines).
56+ #
57+ # - none: Do not perform any alignment.
58+ #
59+ # Default: global.
60+ align: global
61+
62+ # The following options affect only import list alignment.
63+ #
64+ # List align has following options:
65+ #
66+ # - after_alias: Import list is aligned with end of import including
67+ # 'as' and 'hiding' keywords.
68+ #
69+ # > import qualified Data.List as List (concat, foldl, foldr, head,
70+ # > init, last, length)
71+ #
72+ # - with_alias: Import list is aligned with start of alias or hiding.
73+ #
74+ # > import qualified Data.List as List (concat, foldl, foldr, head,
75+ # > init, last, length)
76+ #
77+ # - with_module_name: Import list is aligned `list_padding` spaces after
78+ # the module name.
79+ #
80+ # > import qualified Data.List as List (concat, foldl, foldr, head,
81+ # init, last, length)
82+ #
83+ # This is mainly intended for use with `pad_module_names: false`.
84+ #
85+ # > import qualified Data.List as List (concat, foldl, foldr, head,
86+ # init, last, length, scanl, scanr, take, drop,
87+ # sort, nub)
88+ #
89+ # - new_line: Import list starts always on new line.
90+ #
91+ # > import qualified Data.List as List
92+ # > (concat, foldl, foldr, head, init, last, length)
93+ #
94+ # Default: after_alias
95+ list_align: after_alias
96+
97+ # Right-pad the module names to align imports in a group:
98+ #
99+ # - true: a little more readable
100+ #
101+ # > import qualified Data.List as List (concat, foldl, foldr,
102+ # > init, last, length)
103+ # > import qualified Data.List.Extra as List (concat, foldl, foldr,
104+ # > init, last, length)
105+ #
106+ # - false: diff-safe
107+ #
108+ # > import qualified Data.List as List (concat, foldl, foldr, init,
109+ # > last, length)
110+ # > import qualified Data.List.Extra as List (concat, foldl, foldr,
111+ # > init, last, length)
112+ #
113+ # Default: true
114+ pad_module_names: false
115+
116+ # Long list align style takes effect when import is too long. This is
117+ # determined by 'columns' setting.
118+ #
119+ # - inline: This option will put as much specs on same line as possible.
120+ #
121+ # - new_line: Import list will start on new line.
122+ #
123+ # - new_line_multiline: Import list will start on new line when it's
124+ # short enough to fit to single line. Otherwise it'll be multiline.
125+ #
126+ # - multiline: One line per import list entry.
127+ # Type with constructor list acts like single import.
128+ #
129+ # > import qualified Data.Map as M
130+ # > ( empty
131+ # > , singleton
132+ # > , ...
133+ # > , delete
134+ # > )
135+ #
136+ # Default: inline
137+ long_list_align: new_line
138+
139+ # Align empty list (importing instances)
140+ #
141+ # Empty list align has following options
142+ #
143+ # - inherit: inherit list_align setting
144+ #
145+ # - right_after: () is right after the module name:
146+ #
147+ # > import Vector.Instances ()
148+ #
149+ # Default: inherit
150+ empty_list_align: right_after
151+
152+ # List padding determines indentation of import list on lines after import.
153+ # This option affects 'long_list_align'.
154+ #
155+ # - <integer>: constant value
156+ #
157+ # - module_name: align under start of module name.
158+ # Useful for 'file' and 'group' align settings.
159+ #
160+ # Default: 4
161+ list_padding: 2
162+
163+ # Separate lists option affects formatting of import list for type
164+ # or class. The only difference is single space between type and list
165+ # of constructors, selectors and class functions.
166+ #
167+ # - true: There is single space between Foldable type and list of it's
168+ # functions.
169+ #
170+ # > import Data.Foldable (Foldable (fold, foldl, foldMap))
171+ #
172+ # - false: There is no space between Foldable type and list of it's
173+ # functions.
174+ #
175+ # > import Data.Foldable (Foldable(fold, foldl, foldMap))
176+ #
177+ # Default: true
178+ separate_lists: false
179+
180+ # Space surround option affects formatting of import lists on a single
181+ # line. The only difference is single space after the initial
182+ # parenthesis and a single space before the terminal parenthesis.
183+ #
184+ # - true: There is single space associated with the enclosing
185+ # parenthesis.
186+ #
187+ # > import Data.Foo ( foo )
188+ #
189+ # - false: There is no space associated with the enclosing parenthesis
190+ #
191+ # > import Data.Foo (foo)
192+ #
193+ # Default: false
194+ space_surround: false
195+
196+ # Language pragmas
197+ - language_pragmas:
198+ # We can generate different styles of language pragma lists.
199+ #
200+ # - vertical: Vertical-spaced language pragmas, one per line.
201+ #
202+ # - compact: A more compact style.
203+ #
204+ # - compact_line: Similar to compact, but wrap each line with
205+ # `{-#LANGUAGE #-}'.
206+ #
207+ # Default: vertical.
208+ style: vertical
209+
210+ # Align affects alignment of closing pragma brackets.
211+ #
212+ # - true: Brackets are aligned in same column.
213+ #
214+ # - false: Brackets are not aligned together. There is only one space
215+ # between actual import and closing bracket.
216+ #
217+ # Default: true
218+ align: false
219+
220+ # stylish-haskell can detect redundancy of some language pragmas. If this
221+ # is set to true, it will remove those redundant pragmas. Default: true.
222+ remove_redundant: true
223+
224+ # Language prefix to be used for pragma declaration, this allows you to
225+ # use other options non case-sensitive like "language" or "Language".
226+ # If a non correct String is provided, it will default to: LANGUAGE.
227+ language_prefix: LANGUAGE
228+
229+ # Replace tabs by spaces. This is disabled by default.
230+ # - tabs:
231+ # # Number of spaces to use for each tab. Default: 8, as specified by the
232+ # # Haskell report.
233+ # spaces: 8
234+
235+ # Remove trailing whitespace
236+ - trailing_whitespace: {}
237+
238+ # Squash multiple spaces between the left and right hand sides of some
239+ # elements into single spaces. Basically, this undoes the effect of
240+ # simple_align but is a bit less conservative.
241+ - squash: {}
242+
243+# A common setting is the number of columns (parts of) code will be wrapped
244+# to. Different steps take this into account.
245+#
246+# Set this to null to disable all line wrapping.
247+#
248+# Default: 80.
249+columns: 80
250+
251+# By default, line endings are converted according to the OS. You can override
252+# preferred format here.
253+#
254+# - native: Native newline format. CRLF on Windows, LF on other OSes.
255+#
256+# - lf: Convert to LF ("\n").
257+#
258+# - crlf: Convert to CRLF ("\r\n").
259+#
260+# Default: native.
261+newline: native
262+
263+# Sometimes, language extensions are specified in a cabal file or from the
264+# command line instead of using language pragmas in the file. stylish-haskell
265+# needs to be aware of these, so it can parse the file correctly.
266+#
267+# No language extensions are enabled by default.
268+language_extensions:
269+ - BangPatterns
270+ - ConstraintKinds
271+ - CPP
272+ - DataKinds
273+ - DefaultSignatures
274+ - DeriveFoldable
275+ - DeriveFunctor
276+ - DeriveGeneric
277+ - DeriveTraversable
278+ - DerivingVia
279+ - DuplicateRecordFields
280+ - ExistentialQuantification
281+ - ExplicitNamespaces
282+ - FlexibleInstances
283+ - FunctionalDependencies
284+ - GADTs
285+ - GeneralizedNewtypeDeriving
286+ - KindSignatures
287+ - LambdaCase
288+ - MultiParamTypeClasses
289+ - MultiWayIf
290+ - NamedFieldPuns
291+ - NoMonadFailDesugaring
292+ - NumDecimals
293+ - OverloadedStrings
294+ - PartialTypeSignatures
295+ - PolyKinds
296+ - QuantifiedConstraints
297+ - QuasiQuotes
298+ - RankNTypes
299+ - RecordWildCards
300+ - RecursiveDo
301+ - RoleAnnotations
302+ - ScopedTypeVariables
303+ - StandaloneDeriving
304+ - TemplateHaskell
305+ - TupleSections
306+ - TypeApplications
307+ - TypeFamilies
308+ - TypeInType
309+ - TypeOperators
310+ - UndecidableInstances
311+ - ViewPatterns
312+ - OverloadedLabels
313+
314+# Attempt to find the cabal file in ancestors of the current directory, and
315+# parse options (currently only language extensions) from that.
316+#
317+# Default: true
318+cabal: false
Caddyfilenew file+1
@@ -0,0 +1 @@
1+Caddyfile2
Caddyfile1new file+42
@@ -0,0 +1,42 @@
1+localhost:8000
2+
3+mime .css text/css
4+
5+rewrite /static/styles {
6+ r (.*)
7+ to /octopod-css/production/styles/{1}
8+}
9+
10+mime .js application/javascript
11+
12+rewrite /static/vendors {
13+ r (.*)
14+ to /octopod-css/production/vendors/{1}
15+}
16+
17+rewrite /static/scripts {
18+ r (.*)
19+ to /octopod-css/production/scripts/{1}
20+}
21+
22+rewrite /static/images {
23+ r (.*)
24+ to /octopod-css/production/images/{1}
25+}
26+
27+proxy /api localhost:3002 {
28+ transparent
29+}
30+
31+mime .json application/json
32+rewrite /config.json /octopod-config.json
33+
34+rewrite /ghcjs /frontend-result/bin/frontend.jsexe/index.html
35+
36+proxy / localhost:3003 {
37+ transparent
38+ websocket
39+ except /octopod-css/ /frontend-result/ /octopod-config.json
40+}
41+
42+log / stdout
Caddyfile2new file+27
@@ -0,0 +1,27 @@
1+http://localhost:8000
2+
3+
4+file_server
5+
6+@production {
7+ path_regexp production /static/(.*)
8+}
9+
10+rewrite @production /octopod-css/production/{http.regexp.production.1}
11+
12+reverse_proxy /api localhost:3002
13+
14+rewrite /config.json /octopod-config.json
15+
16+rewrite /ghcjs /frontend-result/bin/frontend.jsexe/index.html
17+
18+@3003 {
19+ not path /octopod-css/* /frontend-result/* /octopod-config.json
20+}
21+
22+reverse_proxy @3003 localhost:3003
23+
24+log {
25+ output stdout
26+ format single_field common_log
27+}
ChangeLog.mdnew file+3
@@ -0,0 +1,3 @@
1+# Changelog for octopod
2+
3+## Version 1.0
Development_guide.mdnew file+73
@@ -0,0 +1,73 @@
1+# Development guide
2+
3+## Git flow
4+
5+`master` contains the latest "release version" only.
6+
7+All development should be done in the `develop` branch.
8+
9+Feature PRs are created to the `develop` branch and merged with all commits **squashed**. This leads to us having every commit in the `develop` branch corresponds to exactly one feature or bug fix.
10+
11+When a release is ready, the `develop` branch is merged into the `master` branch using **rebase and merge**. This makes the `master` branch have every commit be a feature or bug fix. Merging to master triggers a CI script that collects all commits since the last merge and creates a new release with a change log of all commits.
12+
13+## Building
14+
15+### Nix Installation
16+
17+Everything is built with [nix](https://nixos.org). To build the project you will need to install it.
18+
19+```bash
20+curl https://nixos.org/nix/install | sh
21+```
22+
23+### Nix cache
24+
25+#### Reflex platform cache
26+
27+To speedup initial project builds you will want to set up the Reflex Platform binary nix cache – append the following to `/etc/nix/nix.conf`:
28+
29+```
30+binary-caches = https://cache.nixos.org https://nixcache.reflex-frp.org
31+binary-cache-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
32+binary-caches-parallel-connections = 40
33+```
34+
35+#### Octopod cache
36+
37+The Octopod cache will also be useful to speed up builds:
38+
39+1. Install [Cachix](https://cachix.org):
40+
41+ ```bash
42+ nix-env -iA cachix -f https://cachix.org/api/v1/install
43+ ```
44+2. Add cache:
45+ ```bash
46+ cachix use octopod
47+ ```
48+
49+## Development
50+
51+We have written a `Makefile` with common targets used during development.
52+
53+### Building
54+
55+- `build-backend` – builds a release backend executable.
56+- `build-octo-cli` – builds a release octo CLI executable. NOTE: this is not the octo CLI executable that is used for distribution but the dependencies are close enough for development purposes.
57+- `build-frontend` – build the frontend release.
58+
59+### Development
60+
61+For development, we have set up `ghcid` commands that rebuild the project every time you make a change. The targets should self-explanatory:
62+
63+- `ghcid-backend`
64+- `ghcid-cli`
65+- `ghcid-frontend`
66+
67+### Frontend proxy
68+
69+The frontend should be accessed through a proxy. We have set up [caddy](https://caddyserver.com) configs to ease development. You will need place an `octopod-config.json` file at the root of the repository containing a [config](../../charts/octopod/templates/octopod-nginx-configmap.yaml#L15-L20). `app_auth` can be an arbitrary string – it will not affect anything when running locally.
70+
71+### Stack
72+
73+For convenience, the repo currently also contains a `stack.yaml` that can be used for development. It is only used to build the macOS octo CLI release but supports building both octo CLI and the _Octopod Server_ in an environment close enough to the release environment to be useful during development if you prefer stack.
LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright Typeable LLC (c) 2020
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Author name here nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Makefilenew file+39
@@ -0,0 +1,39 @@
1+.PHONY: build-backend build-octo-cli build-frontend docs backend-docs frontend-docs repl shell shell-ghcjs ghcid ghcid-cli ghcid-frontend push-octopod
2+
3+build-backend:
4+ nix-build . -A ghc.octopod-backend
5+
6+build-octo-cli:
7+ nix-build . -A ghc.octo-cli
8+
9+build-frontend:
10+ nix-build . -A ghcjs.octopod-frontend -o frontend-result
11+
12+docs: backend-docs frontend-docs
13+
14+backend-docs:
15+ nix-build . -A ghc.octopod-backend.doc
16+
17+frontend-docs:
18+ nix-build . -A ghcjs.octopod-frontend.doc
19+
20+repl:
21+ nix-shell . -A shells.ghc --run "cabal repl lib:octopod-backend"
22+
23+shell:
24+ nix-shell . -A shells.ghc
25+
26+shell-ghcjs:
27+ nix-shell . -A shells.ghcjs
28+
29+ghcid-backend:
30+ nix-shell . -A shells.ghc --run 'ghcid -c "cabal new-repl octopod-backend"'
31+
32+ghcid-cli:
33+ nix-shell . -A shells.ghc --run 'ghcid -c "cabal new-repl octo-cli"'
34+
35+ghcid-frontend:
36+ nix-shell . -A shells.ghc --run 'ghcid -c "cabal new-repl octopod-frontend -fdevelopment" --test 'Main.main''
37+
38+push-octopod:
39+ ./build.sh build-and-push latest
README.mdnew file+53
@@ -0,0 +1,53 @@
1+# 🐙 Octopod ![Octopod Docker Image](https://github.com/typeable/octopod/workflows/Octopod%20Docker%20Image/badge.svg?branch=master) ![octo CLI](https://github.com/typeable/octopod/workflows/octo%20CLI/badge.svg?branch=master) ![Documentation](https://github.com/typeable/octopod/workflows/Documentation/badge.svg?branch=master)
2+
3+_Octopod_ is a fully open-source self-hosted solution for managing multiple deployments in a _Kubernetes_ cluster with a user-friendly web interface. Managing deployments does not require any technical expertise.
4+
5+We created _Octopod_ because we believe that everything we release should be rigorously tested, however, such desires greatly [complicate the development workflow](docs/en/PM_case_study.md) leading to longer release cycles. We use _Octopod_ to mitigate the downsides of rigorously testing each feature by deploying every single change we make to a separate staging environment allowing QA to investigate each feature independently and in parallel.
6+
7+## 🖥 Demo
8+
9+<p align="center"><img src="img/demo.gif"></img></p>
10+
11+## 📑 Documentation
12+
13+### 🔭 High-level notes
14+- [🐙 Overview](docs/en/Overview.md)
15+- [🧑‍🔬 Project managment case study](docs/en/PM_case_study.md)
16+- [🧑‍💻 Technical case study](docs/en/Tech_case_study.md)
17+
18+### 🛠️ Technical documentation
19+- [🏗 Technical architecture](docs/en/Technical_architecture.md) [[RU](docs/ru/Technical_architecture.md)]
20+- [⚙️ Control script guide](docs/en/Control_scripts.md) [[RU](docs/ru/Control_scripts.md)]
21+- [🔧🐙 Octopod deployment guide](docs/en/Octopod_deployment_guide.md) [[RU](docs/ru/Octopod_deployment_with_K8S.md)]
22+- [🔧🚀 Helm-based Octopod project setup](docs/en/Helm-based_deployment_guide.md) [[RU](docs/ru/Helm-based_deployment_guide.md)]
23+- [🐙🎛 octo CLI user guide](docs/en/Octo_user_guide.md) [[RU](docs/ru/Octo_user_guide.md)]
24+- [🤖 CI integration](docs/en/Integration.md)
25+- [🔒 Octopod security model](docs/en/Security_model.md) [[RU](docs/ru/Security_model.md)]
26+
27+## ℹ️ FAQ
28+
29+### How long does it take to set up _Octopod_?
30+
31+The longest part of setting up _Octopod_ for your project will probably be writing [_Control Scripts_](docs/en/Control_scripts.md). In total you should be able to get things running in about a day.
32+
33+### Will _Octopod_ work with my project if it uses X?
34+
35+Yes. _Octopod_ is project-agnostic. If you can run your project in a Docker container, then you can use _Octopod_ with that project.
36+
37+### What do I need to know to set up Octopod?
38+
39+You need to understand the basics of _Kubernetes_ and be familiar with whatever hosting provider you will be using. There is no need to know any special language – you can write [_Control Scripts_](docs/en/Control_scripts.md) in whatever language you like.
40+
41+### Does _Octopod_ work with my CI?
42+
43+Yes. If you can run arbitrary executables in your CI, then you will be able to integrate it with _Octopod_. Integration basically consists of calling our _octo CLI_ tool to perform desired actions. You can find more detail in the [CI integration](docs/en/Integration.md) doc.
44+
45+### How come I can't see the deployment logs in Octopod web app?
46+
47+It's been excluded from the GUI because we don't have a good security story to accompany this feature yet. Some secret and credentials may leak to the project team using Octopod and, potentially, not everyone should have access to this data.
48+
49+### Why Haskell and Rust?
50+
51+We believe that there is a lot to be gained in programming in general by being able to statically ensure invariants in your code. One of the most practical ways of ensuring invariants is a good static type system. Haskell and Rust are both languages that have very strong type systems. This allows us to move fast without breaking things in the process.
52+
53+<p align="center"><a href="https://typeable.io"><img src="img/typeable.png" width="177px"></img></a></p>
Release_checklist.mdnew file+18
@@ -0,0 +1,18 @@
1+# Release checklist
2+
3+1. Merge the `develop` branch into `master`.
4+2. CI will automatically create a new release in GitHub with _octo CLI_ and update the `latest` tag for both `octo` and `octopod`. Wait for CI to complete.
5+3. Edit the created release in GitHub to match the version you are releasing.
6+ 1. Change the release name to the version being released.
7+ 2. Uncheck "This is a pre-release"
8+4. Push the new release of `octo` and `octopod`. To do this run `./release.sh <version>`.
9+5. Update the referenced tags in documentation
10+6. If there were changes to the examples:
11+ 1. Build and push the new containers:
12+ 1. octopod-web-app-example
13+ 2. octopod-helm-example
14+ 2. Create a new tag incrementing the integer version number of the tag:
15+ 1. Pull the image (`docker pull`)
16+ 2. Tag it with the new `v<integer>` (`docker tag`)
17+ 3. Push the new tag (`docker push`)
18+ 3. Update docs where the tags are referenced.
build.shnew file+66
@@ -0,0 +1,66 @@
1+#!/usr/bin/env bash
2+
3+set -e
4+
5+build_octo_cli_docker_image() {
6+ nix build nixpkgs.octo-cli-container \
7+ -I nixpkgs=nix \
8+ -o "$1"
9+}
10+
11+build_octopod_server_docker_image() {
12+ nix build nixpkgs.octopod-server-container \
13+ --arg migrations "$1" \
14+ -I nixpkgs=nix \
15+ -o "$2"
16+}
17+
18+push_docker_images() {
19+ outfile=latest-octopod-server-docker
20+ for image_name in $octo_cli_docker $octopod_server_docker; do
21+ image_type=$(echo "$image_name" | cut -d- -f1)
22+ image=$(ls -ls "$image_name" | awk '{print $12}')
23+ echo "size: $(du -sh $image)"
24+ docker load --input "$image" | tee "$outfile"
25+ nixcontainer=$(awk '{print $3}' $outfile)
26+ docker tag "$nixcontainer" "typeable/${image_type}:$1"
27+ docker push "typeable/${image_type}:$1"
28+ echo "Published: ${image_type}:$1"
29+ done
30+ rm $outfile
31+}
32+
33+build_docker_images() {
34+ build_octo_cli_docker_image "$octo_cli_docker"
35+ build_octopod_server_docker_image "$migrations" "$octopod_server_docker"
36+}
37+
38+export tag=$(git rev-parse HEAD)
39+export migrations="./migrations"
40+export octo_cli_docker="octo-docker"
41+export octopod_server_docker="octopod-server-docker"
42+
43+case "$1" in
44+ build-and-push)
45+ echo "$1 mode"
46+
47+ if test -z "$2"
48+ then
49+ echo "Please provide a tag to upload to"
50+ exit 1
51+ fi
52+
53+ build_docker_images
54+ push_docker_images $2
55+ ;;
56+ build)
57+ echo "$1 mode"
58+ build_docker_images
59+ ;;
60+ *)
61+ echo "usage:"
62+ echo " $0 build Builds the docker images."
63+ echo " $0 build-and-push <tag> Builds the docker images and uploads it to Docker Hub under the tag <tag>."
64+ exit 1
65+ ;;
66+esac
cabal.projectnew file+6
@@ -0,0 +1,6 @@
1+packages:
2+ octo-cli/
3+ octopod-api/
4+ octopod-backend/
5+ octopod-common/
6+ octopod-frontend/
charts/cert-control/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
charts/cert-control/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: cert-control
5+version: 0.1.0
charts/cert-control/templates/cert-control-clusterrole.yamlnew file+8
@@ -0,0 +1,8 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRole
3+metadata:
4+ name: cert-control-clusterrole
5+rules:
6+ - apiGroups: ["cert-manager.io"]
7+ resources: ["certificates"]
8+ verbs: ["list", "delete", "deletecollection"]
charts/cert-control/templates/cert-control-rolebinding.yamlnew file+13
@@ -0,0 +1,13 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: RoleBinding
3+metadata:
4+ name: {{ .Values.serviceaccount }}-cert-control-rolebinding
5+ namespace: {{ .Values.namespace }}
6+roleRef:
7+ kind: ClusterRole
8+ apiGroup: rbac.authorization.k8s.io
9+ name: cert-control-clusterrole
10+subjects:
11+ - kind: ServiceAccount
12+ name: {{ .Values.serviceaccount }}
13+ namespace: {{ .Values.octopod_namespace | default .Values.namespace }}
charts/cert-control/values.yamlnew file+3
@@ -0,0 +1,3 @@
1+namespace: deployment
2+octopod_namespace: octopod
3+serviceaccount: octopod
charts/helm-access/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
charts/helm-access/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: helm-access
5+version: 0.1.0
charts/helm-access/templates/helm-clusterrole.yamlnew file+11
@@ -0,0 +1,11 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRole
3+metadata:
4+ name: helm-clusterrole
5+rules:
6+ - apiGroups: [""]
7+ resources: ["pods/portforward"]
8+ verbs: ["create"]
9+ - apiGroups: [""]
10+ resources: ["pods"]
11+ verbs: ["list", "get"]
charts/helm-access/templates/helm-clusterrolebinding.yamlnew file+12
@@ -0,0 +1,12 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRoleBinding
3+metadata:
4+ name: {{ .Values.serviceaccount }}-helm-clusterrolebinding
5+roleRef:
6+ kind: ClusterRole
7+ apiGroup: rbac.authorization.k8s.io
8+ name: helm-clusterrole
9+subjects:
10+ - kind: ServiceAccount
11+ name: {{ .Values.serviceaccount }}
12+ namespace: {{ .Values.namespace }}
charts/helm-access/values.yamlnew file+2
@@ -0,0 +1,2 @@
1+namespace: octopod
2+serviceaccount: octopod
charts/kubedog-access/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: helm-access
5+version: 0.1.0
charts/kubedog-access/templates/kubedog-clusterrole.yamlnew file+17
@@ -0,0 +1,17 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRole
3+metadata:
4+ name: kubedog-clusterrole
5+rules:
6+ - apiGroups: ["apps"]
7+ resources: ["deployments"]
8+ verbs: ["list", "watch"]
9+ - apiGroups: ["apps"]
10+ resources: ["statefulsets"]
11+ verbs: ["list", "watch"]
12+ - apiGroups: ["apps"]
13+ resources: ["replicasets"]
14+ verbs: ["list", "watch"]
15+ - apiGroups: [""]
16+ resources: ["events"]
17+ verbs: ["list"]
charts/kubedog-access/templates/kubedog-clusterrolebinding.yamlnew file+12
@@ -0,0 +1,12 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRoleBinding
3+metadata:
4+ name: {{ .Values.serviceaccount }}-kubedog-clusterrolebinding
5+roleRef:
6+ kind: ClusterRole
7+ apiGroup: rbac.authorization.k8s.io
8+ name: kubedog-clusterrole
9+subjects:
10+ - kind: ServiceAccount
11+ name: {{ .Values.serviceaccount }}
12+ namespace: {{ .Values.namespace }}
charts/kubedog-access/values.yamlnew file+2
@@ -0,0 +1,2 @@
1+namespace: octopod
2+serviceaccount: octopod
charts/octopod-infra/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
charts/octopod-infra/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: octopod-infra
5+version: 0.1.0
charts/octopod-infra/templates/postgres-configmap.yamlnew file+11
@@ -0,0 +1,11 @@
1+apiVersion: v1
2+kind: ConfigMap
3+metadata:
4+ name: {{ .Release.Name }}-postgres-config
5+ namespace: {{ .Release.Namespace }}
6+ labels:
7+ app: {{ .Release.Name }}-postgres
8+data:
9+ POSTGRES_DB: {{ .Values.postgres_db | default .Release.Name }}
10+ POSTGRES_USER: {{ .Values.postgres_user | default "postgres" }}
11+ POSTGRES_PASSWORD: {{ .Values.postgres_password | default "password" }}
charts/octopod-infra/templates/postgres-service.yamlnew file+14
@@ -0,0 +1,14 @@
1+apiVersion: v1
2+kind: Service
3+metadata:
4+ name: {{ .Release.Name }}-postgres
5+ namespace: {{ .Release.Namespace }}
6+ labels:
7+ name: {{ .Release.Name }}-postgres
8+spec:
9+ selector:
10+ app: {{ .Release.Name }}-postgres
11+ clusterIP: None
12+ ports:
13+ - port: 5432
14+ name: postgres
charts/octopod-infra/templates/postgres-statefulset.yamlnew file+51
@@ -0,0 +1,51 @@
1+apiVersion: apps/v1
2+kind: StatefulSet
3+metadata:
4+ name: {{ .Release.Name }}-postgres
5+ namespace: {{ .Release.Namespace }}
6+spec:
7+ serviceName: {{ .Release.Name }}-postgres
8+ replicas: 1
9+ selector:
10+ matchLabels:
11+ app: {{ .Release.Name }}-postgres
12+ template:
13+ metadata:
14+ labels:
15+ app: {{ .Release.Name }}-postgres
16+ spec:
17+ nodeSelector:
18+ role: {{ .Values.nodeselector }}
19+ terminationGracePeriodSeconds: 120
20+ containers:
21+ - name: postgres
22+ image: postgres:10
23+ envFrom:
24+ - configMapRef:
25+ name: {{ .Release.Name }}-postgres-config
26+ resources:
27+ requests:
28+ cpu: {{ .Values.requests.cpu }}
29+ memory: {{ .Values.requests.memory }}
30+ limits:
31+ cpu: {{ .Values.limits.cpu }}
32+ memory: {{ .Values.limits.memory }}
33+ ports:
34+ - containerPort: 5432
35+ name: postgredb
36+ volumeMounts:
37+ - name: postgredb
38+ mountPath: /var/lib/postgresql/data
39+ subPath: postgres
40+ volumeClaimTemplates:
41+ - metadata:
42+ name: postgredb
43+ labels:
44+ app: {{ .Release.Name }}-postgres
45+ spec:
46+ accessModes:
47+ - "ReadWriteOnce"
48+ storageClassName: {{ .Values.storage_class | default "default" }}
49+ resources:
50+ requests:
51+ storage: {{ .Values.storage_size }}
charts/octopod-infra/values.yamlnew file+20
@@ -0,0 +1,20 @@
1+global:
2+ image_prefix:
3+ image: default
4+ image_tag:
5+
6+namespace: octopod
7+nodeselector: stand
8+
9+nodeselector: stand
10+namespace: octopod
11+postgres_db: octopod
12+postgres_user: octopod
13+postgres_password: octopod
14+storage_size: 1Gi
15+requests:
16+ cpu: 0.2
17+ memory: 256Mi
18+limits:
19+ cpu: 0.2
20+ memory: 512Mi
charts/octopod/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
charts/octopod/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: octopod
5+version: 0.1.0
charts/octopod/templates/_helpers.tplnew file+7
@@ -0,0 +1,7 @@
1+{{/*
2+Set dbname
3+*/}}
4+{{- define "dbname" -}}
5+{{- $dbname_release := .Release.Name | replace "." "_" | replace "-" "_" -}}
6+{{- .Values.dbname | default $dbname_release }}
7+{{- end -}}
charts/octopod/templates/clean-archive-cronjob.yamlnew file+47
@@ -0,0 +1,47 @@
1+apiVersion: batch/v1beta1
2+kind: CronJob
3+metadata:
4+ name: {{ .Release.Name }}-clean-archive-cronjob
5+ namespace: {{ .Values.namespace }}
6+spec:
7+ schedule: "0 */1 * * *"
8+ jobTemplate:
9+ spec:
10+ template:
11+ metadata:
12+ labels:
13+ app: {{ .Release.Name }}
14+ annotations:
15+ checksum/config: "{{ .Values.global.deploy_checksum }}"
16+ spec:
17+ nodeSelector:
18+ role: {{ .Values.nodeselector }}
19+ containers:
20+ - name: octo
21+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.octo_image }}:{{ .Values.global.image_tag }}
22+ command:
23+ - /app/octo
24+ args:
25+ - clean-archive
26+ env:
27+ - name: OCTOPOD_URL
28+ value: https://{{ .Values.power_app_domain }}:443
29+ volumeMounts:
30+ - name: certs
31+ mountPath: /cert.pem
32+ subPath: client_cert.pem
33+ - name: certs
34+ mountPath: /key.pem
35+ subPath: client_key.pem
36+ resources:
37+ requests:
38+ cpu: 0.1
39+ memory: 256Mi
40+ limits:
41+ cpu: 0.1
42+ memory: 512Mi
43+ restartPolicy: Never
44+ volumes:
45+ - name: certs
46+ configMap:
47+ name: octopod-certs
charts/octopod/templates/octopod-app-ingress.yamlnew file+32
@@ -0,0 +1,32 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-app-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ kubernetes.io/tls-acme: "true"
9+ cert-manager.io/issuer: "{{ .Release.Name }}-certs"
10+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
12+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
13+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
14+ nginx.ingress.kubernetes.io/auth-secret: octopod-basic-auth
15+ nginx.ingress.kubernetes.io/auth-secret-type: auth-file
16+ nginx.ingress.kubernetes.io/auth-type: basic
17+ nginx.ingress.kubernetes.io/enable-cors: "true"
18+ nginx.ingress.kubernetes.io/cors-allow-origin: "https://{{ .Values.domain }}"
19+ nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, PATCH, OPTIONS"
20+spec:
21+ tls:
22+ - hosts:
23+ - {{ .Values.app_domain }}
24+ secretName: {{ .Release.Name }}-app-tls
25+ rules:
26+ - host: {{ .Values.app_domain }}
27+ http:
28+ paths:
29+ - path: /
30+ backend:
31+ serviceName: {{ .Release.Name }}
32+ servicePort: 81
charts/octopod/templates/octopod-configmap.yamlnew file+19
@@ -0,0 +1,19 @@
1+apiVersion: v1
2+kind: ConfigMap
3+metadata:
4+ name: {{ .Release.Name }}-config
5+ namespace: {{ .Values.namespace }}
6+data:
7+ PROJECT_NAME: {{ .Values.project_name }}
8+ BASE_DOMAIN: {{ .Values.base_domain }}
9+ NAMESPACE: {{ .Values.target_namespace }}
10+ STATUS_UPDATE_TIMEOUT: "{{ .Values.status_update_timeout }}"
11+ ARCHIVE_RETENTION: "1209600"
12+ CREATION_COMMAND: /utils/create
13+ UPDATE_COMMAND: /utils/update
14+ ARCHIVE_COMMAND: /utils/archive
15+ CHECKING_COMMAND: /utils/check
16+ CLEANUP_COMMAND: /utils/cleanup
17+ ARCHIVE_CHECKING_COMMAND: /utils/archive_check
18+ TAG_CHECKING_COMMAND: /utils/tag_check
19+ INFO_COMMAND: /utils/info
charts/octopod/templates/octopod-deployment.yamlnew file+150
@@ -0,0 +1,150 @@
1+apiVersion: apps/v1
2+kind: Deployment
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+spec:
7+ replicas: {{ .Values.replicas }}
8+ selector:
9+ matchLabels:
10+ app: {{ .Release.Name }}
11+ template:
12+ metadata:
13+ name: {{ .Release.Name }}
14+ labels:
15+ app: {{ .Release.Name }}
16+ annotations:
17+ checksum/config: "{{ .Values.global.deploy_checksum }}"
18+ spec:
19+ serviceAccountName: {{ .Values.service_account }}
20+ nodeSelector:
21+ role: {{ .Values.nodeselector }}
22+ terminationGracePeriodSeconds: 600
23+ initContainers:
24+ - name: copy-utils
25+ image: {{ .Values.global.utils_image_prefix }}/{{ .Values.global.utils_image }}:{{ .Values.global.utils_image_tag }}
26+ command:
27+ - sh
28+ - -c
29+ - 'cp /utils/* /copy/'
30+ volumeMounts:
31+ - name: utils
32+ mountPath: /copy
33+
34+ - name: init
35+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.image }}:{{ .Values.global.image_tag }}
36+ command:
37+ - sh
38+ - -c
39+ - '/utils/init'
40+ securityContext:
41+ runAsGroup: 1000
42+ runAsUser: 1000
43+ volumeMounts:
44+ - name: home
45+ mountPath: /home/octopod
46+ - name: utils
47+ mountPath: /utils
48+
49+ - name: copy-www
50+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.image }}:{{ .Values.global.image_tag }}
51+ command:
52+ - sh
53+ - -c
54+ - 'cp -a /www/* /copy/'
55+ volumeMounts:
56+ - name: www
57+ mountPath: /copy
58+
59+ containers:
60+ - name: main
61+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.image }}:{{ .Values.global.image_tag }}
62+ ports:
63+ - containerPort: {{ .Values.port }}
64+ protocol: TCP
65+ - containerPort: {{ .Values.ui_port }}
66+ protocol: TCP
67+ args:
68+ - "--port"
69+ - "{{ .Values.port }}"
70+ - "--ui-port"
71+ - "{{ .Values.ui_port }}"
72+ - "--ws-port"
73+ - "{{ .Values.ws_port }}"
74+ - "--db"
75+ - "host='{{ .Values.pg_host }}' port=5432 user='octopod' password='octopod'"
76+ - "--db-pool-size"
77+ - "10"
78+ - "--tls-cert-path"
79+ - "/tls/server_cert.pem"
80+ - "--tls-key-path"
81+ - "/tls/server_key.pem"
82+ - "--tls-store-path"
83+ - "/tls_store"
84+ envFrom:
85+ - configMapRef:
86+ name: {{ .Release.Name }}-config
87+ securityContext:
88+ runAsGroup: 1000
89+ runAsUser: 1000
90+ volumeMounts:
91+ - name: home
92+ mountPath: /home/octopod
93+ - name: utils
94+ mountPath: /utils
95+ - name: certs
96+ mountPath: /tls/server_cert.pem
97+ subPath: server_cert.pem
98+ - name: certs
99+ mountPath: /tls/server_key.pem
100+ subPath: server_key.pem
101+ - name: certs
102+ mountPath: /tls_store/server_cert.pem
103+ subPath: server_cert.pem
104+ resources:
105+ requests:
106+ cpu: 0.2
107+ memory: 256Mi
108+ limits:
109+ cpu: 0.2
110+ memory: 512Mi
111+ readinessProbe:
112+ httpGet:
113+ port: {{ .Values.ui_port }}
114+ path: /api/v1/ping
115+ periodSeconds: 20
116+ livenessProbe:
117+ httpGet:
118+ port: {{ .Values.ui_port }}
119+ path: /api/v1/ping
120+ initialDelaySeconds: 15
121+ periodSeconds: 5
122+
123+ - name: nginx
124+ image: nginx:1.17.5
125+ volumeMounts:
126+ - name: nginx-config
127+ mountPath: /etc/nginx/conf.d/app.conf
128+ subPath: app.conf
129+ - name: nginx-config
130+ mountPath: /www/config.json
131+ subPath: config.json
132+ - name: www
133+ mountPath: /www
134+ ports:
135+ - containerPort: 80
136+ protocol: TCP
137+
138+ volumes:
139+ - name: home
140+ emptyDir: {}
141+ - name: utils
142+ emptyDir: {}
143+ - name: www
144+ emptyDir: {}
145+ - name: nginx-config
146+ configMap:
147+ name: {{ .Release.Name }}-nginx-config
148+ - name: certs
149+ configMap:
150+ name: octopod-certs
charts/octopod/templates/octopod-ingress.yamlnew file+34
@@ -0,0 +1,34 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ kubernetes.io/tls-acme: "true"
9+ cert-manager.io/issuer: "{{ .Release.Name }}-certs"
10+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
12+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
13+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
14+
15+ {{- if .Values.global.auth_url }}
16+ nginx.ingress.kubernetes.io/auth-url: "{{ .Values.global.auth_url }}"
17+ {{- end }}
18+
19+ {{- if .Values.global.auth_signin }}
20+ nginx.ingress.kubernetes.io/auth-signin: "{{ .Values.global.auth_signin }}"
21+ {{- end }}
22+spec:
23+ tls:
24+ - hosts:
25+ - {{ .Values.domain }}
26+ secretName: {{ .Release.Name }}-tls
27+ rules:
28+ - host: {{ .Values.domain }}
29+ http:
30+ paths:
31+ - path: /
32+ backend:
33+ serviceName: {{ .Release.Name }}
34+ servicePort: 80
charts/octopod/templates/octopod-issuer.yamlnew file+18
@@ -0,0 +1,18 @@
1+apiVersion: cert-manager.io/v1
2+kind: Issuer
3+metadata:
4+ name: {{ .Release.Name }}-certs
5+ namespace: {{ .Release.Namespace }}
6+spec:
7+ acme:
8+ email: {{ .Values.acme_registration_email }}
9+ server: https://acme-v02.api.letsencrypt.org/directory
10+ privateKeySecretRef:
11+ name: {{ .Release.Name }}-letsencrypt
12+ # ACME HTTP-01 provider configurations
13+ solvers:
14+ # An empty 'selector' means that this solver matches all domains
15+ - selector: {}
16+ http01:
17+ ingress:
18+ class: nginx
charts/octopod/templates/octopod-migration-job.yamlnew file+75
@@ -0,0 +1,75 @@
1+{{- if .Values.migrations }}
2+apiVersion: batch/v1
3+kind: Job
4+metadata:
5+ name: {{ .Release.Name }}-migration-job
6+ namespace: {{ .Release.Namespace }}
7+
8+ annotations:
9+ "helm.sh/hook": pre-install,pre-upgrade
10+ "helm.sh/hook-delete-policy": hook-succeeded
11+
12+spec:
13+ activeDeadlineSeconds: 600
14+ template:
15+ spec:
16+ containers:
17+
18+ - name: copy
19+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.image }}:{{ .Values.global.image_tag }}
20+ command:
21+ - "bash"
22+ - "-ec"
23+ - |
24+ set -ex
25+
26+ # copy migrations
27+ cp -av /migrations/* /mymigrations
28+
29+ # create sqitch.conf
30+ echo '[core]' > /mymigrations/sqitch.conf
31+ echo 'engine = pg' >> /mymigrations/sqitch.conf
32+ echo 'plan_file = sqitch.plan' >> /mymigrations/sqitch.conf
33+ echo 'top_dir = .' >> /mymigrations/sqitch.conf
34+ echo '[engine "pg"]' >> /mymigrations/sqitch.conf
35+ echo ' registry = sqitch' >> /mymigrations/sqitch.conf
36+ echo '[deploy]' >> /mymigrations/sqitch.conf
37+ echo ' verify = true' >> /mymigrations/sqitch.conf
38+ echo '[rebase]' >> /mymigrations/sqitch.conf
39+ echo ' verify = true' >> /mymigrations/sqitch.conf
40+ echo '[target "octopod"]' >> /mymigrations/sqitch.conf
41+ echo 'uri = db:pg://{{ .Values.connections.pg_instance }}/{{ template "dbname" . }}' >> /mymigrations/sqitch.conf
42+ volumeMounts:
43+ - name: migrations
44+ mountPath: /mymigrations
45+
46+ - name: migrations
47+ image: {{ .Values.global.image_prefix }}/{{ .Values.global.image }}:sqitch-v2.0.0
48+ command:
49+ - "bash"
50+ - "-ec"
51+ - |
52+ set -ex
53+{{- if .Values.seed }}
54+ echo 'check db'
55+ POSTGRESQL_CONN="psql postgresql://{{ .Values.connections.pg_instance }}/postgres"
56+ DBNAME={{ template "dbname" . }}
57+ ($POSTGRESQL_CONN -Atc "SELECT count(*) FROM pg_database WHERE lower(datname) = lower('$DBNAME');" | grep 1) || $POSTGRESQL_CONN -Atc "create database $DBNAME;"
58+{{- end }}
59+ echo 'run migrations...'
60+ cd /migrations && /usr/local/bin/sqitch deploy octopod
61+{{- if .Values.seed }}
62+ echo 'seed'
63+ DB_CONN="psql postgresql://{{ .Values.connections.pg_instance }}/{{ template "dbname" . }}"
64+ cd /migrations && $DB_CONN -1 -f seeds.sql || echo 'ok'
65+{{- end }}
66+ volumeMounts:
67+ - name: migrations
68+ mountPath: /migrations
69+
70+ volumes:
71+ - name: migrations
72+ emptyDir: {}
73+ restartPolicy: Never
74+ backoffLimit: 2
75+{{- end }}
charts/octopod/templates/octopod-nginx-configmap.yamlnew file+20
@@ -0,0 +1,20 @@
1+apiVersion: v1
2+kind: ConfigMap
3+metadata:
4+ name: {{ .Release.Name }}-nginx-config
5+ namespace: {{ .Values.namespace }}
6+data:
7+ app.conf: |
8+ server {
9+ listen 80 default_server;
10+ server_name _;
11+ root /www;
12+ index index.html;
13+ error_page 404 =200 /index.html;
14+ }
15+ config.json: |
16+ {
17+ "app_url": "https://{{ .Values.app_domain }}",
18+ "ws_url": "wss://{{ .Values.ws_domain }}",
19+ "app_auth": "Basic {{ .Values.basic_auth_token }}"
20+ }
charts/octopod/templates/octopod-power-app-ingress.yamlnew file+25
@@ -0,0 +1,25 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-power-app-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
9+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
10+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
12+ nginx.ingress.kubernetes.io/ssl-passthrough: "true"
13+ nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
14+spec:
15+ rules:
16+ - host: {{ .Values.power_app_domain }}
17+ http:
18+ paths:
19+ - path: /
20+ backend:
21+ serviceName: {{ .Release.Name }}
22+ servicePort: 443
23+ tls:
24+ - hosts:
25+ - {{ .Values.power_app_domain }}
charts/octopod/templates/octopod-service.yamlnew file+23
@@ -0,0 +1,23 @@
1+apiVersion: v1
2+kind: Service
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+ labels:
7+ app: {{ .Release.Name }}
8+spec:
9+ selector:
10+ app: {{ .Release.Name }}
11+ ports:
12+ - name: octopod-power-app
13+ port: 443
14+ targetPort: {{ .Values.port }}
15+ - name: octopod-ui
16+ port: 80
17+ targetPort: 80
18+ - name: octopod-app
19+ port: 81
20+ targetPort: {{ .Values.ui_port }}
21+ - name: octopod-ws
22+ port: 82
23+ targetPort: {{ .Values.ws_port }}
charts/octopod/templates/octopod-ws-ingress.yamlnew file+26
@@ -0,0 +1,26 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-ws-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ kubernetes.io/tls-acme: "true"
9+ cert-manager.io/issuer: "{{ .Release.Name }}-certs"
10+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
12+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
13+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
14+spec:
15+ tls:
16+ - hosts:
17+ - {{ .Values.ws_domain }}
18+ secretName: {{ .Release.Name }}-ws-tls
19+ rules:
20+ - host: {{ .Values.ws_domain }}
21+ http:
22+ paths:
23+ - path: /
24+ backend:
25+ serviceName: {{ .Release.Name }}
26+ servicePort: 82
charts/octopod/values.yamlnew file+38
@@ -0,0 +1,38 @@
1+global:
2+ image_prefix:
3+ image: octopod
4+ octo_image: octo
5+ image_tag:
6+ utils_image_prefix:
7+ utils_image:
8+ utils_image_tag:
9+
10+namespace: octopod
11+target_namespace: deployment
12+nodeselector: stand
13+service_account: octopod
14+port: 4443
15+ui_port: 4000
16+ws_port: 4020
17+dbname: octopod
18+seed: false
19+migrations: true
20+replicas: 1
21+domain: octopod.stage.example.com
22+app_domain: octopod-app.stage.example.com
23+ws_domain: octopod-ws.stage.example.com
24+power_app_domain: octopod-power-app.stage.example.com
25+base_domain: stage.example.com
26+project_name: Octopod
27+status_update_timeout: 600
28+acme_registration_email:
29+basic_auth_token:
30+connections:
31+ pg_instance: octopod:octopod@octopod-infra-postgres-0.octopod-infra-postgres.octopod:5432
32+pg_host: octopod-infra-postgres-0.octopod-infra-postgres.octopod
33+requests:
34+ cpu: 200m
35+ memory: 256Mi
36+limits:
37+ cpu: 200m
38+ memory: 512Mi
charts/pvc-control/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
charts/pvc-control/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: pvc-control
5+version: 0.1.0
charts/pvc-control/templates/pvc-control-clusterrole.yamlnew file+8
@@ -0,0 +1,8 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: ClusterRole
3+metadata:
4+ name: pvc-control-clusterrole
5+rules:
6+ - apiGroups: [""]
7+ resources: ["persistentvolumeclaims"]
8+ verbs: ["list", "delete", "deletecollection"]
charts/pvc-control/templates/pvc-control-rolebinding.yamlnew file+13
@@ -0,0 +1,13 @@
1+apiVersion: rbac.authorization.k8s.io/v1
2+kind: RoleBinding
3+metadata:
4+ name: {{ .Values.serviceaccount }}-pvc-control-rolebinding
5+ namespace: {{ .Values.namespace }}
6+roleRef:
7+ kind: ClusterRole
8+ apiGroup: rbac.authorization.k8s.io
9+ name: pvc-control-clusterrole
10+subjects:
11+ - kind: ServiceAccount
12+ name: {{ .Values.serviceaccount }}
13+ namespace: {{ .Values.octopod_namespace | default .Values.namespace }}
charts/pvc-control/values.yamlnew file+3
@@ -0,0 +1,3 @@
1+namespace: deployment
2+octopod_namespace: octopod
3+serviceaccount: octopod
default.nixnew file+54
@@ -0,0 +1,54 @@
1+{ sources ? import ./nix/sources.nix
2+, reflex-platform ? sources.reflex-platform
3+}:
4+(import reflex-platform { }).project ({ pkgs, ... }: {
5+ useWarp = true;
6+
7+ packages = {
8+ octopod-common = ./octopod-common;
9+ octopod-frontend = ./octopod-frontend;
10+ octopod-backend = ./octopod-backend;
11+ octo-cli = ./octo-cli;
12+ octopod-api = ./octopod-api;
13+ };
14+
15+ overrides = hself: hsuper: {
16+ servant-reflex = hsuper.callCabal2nix "servant-reflex" sources.servant-reflex { };
17+ tabulation = hsuper.callCabal2nix "tabulation" "${sources.obelisk}/lib/tabulation" { };
18+ obelisk-executable-config-lookup = hsuper.callCabal2nix "obelisk-executable-config-lookup" "${sources.obelisk}/lib/executable-config/lookup" { };
19+ obelisk-route = hsuper.callCabal2nix "obelisk-route" "${sources.obelisk}/lib/route" { };
20+ hspec-webdriver = hsuper.callCabal2nix "hspec-webdriver" sources.hspec-webdriver-clone { };
21+ servant = pkgs.haskell.lib.overrideCabal hsuper.servant (old: {
22+ postInstall = "";
23+ });
24+ servant-websockets = hsuper.callHackageDirect
25+ {
26+ pkg = "servant-websockets";
27+ ver = "2.0.0";
28+ sha256 = "01bmwg3ysj8gijcqghykxfsd62sqz1pfby2irpzh5ybwyh285pvg";
29+ } { };
30+ deriving-aeson = hsuper.callHackageDirect
31+ {
32+ pkg = "deriving-aeson";
33+ ver = "0.2.3";
34+ sha256 = "0ckwdi9pr4aqp9psag4mdbx30nygxkkpdf21rg9rfz16cz8079j7";
35+ } { };
36+ table-layout = hsuper.callHackageDirect
37+ {
38+ pkg = "table-layout";
39+ ver = "0.9.0.1";
40+ sha256 = "12nllfnh6b5mjda9qxfy192v0r0sx181w9zc9j70kvjdn7hgrb0y";
41+ } { };
42+ data-default-instances-base = hsuper.callHackageDirect
43+ {
44+ pkg = "data-default-instances-base";
45+ ver = "0.1.0.1";
46+ sha256 = "18basdy4qjn246phw008ll9zbi3rpdn6bh2dk0i81a60gsmyn58q";
47+ } { };
48+ };
49+
50+ shells = {
51+ ghc = [ "octopod-common" "octopod-backend" "octopod-frontend" "octopod-api" "octo-cli" ];
52+ ghcjs = [ "octopod-common" "octopod-frontend" ];
53+ };
54+})
docs/Makefilenew file+31
@@ -0,0 +1,31 @@
1+.PHONY: docs mermaid plantuml
2+
3+docs: mermaid plantuml
4+
5+mermaid:
6+ # to install mmdc use https://github.com/mermaidjs/mermaid.cli
7+ #
8+ # mmdc does not support stateDiagram-v2 (technical-architecture-deployment-statuses-fsm.mmd),
9+ # use https://mermaid-js.github.io/mermaid-live-editor to render it
10+ for src in `ls diagrams/src/*.mmd | grep -v technical-architecture-deployment-statuses-fsm.mmd`; do \
11+ name=`basename $$src .mmd`; \
12+ mmdc -i $$src -o "diagrams/images/$$name.png" --scale 4 --cssFile style.css; \
13+ done
14+
15+plantuml:
16+ # to install plantuml go to https://plantuml.com/command-line
17+ #
18+ # plantuml takes output paths relative to input file
19+ for src in `ls diagrams/src/*.puml`; do \
20+ name=`basename $$src .mmd`; \
21+ plantuml -I $$src -o "../../diagrams/images" -tpng; \
22+ done
23+
24+plantuml:
25+ # to install plantuml go to https://plantuml.com/command-line
26+ #
27+ # plantuml takes output paths relative to input file
28+ for src in `ls diagrams/src/*.puml`; do \
29+ name=`basename $$src .mmd`; \
30+ plantuml -I $$src -o "../../diagrams/images" -tpng; \
31+ done
docs/README.mdnew file+15
@@ -0,0 +1,15 @@
1+# 🐙📑 Octopod documentation
2+
3+## 🔭 High-level notes
4+- [🐙 Overview](en/Overview.md)
5+- [🧑‍🔬 Project managment case study](en/PM_case_study.md)
6+- [🧑‍💻 Technical case study](en/Tech_case_study.md)
7+
8+## 🛠️ Technical documentation
9+- [🏗 Technical architecture](en/Technical_architecture.md) [[RU](ru/Technical_architecture.md)]
10+- [⚙️ Control script guide](en/Control_scripts.md) [[RU](ru/Control_scripts.md)]
11+- [🔧🐙 Octopod deployment guide](en/Octopod_deployment_guide.md) [[RU](ru/Octopod_deployment_with_K8S.md)]
12+- [🔧🚀 Helm-based Octopod project setup](en/Helm-based_deployment_guide.md) [[RU](ru/Helm-based_deployment_guide.md)]
13+- [🐙🎛 octo CLI user guide](en/Octo_user_guide.md) [[RU](ru/Octo_user_guide.md)]
14+- [🤖 CI integration](en/Integration.md)
15+- [🔒 Octopod security model](en/Security_model.md) [[RU](ru/Security_model.md)]
docs/diagrams/images/app-architecture.pngnew file

Binary file not shown.

docs/diagrams/images/sample_architecture.pngnew file

Binary file not shown.

docs/diagrams/images/sample_deployment.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-archive-via-cli.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-archive-via-ui.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-cleanup-via-cli.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-cleanup-via-ui.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-create-via-cli.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-create-via-ui.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-deployment-states-fsm.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-restore-via-cli.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-restore-via-ui.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-update-via-cli.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-update-via-ui.pngnew file

Binary file not shown.

docs/diagrams/src/sample_architecture.pumlnew file+16
@@ -0,0 +1,16 @@
1+@startuml
2+
3+top to bottom direction
4+
5+database "Redis" as OBR
6+database "Postgres" as OBP
7+boundary "nginx" as OBN
8+
9+rectangle "Server" as OBSer
10+
11+OBSer -down-> OBR
12+OBSer -down-> OBP
13+OBN -right-> OBSer
14+
15+
16+@enduml
docs/diagrams/src/sample_deployment.pumlnew file+61
@@ -0,0 +1,61 @@
1+@startuml
2+
3+top to bottom direction
4+
5+node "Kubernetes cluster" {
6+ boundary "Kube API Server" as K8sAPI
7+
8+ rectangle "Kubernetes" as K8s
9+
10+ K8sAPI -> K8s
11+
12+ cloud "<i>Orange button</i> staging" as OBS #OldLace {
13+ frame "Common infrastructure" #lightcyan {
14+ database "Redis" as OBR #PowderBlue
15+ database "Postgres" as OBP #PowderBlue
16+ boundary "nginx" as OBN #PowderBlue
17+ }
18+
19+ rectangle "Server with an <i>orange button</i>" as OBSer #Wheat
20+
21+ OBSer -down-> OBR
22+ OBSer -down-> OBP
23+ OBN -right-> OBSer
24+ }
25+
26+ cloud "<i>Green button</i> staging" as GBS #technology {
27+ frame "Common infrastructure " #lightcyan {
28+ database "Redis" as GBR #PowderBlue
29+ database "Postgres" as GBP #PowderBlue
30+ boundary "nginx" as GBN #PowderBlue
31+ }
32+
33+ rectangle "Server with a <i>green button</i>" as GBSer #Greenyellow
34+
35+ GBSer -down-> GBR
36+ GBSer -down-> GBP
37+ GBN -right-> GBSer
38+ }
39+
40+ K8s -down-> OBS : create the staging
41+ K8s -down-> GBS : create the staging
42+}
43+
44+
45+node Octopod {
46+ boundary "Web UI" as UI
47+ rectangle "Octopod Server" as OctoS
48+ rectangle "Staging control scripts" as SCS
49+
50+ UI -> OctoS
51+ OctoS -> SCS : delegates k8s logic to control script
52+}
53+
54+SCS -down-> K8sAPI : set up the stagings
55+
56+
57+actor Developer
58+
59+Developer -down-> UI : Specifies the git comit hash to deploy in the web UI
60+
61+@enduml
docs/diagrams/src/technical-architecture-archive-via-cli.mmdnew file+37
@@ -0,0 +1,37 @@
1+sequenceDiagram
2+ participant octo CLI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant UI
6+ participant Octopod Server/BgWorker
7+ participant Octopod Server/StatusUpdater
8+ participant ControlScripts
9+ participant KubeAPI
10+ octo CLI->>Octopod Server: archive(name)
11+ Octopod Server->>PostgreSQL: status=ArchivePending
12+ alt name not found
13+ PostgreSQL-->>Octopod Server: error: name not found
14+ Octopod Server-->>octo CLI: error: name not found
15+ else
16+ PostgreSQL-->>Octopod Server: ok
17+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
18+ UI->>Octopod Server: get deployments info
19+ Octopod Server-->>UI: deployments info
20+ Octopod Server->>Octopod Server/BgWorker: archive
21+ Octopod Server-->>octo CLI: done
22+ Octopod Server/BgWorker->>ControlScripts: archive
23+ ControlScripts->>KubeAPI: archive deployment
24+ KubeAPI-->>ControlScripts: done
25+ ControlScripts-->>Octopod Server/BgWorker: done
26+ Octopod Server/BgWorker->>PostgreSQL: write logs
27+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
28+ UI->>Octopod Server: get deployments info
29+ Octopod Server-->>UI: deployments info
30+ Note over Octopod Server/StatusUpdater: wait 5 minutes
31+ loop check deployment status every 30 seconds
32+ Octopod Server/StatusUpdater->>PostgreSQL: status=Archived/ArchivePending
33+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
34+ UI->>Octopod Server: get deployments info
35+ Octopod Server-->>UI: deployments info
36+ end
37+ end
docs/diagrams/src/technical-architecture-archive-via-ui.mmdnew file+36
@@ -0,0 +1,36 @@
1+sequenceDiagram
2+ participant UI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant Octopod Server/BgWorker
6+ participant Octopod Server/StatusUpdater
7+ participant ControlScripts
8+ participant KubeAPI
9+ UI->>Octopod Server: archive(name)
10+ Octopod Server->>PostgreSQL: status=ArchivePending
11+ alt name not found
12+ PostgreSQL-->>Octopod Server: error: name not found
13+ Octopod Server-->>UI: error: name not found
14+ else
15+ PostgreSQL-->>Octopod Server: ok
16+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
17+ UI->>Octopod Server: get deployments info
18+ Octopod Server-->>UI: deployments info
19+ Octopod Server->>Octopod Server/BgWorker: archive
20+ Octopod Server-->>UI: done
21+ Octopod Server/BgWorker->>ControlScripts: archive
22+ ControlScripts->>KubeAPI: archive deployment
23+ KubeAPI-->>ControlScripts: done
24+ ControlScripts-->>Octopod Server/BgWorker: done
25+ Octopod Server/BgWorker->>PostgreSQL: write logs
26+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
27+ UI->>Octopod Server: get deployments info
28+ Octopod Server-->>UI: deployments info
29+ Note over Octopod Server/StatusUpdater: wait 5 minutes
30+ loop check deployment status every 30 seconds
31+ Octopod Server/StatusUpdater->>PostgreSQL: status=Archived/ArchivePending
32+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
33+ UI->>Octopod Server: get deployments info
34+ Octopod Server-->>UI: deployments info
35+ end
36+ end
docs/diagrams/src/technical-architecture-cleanup-via-cli.mmdnew file+20
@@ -0,0 +1,20 @@
1+sequenceDiagram
2+ participant octo CLI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant UI
6+ participant Octopod Server/BgWorker
7+ participant Octopod Server/StatusUpdater
8+ participant ControlScripts
9+ participant KubeAPI
10+ octo CLI->>Octopod Server: cleanup(name)
11+ Octopod Server->>Octopod Server/BgWorker: cleanup
12+ Octopod Server-->>octo CLI: done
13+ Octopod Server/BgWorker->>ControlScripts: cleanup
14+ ControlScripts->>KubeAPI: cleanup deployment
15+ KubeAPI-->>ControlScripts: done
16+ ControlScripts-->>Octopod Server/BgWorker: done
17+ Octopod Server->>PostgreSQL: delete config and logs
18+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
19+ UI->>Octopod Server: get deployments info
20+ Octopod Server-->>UI: deployments info
docs/diagrams/src/technical-architecture-cleanup-via-ui.mmdnew file+19
@@ -0,0 +1,19 @@
1+sequenceDiagram
2+ participant UI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant Octopod Server/BgWorker
6+ participant Octopod Server/StatusUpdater
7+ participant ControlScripts
8+ participant KubeAPI
9+ UI->>Octopod Server: cleanup(name)
10+ Octopod Server->>Octopod Server/BgWorker: cleanup
11+ Octopod Server-->>UI: done
12+ Octopod Server/BgWorker->>ControlScripts: cleanup
13+ ControlScripts->>KubeAPI: cleanup deployment resources
14+ KubeAPI-->>ControlScripts: done
15+ ControlScripts-->>Octopod Server/BgWorker: done
16+ Octopod Server->>PostgreSQL: delete config and logs
17+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
18+ UI->>Octopod Server: get deployments info
19+ Octopod Server-->>UI: deployments info
docs/diagrams/src/technical-architecture-create-via-cli.mmdnew file+41
@@ -0,0 +1,41 @@
1+sequenceDiagram
2+ participant octo CLI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant UI
6+ participant Octopod Server/BgWorker
7+ participant Octopod Server/StatusUpdater
8+ participant ControlScripts
9+ participant KubeAPI
10+ octo CLI->>Octopod Server: create(name, tag, [override])
11+ Octopod Server->>PostgreSQL: store config, status=CreatePending
12+ alt name already exists
13+ PostgreSQL->>Octopod Server: error: deployment already exists
14+ Octopod Server-->>octo CLI: error: deployment already exists
15+ else
16+ alt tag not found
17+ Octopod Server-->>octo CLI: error: tag not found
18+ else
19+ PostgreSQL-->>Octopod Server: ok
20+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
21+ UI->>Octopod Server: get deployments info
22+ Octopod Server-->>UI: deployments info
23+ Octopod Server->>Octopod Server/BgWorker: create
24+ Octopod Server-->>octo CLI: done
25+ Octopod Server/BgWorker->>ControlScripts: create
26+ ControlScripts->>KubeAPI: create deployment
27+ KubeAPI-->>ControlScripts: done
28+ ControlScripts-->>Octopod Server/BgWorker: done
29+ Octopod Server/BgWorker->>PostgreSQL: write logs
30+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31+ UI->>Octopod Server: get deployments info
32+ Octopod Server-->>UI: deployments info
33+ Note over Octopod Server/StatusUpdater: wait 5 minutes
34+ loop check deployment status every 30 seconds
35+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37+ UI->>Octopod Server: get deployments info
38+ Octopod Server-->>UI: deployments info
39+ end
40+ end
41+ end
docs/diagrams/src/technical-architecture-create-via-ui.mmdnew file+40
@@ -0,0 +1,40 @@
1+sequenceDiagram
2+ participant UI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant Octopod Server/BgWorker
6+ participant Octopod Server/StatusUpdater
7+ participant ControlScripts
8+ participant KubeAPI
9+ UI->>Octopod Server: create(name, tag, [override])
10+ Octopod Server->>PostgreSQL: store config, status=CreatePending
11+ alt name already exists
12+ PostgreSQL-->>Octopod Server: error: deployment already exists
13+ Octopod Server-->>UI: error: deployment already exists
14+ else
15+ alt tag not found
16+ Octopod Server-->>UI: error: tag not found
17+ else
18+ PostgreSQL-->>Octopod Server: ok
19+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
20+ UI->>Octopod Server: get deployments info
21+ Octopod Server-->>UI: deployments info
22+ Octopod Server->>Octopod Server/BgWorker: create
23+ Octopod Server-->>UI: done
24+ Octopod Server/BgWorker->>ControlScripts: create
25+ ControlScripts->>KubeAPI: create deployment
26+ KubeAPI-->>ControlScripts: done
27+ ControlScripts-->>Octopod Server/BgWorker: done
28+ Octopod Server/BgWorker->>PostgreSQL: write logs
29+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30+ UI->>Octopod Server: get deployments info
31+ Octopod Server-->>UI: deployments info
32+ Note over Octopod Server/StatusUpdater: wait 5 minutes
33+ loop check deployment status every 30 seconds
34+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36+ UI->>Octopod Server: get deployments info
37+ Octopod Server-->>UI: deployments info
38+ end
39+ end
40+ end
docs/diagrams/src/technical-architecture-deployment-states-fsm.mmdnew file+16
@@ -0,0 +1,16 @@
1+stateDiagram-v2
2+ [*] --> CreatePending: create
3+ Running --> UpdatePending: update
4+ Failure --> UpdatePending: update
5+ Running --> ArchivePending: archive
6+ Failure --> ArchivePending: archive
7+ Archived --> CreatePending: restore
8+ Archived --> [*]: cleanup
9+ Running --> Failure: 30s passed and 'check' said "nok"
10+ Failure --> Running: 30s passed and 'check' said "ok"
11+ CreatePending --> Running: 5m passed and 'check' said "ok"
12+ CreatePending --> Failure: 5m passed and 'check' said "nok"
13+ UpdatePending --> Running: 5m passed and 'check' said "ok"
14+ UpdatePending --> Failure: 5m passed and 'check' said "nok"
15+ ArchivePending --> Archived: 30s passed and 'archive_check' said "ok"
16+ ArchivePending --> ArchivePending: 30s passed and 'archive_check' said "nok"
docs/diagrams/src/technical-architecture-restore-via-cli.mmdnew file+41
@@ -0,0 +1,41 @@
1+sequenceDiagram
2+ participant octo CLI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant UI
6+ participant Octopod Server/BgWorker
7+ participant Octopod Server/StatusUpdater
8+ participant ControlScripts
9+ participant KubeAPI
10+ octo CLI->>Octopod Server: restore(name)
11+ Octopod Server->>PostgreSQL: status=CreatePending
12+ alt name not found
13+ PostgreSQL-->>Octopod Server: error: name not found
14+ Octopod Server-->>octo CLI: error: name not found
15+ else
16+ alt tag not found
17+ Octopod Server-->>octo CLI: error: tag not found
18+ else
19+ PostgreSQL-->>Octopod Server: ok
20+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
21+ UI->>Octopod Server: get deployments info
22+ Octopod Server-->>UI: deployments info
23+ Octopod Server->>Octopod Server/BgWorker: create
24+ Octopod Server-->>octo CLI: done
25+ Octopod Server/BgWorker->>ControlScripts: create
26+ ControlScripts->>KubeAPI: restore deployment
27+ KubeAPI-->>ControlScripts: done
28+ ControlScripts-->>Octopod Server/BgWorker: done
29+ Octopod Server/BgWorker->>PostgreSQL: write logs
30+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31+ UI->>Octopod Server: get deployments info
32+ Octopod Server-->>UI: deployments info
33+ Note over Octopod Server/StatusUpdater: wait 5 minutes
34+ loop check deployment status every 30 seconds
35+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37+ UI->>Octopod Server: get deployments info
38+ Octopod Server-->>UI: deployments info
39+ end
40+ end
41+ end
docs/diagrams/src/technical-architecture-restore-via-ui.mmdnew file+40
@@ -0,0 +1,40 @@
1+sequenceDiagram
2+ participant UI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant Octopod Server/BgWorker
6+ participant Octopod Server/StatusUpdater
7+ participant ControlScripts
8+ participant KubeAPI
9+ UI->>Octopod Server: restore(name)
10+ Octopod Server->>PostgreSQL: status=CreatePending
11+ alt name not found
12+ PostgreSQL-->>Octopod Server: error: name not found
13+ Octopod Server-->>UI: error: name not found
14+ else
15+ alt tag not found
16+ Octopod Server-->>UI: error: tag not found
17+ else
18+ PostgreSQL-->>Octopod Server: ok
19+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
20+ UI->>Octopod Server: get deployments info
21+ Octopod Server-->>UI: deployments info
22+ Octopod Server->>Octopod Server/BgWorker: create
23+ Octopod Server-->>UI: done
24+ Octopod Server/BgWorker->>ControlScripts: create
25+ ControlScripts->>KubeAPI: restore deployment
26+ KubeAPI-->>ControlScripts: done
27+ ControlScripts-->>Octopod Server/BgWorker: done
28+ Octopod Server/BgWorker->>PostgreSQL: write logs
29+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30+ UI->>Octopod Server: get deployments info
31+ Octopod Server-->>UI: deployments info
32+ Note over Octopod Server/StatusUpdater: wait 5 minutes
33+ loop check deployment status every 30 seconds
34+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36+ UI->>Octopod Server: get deployments info
37+ Octopod Server-->>UI: deployments info
38+ end
39+ end
40+ end
docs/diagrams/src/technical-architecture-update-via-cli.mmdnew file+41
@@ -0,0 +1,41 @@
1+sequenceDiagram
2+ participant octo CLI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant UI
6+ participant Octopod Server/BgWorker
7+ participant Octopod Server/StatusUpdater
8+ participant ControlScripts
9+ participant KubeAPI
10+ octo CLI->>Octopod Server: create(name, tag, [override])
11+ Octopod Server->>PostgreSQL: store config, status=UpdatePending
12+ alt name not found
13+ PostgreSQL-->>Octopod Server: error: name not found
14+ Octopod Server-->>octo CLI: error: name not found
15+ else
16+ alt tag not found
17+ Octopod Server-->>octo CLI: error: tag not found
18+ else
19+ PostgreSQL-->>Octopod Server: ok
20+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
21+ UI->>Octopod Server: get deployments info
22+ Octopod Server-->UI: deployments info
23+ Octopod Server->>Octopod Server/BgWorker: update
24+ Octopod Server-->>octo CLI: done
25+ Octopod Server/BgWorker->>ControlScripts: update
26+ ControlScripts->>KubeAPI: upgrade deployment
27+ KubeAPI-->>ControlScripts: done
28+ ControlScripts-->>Octopod Server/BgWorker: done
29+ Octopod Server/BgWorker->>PostgreSQL: write logs
30+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31+ UI->>Octopod Server: get deployments info
32+ Octopod Server-->UI: deployments info
33+ Note over Octopod Server/StatusUpdater: wait 5 minutes
34+ loop check deployment status every 30 seconds
35+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37+ UI->>Octopod Server: get deployments info
38+ Octopod Server-->UI: deployments info
39+ end
40+ end
41+ end
docs/diagrams/src/technical-architecture-update-via-ui.mmdnew file+40
@@ -0,0 +1,40 @@
1+sequenceDiagram
2+ participant UI
3+ participant Octopod Server
4+ participant PostgreSQL
5+ participant Octopod Server/BgWorker
6+ participant Octopod Server/StatusUpdater
7+ participant ControlScripts
8+ participant KubeAPI
9+ UI->>Octopod Server: create(name, tag, [override])
10+ Octopod Server->>PostgreSQL: store config, status=UpdatePending
11+ alt name not found
12+ PostgreSQL-->>Octopod Server: error: name not found
13+ Octopod Server-->>UI: error: name not found
14+ else
15+ alt tag not found
16+ Octopod Server-->>UI: error: tag not found
17+ else
18+ PostgreSQL-->>Octopod Server: ok
19+ Octopod Server-xUI: event FrontendPleaseUpdateEverything
20+ UI->>Octopod Server: get deployments info
21+ Octopod Server-->>UI: deployments info
22+ Octopod Server->>Octopod Server/BgWorker: update
23+ Octopod Server-->>UI: done
24+ Octopod Server/BgWorker->>ControlScripts: update
25+ ControlScripts->>KubeAPI: upgrade deployment
26+ KubeAPI-->>ControlScripts: done
27+ ControlScripts-->>Octopod Server/BgWorker: done
28+ Octopod Server/BgWorker->>PostgreSQL: write logs
29+ Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30+ UI->>Octopod Server: get deployments info
31+ Octopod Server-->>UI: deployments info
32+ Note over Octopod Server/StatusUpdater: wait 5 minutes
33+ loop check deployment status every 30 seconds
34+ Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35+ Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36+ UI->>Octopod Server: get deployments info
37+ Octopod Server-->>UI: deployments info
38+ end
39+ end
40+ end
docs/en/Control_scripts.mdnew file+294
@@ -0,0 +1,294 @@
1+# Control scripts
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [General behavior](#general-behavior)
7+- [Scripts](#scripts)
8+ - [🔁 init](#-init)
9+ - [Description](#description)
10+ - [Sample implementation](#sample-implementation)
11+ - [✨ create](#-create)
12+ - [Description](#description-1)
13+ - [Execution example](#execution-example)
14+ - [Sample implementation](#sample-implementation-1)
15+ - [🔧 update](#-update)
16+ - [Description](#description-2)
17+ - [Execution example](#execution-example-1)
18+ - [Sample implementation](#sample-implementation-2)
19+ - [🗃 archive](#-archive)
20+ - [Description](#description-3)
21+ - [Execution example](#execution-example-2)
22+ - [Sample implementation](#sample-implementation-3)
23+ - [✅ check](#-check)
24+ - [Description](#description-4)
25+ - [Execution example](#execution-example-3)
26+ - [Sample implementation](#sample-implementation-4)
27+ - [🚮 cleanup](#-cleanup)
28+ - [Description](#description-5)
29+ - [Execution example](#execution-example-4)
30+ - [Sample implementation](#sample-implementation-5)
31+ - [🗃✅ archive_check](#-archive_check)
32+ - [Description](#description-6)
33+ - [Execution example](#execution-example-5)
34+ - [Sample implementation](#sample-implementation-6)
35+ - [🐋✅ tag_check](#-tag_check)
36+ - [Description](#description-7)
37+ - [Execution example](#execution-example-6)
38+ - [👀 info](#-info)
39+ - [Description](#description-8)
40+ - [Execution example](#execution-example-7)
41+ - [Sample implementation](#sample-implementation-7)
42+
43+</details>
44+
45+## General behavior
46+
47+All _control scripts_ receive input as CLI arguments. After executing the required logic they must finish with an _exit code_ of `0` if no errors have occurred and the required actions have all completed. If there was an error and some steps were not executed, the *script* should exit with an *exit code* **distinct from `0`**. Any non-zero exit code will indicate an error.
48+
49+Everything the _scripts_ write to _stdout_ and _stderr_ will be collected and stored. DevOps engineers can then view these logs from the _octo CLI_, should that be needed.
50+
51+> *NOTE: Logs from `check`, `archive_check` and `tag_check` are not collected because they are called very often.*
52+
53+There are four arguments that are passed to **all** *scripts*. The first three arguments come from the [_Kubernetes ConfigMap_][configmap]:
54+* `--project-name` – the name of the project. It is supplied mostly for informational purposes and can be useful for sending notifications if that is necessary.
55+* `--base-domain` – the base domain. It can be useful for generating the URLs of deployments.
56+* `--namespace` – The namespace in which the deployment should be created.
57+* `--name` – The name of the deployment supplied in the _Web UI_. It can be useful for generating the deployment URL.
58+
59+<a id="star"></a>*NOTE:* If an argument is marked with a ⭐, it means that the argument can be passed any number of times.
60+
61+## Scripts
62+
63+### 🔁 init
64+
65+#### Description
66+
67+This script is called **once** during the creation of the `Octopod Server` *Kubernetes Pod* to set up the proper environment to execute all other scripts.
68+
69+It is guaranteed that this script will be called **before** any of the other scripts.
70+
71+You could, for example, set up access to your *version control system*, *cloud providers*, etc. This can be achieved by saving the configuration into files in the `$HOME` directory.
72+
73+Unlike all other scripts, this script receives no arguments.
74+
75+#### Sample implementation
76+
77+```bash
78+mkdir $HOME/.ssh
79+echo -e "Host github.com\nHostname github.com\nPort 22\nUser git\nIdentityFile $HOME/.ssh/deploy.key" > $HOME/.ssh/config
80+echo "MY_DEPLOY_KEY" > $HOME/.ssh/deploy.key"
81+```
82+
83+### ✨ create
84+
85+#### Description
86+
87+Creates a new deployment in the _Kubernetes_ cluster.
88+
89+This script receives the following additional command-line arguments as input:
90+* `--tag` – The _Docker Image tag_ that should be deployed. (In practice you can use some other string that identifies a version of your system to deploy – you will need to process it accordingly in the script.)
91+* `--app-env-override` [⭐](#star) – App-level overrides. These overrides should be passed to the server being deployed. These overrides are specified in the _Web UI_. They are passed in the format of `KEY=VALUE` pairs.
92+* `--deployment-override` [⭐](#star) – Deployment-level overrides. These overrides should be used to set up the deployment environment itself, rather than be passed to the server being deployed. These overrides are specified in the _Web UI_. They are passed in the format of `KEY=VALUE` pairs.
93+
94+#### Execution example
95+
96+The script might be called something like this:
97+
98+```bash
99+create --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button" --tag "c9bbc3fcc69e5aa094bca110c6f79419ab7be77a" --app-env-override "EMAIL_TOKEN=123123" --app-env-override "SECRET_BUTTON_ENABLED=True" --deployment-override "FANCY_DATABASE=True"
100+```
101+
102+#### Sample implementation
103+
104+```bash
105+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
106+ --set "global.project-name=$project_name" \
107+ --set "global.base-domain=$base-domain" \
108+ --set "app.tag=$tag" \
109+ --set "app.env.foo=$app_env_override_1" \
110+ --set "app.bar=$deployment_override_1" \
111+ --wait \
112+ --timeout 300
113+```
114+
115+### 🔧 update
116+
117+#### Description
118+
119+Updates a deployment in _Kubernetes_ to a new *Docker Image tag*.
120+
121+This script receives the same additional command-line arguments as [`create`](#-create):
122+* `--tag` – The _Docker Image tag_ that should be deployed. (In practice you can use some other string that identifies a version of your system to deploy – you will need to process it accordingly in the script.)
123+* `--app-env-override` [⭐](#star) – App-level overrides. These overrides should be passed to the server being deployed. These overrides are specified in the _Web UI_. They are passed in the format of `KEY=VALUE` pairs.
124+* `--deployment-override` [⭐](#star) – Deployment-level overrides. These overrides should be used to set up the deployment environment itself, rather than be passed to the server being deployed. These overrides are specified in the _Web UI_. They are passed in the format of `KEY=VALUE` pairs.
125+
126+#### Execution example
127+
128+The script might be called something like this:
129+
130+```bash
131+update --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button" --tag "c9bbc3fcc69e5aa094bca110c6f79419ab7be77a" --app-env-override "EMAIL_TOKEN=123123" --app-env-override "SECRET_BUTTON_ENABLED=True" --deployment-override "FANCY_DATABASE=True"
132+```
133+
134+#### Sample implementation
135+
136+```bash
137+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
138+ --set "global.project-name=$project_name" \
139+ --set "global.base-domain=$base-domain" \
140+ --set "app.tag=$tag" \
141+ --set "app.env.foo=$app_env_override_1" \
142+ --set "app.bar=$deployment_override_1" \
143+ --wait \
144+ --timeout 300
145+```
146+
147+### 🗃 archive
148+
149+#### Description
150+
151+"Archives" a deployment. This script should only free the computational resources used by the deployment ― it should remove the _Kubernetes Pods_, but not remove any _Persistent Volumes_ associated with the deployment. It is done this way to provide a period of time in which the user can recover a deployment in the state it was in.
152+
153+Deleting the _Persistent Volume Claims_ should be done in the [`cleanup`](#-cleanup) script.
154+
155+This script should in some sense be the inverse of [`create`](#-create) (up to _Persistent Volumes_).
156+
157+This script receives only [the default command-line arguments](#general-behavior) as input.
158+
159+#### Execution example
160+
161+The script might be called something like this:
162+
163+```bash
164+archive --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button"
165+```
166+
167+#### Sample implementation
168+
169+```bash
170+helm delete "$name" --purge
171+```
172+
173+### ✅ check
174+
175+#### Description
176+
177+This script checks the status of the deployment.
178+
179+If the script exits with `0`, it means that the deployment is healthy and up. If the script exits with a non-zero exit code, it means that the deployment is not healthy or down.
180+
181+This script receives only [the default command-line arguments](#general-behavior) as input.
182+
183+#### Execution example
184+
185+The script might be called something like this:
186+
187+```bash
188+check --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button"
189+```
190+
191+#### Sample implementation
192+
193+```bash
194+echo "{\"Deployments\": [{\"ResourceName\": \"app-${name}\", \"Namespace\": \"${namespace}\"}], \"StatefulSets\": [{\"ResourceName\": \"db-${name}\", \"Namespace\": \"${namespace}\"}]}" | \
195+ kubedog multitrack -t 3
196+```
197+
198+### 🚮 cleanup
199+
200+#### Description
201+
202+Cleans up any persistent resources a deployment might have allocated, such as _Persistent Volumes_.
203+
204+This script will always be called **after** [`archive`](#-archive) has been called on the given deployment.
205+
206+This script receives only [the default command-line arguments](#general-behavior) as input.
207+
208+#### Execution example
209+
210+The script might be called something like this:
211+
212+```bash
213+cleanup --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button"
214+```
215+
216+#### Sample implementation
217+
218+```bash
219+kubectl delete pvc -n $namespace -l "app=$name"
220+```
221+
222+### 🗃✅ archive_check
223+
224+#### Description
225+
226+This script checks that a given deployment really has been archived and is no longer running.
227+
228+If the scripts exits with `0`, it means that the deployment has been archived successfully. If the script exits with a non-zero exit code, it means that the deployment has not been archived.
229+
230+
231+This script receives only [the default command-line arguments](#general-behavior) as input.
232+
233+#### Execution example
234+
235+The script might be called something like this:
236+
237+```bash
238+archive_check --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button"
239+```
240+
241+#### Sample implementation
242+
243+```bash
244+helm status $name
245+```
246+
247+### 🐋✅ tag_check
248+
249+#### Description
250+
251+This script is called right before [`create`](#-create) and [`update`](#-update) scripts to check that a given _Docker Image tag_ exists. This can be useful since it can be very easy to make a typo in the _Docker Image tag_ and deployments are typically not instant. Implementing this script would allow the user of the _Web UI_ to instantly get an error specifically about a wrong _Docker Image tag_.
252+
253+This script receives the following additional command-line arguments as input:
254+* `--tag` – The _Docker Image tag_ that should be checked.
255+
256+#### Execution example
257+
258+The script might be called something like this:
259+
260+```bash
261+tag_check --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button" --tag "c9bbc3fcc69e5aa094bca110c6f79419ab7be77a"
262+```
263+
264+### 👀 info
265+
266+#### Description
267+
268+This script returns user-facing metadata about a deployment. Currently, the metadata consists of URLs that are relevant for the deployment. Things like the deployment URL, the URL to view logs, and the database URL.
269+
270+The script should return the metadata as a two-column CSV table:
271+
272+```
273+app,https://foo.example.com
274+api,https://api.foo.example.com
275+```
276+
277+This script receives only [the default command-line arguments](#general-behavior) as input.
278+
279+#### Execution example
280+
281+The script might be called something like this:
282+
283+```bash
284+info --project-name "Cactus store" --base-domain "cactus-store.com" --namespace "cactus" --name "orange-button"
285+```
286+
287+#### Sample implementation
288+
289+```bash
290+echo "app,https://${name}.example.com"
291+echo "api,https://api.${name}.example.com"
292+```
293+
294+[configmap]: https://kubernetes.io/docs/concepts/configuration/configmap/
docs/en/Helm-based_deployment_guide.mdnew file+177
@@ -0,0 +1,177 @@
1+# Helm-based deployment guide
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [The web application](#the-web-application)
7+- [Setting up Octopod](#setting-up-octopod)
8+ - [Control scripts](#control-scripts)
9+ - [A word about TLS](#a-word-about-tls)
10+ - [Deploying Octopod](#deploying-octopod)
11+- [Testing out the deployment](#testing-out-the-deployment)
12+ - [Setting up _octo CLI_](#setting-up-octo-cli)
13+ - [Setting up certificates](#setting-up-certificates)
14+ - [Setting up the API URL](#setting-up-the-api-url)
15+ - [Creating a deployment](#creating-a-deployment)
16+ - [Adding an override](#adding-an-override)
17+ - [Updating the deployment version](#updating-the-deployment-version)
18+ - [Changing the number of replicas](#changing-the-number-of-replicas)
19+
20+</details>
21+
22+In this guide, we will examine a very simple web application and explore setting up _Octopod_ to deploy it.
23+
24+## The web application
25+
26+The web application we will be using is a very simple application that serves a single endpoint `/`. The returned HTML markup contains the environment variables the executable has read from the environment. The only variables returned are the ones whose name starts with `APP_ENV`.
27+
28+![](../images/first.png)
29+
30+The source code can be found in the [examples/web-app](../../examples/web-app) folder of this repository.
31+
32+You can also find a second version of the server in the [examples/web-app-v2](../../examples/web-app-v2) folder of this repository. The second version is identical to the first version with the only difference being that it returns the variables as an unordered list.
33+
34+![](../images/second.png)
35+
36+We have already built and pushed the two versions of the application into the [typeable/octopod-web-app-example](https://hub.docker.com/repository/docker/typeable/octopod-web-app-example) DockerHub registry under the `v1` and `v2` tags.
37+
38+## Setting up Octopod
39+
40+### Control scripts
41+
42+The only thing you need to do to configure _Octopod_ to work with your application is to write appropriate [_control scripts_](Control_scripts.md) to manipulate your deployments. We have already written the appropriate _control scripts_ for this application. You can find them in the [examples/helm-based-control-scripts](../../examples/helm-based-control-scripts) folder of this repository. The scripts are written in the _Rust_ programming language.
43+
44+The most interesting of them all is the [create.rs](../../examples/helm-based-control-scripts/src/bin/create.rs) script. The basic order of operations is:
45+
46+1. Read the passed command-line arguments
47+2. Clone the repo to get the _charts_ used to deploy the application with _helm_
48+3. Generate the arguments that should be passed to _helm_
49+4. Call _helm_ with the downloaded _charts_ and the generated arguments
50+
51+> 💡 **NOTE:** You might have noticed that there is no `update.rs`. That is because our application is stateless and packaged up into a single _chart_. This allows us to simply reuse the same script for both creating and updating a deployment. If you have a more complicated setup with a database, for example, you will most likely need a distinct implementation for `update`.
52+
53+### A word about TLS
54+
55+If you are deploying Web applications, as we are here, you probably want to use TLS to encrypt your connections to your deployment. The most straightforward way of doing this is generating a separate TLS certificate for every deployment (for every subdomain). [_Cert Manager_][cert-manager] creates TLS certificates through [_Let’s Encrypt_][lets-encrypt] and [_Let’s Encrypt_][lets-encrypt] has [a limit on the amount of certificates][lets-encrypt-rate-limits] you can issue within a given time interval. If you exceed this limit you will start getting a _too many registrations for this IP_ error. If that is the case moving the [_Cert Manager_][cert-manager] _Pod_ might help.
56+
57+### Deploying Octopod
58+
59+To deploy _Octopod_ you will need to follow the [_Octopod_ deployment guide](Octopod_deployment_guide.md). The only modification will be that you will replace the "Control Scripts Setup" section in the last step with the appropriate values.
60+
61+These values point to a docker registry where we have already packaged up these _control scripts_ into a _Docker Image_.
62+
63+```bash
64+#################################################
65+# Control Scripts Setup
66+#
67+# if you are just testing things out you can paste the values
68+# from the Helm Deployment Guide example
69+#################################################
70+
71+# The name of the registry with control scripts
72+utils_registry="typeable"
73+
74+# The name of the image with control scripts
75+utils_image="octopod-helm-example"
76+
77+# The tag of the image to use
78+utils_image_tag="1.0"
79+```
80+
81+## Testing out the deployment
82+
83+### Setting up _octo CLI_
84+
85+Using the Web UI is fairly straightforward, so we will examine creating deployments with the [_octo CLI_](Octo_user_guide.md).
86+
87+#### Setting up certificates
88+
89+You will need to get the paths to `client_cert.pem` and `client_key.pem` generated in the [Creating SSL certificates](Octopod_deployment_guide.md#creating-ssl-certificates) step and place them into `TLS_CERT_PATH` and `TLS_KEY_PATH` environment variables:
90+
91+```bash
92+export TLS_CERT_PATH=/tmp/octopod/certs/client_cert.pem
93+export TLS_KEY_PATH=/tmp/octopod/certs/client_key.pem
94+```
95+
96+#### Setting up the API URL
97+
98+You will also need to set the power API URL (the `power_app_domain` value from the [Installing _Octopod Server_](Octopod_deployment_guide.md#installing-octopod-server) section) as the `OCTOPOD_URL` environment variable:
99+
100+```bash
101+export OCTOPOD_URL=<power_app_domain>
102+```
103+
104+### Creating a deployment
105+
106+To create a deployment you can now run:
107+
108+```bash
109+$ octo create -n hello-octopod -t v1 -e APP_ENV_KEY1=VALUE1
110+```
111+
112+The options are:
113+- `-n hello-octopod` – specifies that the name (subdomain) of the deployment should be `hello-octopod`
114+- `-t v1` specifies the version (Docker Image Tag) of the application to deploy to be `v1`
115+- `-e APP_ENV_KEY1=VALUE1` specifies adds an application-level key-value pair `APP_ENV_KEY1=VALUE1`
116+
117+> 💡 **NOTE:** For more detail on _octo CLI_ options please see the [octo CLI user guide](Octo_user_guide.md).
118+
119+This will run the `create` _control script_, which in turn will call `helm`. After waiting a couple of seconds you can visit `http://hello-octopod.<base_domain>` to see the running application:
120+
121+![](../images/hello-octopod-1.png)
122+
123+You can also see the deployed pod in the cluster using `kubectl`:
124+
125+```bash
126+$ kubectl get pods -n deployment
127+NAME READY STATUS RESTARTS AGE
128+app-hello-octopod-8965856-qbwvq 1/1 Running 0 15s
129+```
130+
131+### Adding an override
132+
133+You can modify deployments by adding or removing overrides. To add a new application-level override run:
134+
135+```bash
136+$ octo update -n hello-octopod -t v1 -e APP_ENV_KEY2=VALUE2
137+```
138+
139+This will run the `update` _control script_ (which is identical to the `create` script in our case), which in turn will call `helm`. After waiting a few seconds you visit the deployment URL again and see the redeployed version:
140+
141+![](../images/hello-octopod-2.png)
142+
143+### Updating the deployment version
144+
145+You can change the version (_Docker Image Tag_) of your deployment like so:
146+
147+```bash
148+$ octo update -n hello-octopod -t v2
149+```
150+
151+After waiting a few seconds you visit the deployment URL again and see the redeployed version:
152+
153+![](../images/hello-octopod-3.png)
154+
155+### Changing the number of replicas
156+
157+You can change the number of replicas of your deployment (this is [essentially implemented in the _charts_ that we use](../../examples/web-app/charts/web-app/templates/deployment.yaml#L7)) like so:
158+
159+```bash
160+$ octo update -n hello-octopod -t v2 -o replicas=3
161+```
162+
163+`-o replicas=3` adds a deployment-level key-value pair (override) `replicas=3`.
164+
165+You can verify that the new replicas have been deployed using `kubectl`:
166+
167+```bash
168+$ kubectl get pods -n deployment
169+NAME READY STATUS RESTARTS AGE
170+app-hello-octopod-8965856-qbwvq 1/1 Running 0 97m
171+app-hello-octopod-8965856-v585c 1/1 Running 0 15s
172+app-hello-octopod-8965856-v88md 1/1 Running 0 15s
173+```
174+
175+[cert-manager]: https://cert-manager.io/docs
176+[lets-encrypt]: https://letsencrypt.org
177+[lets-encrypt-rate-limits]: https://letsencrypt.org/docs/rate-limits
docs/en/Integration.mdnew file+35
@@ -0,0 +1,35 @@
1+# Integration into existing CI/CD pipelines
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [✨ Creating deployments](#-creating-deployments)
7+- [🚀 Updating deployments](#-updating-deployments)
8+
9+</details>
10+
11+You likely already have some form of CI integration with your version control system, such as *GitHub Action* or *Travis CI*, to run various checks on your code. Most of these services are set up by providing what is essentially just a shell script that is run under specific conditions.
12+
13+You might want to automate deployments even further ― you might want deployments to be automatically created and updated when developers create and update *Pull Requests*.
14+
15+_Octopod_ can be interacted with through the _octo CLI_ tool. This tool can be easily called from within a *CI* script.
16+
17+## ✨ Creating deployments
18+
19+To create a deployment (given that you have already obtained a *Docker Image* and uploaded it to your _Image Registry_ in one of the previous *CI* steps) you simply need to call _octo CLI_ with the following arguments:
20+
21+```bash
22+octo create -n $NAME -t $IMAGE_TAG
23+```
24+
25+`$NAME` is the name of the deployment you want to create. You can set it to be the name of the branch for example.
26+
27+`$IMAGE_TAG` is the _tag_ of the docker image you want to deploy.
28+
29+## 🚀 Updating deployments
30+
31+Updating deployments is done using the same arguments, but you need to call `create` command, instead of the `update` command:
32+
33+```bash
34+octo update -n $NAME -t $IMAGE_TAG
35+```
docs/en/Octo_user_guide.mdnew file+251
@@ -0,0 +1,251 @@
1+# Octo CLI User Guide
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Environment variables](#environment-variables)
7+ - [`OCTOPOD_URL`](#octopod_url)
8+ - [`TLS_CERT_PATH` and `TLS_KEY_PATH`](#tls_cert_path-and-tls_key_path)
9+- [Commands](#commands)
10+ - [create](#create)
11+ - [Description](#description)
12+ - [Options](#options)
13+ - [Usage example](#usage-example)
14+ - [list](#list)
15+ - [Description](#description-1)
16+ - [Options](#options-1)
17+ - [Usage example](#usage-example-1)
18+ - [archive](#archive)
19+ - [Description](#description-2)
20+ - [Options](#options-2)
21+ - [Usage example](#usage-example-2)
22+ - [update](#update)
23+ - [Description](#description-3)
24+ - [Options](#options-3)
25+ - [Usage example](#usage-example-3)
26+ - [info](#info)
27+ - [Description](#description-4)
28+ - [Options](#options-4)
29+ - [Usage example](#usage-example-4)
30+ - [cleanup](#cleanup)
31+ - [Description](#description-5)
32+ - [Options](#options-5)
33+ - [Usage example](#usage-example-5)
34+ - [restore](#restore)
35+ - [Description](#description-6)
36+ - [Options](#options-6)
37+ - [Usage example](#usage-example-6)
38+ - [clean-archive](#clean-archive)
39+ - [Description](#description-7)
40+ - [Options](#options-7)
41+ - [Usage example](#usage-example-7)
42+ - [logs](#logs)
43+ - [Description](#description-8)
44+ - [Options](#options-8)
45+ - [Usage example](#usage-example-8)
46+
47+</details>
48+
49+## Environment variables
50+
51+All commands _octo CLI_ executes require the executable to send authenticated requests to the _Octopod Server_. For this purpose _octo CLI_ needs both a way to reach your particular instance of _Octopod Server_, and a way for _Octopod Server_ to identify that you are allowed to make the given request.
52+
53+### `OCTOPOD_URL`
54+
55+> **_NOTE:_** this argument is **required** for _octo CLI_ to function.
56+
57+`OCTOPOD_URL` is an environment variable _octo CLI_ reads to find your particular _Octopod Server_ installation. For example, it could contain `https://octopod-power-app.example.com:443`.
58+
59+### `TLS_CERT_PATH` and `TLS_KEY_PATH`
60+
61+`TLS_CERT_PATH` should contain the path to the TLS certificate you generated when setting up _Octopod Server_ and `TLS_KEY_PATH` should contain the path to the TLS key you generated when setting up _Octopod Server_. These files are used to authenticate the requests to _Octopod Server_.
62+
63+If these variables are not set, then _octo CLI_ tries to read the certificate from the path `./cert.pem`, and the key from the path `./key.pem`.
64+
65+## Commands
66+
67+> <a id="star"></a>***NOTE:*** If an argument is marked with a ⭐, it means that the argument can be passed any number of times.
68+
69+### create
70+
71+#### Description
72+
73+Creates a new deployment.
74+
75+#### Options
76+
77+- `-n,--name ARG` – The name of the deployment to create
78+- `-t,--tag ARG` – The _Docker tag_ to deploy
79+- `-e,--set-app-env-override ARG` [⭐](#star) – Set an application-level override. Expects a string in the format `KEY=VALUE`.
80+- `-o,--set-deployment-override ARG` [⭐](#star) – Set a deployment-level override. Expects a string in the format `KEY=VALUE`.
81+
82+#### Usage example
83+
84+```bash
85+$ octo create -n hello-octopod -t ca5fd1fe08389f6422a506a59b68a5272ac37ba6 -e KEY1=VALUE1 -e KEY2=VALUE2
86+```
87+
88+### list
89+
90+#### Description
91+
92+Gets a list of all deployment names – both archived and active.
93+
94+#### Options
95+
96+This command does not require any arguments.
97+
98+#### Usage example
99+
100+```bash
101+$ octo list
102+hello-octopod
103+foo
104+bar
105+```
106+
107+### archive
108+
109+#### Description
110+
111+Archives a given deployment.
112+
113+#### Options
114+
115+- `-n,--name ARG` – The name of the deployment to archive.
116+
117+#### Usage example
118+
119+```bash
120+$ octo archive -n hello-octopod
121+```
122+
123+### update
124+
125+#### Description
126+
127+Updates the parameters of a given deployment.
128+
129+#### Options
130+
131+- `-n,--name ARG` – The name of the deployment to update
132+- `-t,--tag ARG` – The new _Docker tag_ to update the deployment to
133+- `-e,--set-app-env-override ARG` [⭐](#star) – Add a new or replace an existing application-level override. Expects a string in the format `KEY=VALUE`.
134+- `-E,--unset-app-env-override ARG` [⭐](#star) – Removes an existing application-level override.
135+- `-o,--set-deployment-override ARG` [⭐](#star) – Add a new or replace an existing deployment-level override. Expects a string in the format `KEY=VALUE`.
136+- `-O,--unset-deployment-override` [⭐](#star) – Removes an existing deployment-level override.
137+
138+#### Usage example
139+
140+```bash
141+$ octo update -n octopod -t 015f16ecf398fcadaac508c1855ae160af0969c4 -E KEY1 -e KEY2=VALUE22222 -a KEY3=VALUE8
142+```
143+
144+### info
145+
146+#### Description
147+
148+Gets detailed information about a deployment, including a log of all preformed actions and the current parameters.
149+
150+#### Options
151+
152+- `-n,--name ARG` – The name of the deployment
153+
154+#### Usage example
155+
156+```bash
157+$ octo info -n hello-octopod
158+Current settings:
159+tag: v1
160+application overrides: app=1 (Public)
161+
162+deployment overrides: dep=2 (Public)
163+
164+metadata:
165+ app: https://ree.lvh.me
166+
167+Last logs:
168+┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
169+┃ Created at ┃ Action id ┃ Action ┃ Tag ┃ App overrides ┃ Deployment overrides ┃ Exit code ┃
170+┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
171+│ 2020-11-02T17:14:03 │ 7 │ create │ v1 │ app=1 (Public) │ dep=2 (Public) │ 1 │
172+├─────────────────────┼───────────┼────────┼─────┼────────────────┼──────────────────────┼───────────┤
173+│ 2020-11-02T19:01:02 │ 8 │ update │ v1 │ app=1 (Public) │ dep=2 (Public) │ 1 │
174+└─────────────────────┴───────────┴────────┴─────┴────────────────┴──────────────────────┴───────────┘
175+```
176+
177+### cleanup
178+
179+#### Description
180+
181+Frees all resources used by a given archived deployment. It will not succeed if the deployment is not archived. You can not recover the deployment after this command.
182+
183+#### Options
184+
185+- `-n,--name ARG` – The name of the deployment
186+
187+#### Usage example
188+
189+```bash
190+$ octo cleanup -n hello-octopod
191+```
192+
193+### restore
194+
195+#### Description
196+
197+Restores a previously archived deployment.
198+
199+#### Options
200+
201+- `-n,--name ARG` – The name of the deployment
202+
203+#### Usage example
204+
205+```bash
206+$ octo restore -n hello-octopod
207+```
208+
209+### clean-archive
210+
211+#### Description
212+
213+Calls `octo cleanup` on all deployments that were archived more than two weeks ago. This command is used in a cronjob which is automatically set up when deploying _Octopod Server_.
214+
215+#### Options
216+
217+This command does not have any options.
218+
219+#### Usage example
220+
221+```bash
222+$ octo clean-archive
223+```
224+
225+### logs
226+
227+#### Description
228+
229+Outputs the logs collected while running an action on a deployment. For example when deploying or updating a deployment.
230+
231+#### Options
232+
233+- `-a,--action ARG` – the id of the action to print logs for
234+- `-l,--log-type ARG` – the types of logs that should be printed. Possible values are: `stdout`, `stderr`, `all`. The default value is `all`.
235+
236+#### Usage example
237+
238+```
239+$ octo logs -a 13
240+ stdout:
241+
242+
243+ stderr:
244+
245+error: Found argument '--deployment-override' which wasn't expected, or isn't valid in this context
246+
247+USAGE:
248+ update --app-env-override <app-env-override>... --base-domain <base-domain> --name <name> --namespace <namespace> --project-name <project-name> --tag <tag>
249+
250+For more information try --help
251+```
docs/en/Octopod_deployment_guide.mdnew file+399
@@ -0,0 +1,399 @@
1+# Octopod Server deployment guide
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Installing required utilities](#installing-required-utilities)
7+- [Setting up your cluster](#setting-up-your-cluster)
8+ - [General utilities](#general-utilities)
9+ - [Tiller (Helm)](#tiller-helm)
10+ - [Cluster access privileges](#cluster-access-privileges)
11+ - [A word about TLS](#a-word-about-tls)
12+- [Downloading project sources code](#downloading-project-sources-code)
13+- [Creating required namespaces](#creating-required-namespaces)
14+- [Creating required _Service Accounts_](#creating-required-service-accounts)
15+ - [Creating the actual service account](#creating-the-actual-service-account)
16+ - [Giving the appropriate _Service Account_ roles](#giving-the-appropriate-service-account-roles)
17+- [Web UI authentication secrets](#web-ui-authentication-secrets)
18+- [_octo CLI_ authentication certificates](#octo-cli-authentication-certificates)
19+ - [Creating SSL certificates](#creating-ssl-certificates)
20+ - [Enabling SSL passthrough](#enabling-ssl-passthrough)
21+- [Setting up DNS](#setting-up-dns)
22+ - [Deploying _Octopod_ on localhost](#deploying-octopod-on-localhost)
23+- [Installing _Octopod_ infrastructure](#installing-octopod-infrastructure)
24+ - [Installing the appropriate _Storage Class_](#installing-the-appropriate-storage-class)
25+ - [Installing the actual infrastructure](#installing-the-actual-infrastructure)
26+- [Installing _Octopod Server_](#installing-octopod-server)
27+
28+</details>
29+
30+## Installing required utilities
31+
32+Installing _Octopod Server_ in your cluster will require that you have the following tools installed on your system:
33+1. [_kubectl_][kubectl]
34+2. [_helm 2_][helm]
35+
36+## Setting up your cluster
37+
38+### General utilities
39+
40+_Octopod Server_ requires the following utilities to be installed in your cluster:
41+
42+1. [_Ingress Nginx_][ingress-nginx]
43+2. [_Cert Manager_][cert-manager]
44+
45+_Octopod Server_ require the following minimal resources to function properly: 2 CPU, 2 GB of RAM. Make sure you have sufficient resources in your cluster.
46+
47+By default _Octopod Server_ will be deployed on nodes with the `role=stand` label. Please make sue you have the appropriate label set in your cluster:
48+
49+```bash
50+kubectl label node <your_node> role=stand
51+```
52+
53+### Tiller (Helm)
54+
55+[_Tiller_][tiller] is a cluster-side service used by [_helm 2_][helm] to manage deployments. The easiest way to install it is using the following command:
56+
57+```bash
58+helm init
59+```
60+
61+#### Cluster access privileges
62+
63+When installing _Octopod Server_ you might encounter [a problem with cluster access privileges](https://github.com/helm/helm/issues/5100) related to [_Tiller_][tiller].
64+
65+To give sufficient privileges to [_Tiller_][tiller] you can use the following commands:
66+
67+```bash
68+kubectl create -n kube-system serviceaccount tiller
69+kubectl --namespace kube-system create clusterrolebinding tiller-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
70+kubectl --namespace kube-system patch deploy tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
71+```
72+
73+### A word about TLS
74+
75+To function properly _Octopod_ needs to generate three TLS certificates for the three subdomains it will be using. [_Cert Manager_][cert-manager] creates TLS certificates through [_Let’s Encrypt_][lets-encrypt] and [_Let’s Encrypt_][lets-encrypt] has [a limit on the amount of certificates][lets-encrypt-rate-limits] you can issue within a given time interval. If you exceed this limit you will start getting a _too many registrations for this IP_ error. If that is the case moving the [_Cert Manager_][cert-manager] _Pod_ might help.
76+
77+## Downloading project sources code
78+
79+To download the source code required to install _Octopod Sever_ you will need to clone the git repository:
80+
81+```bash
82+git clone https://github.com/typeable/octopod.git /tmp/octopod
83+```
84+
85+## Creating required namespaces
86+
87+_Octopod_ uses the following namespaces in your cluster:
88+
89+1. `deployment` – as the name would suggest your deployments will be installed in this namespace
90+2. `octopod` – this namespace will be used to install the _Octopod_ infrastructure
91+
92+To create the two namespaces you can use these commands:
93+
94+```bash
95+kubectl create namespace deployment
96+kubectl create namespace octopod
97+```
98+
99+## Creating required [_Service Accounts_][kubernetes-service-account]
100+
101+### Creating the actual service account
102+
103+_Octopod Server_ requires an `octopod` [_Service Account_][kubernetes-service-account] to function. You can create it using the following command:
104+
105+```bash
106+kubectl create -n octopod serviceaccount octopod
107+```
108+
109+### Giving the appropriate _Service Account_ roles
110+
111+1. If you are planning to use [_helm 2_][helm] in your [_Control scripts_](Control_scripts.md) to deploy your deployments, you will need to give appropriate permissions to the `octopod` _Service Account_:
112+ ```bash
113+ cd /tmp/octopod/charts
114+ helm install --name octopod-helm-access ./helm-access
115+ ```
116+
117+2. If you are planning to delete [_Persistent Volumes Claims_][kubernetes-pvc] in your [_Control scripts_](Control_scripts.md) (might be useful for the `cleanup` script), you will need to give appropriate permissions to the `octopod` _Service Account_:
118+
119+ ```bash
120+ cd /tmp/octopod/charts
121+ helm install --name octopod-pvc-control ./pvc-control
122+ ```
123+
124+3. If you are planning to use _Octopod_ to delete unused certificates in your [_Control scripts_](Control_scripts.md) (might be useful for the `cleanup` script), you will need to give appropriate permissions to the `octopod` _Service Account_:
125+
126+ ```bash
127+ cd /tmp/octopod/charts
128+ helm install --name octopod-cert-control ./cert-control
129+ ```
130+
131+4. If you are planning to use [_kubedog_][kubedog] to check the state of your deployments in your [_Control scripts_](Control_scripts.md) (might be useful for the `check` script), you will need to give appropriate permissions to the `octopod` _Service Account_:
132+
133+ ```bash
134+ cd /tmp/octopod/charts
135+ helm install --name octopod-kubedog-access ./kubedog-access
136+ ```
137+
138+## Web UI authentication secrets
139+
140+[Authentication](Security_model.md#web-ui-authentication) between _Octopod Server_ and the _Web UI_ is done through _Basic Auth_. This implies that there needs to be a username and password associated with it.
141+
142+You can generate the username and password, and push into your cluster using the following command (of course you will want to generate a secure pair):
143+
144+```bash
145+username="octopod"
146+password="password" # Please change it to a more secure password
147+kubectl create secret generic octopod-basic-auth -n octopod --from-literal=auth=$(htpasswd -bn $username $password)
148+```
149+
150+## _octo CLI_ authentication certificates
151+
152+### Creating SSL certificates
153+
154+[Authentication](Security_model.md#octo-cli-authentication) between _octo CLI_ and _Octopod Server_ is performed through self-signed SSL certificates.
155+
156+You can generate the certificates and push them into your cluster using the following commands:
157+
158+```bash
159+mkdir certs
160+(cd certs && \
161+openssl req -x509 -newkey rsa:4096 -keyout server_key.pem -out server_cert.pem -nodes -subj "/CN=localhost/O=Server" && \
162+openssl req -newkey rsa:4096 -keyout client_key.pem -out client_csr.pem -nodes -subj "/CN=Client" && \
163+openssl x509 -req -in client_csr.pem -CA server_cert.pem -CAkey server_key.pem -out client_cert.pem -set_serial 01 -days 3650)
164+kubectl create configmap octopod-certs -n octopod --from-file=./certs
165+```
166+
167+After executing these command you will find a new `certs` directory containing the certificates used for authentication between _octo CLI_ and _Octopod Server_. `client_key.pem` and `client_cert.pem` should then be [passed to _octo CLI_ through environment variables](Octo_user_guide.md#tls_cert_path-and-tls_key_path).
168+
169+### Enabling SSL passthrough
170+
171+Since we use custom self-signed SSL certificates for authentication, we will need the certificates used with requests to be passed to the server as-is without any modification. This is not support in default [_ingress-nginx_][ingress-nginx] configurations so you will most likely need to modify it manually.
172+
173+Enabling SSL passthrough in [_ingress-nginx_][ingress-nginx] can be done by adding the `--enable-ssl-passthrough` command-line argument to the [_ingress-nginx_][ingress-nginx] config in your cluster.
174+
175+To do this you can execute a command similar to this (you will need to lookup the names of the namespace and the deployment in your particular cluster):
176+
177+```bash
178+kubectl edit deploy -n ingress-nginx ingress-nginx-controller
179+```
180+
181+An editor with a YAML config should open up. You will need to modify to it have, among other things, this parameter:
182+
183+```yaml
184+spec:
185+ ...
186+ template:
187+ ...
188+ spec:
189+ ...
190+ containers:
191+ ...
192+ - args:
193+ ...
194+ - --enable-ssl-passthrough
195+```
196+
197+
198+## Setting up DNS
199+
200+You will need to set up DNS records to point subdomains of your domain to the IP address of your cluster. The DNS record should look something like this:
201+
202+```
203+*.octo.example.com A 1.2.3.4
204+octo.example.com A 1.2.3.4
205+```
206+
207+### Deploying _Octopod_ on localhost
208+
209+If you are deploying locally and don't have a separate domain you are trying set up, the lvh.me domain can be useful – it is set up to point to `localhost` and you can use it to work with subdomains. Even so, deploying a fully-functional version of _Octopod_ on `localhost` is non-trivial and will require modifying the deployment _Charts_ to disable HTTPS redirects. (This guide does not cover that.)
210+
211+## Installing _Octopod_ infrastructure
212+
213+### Installing the appropriate _Storage Class_
214+
215+Before installing the infrastructure you will first need to make sure you have a [_Storage Class_][kubernetes-storage-classes] named `default` installed in your cluster. You can check installed [_Storage Classes_][kubernetes-storage-classes] with the following command:
216+
217+```bash
218+kubectl get storageclass
219+```
220+
221+If you do not have it, you will need to install it. Installing the [_Storage Class_][kubernetes-storage-classes] in [_minikube_][minikube] can be done with the following command (you will need to modify it to suit your cluster hosting provider):
222+
223+```bash
224+cat <<EOF | kubectl apply -f-
225+---
226+apiVersion: storage.k8s.io/v1
227+kind: StorageClass
228+metadata:
229+ name: default
230+provisioner: k8s.io/minikube-hostpath
231+reclaimPolicy: Delete
232+volumeBindingMode: Immediate
233+EOF
234+```
235+
236+### Installing the actual infrastructure
237+
238+The only infrastructure _Octopod_ currently requires is _PostgreSQL_. You can install it in your cluster using the following command:
239+
240+```bash
241+cd /tmp/octopod/charts
242+helm upgrade --install octopod-infra ./octopod-infra --namespace octopod\
243+ --wait --timeout 600 --debug
244+```
245+
246+## Installing _Octopod Server_
247+
248+To install _Octopod Server_ in your cluster you will need to customize the variables in the following script and run it:
249+
250+ ```bash
251+cd /tmp/octopod/charts
252+
253+#################################################
254+# Octopod Images Setup
255+#
256+# you probably don't need to change it
257+#################################################
258+
259+registry="typeable"
260+tag="1.0"
261+image="octopod"
262+octo_image="octo"
263+
264+
265+#################################################
266+# General Octopod Setup
267+#################################################
268+
269+# The name of your project – only used to display in the Web UI
270+project_name="MyProject"
271+
272+# The email used to register Let's Encrypt SSL certificates
273+acme_registration_email="certbot@example.com"
274+
275+
276+#################################################
277+# Control Scripts Setup
278+#
279+# if you are just testing things out you can paste the values
280+# from the Helm Deployment Guide example
281+#################################################
282+
283+# The name of the registry with control scripts
284+utils_registry="registry_name"
285+
286+# The name of the image with control scripts
287+utils_image="utils"
288+
289+# The tag of the image to use
290+utils_image_tag="1.0"
291+
292+
293+#################################################
294+# Web UI OAuth Authentication
295+#
296+# These parameters are passed to ingress-nginx to
297+# enable authentication for user accessing the
298+# Web UI.
299+#
300+# You can use OAuth2 Proxy to provide OAuth2 authentication.
301+#
302+# For more information see the Security Model doc.
303+#
304+# You can leave both these variables blank to disable
305+# authentication in your Web UI altogether.
306+#################################################
307+
308+# URL for the OAuth authentication service
309+auth_url="https://oauth.exmaple.com/oauth2/auth"
310+
311+# URL for the login page on the OAuth authentication service
312+auth_signin="https://oauth.exmaple.com/oauth2/start?rd=/redirect/$http_host$request_uri"
313+
314+
315+#################################################
316+# Domain Setup
317+#################################################
318+
319+# The domain from which the Web UI should be served
320+domain="octo.example.com"
321+
322+# The domain from which the user API should be served
323+# (used by the Web UI)
324+app_domain="api.octo.example.com"
325+
326+# The domain from which the WebSocket notification service should be served
327+# (used by the Web UI)
328+ws_domain="ws.octo.example.com"
329+
330+# The domain from which the power user API should be served
331+# (used by octo CLI)
332+power_app_domain="power.octo.example.com"
333+
334+# The domain under which deployment subdomains should be created
335+base_domain="octo.example.com"
336+
337+
338+#################################################
339+# Basic Auth Setup
340+#
341+# These parameters should match the ones used in the
342+# "Web UI authentication secrets" step
343+#################################################
344+
345+username="octopod"
346+password="password"
347+
348+
349+#################################################
350+# Other Setup
351+#################################################
352+
353+# NOTE: on macOS you will need to replace `sha256sum` with `shasum -a 256`
354+sha256_sum=$(sha256sum octopod/values.yaml octopod/templates/* | awk '{print $1}' | sha256sum | awk '{print $1}')
355+
356+base64_of_username_and_password=$(echo -n "$username:$password" | base64)
357+status_update_timeout=600
358+
359+
360+#################################################
361+# Actual installation in the cluster
362+#################################################
363+
364+helm upgrade --install octopod ./octopod \
365+ --namespace octopod \
366+ --set "global.deploy_checksum=$sha256_sum" \
367+ --set "global.image_prefix=$registry" \
368+ --set "global.image_tag=$tag" \
369+ --set "global.image=$image" \
370+ --set "global.octo_image=$octo_image" \
371+ --set "global.utils_image_prefix=$utils_registry" \
372+ --set "global.utils_image=$utils_image" \
373+ --set "global.utils_image_tag=$utils_image_tag" \
374+ --set "global.acme_registration_email=$acme_registration_email" \
375+ --set "global.auth_url=$auth_url" \
376+ --set "global.auth_signin=$auth_signin" \
377+ --set "basic_auth_token=$base64_of_username_and_password" \
378+ --set "project_name=$project_name" \
379+ --set "domain=$domain" \
380+ --set "app_domain=$app_domain" \
381+ --set "ws_domain=$ws_domain" \
382+ --set "power_app_domain=$power_app_domain" \
383+ --set "base_domain=$base_domain" \
384+ --set "status_update_timeout=$status_update_timeout" \
385+ --wait --timeout 600 --debug
386+ ```
387+
388+[kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/
389+[helm]: https://v2.helm.sh/docs/using_helm/#installing-helm
390+[ingress-nginx]: https://kubernetes.github.io/ingress-nginx
391+[cert-manager]: https://cert-manager.io/docs/
392+[kubernetes-service-account]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account
393+[kubernetes-pvc]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims
394+[kubernetes-storage-classes]: https://kubernetes.io/docs/concepts/storage/storage-classes
395+[minikube]: https://kubernetes.io/ru/docs/tasks/tools/install-minikube/
396+[tiller]: https://v2.helm.sh/docs/install/
397+[kubedog]: https://github.com/werf/kubedog
398+[lets-encrypt]: https://letsencrypt.org
399+[lets-encrypt-rate-limits]: https://letsencrypt.org/docs/rate-limits
docs/en/Overview.mdnew file+98
@@ -0,0 +1,98 @@
1+# Octopod overview
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Intro](#intro)
7+- [🎯 The aim of Octopod](#-the-aim-of-octopod)
8+- [🔬 Example](#-example)
9+ - [💽 The server](#-the-server)
10+ - [🎨 Changing button colors](#-changing-button-colors)
11+ - [😖 Why have such a complicated staging deployment?](#-why-have-such-a-complicated-staging-deployment)
12+- [🛠 The way Octopod is set up](#-the-way-octopod-is-set-up)
13+- [🎛️ CLI](#️-cli)
14+- [🔒 Authentication in the UI](#-authentication-in-the-ui)
15+- [🤖 Automatic deployment / CD](#-automatic-deployment--cd)
16+- [📗 Glossary](#-glossary)
17+
18+</details>
19+
20+**NOTE: it is not recommended to use Octopod for managing production environments**
21+
22+## Intro
23+
24+_Octopod_ is a tool which implements the multi-staging deployment model (MSDM) on top of _Kubernetes_. _MSDM_ implies that every developed feature needs to be not only deployed in a separate environment for QA testing, but also needs to be updated when bugs found during testing are fixed and the feature is refined.
25+
26+_Octopod_ exists to reduce the overhead in deploying and maintaining per-feature staging environments. This responsibility may otherwise fall to:
27+
28+1. **DevOps engineers** – this might seem natural since deploying and updating systems in new environments is typically the task of a DevOps engineer who has experience in system administration. However, developers and QA engineers would be blocked for additional periods while DevOps engineers deal with the additional load.
29+2. **Developers** – they might take on the responsibility for deploying and maintaining their feature stagings ― this would most likely waste a lot of time since developers might not have the required experience.
30+
31+The process of deploying and updating stagings is likely to be extremely similar across different developed features ― changing the behavior of a button and updating pricing calculations would probably be identical from the point of view of system administration ― a new version of the system needs to be deployed with the same default configuration (the staging configuration, as opposed to a production configuration).
32+
33+## 🎯 The aim of Octopod
34+
35+Octopod aims to extract the mentioned similarity between deploying different staging deployments while still allowing a certain amount of configuration where per-feature setup is still required. The result is a simple interface, which allows users to manage staging deployments without any system administration expertise or, for that matter, even without deep technical expertise.
36+
37+## 🔬 Example
38+
39+### 💽 The server
40+
41+You are developing a server, which is accessed through [_nginx_](https://www.nginx.com), and the server needs access to a [*Postgres*](https://www.postgresql.org) database and a [*Redis*](https://redis.io) database
42+
43+![](../diagrams/images/sample_architecture.png)
44+
45+### 🎨 Changing button colors
46+
47+Your server serves HTML to the browser, which displays two buttons. Both buttons currently have the same ugly color as the background, and you have two separate tasks: one task to change the first button be orange and another task to make the second button green. (Note that this is a toy example ― imagine that these are two separate complex tasks.)
48+
49+Now imagine that two different developers each completed one of the tasks, and you are now deploying the new and updated version of your server to a staging environment. You are very surprised when you find that for some reason the background of the whole page suddenly became pink. Every developer says that they did not make the change, and yet it is there. (Here the background color changing to pink denotes an undesirable change, which impacts the product in significant and apparent ways, and was not made intentionally.)
50+
51+A way to mitigate this situation is to test each feature separately, in its own staging deployment, and verify which change made the page background pink, and ideally, you would check each feature before merging them into the final product (merging it into the `master` branch, for example).
52+
53+To check each feature before merging would require every developer to build the new version of the server, and set up all required services: [_nginx_](https://www.nginx.com), [*Postgres*](https://www.postgresql.org), [*Redis*](https://redis.io). Developers would also have to manage access to the set up environments ― set up SSL certificates, set up subdomains, make sure databases are not exposed, and make sure the connection between every component is secure and authenticated.
54+
55+This is **a lot** of overhead just to test the color of a button. Note that most of the described work would be identical across the vast majority of features ― changing the deployment architecture is a relatively rare task in most projects. Databases, load balancing, caching, and proxying would be set up in much the same way for the majority of feature-specific stagings. The server itself is probably also compiled in exactly the same way for most features.
56+
57+_Octopod_ aims to factor out the common parts of creating and deploying a staging.
58+
59+If developers were using _Octopod_ to deploy stagings, literally the only thing needed from them would be to specify the git commit hash in the _Web UI_ of _Octopod_. The common infrastructure (shown in **blue**) which is the same across different stagings would not require any additional setup. The only difference between the two button feature staging would be the actual server that required changing the color. And that server is most likely also built in a uniform way, meaning it can be done automatically.
60+
61+![](../diagrams/images/sample_deployment.png)
62+
63+### 😖 Why have such a complicated staging deployment?
64+
65+The purpose of having a staging deployment is to verify the correctness of an implementation of a task as it would behave in a production environment. After all, deploying in a production environment is the only real goal of implementing anything.
66+
67+Having a staging deployment that is different from a production environment in any significant way can lead to unexpected behavior that was not obvious ― heavy caching of a request can lead to an inconsistent state and break a feature for example.
68+
69+## 🛠 The way Octopod is set up
70+
71+To integrate Octopod into your development workflow, a DevOps engineer needs to implement some common staging orchestration logic once for it to be reused by the whole development team. This can be done by implementing [*staging control scripts*](Control_scripts.md) in any programming language which can be executed in the Octopod environment. Statically linked executables don't depend on their environment at all, so languages such as [*Rust*](https://www.rust-lang.org) and [*Go*](https://golang.org) are a good fit.
72+
73+When stagings are managed through the *Web UI*, *Octopod* executes appropriate _staging control scripts_ behind the scenes to set up your particular environment.
74+
75+## 🎛️ CLI
76+
77+For more in-depth control over the staging cluster, we also ship a CLI with _superuser privileges_ which allows a DevOps engineer to examine deployment logs to resolve issues, should they arise.
78+
79+You can read more about the _octo CLI_ in the [octo CLI user guide](docs/../Octo_user_guide.md).
80+
81+## 🔒 Authentication in the UI
82+
83+Authentication to the _web UI_ can be set up through [_Ingress_](https://kubernetes.io/docs/concepts/services-networking/ingress/) which, for example, [supports OAuth authentication](https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/). This allows you to set up Octopod so that every developer in your GitHub organization has access to manage stagings without any additional permissions management.
84+
85+## 🤖 Automatic deployment / CD
86+
87+It is possible to set up your existing CI to automatically deploy new versions of your feature staging, reducing friction even further. This can be done by using the CLI to update a staging with the same name as the current branch in git for example. The CLI command can be executed straight in your CI script in services like GitHub Actions, Travis CI, Azure Pipelines, etc.
88+
89+Fot more information see the [integration guide](Integration.md).
90+
91+## 📗 Glossary
92+
93+- _Octopod_ ― the deployment manager, this very system
94+- _octo CLI_ ― a command-line client, used to access Octopod with _superuser_ privileges
95+- _Octopod Server_ ― the server responsible for managing deployments
96+- _deployment control scripts_ ― scripts used to interact with your specific environment setup
97+- _web UI_ ― the interface developers, project managers, QA engineers, etc. use to manage stagings.
98+- <a id="overrides"></a>_overrides_ ― a set of environment variable key-value pairs, which have precedence over the default pairs set up by DevOps engineer. These environment variables are passed to your system during deployment.
docs/en/PM_case_study.mdnew file+122
@@ -0,0 +1,122 @@
1+# Octopod Case Study
2+
3+A deployment model is a critical part of any IT company and is usually
4+deeply integrated into multiple development processes. Having a good
5+deployment model lets successful companies create a well-tuned
6+development workflow – a key to fast and reliable delivery of business
7+features.
8+
9+Development models may vary significantly from one company to another.
10+However, the problems companies are facing when picking the right model
11+are usually pretty common. We want new features to be tested rigorously
12+before they hit production and become available for end users. We also
13+want the team to deliver in a timely manner, which means the ideal
14+workflow should exclude any blockers that force one team to wait when
15+the other team finishes their work.
16+
17+## The two models
18+### The 3-tiered model
19+
20+A common approach is to have a 3-tier deployment model which implies
21+having Development, Staging, and Production Environments. Although there
22+could be some variations, basically this model is used in the majority
23+of development teams.
24+
25+In such a 3-tier model, the Development server usually pulls the changes
26+from a master branch where developers merge their changes once they’re
27+done with the implementation in the feature branch. Once the branch is
28+merged, the feature can be seen and tested on the Development server. If
29+the change makes the application unstable or does not work as expected,
30+a developer has to revert it, push the fix and then redeploy. Once the
31+feature is verified, the new code is integrated into a Staging
32+environment.
33+
34+Even though this flow is pretty common, it has few significant
35+downsides which we will examine later in this document.
36+
37+### The multi-staging model
38+
39+A more interesting approach is a multi-staging model where a new Staging
40+server is created for each new feature branch. Staging servers are
41+accessible by unique URLs, like my-cool-feature.staging.company.com, and
42+are used for both development and QA verification. Albeit from the
43+developer’s perspective the process is similar, with this approach each
44+feature can be implemented and tested independently without a need to be
45+merged into the master branch first, making the process of testing a
46+release candidate on the Pre-Production server independent.
47+
48+## Breaking features
49+
50+### The 3-tiered model
51+
52+One issue is that no matter how well developers test their feature, it
53+may still interact with someone else’s code in unexpected ways. It means that a successful
54+verification of a particular feature on the Development server does not
55+guarantee that the feature won’t unexpectedly break someone else’s code.
56+If a critical part of the application, like authentication, gets broken,
57+this may block the testing process completely until the problem is
58+resolved. And to top it off, when this happens, it might not be
59+immediately clear who’s change is to blame. Thus, everyone who did
60+recent merges begin looking into their code trying to see whether it was
61+their check-in that broke the server. Of course, it takes time and
62+blocks the development process as well until the culprit merge is found
63+and confirmed.
64+
65+![](../images/break1.png)
66+
67+### The multi-staging model
68+
69+With the multi-staging model every deployed staging is separated from a known-to-be-good commit by exactly one feature implementation or bug fix. This means that if at any point we discover that a staging contains a critical bug, we will for sure know which feature broke the project. The "bad" feature can be identified without delaying the merge of "good" branches.
70+
71+![](../images/break2.png)
72+
73+## Configurable testing environments
74+
75+Sometimes features require a special or dangerous environment to be tested in. For example, a feature might require handling a payment error that does not occur in the test payment processing environment, thus the feature will need to be tested in a production payment processing environment.
76+
77+### The 3-tiered model
78+
79+When QA is forced to test everything on a single staging deployment, by necessity all features tested during the deployment which includes the said feature will need to be tested in a production payment processing environment. This is bad at least because it increases the likelihood of unintentional charges and might complicate the testing of other features.
80+
81+![](../images/env1.png)
82+
83+### The multi-staging model
84+
85+With a multi-staging this issue is mitigated completely since every single feature is tested in a completely separate environment that can be set up the way the feature requires.
86+
87+![](../images/env2.png)
88+
89+## Feature development lifecycle
90+
91+### The 3-tiered model
92+
93+Having features deployed and tested in wave-like cycles in tandem with the staging server being occupied by integration tests for a given release can increase the time interval between the developer submitting a feature for testing and getting feedback from QA. This leads to the developer losing the context of the task. Furthermore, an unfortunate series of events can significantly increase the time-to-production of a feature.
94+
95+![](../images/dev1.png)
96+
97+### The multi-staging model
98+
99+With a multi-staging model the problem is lessened significantly. Allowing features to be deployed to staging environments independently reduces both the QA feedback time and the time-to-production of a feature under the same conditions.
100+
101+> **NOTE:** Relative block widths have been kept consistent with the previous diagram.
102+
103+![](../images/dev2.png)
104+
105+## Octopod for the multi-staging model
106+
107+While the multi-staging model has many upsides, having a dedicated Staging server for each new feature or bugfix usually
108+requires a more complicated environment and forces developers to spend
109+more time on deploying each piece of code. Difficulties in managing such
110+a swarm of servers often leads to introducing orchestration tools like
111+Kubernetes, which may not be easy to learn for everyone in the team,
112+especially when containers are built on top of AWS infrastructure which
113+implies using AWS-specific commands and tools. Thus, even though this
114+model provides a significant workflow improvement, it requires
115+developers to have certain DevOps expertise.
116+
117+To overcome these limitations and let more teams use multi-staging
118+deployment models, we created Octopod. Octopod is a service
119+that, once installed and set up, supports your multi-tier development
120+workflow at a level that does not require deep technical knowledge. Octopod
121+simplifies the procedure of creating new servers and allows the
122+implementation of common CD solutions in just a few clicks.
docs/en/README.mdnew file+15
@@ -0,0 +1,15 @@
1+# 🐙📑 Octopod documentation
2+
3+## 🔭 High-level notes
4+- [🐙 Overview](Overview.md)
5+- [🧑‍🔬 Project managment case study](PM_case_study.md)
6+- [🧑‍💻 Technical case study](Tech_case_study.md)
7+
8+## 🛠️ Technical documentation
9+- [🏗 Technical architecture](Technical_architecture.md)
10+- [⚙️ Control script guide](Control_scripts.md)
11+- [🔧🐙 Octopod deployment guide](Octopod_deployment_guide.md)
12+- [🔧🚀 Helm-based Octopod project setup](Helm-based_deployment_guide.md)
13+- [🐙🎛 octo CLI user guide](Octo_user_guide.md)
14+- [🤖 CI integration](Integration.md)
15+- [🔒 Octopod security model](Security_model.md)
docs/en/Security_model.mdnew file+114
@@ -0,0 +1,114 @@
1+# Security model
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Octopod roles](#octopod-roles)
7+- [Kubernetes role-based access control](#kubernetes-role-based-access-control)
8+ - [Privileges to delete certificates](#privileges-to-delete-certificates)
9+ - [Privileges to delete _Persistent Volumes Claims_](#privileges-to-delete-persistent-volumes-claims)
10+- [Web UI authentication](#web-ui-authentication)
11+- [Web UI OAuth](#web-ui-oauth)
12+- [octo CLI authentication](#octo-cli-authentication)
13+
14+</details>
15+
16+## Octopod roles
17+
18+There are two user roles in _Octopod_:
19+* _user_
20+* _admin_
21+
22+| role | managing deployments | viewing deployment logs |
23+| :---: | :------------------: | :---------------------: |
24+| user | ✅ | ❌ |
25+| admin | ✅ | ✅ |
26+
27+_Web UI_ users have the _user_ role.
28+
29+_octo CLI_ users have the _admin_ role.
30+
31+There is currently no way to give someone access to _octo CLI_ without giving them the _admin_ role since authentication is done through SSL certificates instead of through OAuth.
32+
33+## Kubernetes role-based access control
34+
35+_Octopod Server_ is deployed in the `octopod` _Kubernetes_ namespace. Deployments are deployed in the `deployments` namespace.
36+_Octopod Server_ uses the `octopod` [_Service Account_][kubernetes-service-account].
37+
38+Freeing resources might require _Octopod Server_ / _control scripts_ to have privileges to delete certificates and [_Persistent Volumes Claims_][kubernetes-pvc]. (It depends on the specifics of the _Kubernetes_ setup and _control scripts_)
39+
40+Access can be configured through [_RBAC_][kubernetes-rbac]:
41+
42+### Privileges to delete certificates
43+
44+```yaml
45+---
46+apiVersion: rbac.authorization.k8s.io/v1
47+kind: ClusterRole
48+metadata:
49+ name: cert-control-clusterrole
50+rules:
51+ - apiGroups: ["cert-manager.io"]
52+ resources: ["certificates"]
53+ verbs: ["list", "delete", "deletecollection"]
54+---
55+apiVersion: rbac.authorization.k8s.io/v1
56+kind: RoleBinding
57+metadata:
58+ name: octopod-cert-control-rolebinding
59+ namespace: deployments
60+roleRef:
61+ kind: ClusterRole
62+ apiGroup: rbac.authorization.k8s.io
63+ name: cert-control-clusterrole
64+subjects:
65+ - kind: ServiceAccount
66+ name: octopod
67+ namespace: octopod
68+```
69+
70+### Privileges to delete _Persistent Volumes Claims_
71+
72+```yaml
73+---
74+apiVersion: rbac.authorization.k8s.io/v1
75+kind: ClusterRole
76+metadata:
77+ name: pvc-control-clusterrole
78+rules:
79+ - apiGroups: [""]
80+ resources: ["persistentvolumeclaims"]
81+ verbs: ["list", "delete", "deletecollection"]
82+---
83+apiVersion: rbac.authorization.k8s.io/v1
84+kind: RoleBinding
85+metadata:
86+ name: octopod-pvc-control-rolebinding
87+ namespace: deployments
88+roleRef:
89+ kind: ClusterRole
90+ apiGroup: rbac.authorization.k8s.io
91+ name: pvc-control-clusterrole
92+subjects:
93+ - kind: ServiceAccount
94+ name: octopod
95+ namespace: octopod
96+```
97+
98+## Web UI authentication
99+
100+Authentication between the _Web UI_ and _Octopod Server_ is done through _Basic Auth_. The _Bearer token_ is read by the _Web UI_ after the page is loaded as part of [the config](../../charts/octopod/templates/octopod-nginx-configmap.yaml#L15-L20). By default, everything, including the config, can be accessed without any authentication. For ways of mitigating this please see the next section.
101+
102+## Web UI OAuth
103+
104+The [_Web UI_](Technical_architecture.md#-web-ui) on its own does not have any authentication whatsoever, meaning that anyone can open it and manage your deployments. Luckily, _Kubernetes_ [can be configured](../../charts/octopod/templates/octopod-ingress.yaml#L15-L21) to authenticate users before they get access to the _Web UI_. It can be set up to authenticate users through [_Ingress_](https://kubernetes.io/docs/concepts/services-networking/ingress/) which [supports external authentication services][kubernetes-ingress-nginx-external-auth]. You can set up [_OAuth2 Proxy_][oauth2-proxy] in your cluster to support numerous OAuth services. For example, if you use GitHub, you can set up [_OAuth2 Proxy_][oauth2-proxy] to use GitHub to automatically grant users access to Octopod when you add them to your organization in GitHub.
105+
106+## octo CLI authentication
107+
108+Authentication between _octo CLI_ and _Octopod Server_ is done through an SSL certificate that is generated [when deploying _Octopod_](../en/Octopod_deployment_guide.md#creating-ssl-certificates).
109+
110+[kubernetes-service-account]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account
111+[kubernetes-rbac]: https://kubernetes.io/docs/reference/access-authn-authz/rbac
112+[kubernetes-pvc]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims
113+[kubernetes-ingress-nginx-external-auth]: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#external-authentication
114+[oauth2-proxy]: https://oauth2-proxy.github.io/oauth2-proxy
docs/en/Tech_case_study.mdnew file+74
@@ -0,0 +1,74 @@
1+# Octopod technical case study
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Single staging model](#single-staging-model)
7+- [The multi-staging model](#the-multi-staging-model)
8+- [Implications](#implications)
9+ - [Isolation – separate configurations](#isolation--separate-configurations)
10+ - [Isolation – more information](#isolation--more-information)
11+ - [Isolation – independent state](#isolation--independent-state)
12+ - [Isolation – freedom to experiment](#isolation--freedom-to-experiment)
13+ - [Infrastructure reuse](#infrastructure-reuse)
14+ - [Continuous staging deployment](#continuous-staging-deployment)
15+
16+</details>
17+
18+In this case study, we consider developing a system that is mostly comprised of a server.
19+
20+## Single staging model
21+
22+This model implies having a single machine (physical or virtual) which is considered a staging environment. This single machine essentially becomes the contact point between Developers and QA engineers.
23+
24+This in essence means that at any given point a single build of the service in a single configuration is available to the QA engineers.
25+
26+## The multi-staging model
27+
28+This model implies having a separate machine (physical or virtual) for *every* developed feature. Of course, deployments that are no longer needed will be removed.
29+
30+This means that QA engineers can have access to an indefinite amount of independent builds and configurations of the product.
31+
32+## Implications
33+
34+### Isolation – separate configurations
35+
36+It is sometimes the case that certain features need a "special" environment to be tested in, for example testing a feature might require payment errors to occur that only happen in a production payment processing environment. Having a single staging will require either not testing the said feature, or testing **all** "current" features with a production payment processor. This might prove to be either more difficult than necessary, or might lead to other undesirable consequences.
37+
38+"Current" is used here to denote features that have been completed after the last time QA engineers had the opportunity to test the staging of the given project. It is assumed that the completed features are merged into something analogous to a `master` branch in git.
39+
40+A way to mitigate the issue might be to have fine-grained control over what features are allowed to be merged into the `master` branch so that when we deploy it, we will have only the one feature which needs the "special" environment, in a sense, batching features based on the environment they require. The main problem with this is that the payment processor is in all likelihood a tiny sliver, a single dimension of the whole space of possible environment setups. Trying to capture and institutionalize the whole breadth of a production environment in your development workflow seems like a futile endeavor.
41+
42+Having per-feature stagings allow you to have completely separate and independent configurations for every single feature you develop and test at no additional cost.
43+
44+### Isolation – more information
45+
46+Sometimes making a change in one part of the codebase can lead to unexpected behavior in seemingly unrelated parts of the project. For example, an implementation of a feature might break the login form altogether.
47+
48+With a single staging after discovering that the newly deployed version of your project breaks the login form, you will need to inspect every single feature implemented since the last deployment of your staging environment to find the offending feature. Depending on your workflow, there can be a large number of features.
49+
50+With per-feature stagings, you will know precisely what change broke the login form since every single deployment would differ from a known-to-be-stable version by *exactly* one feature implementation in most cases – if you detect an unexpected breakage on one of the stagings, then you know exactly which feature broke it.
51+
52+### Isolation – independent state
53+
54+Some developed features might require some intricate and fragile state to be properly tested, for example, you might want something special for your 1000th customer each day. (This is a very contrived example. In reality, there will be numerous more subtle situations.) With a single staging environment, it might be very easy to trigger the desired state accidentally while testing a different feature. Having per-feature stagings implies that every staging has a separate state – this problem is mitigated.
55+
56+As a bonus, it reduces the amount of data on the staging – this might make reproducing found bugs easier for the developer since the database would have orders of magnitude less data, and only the data relevant to the feature will be present.
57+
58+### Isolation – freedom to experiment
59+
60+Sometimes it can be useful for a business to test out an idea that might end up not being suitable for production at this time for whatever reason. (We are assuming that the feature is such that this becomes obvious only after implementing a prototype.)
61+
62+When you have a single staging, the only real way to test out an idea is to merge into the rest of the codebase and deploy it to the staging in the usual way. If the feature is deemed an improvement – all is well, just continue development. If it is decided that the feature should not be pushed to production, you now need to "unmerge" the changes from the rest of the codebase. Depending on the timeline, other features might have been implemented that rely on the code. In most cases rolling a feature back is a non-trivial task.
63+
64+With per-feature stagings, you could, in essence, make a branch of your whole product, and experiment with it however you like without fear that the changes will be relied upon by the rest of the team. Rolling back a feature becomes as easy as removing the git branch.
65+
66+### Infrastructure reuse
67+
68+When setting up Octopod you will need to implement the exact steps required to set up your whole project infrastructure in a reliable and reproducible way. This clarity, which is otherwise most likely absent due to ad-hoc deployment procedures, allows you to bootstrap new projects quickly. New projects will likely reuse some of the technologies your teams have experience with on other projects; you will be able to easily copy over the already implemented parts of the infrastructure.
69+
70+### Continuous staging deployment
71+
72+It is easy to set up continuous deployment procedures with Octopod. (Continuous deployment here refers to the process of automatically deploying branches of your repository when they are updated.) This allows you to optimize your development workflow even further.
73+
74+A not-so-obvious usage might be to set up a staging to be automatically updated with the `master` branch of your project repository. This has the advantage that anyone can at any time easily test the behavior of the `master` branch.
docs/en/Technical_architecture.mdnew file+292
@@ -0,0 +1,292 @@
1+# Technical architecture
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [⚒️ Used tools](#️-used-tools)
7+- [📐 App architecture](#-app-architecture)
8+ - [🖥 Web UI](#-web-ui)
9+ - [🐙 Octopod Server](#-octopod-server)
10+ - [🐘 PostgreSQL](#-postgresql)
11+ - [🎛 octo CLI](#-octo-cli)
12+ - [📑 Control scripts](#-control-scripts)
13+ - [🚮⏲ Clean Archive CronJob](#-clean-archive-cronjob)
14+ - [Kube API Server](#kube-api-server)
15+- [📦 Octopod Distribution model](#-octopod-distribution-model)
16+- [Process view](#process-view)
17+ - [✨ Create](#-create)
18+ - [🔧 Update](#-update)
19+ - [🗃 Archive](#-archive)
20+ - [🚮 Cleanup](#-cleanup)
21+ - [🔁 Restore](#-restore)
22+- [👨‍💻👩‍💻 How we use it](#-how-we-use-it)
23+- [🗂️ Deployment state transitions](#️-deployment-state-transitions)
24+
25+</details>
26+
27+## ⚒️ Used tools
28+
29+The main goal of _Octopod_ is to simplify deployment in [_Kubernetes_][kube].
30+
31+When developing _Octopod_ we were expecting _Octopod_ itself to also be deployed with [_Kubernetes_][kube].
32+
33+## 📐 App architecture
34+
35+Users can interact with _Octopod_ through:
36+1. the [_Web UI_](#-web-ui) (expected to be used by developers, project managers, QA engineers, etc.)
37+2. the [_octo CLI_](#-octo-cli) (expected to be used by DevOps engineers and programmatically, e. g. on CI)
38+
39+Interaction between _Octopod_ and [_Kubernetes_][kube] is done entirely through the [_control scripts_](#-control-scripts). This allows _Octopod_ to be adapted for use in practically any deployment setup.
40+
41+_Octopod_ stores all data about deployments and all performed operations in [_PostgreSQL_](#-postgresql).
42+
43+![App architecture](../diagrams/images/app-architecture.png)
44+
45+### 🖥 Web UI
46+
47+_Web UI_ – the user interface used to manipulate deployments. It interacts with [_Octopod Server_](#-octopod-server) through HTTP/1.1 requests and receives events from [_Octopod Server_](#-octopod-server) through _Websockets_. Authentication between _Web UI_ and [_Octopod Server_](#-octopod-server) is done through Basic Auth. The Basic Auth token is read from a *JSON* config which is requested when the page is loaded. Access to the config should be configured through [_Ingress_][ingress].
48+
49+The interface does not contain technical details related to administering deployments ― managing deployments is done in a simple way. The interface is geared towards being used by developers of any level, QA engineers, project managers, and people without a technical background.
50+
51+### 🐙 Octopod Server
52+
53+_Octopod Server_ – the server that processes deployment management requests and delegates [_Kubernetes_][kube]-specific logic to [_control scripts_](#-control-scripts).
54+
55+
56+The server receives commands from the [_octo CLI_](#-octo-cli) and the [_Web UI_](#-web-ui) through HTTP/1.1 and updates the state of deployments. The server also sends updates to the [_Web UI_](#-web-ui) through _Websockets_. _Octopod Server_ interacts with [_Kube API Server_](#kube-api-server) through the [_control scripts_](#-control-scripts). Settings, deployment states and user action logs are stored in [_PostgreSQL_](#-postgresql).
57+
58+### 🐘 PostgreSQL
59+
60+[_PostgreSQL_](https://www.postgresql.org) – DBMS used to store settings, deployment states and user action logs.
61+
62+### 🎛 octo CLI
63+
64+_octo CLI_ – a command-line interface used to manage deployments. It sends HTTP/1.1 requests to [_Octopod Server_](#-octopod-server). The requests are [authenticated through SSL certificates](Security_model.md#octo-cli-authentication).
65+
66+It can perform all actions available in the [_Web UI_](#-web-ui), but also has access to view deployment logs.
67+
68+The CLI is expected to be used by DevOps engineers, but can also be used if it is necessary to automate deployment management in some way, for example [in CI scripts](Integration.md).
69+
70+### 📑 Control scripts
71+
72+_Control scripts_ – a _Docker Container_ with executables which encapsulates all of the logic of interacting with the [_Kube API Server_](#kube-api-server), cloud providers, deployments, version control, etc.
73+
74+This is necessary to make _Octopod_ itself independent from any particular deployment setup ― it can be set up to work with practically any setup.
75+
76+When the [_Octopod Server_](#-octopod-server) _Pod_ starts, the contents of the *control scripts* container are copied into the _Octopod Server_ container file system. This means that the executables need to be either statically linked or interpreted through _Bash_ since it needs to be executed in the _Octopod Server_ container environment.
77+
78+These [scripts need to be implemented](Control_scripts.md) to deploy _Octopod_.
79+
80+### 🚮⏲ Clean Archive CronJob
81+
82+_Clean Archive CronJob_ – a CronJob which is run every hour. It deletes archived deployments older than 14 days. It is done by calling [_octo CLI_](#-octo-cli).
83+
84+It is necessary because "deleting" (archiving) deployments should only free the occupied computational resources, _Persistent Volumes_ should not be freed when deleting a deployment. This gives us a window of time in which a deployment can be recovered in the state it was in before being archived.
85+
86+### Kube API Server
87+
88+[Kube API Server](https://kubernetes.io/docs/concepts/overview/kubernetes-api/) – an API server in [_Kubernetes_][kube] which should be called from [_control scripts_](#-control-scripts).
89+
90+## 📦 Octopod Distribution model
91+
92+[_octo CLI_](#-octo-cli) is distributed as a *statically linked executable*. The prebuilt binaries can be found in the "Releases" tab of the GitHub repository.
93+
94+[_Octopod Server_](#-octopod-server) and [_Web UI_](#-web-ui) are distributed as a single _Docker Image_. [_Charts_][chart] are used [to deploy](Octopod_deployment_guide.md) it in [_Kubernetes_][kube].
95+
96+A _Docker Image_ with [_control scripts_](#-control-scripts) should be provided by the user. They are available in [our _Docker Hub_ registry](https://hub.docker.com/orgs/typeable/repositories).
97+
98+## Process view
99+
100+Here we provide sequence diagrams for every basic operation that can be performed in _Octopod_. These operations call [_control scripts_](#-control-scripts). On the diagrams, they are labeled as _ControlScripts_.
101+
102+### ✨ Create
103+
104+_Create_ – creates a new deployment. The main inputs include the name of the deployment, the _Docker Image tag_ and optional overrides. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-create).
105+
106+The arguments are forwarded to the [_create_](Control_scripts.md#-create) script which in turn creates the deployment in the _Kubernetes cluster_. It might call something like:
107+
108+```bash
109+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
110+ --set "global.project-name=$project_name" \
111+ --set "global.base-domain=$base-domain" \
112+ --set "app.tag=$tag" \
113+ --set "app.env.foo=$app_env_override_1" \
114+ --set "app.bar=$deployment_override_1" \
115+ --wait \
116+ --timeout 300
117+```
118+
119+<details>
120+ <summary>Create via CLI sequence diagram</summary>
121+
122+![Create](../diagrams/images/technical-architecture-create-via-cli.png)
123+
124+</details>
125+
126+<details>
127+ <summary>Create via UI sequence diagram</summary>
128+
129+![Create](../diagrams/images/technical-architecture-create-via-ui.png)
130+
131+</details>
132+
133+### 🔧 Update
134+
135+_Update_ – updates an existing deployment. The main inputs include the name of the deployment, the _Docker Image tag_ and optional overrides. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-update).
136+
137+[_Overrides_](Overview.md#overrides) are read from the database and merged with the new changes. All arguments are forwarded to the [_update_](Control_scripts.md#-update) script which in turn updates the specified deployment with the new parameters in the _Kubernetes cluster_. It might call something like:
138+
139+```bash
140+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
141+ --set "global.project-name=$project_name" \
142+ --set "global.base-domain=$base-domain" \
143+ --set "app.tag=$tag" \
144+ --set "app.env.foo=$app_env_override_1" \
145+ --set "app.bar=$deployment_override_1" \
146+ --wait \
147+ --timeout 300
148+```
149+
150+
151+<details>
152+ <summary>Update via CLI sequence diagram</summary>
153+
154+![Update](../diagrams/images/technical-architecture-update-via-cli.png)
155+
156+</details>
157+
158+<details>
159+ <summary>Update via UI sequence diagram</summary>
160+
161+![Update](../diagrams/images/technical-architecture-update-via-ui.png)
162+
163+</details>
164+
165+### 🗃 Archive
166+
167+_Delete_ – archives a deployment. It should only free the computational resources (_Pods_). _Persistent Volumes_ should not be deleted ― they are cleared in the [_cleanup_](#-cleanup) process. This operation can be undone with the [_restore_](#-restore) command.
168+
169+The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-archive).
170+
171+The arguments are forwarded to the [_delete_](Control_scripts.md#-archive) script which in turn frees the computational resources. It might call something like:
172+
173+```bash
174+helm delete "$name" --purge
175+```
176+
177+<details>
178+ <summary>Archive via CLI sequence diagram</summary>
179+
180+![Archive](../diagrams/images/technical-architecture-archive-via-cli.png)
181+
182+</details>
183+
184+<details>
185+ <summary>Archive via UI sequence diagram</summary>
186+
187+![Archive](../diagrams/images/technical-architecture-archive-via-ui.png)
188+
189+</details>
190+
191+### 🚮 Cleanup
192+
193+_Cleanup_ – releases **all** resources captured by the deployment.
194+
195+The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-cleanup). It can only be called after [_archive_](#-archive) has been executed.
196+
197+The arguments are forwarded to the [_cleanup_](Control_scripts.md#-cleanup) script which in turn frees all resources captured by the given deployment. It might call something like:
198+
199+```bash
200+kubectl delete pvc -n "$namespace" "$name-postgres-pvc"
201+kubectl delete certificate -n "$namespace" "$name-postgres-cert"
202+```
203+
204+<details>
205+ <summary>Cleanup via CLI sequence diagram</summary>
206+
207+![Cleanup](../diagrams/images/technical-architecture-cleanup-via-cli.png)
208+
209+</details>
210+
211+<details>
212+ <summary>Cleanup via UI sequence diagram</summary>
213+
214+![Cleanup](../diagrams/images/technical-architecture-cleanup-via-ui.png)
215+
216+</details>
217+
218+### 🔁 Restore
219+
220+_restore_ – restores an archived deployment in the state it was last in. Calls the same _script_ that is called in [_create_](#-create).
221+
222+The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-create). It can only be called after [_archive_](#-archive) has been executed.
223+
224+All necessary setup information is read from the database: [_overrides_](Overview.md#overrides) and the _Docker Image tag_. The arguments are forwarded to the [_create_](Control_scripts.md#-create) script which in turn recreates the deployment. It might call something like:
225+
226+```bash
227+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
228+ --set "global.project-name=$project_name" \
229+ --set "global.base-domain=$base-domain" \
230+ --set "app.tag=$tag" \
231+ --set "app.env.foo=$app_env_override_1" \
232+ --set "app.bar=$deployment_override_1" \
233+ --wait \
234+ --timeout 300
235+```
236+
237+<details>
238+ <summary>Restore via CLI sequence diagram</summary>
239+
240+![Restore](../diagrams/images/technical-architecture-restore-via-cli.png)
241+
242+</details>
243+
244+<details>
245+ <summary>Restore via UI sequence diagram</summary>
246+
247+![Restore](../diagrams/images/technical-architecture-restore-via-ui.png)
248+
249+</details>
250+
251+## 👨‍💻👩‍💻 How we use it
252+
253+We deploy several separate [_Kubernetes_][kube] clusters:
254+- We have separate clusters for every product we deploy
255+- We also separate _production_ and _staging_ clusters
256+
257+That makes two clusters per product.
258+
259+So we get a cluster matrix similar to the following table, where each cell is a separate cluster:
260+
261+| | Staging (Has _Octopod_ installed) | Production (_Octopod_ not installed) |
262+| ------------------- | --------------------------------- | ------------------------------------ |
263+| **Cactus shop** | 🟩 🐙 Cluster *A* | 🟨 Cluster *B* |
264+| **Pottery service** | 🟦 🐙 Cluster *C* | 🟪 Cluster *D* |
265+| ... | ... | ... |
266+
267+Every color depicts a separate cluster. A 🐙 indicates that _Octopod_ is installed in that cluster.
268+
269+Every _staging_ cluster has a separate _Octopod_ installation with separate interfaces to manage the deployments.
270+
271+## 🗂️ Deployment state transitions
272+
273+A deployment can exist in one of six states:
274+1. *Running*
275+2. *Failure*
276+3. *CreatePending*
277+4. *UpdatePending*
278+5. *DeletePending*
279+6. *Archived*
280+
281+_Running_, _Failure_, _Archived_ states are "permanent", meaning the deployment is not in the process of executing a command.
282+
283+*CreatePending*, *UpdatePending*, *DeletePending* states are temporary, meaning the deployment is currently in the process of executing a deployment command.
284+
285+![Deployment Statuses](../diagrams/images/technical-architecture-deployment-states-fsm.png)
286+
287+<!-- [kubectl]: https://kubernetes.io/docs/reference/kubectl/ -->
288+<!-- [helm]: https://helm.sh -->
289+<!-- [kubedog]: https://github.com/werf/kubedog -->
290+[kube]: https://kubernetes.io
291+[chart]: https://helm.sh/docs/topics/charts/
292+[ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/
docs/images/break1.pngnew file

Binary file not shown.

docs/images/break2.pngnew file

Binary file not shown.

docs/images/dev1.pngnew file

Binary file not shown.

docs/images/dev2.pngnew file

Binary file not shown.

docs/images/env1.pngnew file

Binary file not shown.

docs/images/env2.pngnew file

Binary file not shown.

docs/images/first.pngnew file

Binary file not shown.

docs/images/hello-octopod-1.pngnew file

Binary file not shown.

docs/images/hello-octopod-2.pngnew file

Binary file not shown.

docs/images/hello-octopod-3.pngnew file

Binary file not shown.

docs/images/second.pngnew file

Binary file not shown.

docs/ru/Control_scripts.mdnew file+209
@@ -0,0 +1,209 @@
1+# Control scripts
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [create](#create)
7+- [update](#update)
8+- [archive](#archive)
9+- [check](#check)
10+- [cleanup](#cleanup)
11+- [archive_check](#archive_check)
12+- [tag_check](#tag_check)
13+- [init](#init)
14+- [info](#info)
15+
16+</details>
17+
18+## create
19+
20+Реализация создания нового развертывания.
21+
22+Получает на вход следующие аргументы:
23+* `--project-name` – название проекта
24+* `--base-domain` – базовый домен
25+* `--namespace` – namespace
26+* `--name` – имя развертывания
27+* `--tag` – тег развертывания
28+* `--app-env-override` – override уровня приложения (формат значения `FOO=BAR`, может быть передано 0 или более раз)
29+* `--deployment-override` – override уровня развертывания (формат значения `FOO=BAR`, может быть передано 0 или более раз)
30+
31+Успешность операции определяется по exit code (`0` – успех).
32+
33+Например `create` с полученными аргументами может выполнять:
34+
35+```bash
36+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
37+ --set "global.project-name=$project_name" \
38+ --set "global.base-domain=$base-domain" \
39+ --set "app.tag=$tag" \
40+ --set "app.env.foo=$app_env_override_1" \
41+ --set "app.bar=$deployment_override_1" \
42+ --wait \
43+ --timeout 300
44+```
45+
46+## update
47+
48+Реализация обновления развертывания.
49+
50+Получает на вход следующие аргументы:
51+* `--project-name` – название проекта
52+* `--base-domain` – базовый домен
53+* `--namespace` – namespace
54+* `--name` – имя развертывания
55+* `--tag` – тег развертывания
56+* `--app-env-override` – override уровня приложения (формат значения `FOO=BAR`, может быть передано 0 или более раз)
57+* `--deployment-override` – override уровня развертывания (формат значения `FOO=BAR`, может быть передано 0 или более раз)
58+
59+Успешность операции определяется по exit code (`0` – успех).
60+
61+Например `update` с полученными аргументами может выполнять:
62+
63+```bash
64+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
65+ --set "global.project-name=$project_name" \
66+ --set "global.base-domain=$base-domain" \
67+ --set "app.tag=$tag" \
68+ --set "app.env.foo=$app_env_override_1" \
69+ --set "app.bar=$deployment_override_1" \
70+ --wait \
71+ --timeout 300
72+```
73+
74+## archive
75+
76+Реализация удаления развертывания.
77+
78+Получает на вход следующие аргументы:
79+* `--project-name` – название проекта
80+* `--base-domain` – базовый домен
81+* `--namespace` – namespace
82+* `--name` – имя развертывания
83+
84+Успешность операции определяется по exit code (`0` – успех).
85+
86+Например `archive` с полученными аргументами может выполнять:
87+
88+```bash
89+helm delete "$name" --purge
90+```
91+
92+## check
93+
94+Реализация проверки состояния развертывания.
95+
96+Получает на вход следующие аргументы:
97+* `--project-name` – название проекта
98+* `--base-domain` – базовый домен
99+* `--namespace` – namespace
100+* `--name` – имя развертывания
101+
102+Успешность операции определяется по exit code (`0` – успех).
103+
104+Например `check` с полученными аргументами может выполнять:
105+
106+```bash
107+echo "{\"Deployments\": [{\"ResourceName\": \"app-${name}\", \"Namespace\": \"${namespace}\"}], \"StatefulSets\": [{\"ResourceName\": \"db-${name}\", \"Namespace\": \"${namespace}\"}]}" | \
108+ kubedog multitrack -t 3
109+```
110+
111+В примере выше используется [_Kubedog_][kubedog].
112+Предполагается, что пользователь сам установит [_Kubedog_][kubedog]: сохранит его в `/` контейнера с _Control scripts_ или установит его в `$HOME` из [init](#init).
113+
114+## cleanup
115+
116+Реализация очистки ресурсов развертывания.
117+
118+Получает на вход следующие аргументы:
119+* `--project-name` – название проекта
120+* `--base-domain` – базовый домен
121+* `--namespace` – namespace
122+* `--name` – имя развертывания
123+
124+Успешность операции определяется по exit code (`0` – успех).
125+
126+Например `cleanup` с полученными аргументами может выполнять:
127+
128+```bash
129+kubectl delete pvc -n $namespace -l "app=$name"
130+```
131+
132+## archive_check
133+
134+Реализация проверки успешности удаления развертывания.
135+
136+Получает на вход следующие аргументы:
137+* `--project-name` – название проекта
138+* `--base-domain` – базовый домен
139+* `--namespace` – namespace
140+* `--name` – имя развертывания
141+
142+Успешность операции определяется по exit code (`0` – успех).
143+
144+Например `archive_check` с полученными аргументами может выполнять:
145+
146+```bash
147+helm status $name
148+```
149+
150+## tag_check
151+
152+Реализация проверки существования тега развертывания.
153+
154+Получает на вход следующие аргументы:
155+* `--project-name` – название проекта
156+* `--base-domain` – базовый домен
157+* `--namespace` – namespace
158+* `--name` – имя развертывания
159+* `--tag` – тег развертывания
160+
161+Успешность операции определяется по exit code (`0` – успех).
162+
163+## init
164+
165+Реализация инициализации рабочего окружения для будущих запусков *Control Scripts*.
166+
167+В этом скрипте предлагается настроить доступ к системам контроля версий, облачным провайдерам и т.д.
168+Для этого можно сохранить настройки в файлы в $HOME.
169+
170+Этот скрипт однократно запускается во время создания пода `Octopod Server`.
171+
172+Успешность операции определяется по exit code (`0` – успех).
173+
174+Например `init` может выполнять:
175+
176+```bash
177+mkdir $HOME/.ssh
178+echo -e "Host github.com\nHostname github.com\nPort 22\nUser git\nIdentityFile $HOME/.ssh/deploy.key" > $HOME/.ssh/config
179+echo "MY_DEPLOY_KEY" > $HOME/.ssh/deploy.key"
180+```
181+
182+## info
183+
184+Реализация получения метаданных о deployment. Содержимое _stdout_ будет использованно в качестве дополнительной информации о deployment в _CLI/UI_.
185+Ожидаемый формат _stdout_ ― _CSV_ `ключ,значение`:
186+
187+```
188+app,https://foo.example.com
189+api,https://api.foo.example.com
190+```
191+
192+Ожидается, что `ключ` не может содержать символ `,`, ограничений на `значение` нет. Пробельные символы остаются без изменений.
193+
194+Получает на вход следующие аргументы:
195+* `--project-name` – название проекта
196+* `--base-domain` – базовый домен
197+* `--namespace` – namespace
198+* `--name` – имя стейджинга
199+
200+Успешность операции определяется по exit code (`0` – успех).
201+
202+Например `info` с полученными аргументами может выполнять:
203+
204+```bash
205+echo "app,https://${name}.example.com"
206+echo "api,https://api.${name}.example.com"
207+```
208+
209+[kubedog]: https://github.com/werf/kubedog
docs/ru/Helm-based_deployment_guide.mdnew file+165
@@ -0,0 +1,165 @@
1+# Helm-based deployment guide
2+
3+## Подгодовка
4+
5+Предполагается, что у вас в системе уже установлен Docker,
6+а также Вы установили _Octopod_. Если Вы _Octopod_ не установили, то пожалуйста воспользуйтесь [инструкцией по установке _Octopod_](Octopod_deployment_with_K8S.md).
7+При установке _Octopod_ потребуется указать название образа с [control scripts](Control_scripts.md):
8+```bash
9+--set "global.utils_image_prefix=typeable" \
10+--set "global.utils_image=octopod-helm-example" \
11+--set "global.utils_image_tag=1.0" \
12+```
13+
14+В итоге должно получиться что-то вроде:
15+
16+```bash
17+helm upgrade octopod ./octopod \
18+ --install \
19+ --namespace octopod \
20+ --set "global.deploy_checksum=$sha256_sum" \
21+ --set "global.image_prefix=typeable" \
22+ --set "global.image_tag=1.0" \
23+ --set "global.utils_image_prefix=typeable" \
24+ --set "global.utils_image=octopod-helm-example" \
25+ --set "global.utils_image_tag=1.0" \
26+ --set "global.acme_registration_email=certbot@example.com" \
27+ --set "global.auth_url=https://oauth.exmaple.com/oauth2/auth" \
28+ --set "global.auth_signin=https://oauth.exmaple.com/oauth2/start?rd=/redirect/$http_host$request_uri" \
29+ --set "project_name=MyProject" \
30+ --set "domain=octopod.example.com" \
31+ --set "app_domain=octopod-app.example.com" \
32+ --set "power_app_domain=octopod-power-app.example.com" \
33+ --set "ws_domain=octopod-ws.example.com" \
34+ --set "base_domain=example.com" \
35+ --set "status_update_timeout=600" \
36+ --wait \
37+ --timeout 600 \
38+ --debug
39+```
40+
41+Для того чтобы развернуть приложение с помощью _Octopod_ потребуется само приложение и набор [control scripts](Control_scripts.md).
42+Разворачивать можно через _Web UI_ или _octo CLI_. В этом примере мы будем использовать только _octo CLI_.
43+Также потребуется Docker для запуска контейнера с _octo CLI_.
44+Перед использованием _octo CLI_ необходимо настроить доступ к _Octopod_, пожалуйста воспользуйтесь [руководством пользователя _octo CLI_](Octo_user_guide.md).
45+
46+В качестве примера мы будем использовать простое веб приложение, которое обслуживает единственный endpoint `/`.
47+По запросу на `/` будет возвращаться список переменных окружения, имена которых начинаются с `APP_ENV`, в HTML разметке.
48+С исходным кодом приложения можно ознакомиться, перейдя по ссылке [web-app](../../examples/web-app).
49+Это приложение было упаковано в докер образ и отправлено в регистри:
50+```bash
51+docker build -f docker/Dockerfile -t typeable/octopod-web-app-example:v1 .
52+docker push typeable/octopod-web-app-example:v1
53+```
54+
55+Также мы будем производить обновление приложения до более новой версии.
56+В новой версии изменена HTML разметка: каждая переменная окружения выводится не в отдельном `div`, а в виде элемента списка `li`.
57+С исходным кодом обновленного приложения можно ознакомиться, перейдя по ссылке [web-app-v2](../../examples/web-app-v2).
58+Это приложение было упаковано в докер образ и отправлено в регистри:
59+```bash
60+docker build -f docker/Dockerfile -t typeable/octopod-web-app-example:v2 .
61+docker push typeable/octopod-web-app-example:v2
62+```
63+
64+Для работы веб приложению нужен 1 TLS сертификат для каждого развертывания. [_Cert Manager_][cert-manager] создает сертификаты через [_Let’s Encrypt_][lets-encrypt].
65+У [_Let’s Encrypt_][lets-encrypt] есть лимиты на создание сертификатов [_Let’s Encrypt Rate Limits_][lets-encrypt-rate-limits].
66+При достижении лимита `too many registrations for this IP` может помочь перемещение Pod [_Cert Manager_][cert-manager] на другую ноду.
67+
68+Набор [control scripts](Control_scripts.md) будет использовать helm v2.x, kubectl, kubedog для управления и проверки статуса деплойментов.
69+С исходным кодом [control scripts](Control_scripts.md) можно ознакомиться, перейдя по ссылке [helm-based-control-scripts](../../examples/helm-based-control-scripts).
70+Набор [control scripts](Control_scripts.md) был упакован в докер образ и отправлен в регистри:
71+
72+```bash
73+docker build -f docker/Dockerfile -t typeable/octopod-helm-example:1.0 .
74+docker push typeable/octopod-helm-example:1.0
75+```
76+**Примечание:**
77+В нашем примере используется Stateless приложение упакованное в один chart.
78+Так что для создания и обновления необходимо выполнить один набор команд (`helm upgrade --install ...`).
79+Поэтому [control scripts](Control_scripts.md) реализован только `create`, и он же используется вместо `update`.
80+В случае более сложного приложения упакованного в несколько chart-ов, возможно потребуется выполнить различный набор команд,
81+это потребует реализации различных `create` и `update` скриптов.
82+
83+## Создание нового deployment
84+
85+Для создания достаточно выполнить
86+```bash
87+octo create -n hello-octopod -t v1 -e APP_ENV_KEY1=VALUE1
88+```
89+
90+- `-n hello-octopod` ― название deployment.
91+- `-t v1` ― тег deployment.
92+- `-e APP_ENV_KEY1=VALUE1` ― переменная окружения уровня приложения.
93+
94+Эта команда вызовет `create` из [control scripts](Control_scripts.md), a `create` вызовет `helm`.
95+Спустя некоторое время Вы увидите созданный _Pod_ нового deployment
96+```
97+$ kubectl get pods -n deployment
98+NAME READY STATUS RESTARTS AGE
99+app-hello-octopod-8965856-qbwvq 1/1 Running 0 15s
100+```
101+
102+Если откроете в браузере `http://hello-octopod.<домен, указанный при развертывании Octopod>` Вы увидите
103+
104+![После создания](../images/hello-octopod-1.png)
105+
106+## Добавление новой переменной окружения уровня приложения
107+
108+Для добавления новой переменной уровня приложения достаточно выполнить
109+```bash
110+octo update -n hello-octopod -t v1 -e APP_ENV_KEY2=VALUE2
111+```
112+
113+- `-n hello-octopod` ― название deployment.
114+- `-t v1` ― тег deployment.
115+- `-e APP_ENV_KEY2=VALUE2` ― переменная окружения уровня приложения.
116+
117+Эта команда вызовет `create` из [control scripts](Control_scripts.md), `create` вызовет `helm` (Смотри примечание раздела _Подготовка_).
118+Спустя несколько секунд, если откроете в браузере `http://hello-octopod.<домен указанный при развертывании Octopod>` Вы увидите новую переменную окружения уровня приложения:
119+
120+![После установки новой переменной окружения уровня приложения](../images/hello-octopod-2.png)
121+
122+## Обновление версии приложения
123+
124+Для обновления приложения до новой версии достаточно выполнить
125+```bash
126+octo update -n hello-octopod -t v2
127+```
128+
129+- `-n hello-octopod` ― название deployment.
130+- `-t v2` ― тег deployment.
131+
132+Эта команда вызовет `create` из [control scripts](Control_scripts.md), `create` вызовет `helm` (Смотри примечание раздела _Подготовка_).
133+Спустя несколько секунд, если откроете в браузере `http://hello-octopod.<домен указанный при развертывании Octopod>` Вы увидите обновленную HTML разметку:
134+
135+![После создания](../images/hello-octopod-3.png)
136+
137+## Увеличение количества реплик через установку переменной окружения уровня deployment
138+
139+Для добавления новой переменной достаточно выполнить
140+```bash
141+octo update -n hello-octopod -t v2 -o replicas=3
142+```
143+
144+- `-n hello-octopod` ― название deployment.
145+- `-t v1` ― тег deployment.
146+- `-o replicas=3` ― переменная окружения уровня deployment.
147+
148+Эта команда вызовет `create` из [control scripts](Control_scripts.md), `create` вызовет `helm` (Смотри примечание раздела _Подготовка_).
149+
150+Спустя некоторое время Вы увидите, что количество _Pod_-ов стало 3
151+```bash
152+$ kubectl get pods -n deployment
153+NAME READY STATUS RESTARTS AGE
154+app-hello-octopod-8965856-qbwvq 1/1 Running 0 97m
155+app-hello-octopod-8965856-v585c 1/1 Running 0 15s
156+app-hello-octopod-8965856-v88md 1/1 Running 0 15s
157+```
158+
159+Если откроете в браузере `http://hello-octopod.<домен указанный при развертывании Octopod>`, Вы увидите, что приложение продолжает обсуживать запросы:
160+
161+![После обновления переменной окружения уровня deployment](../images/hello-octopod-3.png)
162+
163+[cert-manager]: https://cert-manager.io/docs
164+[lets-encrypt]: https://letsencrypt.org
165+[lets-encrypt-rate-limits]: https://letsencrypt.org/docs/rate-limits
docs/ru/Octo_user_guide.mdnew file+173
@@ -0,0 +1,173 @@
1+# Octo user guide
2+
3+_octo CLI_ – консольная утилита для управления deployments через отправку команд в _Octopod Server_.
4+
5+## Environment variables
6+
7+`OCTOPOD_URL` – URL к _Octopod Server_, например `https://octopod-power-app.example.com:443`, **необходимо определить, иначе _octo CLI_ не будет работать**.
8+
9+`TLS_CERT_PATH` – путь к файлу сертификата, который используется для аутентификации в _Octopod Server_, если не определенно, то используется `./cert.pem`.
10+
11+`TLS_KEY_PATH` – путь к файлу ключа, который используется для аутентификации в _Octopod Server_, если не определенно, то используется `./key.pem`.
12+
13+## Subcommands
14+
15+### create
16+
17+Создать новый deployment.
18+
19+Аргументы:
20+
21+* `-n` или `--name` – название deployment
22+* `-t` или `--tag` – тег deployment
23+* `-e` или `--set-app-env-override` – override уровна приложения, формат: КЛЮЧ=ЗНАЧЕНИЕ, можно использовать 0 и более раз
24+* `-o` или `--set-deployment-override` – override увроня deployment, формат: КЛЮЧ=ЗНАЧЕНИЕ, можно использовать 0 и более раз
25+
26+Пример:
27+
28+```bash
29+$ octo create -n hello-octopod -t ca5fd1fe08389f6422a506a59b68a5272ac37ba6 -e KEY1=VALUE1 -e KEY2=VALUE2
30+```
31+
32+### list
33+
34+Получить список всех deployments.
35+
36+Данная subcommand не требует передачи аргументов.
37+
38+Пример:
39+
40+```bash
41+$ octo list
42+hello-octopod
43+```
44+
45+### archive
46+
47+Удалить deployment.
48+
49+Аргументы:
50+
51+* `-n` или `--name` – название deployment
52+
53+Пример:
54+
55+```bash
56+$ octo archive -n hello-octopod
57+```
58+
59+### update
60+
61+Обновить deployment.
62+
63+Аргументы:
64+
65+* `-n` или `--name` – название deployment
66+* `-t` или `--tag` – тег deployment
67+* `-e` или `--set-app-env-override` – override уровна приложения, формат: КЛЮЧ=ЗНАЧЕНИЕ, можно использовать 0 и более раз
68+* `-E` или `--unset-app-env-override` – override уровна приложения, которое надо удалить, формат: КЛЮЧ, можно использовать 0 и более раз
69+* `-o` или `--set-deployment-override` – override увроня deployment, формат: КЛЮЧ=ЗНАЧЕНИЕ, можно использовать 0 и более раз
70+* `-O` или `--unset-deployment-override` – override увроня deployment, которое надо удалить, формат: КЛЮЧ, можно использовать 0 и более раз
71+
72+Пример:
73+
74+```bash
75+$ octo update -n octopod -t 015f16ecf398fcadaac508c1855ae160af0969c4 -E KEY1 -e KEY2=VALUE22222
76+```
77+
78+### info
79+
80+Получить информацию о deployment.
81+
82+Аргументы:
83+
84+* `-n` или `--name` – название deployment
85+
86+Пример:
87+
88+```bash
89+$ octo info -n hello-octopod
90+Current settings:
91+tag: v1
92+application overrides: app=1 (Public)
93+
94+deployment overrides: dep=2 (Public)
95+
96+metadata:
97+ app: https://ree.lvh.me
98+
99+Last logs:
100+┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
101+┃ Created at ┃ Action id ┃ Action ┃ Tag ┃ App overrides ┃ Deployment overrides ┃ Exit code ┃
102+┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
103+│ 2020-11-02T17:14:03 │ 7 │ create │ v1 │ app=1 (Public) │ dep=2 (Public) │ 1 │
104+├─────────────────────┼───────────┼────────┼─────┼────────────────┼──────────────────────┼───────────┤
105+│ 2020-11-02T19:01:02 │ 8 │ update │ v1 │ app=1 (Public) │ dep=2 (Public) │ 1 │
106+└─────────────────────┴───────────┴────────┴─────┴────────────────┴──────────────────────┴───────────┘
107+```
108+
109+### cleanup
110+
111+Очистить ресурсы от deployment.
112+
113+Аргументы:
114+
115+* `-n` или `--name` – название deployment
116+
117+Пример:
118+
119+```bash
120+$ octo cleanup -n hello-octopod
121+```
122+
123+### restore
124+
125+Восстановить deployment.
126+
127+Аргументы:
128+
129+* `-n` или `--name` – название deployment
130+
131+Пример:
132+
133+```bash
134+$ octo restore -n hello-octopod
135+```
136+
137+### clean-archive
138+
139+Удалить архивные deployments старее 2х недель.
140+
141+Данная subcommand не требует передачи аргументов.
142+
143+Пример:
144+
145+```bash
146+$ octo clean-archive
147+```
148+
149+### logs
150+
151+Печатает логи выбранного деплоя.
152+
153+Аргументы:
154+
155+* `-a` или `--action` – action id, который вернулся из команды `octo info`.
156+* `-l` или `--log-type` – Какие логи необходимо вернуть. Значения: `stdout`, `stderr`, `all`. По умолчанию будет `all`
157+
158+Пример:
159+
160+```
161+$ octo logs -a 13
162+ stdout:
163+
164+
165+ stderr:
166+
167+error: Found argument '--deployment-override' which wasn't expected, or isn't valid in this context
168+
169+USAGE:
170+ update --app-env-override <app-env-override>... --base-domain <base-domain> --name <name> --namespace <namespace> --project-name <project-name> --tag <tag>
171+
172+For more information try --help
173+```
docs/ru/Octopod_deployment_with_K8S.mdnew file+275
@@ -0,0 +1,275 @@
1+# Octopod deployment with K8S
2+
3+1. Установка необходимых утилит
4+
5+ Для установки потребуется [_kubectl_][kubectl] и [_helm_][helm] версии 2.
6+
7+2. Настройка кластера
8+
9+ _Octopod_ использует [_Ingress Nginx_][ingress-nginx], [_Cert Manager_][cert-manager]. Убедитесь, что они установленны в вашем кластере.
10+
11+ Для работы _Octopod_ нужны 3 TLS сертификата. [_Cert Manager_][cert-manager] создает сертификаты через [_Let’s Encrypt_][lets-encrypt].
12+ У [_Let’s Encrypt_][lets-encrypt] есть лимиты на создание сертификатов [_Let’s Encrypt Rate Limits_][lets-encrypt-rate-limits].
13+ При достижении лимита `too many registrations for this IP` может помочь перемещение Pod [_Cert Manager_][cert-manager] на другую ноду.
14+
15+ _Octopod_ необходимо не более 2 CPU и 2Gb оперативной памяти, пожалуйста убедитесь что у вас есть необходимые ресурсы.
16+
17+ _Octopod_ будет развернут на ноду с label `role=stand`, пожалуйста добавте label:
18+ ```
19+ kubectl label node <your_node> role=stand
20+ ```
21+
22+3. Также вам потребуется [_Tiller_][tiller] – сервис на стороне кластера, который необходим для работы [_helm 2_][helm]. Самый простой способ его установить это:
23+
24+ ```bash
25+ helm init
26+ ```
27+
28+ Если Вы установили [_Tiller_][tiller], у вас может возникнуть [проблемы с правами доступа](https://github.com/helm/helm/issues/5100) у [_Tiller_][tiller].
29+
30+ Чтобы выдать ему необходимые права будет достаточно выполнить следующие команды:
31+
32+ ```bash
33+ kubectl create -n kube-system serviceaccount tiller
34+ kubectl --namespace kube-system create clusterrolebinding tiller-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
35+ kubectl --namespace kube-system patch deploy tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'
36+ ```
37+
38+
39+4. Скачивание исходного кода проекта
40+
41+ ```bash
42+ git clone https://github.com/typeable/octopod.git /tmp/octopod
43+ ```
44+
45+5. Создание namespace `deployment` и `octopod`
46+
47+ ```bash
48+ kubectl create namespace deployment
49+ kubectl create namespace octopod
50+ ```
51+
52+6. Создание [_Service Account_][kubernetes-service-account] `octopod`
53+
54+ ```bash
55+ kubectl create -n octopod serviceaccount octopod
56+ ```
57+
58+7. Создание секрета, который будет использоваться для Basic Auth между _Web UI_ и _Octopod Server_
59+
60+ ```bash
61+ username="octopod"
62+ password="password" # пожалуйста поменяйте на более безопасный пароль
63+ kubectl create secret generic octopod-basic-auth -n octopod --from-literal=auth=$(htpasswd -bn $username $password)
64+ ```
65+
66+8. Создание ConfigMap с сертификатами и ключами, которые будут использоваться для аутентификации между _octo CLI_ и _Octopod Server_
67+
68+ ```bash
69+ mkdir certs
70+ (cd certs && \
71+ openssl req -x509 -newkey rsa:4096 -keyout server_key.pem -out server_cert.pem -nodes -subj "/CN=localhost/O=Server" && \
72+ openssl req -newkey rsa:4096 -keyout client_key.pem -out client_csr.pem -nodes -subj "/CN=Client" && \
73+ openssl x509 -req -in client_csr.pem -CA server_cert.pem -CAkey server_key.pem -out client_cert.pem -set_serial 01 -days 3650)
74+ kubectl create configmap octopod-certs -n octopod --from-file=./certs
75+ ```
76+
77+ После этого директория `certs` будет содержать сертификаты, используемый для авторизации между _octo CLI_ и _Octopod Server_. `client_key.pem` и `client_cert.pem` необходимо будет [через переменные окружения передать в _octo CLI_](Octo_user_guide.md).
78+
79+9. Далее Вам необходимо будет настроить DNS чтобы поддомены вашего домена указывали на адрес вашего кластера:
80+
81+ ```
82+ *.octo.example.com A 1.2.3.4
83+ ```
84+
85+10. Так как авторизация между _octo CLI_ и _Octopod Server_ будет производиться через SSL сертификаты, которые мы только что сами создали, нужно чтобы используемый в кластере [_ingress-nginx_][ingress-nginx] поддерживал _SSL passthrough_ – чтобы мы могли ему сказать "не трогай сертификаты".
86+
87+ Чтобы этого добиться нам необходимо добавить `--enable-ssl-passthrough` в аргументы запуска [_ingress-nginx_][ingress-nginx] контроллера. Сделать это можно с командой вроде этой (конкретный namespace и название деплоймента нужно будет смотреть в вашем конкретном кластере):
88+
89+ ```bash
90+ kubectl edit deploy -n ingress-nginx nginx-ingress-ingress-nginx-controller
91+ ```
92+
93+ Помимо всех прочих параметров необходимо чтобы там был указан этот:
94+
95+ ```yaml
96+ spec:
97+ ...
98+ template:
99+ ...
100+ spec:
101+ ...
102+ containers:
103+ ...
104+ - args:
105+ ...
106+ - --enable-ssl-passthrough
107+
108+ ```
109+
110+11. Установка инфраструктуры Octopod
111+
112+ Перед началом установки убедитесь, что у вас в кластере есть [_Storage Class_][kubernetes-storage-classes] `default`.
113+ Для [_minikube_][minikube] вы можете создать [_Storage Class_][kubernetes-storage-classes] `default` так:
114+
115+ ```bash
116+ cat <<EOF | kubectl apply -f-
117+ ---
118+ apiVersion: storage.k8s.io/v1
119+ kind: StorageClass
120+ metadata:
121+ name: default
122+ provisioner: k8s.io/minikube-hostpath
123+ reclaimPolicy: Delete
124+ volumeBindingMode: Immediate
125+ EOF
126+ ```
127+
128+ ```bash
129+ cd /tmp/octopod/charts
130+
131+ helm upgrade --install octopod-infra ./octopod-infra --namespace octopod \
132+ --wait --timeout 600 --debug
133+ ```
134+
135+12. Выдача прав [_Service Account_][kubernetes-service-account] `octopod`
136+
137+ 1. Eсли вы планируете использовать helm версии 2 для развертывания стейджингов,
138+ то для этого необходимо выдать права [_Service Account_][kubernetes-service-account] `octopod`:
139+ ```bash
140+ cd /tmp/octopod/charts
141+ helm install --name octopod-helm-access ./helm-access
142+ ```
143+
144+ 2. Eсли вы планируете в `cleanup` из [_Control scripts_](Control_scripts.md) удалять [_Persistent Volumes Claims_][kubernetes-pvc], то для этого необходимо выдать права [_Service Account_][kubernetes-service-account] `octopod`:
145+ ```bash
146+ cd /tmp/octopod/charts
147+ helm install --name octopod-pvc-control ./pvc-control
148+ ```
149+
150+ 3. Eсли вы используете [_Cert Manager_][cert-manager] и планируете в `cleanup` из [_Control scripts_](Control_scripts.md) удалять сертификаты созданные [_cert-manager_][cert-manager], то для этого необходимо выдать права [_Service Account_][kubernetes-service-account] `octopod`:
151+ ```bash
152+ cd /tmp/octopod/charts
153+ helm install --name octopod-cert-control ./cert-control
154+ ```
155+
156+ 4. Если Вы планируете использовать [_kubedog_][kubedog] в `check` из [_Control scripts_](Control_scripts.md) для проверки состояния развертывания, то Вам необходимо будет выдать соответствующие права [_Service Account_][kubernetes-service-account] `octopod`:
157+
158+ ```bash
159+ cd /tmp/octopod/charts
160+ helm install --name octopod-kubedog-access ./kubedog-access
161+ ```
162+
163+13. Установка сервера Octopod
164+
165+ ```bash
166+ cd /tmp/octopod/charts
167+
168+ # название проекта
169+ project_name="MyProject"
170+
171+ # Название вашего docker registry, где находятся docker образы с Octopod Server, octo CLI, Control scripts, sqitch
172+ registry="typeable"
173+
174+ # tag Octopod Server
175+ tag="1.0"
176+
177+ # название docker образа Octopod Server
178+ image="octopod"
179+
180+ # название docker образа _octo CLI_
181+ octo_image="octo"
182+
183+ # Название вашего docker registry, где находится docker образ написанными Вами Control Scripts.
184+ utils_registry="registry_name"
185+
186+ # название docker образа _Control scripts_
187+ utils_image="utils"
188+
189+ # tag _Control scripts_
190+ utils_image_tag="1.0"
191+
192+ # email используемый для регистрации в Let’s Encrypt
193+ acme_registration_email="certbot@example.com"
194+
195+ # Это не обязательный параметр, если не определить, то доступ к Web UI не будет ограничен аутентификацией.
196+ # Этот парамет работает в паре с auth_signin, надо либо оба не определять, либо оба определять.
197+ # Если этот параметр определен, то он передается в ingress-nginx и используется для ограничения доступа через через внешний сервис, например OAuth.
198+ # Для ограничения доступа через OAuth можно использовать OAuth2 Proxy (Смотри примечание ниже).
199+ auth_url="https://oauth.exmaple.com/oauth2/auth"
200+
201+ # Это не обязательный параметр, если не определить, то доступ к Web UI не будет ограничен аутентификацией.
202+ # Этот парамет работает в паре с auth_url, надо либо оба не определять, либо оба определять.
203+ # Если этот параметр определен, то он передается в ingress-nginx и используется для ограничения доступа через через внешний сервис, например OAuth.
204+ # Для ограничения доступа через OAuth можно использовать OAuth2 Proxy (Смотри примечание ниже).
205+ auth_signin="https://oauth.exmaple.com/oauth2/start?rd=/redirect/$http_host$request_uri"
206+
207+ # domain по которому будет доступен UI Web
208+ domain="octo.example.com"
209+
210+ # домен по которому будет доступен Octopod Server для HTTP запросов
211+ app_domain="api.octo.example.com"
212+
213+ # домен по которому будет доступен Octopod Server для WebSocket запросов
214+ ws_domain="ws.octo.example.com"
215+
216+ # домен по которому будет доступен Octopod Server для HTTP запросов от octo CLI
217+ power_app_domain="power.octo.example.com"
218+
219+ # домен который будет использоваться для создания доменов deployment-ов
220+ base_domain="octo.example.com"
221+
222+ # таймаут в секундах после которого обновление состояния deployment считается неудачным
223+ status_update_timeout=600
224+
225+ # контрольная сумма chart'а
226+ # NOTE: на macOS нужно будет `sha256sum` заменить на `shasum -a 256`
227+ sha256_sum=$(sha256sum octopod/values.yaml octopod/templates/* | awk '{print $1}' | sha256sum | awk '{print $1}')
228+
229+ # имя пользователя, должно совпадать с именем пользователя из шага 7
230+ username="octopod"
231+
232+ # пароль, должен совпадать с паролем из шага 7
233+ password="password"
234+
235+ # base64 от имя пользователя и пароля
236+ base64_of_username_and_password=$(echo -n "$username:$password" | base64)
237+
238+ helm upgrade --install octopod ./octopod \
239+ --namespace octopod \
240+ --set "global.deploy_checksum=$sha256_sum" \
241+ --set "global.image_prefix=$registry" \
242+ --set "global.image_tag=$tag" \
243+ --set "global.image=$image" \
244+ --set "global.octo_image=$octo_image" \
245+ --set "global.utils_image_prefix=$utils_registry" \
246+ --set "global.utils_image=$utils_image" \
247+ --set "global.utils_image_tag=$utils_image_tag" \
248+ --set "global.acme_registration_email=$acme_registration_email" \
249+ --set "global.auth_url=$auth_url" \
250+ --set "global.auth_signin=$auth_signin" \
251+ --set "basic_auth_token=$base64_of_username_and_password" \
252+ --set "project_name=$project_name" \
253+ --set "domain=$domain" \
254+ --set "app_domain=$app_domain" \
255+ --set "ws_domain=$ws_domain" \
256+ --set "power_app_domain=$power_app_domain" \
257+ --set "base_domain=$base_domain" \
258+ --set "status_update_timeout=$status_update_timeout" \
259+ --wait --timeout 600 --debug
260+ ```
261+
262+**Примечание**: Более подробно про настройку OAuth описано в [_Security model_](Security_model.md#users-auth-example-with-oauth)
263+
264+[kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl
265+[helm]: https://v2.helm.sh/docs/using_helm/#installing-helm
266+[ingress-nginx]: https://kubernetes.github.io/ingress-nginx
267+[cert-manager]: https://cert-manager.io/docs
268+[lets-encrypt]: https://letsencrypt.org
269+[lets-encrypt-rate-limits]: https://letsencrypt.org/docs/rate-limits
270+[kubernetes-service-account]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account
271+[kubernetes-pvc]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims
272+[kubernetes-storage-classes]: https://kubernetes.io/docs/concepts/storage/storage-classes
273+[minikube]: https://kubernetes.io/ru/docs/tasks/tools/install-minikube
274+[tiller]: https://v2.helm.sh/docs/install
275+[kubedog]: https://github.com/werf/kubedog
docs/ru/README.mdnew file+8
@@ -0,0 +1,8 @@
1+# 🐙📑 Документация Octopod
2+
3+- [🏗 Техническая архитектура](Technical_architecture.md)
4+- [⚙️ Гайд по скриптам управления развертываниямия](Control_scripts.md)
5+- [🔧🐙 Гайд по делою самого Octopod](Octopod_deployment_with_K8S.md)
6+- [🔧🚀 Гайд по интеграции проекта с Octopod](Helm-based_deployment_guide.md)
7+- [🐙🎛 Гайд по использованию octo CLI](Octo_user_guide.md)
8+- [🔒 Модель безопасности Octopod](Security_model.md)
docs/ru/Security_model.mdnew file+120
@@ -0,0 +1,120 @@
1+# Security model
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Octopod roles](#octopod-roles)
7+- [Kubernetes role-based access control](#kubernetes-role-based-access-control)
8+ - [Права на удаление сертификатов](#права-на-удаление-сертификатов)
9+ - [Права на удаление Persistent Volumes Claims](#права-на-удаление-persistent-volumes-claims)
10+- [Users auth example with OAuth](#users-auth-example-with-oauth)
11+- [CLI client Auth](#cli-client-auth)
12+- [UI Auth](#ui-auth)
13+
14+</details>
15+
16+## Octopod roles
17+
18+В _Octopod_ есть 2 роли:
19+* _user_
20+* _admin_
21+
22+| role | управление развертываниями | просмотр логов развертываний |
23+| :---: | :------------------------: | :--------------------------: |
24+| user | ✅ | ❌ |
25+| admin | ✅ | ✅ |
26+
27+Пользователи _UI_ работают с правами _user_.
28+
29+Пользователи _octo CLI_ работают с правами _admin_.
30+
31+## Kubernetes role-based access control
32+
33+_Octopod Server_ разворачивается в `octopod` namespace. _Octopod Server_ разворачивает развертывания в `deployments` namespace.
34+_Octopod Server_ использует `octopod` [_Service Account_][kubernetes-service-account].
35+
36+Для очистки ресурсов _Octopod Server_ / _Контейнеру с утилитами (control scripts)_ может потребоваться
37+(зависит от настроект _Kubernetes_ и релизации _Контейнера с утилитами_)
38+доступ для удаления сертификатов и [_Persistent Volumes Claims_][kubernetes-pvc].
39+
40+Доступ можно выдать через [_RBAC_][kubernetes-rbac]:
41+
42+### Права на удаление сертификатов
43+
44+```yaml
45+---
46+apiVersion: rbac.authorization.k8s.io/v1
47+kind: ClusterRole
48+metadata:
49+ name: cert-control-clusterrole
50+rules:
51+ - apiGroups: ["cert-manager.io"]
52+ resources: ["certificates"]
53+ verbs: ["list", "delete", "deletecollection"]
54+---
55+apiVersion: rbac.authorization.k8s.io/v1
56+kind: RoleBinding
57+metadata:
58+ name: octopod-cert-control-rolebinding
59+ namespace: deployments
60+roleRef:
61+ kind: ClusterRole
62+ apiGroup: rbac.authorization.k8s.io
63+ name: cert-control-clusterrole
64+subjects:
65+ - kind: ServiceAccount
66+ name: octopod
67+ namespace: octopod
68+```
69+
70+### Права на удаление Persistent Volumes Claims
71+
72+```yaml
73+---
74+apiVersion: rbac.authorization.k8s.io/v1
75+kind: ClusterRole
76+metadata:
77+ name: pvc-control-clusterrole
78+rules:
79+ - apiGroups: [""]
80+ resources: ["persistentvolumeclaims"]
81+ verbs: ["list", "delete", "deletecollection"]
82+---
83+apiVersion: rbac.authorization.k8s.io/v1
84+kind: RoleBinding
85+metadata:
86+ name: octopod-pvc-control-rolebinding
87+ namespace: deployments
88+roleRef:
89+ kind: ClusterRole
90+ apiGroup: rbac.authorization.k8s.io
91+ name: pvc-control-clusterrole
92+subjects:
93+ - kind: ServiceAccount
94+ name: octopod
95+ namespace: octopod
96+```
97+
98+## Users auth example with OAuth
99+
100+Доступ к _UI_ по умолчанию не ограничен.
101+Но вы можете добавить аутентификацию через GitHub OAuth в [_Kubernetes Ingress Nginx_][kubernetes-ingress-nginx-external-auth],
102+для этого потребуется дополнительно развернуть [_OAuth2 Proxy_][oauth2-proxy] в кластере.
103+
104+Более подробно описано в примере [_External OAUTH Authentication_][oauth-external-auth].
105+
106+## CLI client Auth
107+
108+Для аутентификации _octo CLI_ в _Octopod Server_ используется сертификат.
109+
110+## UI Auth
111+
112+Для аутентификации _UI_ в _Octopod Server_ используется Basic Auth.
113+
114+
115+[kubernetes-service-account]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account
116+[kubernetes-rbac]: https://kubernetes.io/docs/reference/access-authn-authz/rbac
117+[kubernetes-pvc]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#expanding-persistent-volumes-claims
118+[kubernetes-ingress-nginx-external-auth]: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#external-authentication
119+[oauth2-proxy]: https://oauth2-proxy.github.io/oauth2-proxy
120+[oauth-external-auth]: https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth
docs/ru/Technical_architecture.mdnew file+275
@@ -0,0 +1,275 @@
1+# Technical architecture
2+
3+<details>
4+ <summary>Table of contents</summary>
5+
6+- [Used tools](#used-tools)
7+- [App architecture](#app-architecture)
8+ - [UI](#ui)
9+ - [Octopod Server](#octopod-server)
10+ - [PostgreSQL](#postgresql)
11+ - [octo CLI](#octo-cli)
12+ - [Скрипты управления развертываниями (control scripts)](#скрипты-управления-развертываниями-control-scripts)
13+ - [Clean Archive CronJob](#clean-archive-cronjob)
14+ - [Kube API Server](#kube-api-server)
15+- [Octopod Distribution model](#octopod-distribution-model)
16+- [Process view](#process-view)
17+ - [Create](#create)
18+ - [Create via CLI](#create-via-cli)
19+ - [Create via UI](#create-via-ui)
20+ - [Update](#update)
21+ - [Update via CLI](#update-via-cli)
22+ - [Update via UI](#update-via-ui)
23+ - [Archive](#archive)
24+ - [Archive via CLI](#archive-via-cli)
25+ - [Archive via UI](#archive-via-ui)
26+ - [Cleanup](#cleanup)
27+ - [Cleanup via CLI](#cleanup-via-cli)
28+ - [Cleanup via UI](#cleanup-via-ui)
29+ - [Restore](#restore)
30+ - [Restore via CLI](#restore-via-cli)
31+ - [Restore via UI](#restore-via-ui)
32+- [How we use it](#how-we-use-it)
33+- [Deployment state transitions](#deployment-state-transitions)
34+
35+</details>
36+
37+## Used tools
38+
39+Основная цель *Octopod* ― упрощение управления разными развертываниями сервисов в [_Kubernetes_][kube].
40+
41+При разработке рассчитывалось, что сам _Octopod_ тоже будет разворачиваться при помощи [_Kubernetes_][kube].
42+
43+## App architecture
44+
45+Взаимодействовать с _Octopod_ возможно либо через веб-интерфейс (рассчитано на использование разработчиками, PM, QA и т. д.), либо через консольный клиент [_octo CLI_](#octopod-server) (рассчитано на использования DevOps инженерами и программно, например в CI).
46+
47+Все взаимодействие _Octopod_ с самими развертываниями производится через [_скрипты управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts), которые передаются ему при запуске. Это позволяет сделать _Octopod_ очень гибким ― его возможно настроить для работы с практически в любой организацией развертываний.
48+
49+Все данные об окружениях развертываний и всех операция совершаемых над ними _Octopod_ хранит в базе данных [_PostgreSQL_](#postgresql).
50+
51+![App architecture](../diagrams/images/app-architecture.png)
52+
53+### UI
54+
55+_UI_ – веб интерфейс,
56+используется для управления развертываниями посредством отправки команд управления развертываниями.
57+Взаимодействует с [_Octopod Server_](#octopod-server) посредством отправки HTTP/1.1 запросов, а также получает
58+события от [_Octopod Server_](#octopod-server) по _Websocket_. Между _UI_ и [_Octopod Server_](#octopod-server) Basic Auth. Написан на Haskell и Reflex-Dom.
59+
60+В интерфейсе нет технических деталей системной администрации и оркестрации ― работа с развертываниями производится очень просто. Интерфейс рассчитан на использование разработчиками любого уровня, QA инженерами, PM, а так же людьми без ИТ образования.
61+
62+### Octopod Server
63+
64+_Octopod Server_ – непосредственно сам сервер, который обрабатывает запросы работы с развертываниями и делегирует базовые операции, модифицирующие развертывания, [_скриптам управления стеджингами (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
65+
66+Сервер обрабатывает
67+команды управления развертываниями от [_octo CLI_](#octopod-server) и [_UI_](#ui), обновляет состояние развертываний.
68+[_octo CLI_](#octopod-server) и [_UI_](#ui) взаимодействуют с ним путем отправки HTTP/1.1 запросов.
69+Сервер шлет событие обновления на [_UI_](#ui) по _Websocket_. Сервер взаимодействует с [_Kube API Server_](#kube-api-server)
70+через [_скрипты управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts). Для хранения настроек и статусов развертываний,
71+и логов действий пользователей используется [_PostgreSQL_](#postgresql). Написан на Haskell. Для работы
72+с развертываниями использует [_скрипты управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
73+
74+### PostgreSQL
75+
76+[_PostgreSQL_](https://www.postgresql.org) – РСУБД для хранения настроек и статусов развертываний, и логов
77+действий пользователей.
78+
79+### octo CLI
80+
81+_octo CLI_ – консольный клиент
82+используется для управления развертываниями посредством отправки команд управления
83+развертываниями. Взаимодействует с [_Octopod Server_](#octopod-server) посредством отправки HTTP/1.1 запросов.
84+Написан на Haskell. Между _octo CLI_ и [_Octopod Server_](#octopod-server) аутентификация по сертификату,
85+новый сертификаты создается вовремя каждой сборки и упаковываются в контейнеры с _octo CLI_ и [_Octopod Server_](#octopod-server).
86+
87+В нем можно делать все операции, которые можно делать в [_UI_](#ui), но также есть доступ к расширенным операциям над развертываниями, таким как просмотр логов деплоя, что может быть полезно при диагностике возникающих проблем.
88+
89+Этот клиент рассчитан на использование DevOps инженерами.
90+
91+Его также можно использовать если необходимо автоматизировать управление развертываниями. Например, при настройке автоматического обновления развертываний при успешно пройденном CI.
92+
93+### Скрипты управления развертываниями (control scripts)
94+
95+_Скрипты управления развертываниями (control scripts)_ – контейнер с исполняемыми файлами, в которые инкапсулирована
96+логика работы с [_Kube API Server_](#kube-api-server), облачными провайдерами, развертываниями, системами контроля версий и т.д.
97+
98+Они нужны для того, чтобы _Octopod_ не был завязан на какую-то конкретную организацию развертываний ― его можно настроить для работы с практически любой организацией развертываний.
99+
100+Во время старта пода [_Octopod Server_](#octopod-server) содержимое контейнера с утилитами копируется в ФС контейнера с [_Octopod Server_](#octopod-server),
101+поэтому исполняемые файлы должны быть либо интерпретируемы через _Bash_, либо статически слинкованны.
102+Логику исполняемых файлов предлагается реализовать пользователям _Octopod_ (смотри [_Control scripts_](Control_scripts.md)).
103+
104+### Clean Archive CronJob
105+
106+_Clean Archive CronJob_ – CronJob, которая запускается раз в час и через [_octo CLI_](#octopod-server) удаляет заархивированные развертывания старее 14 дней.
107+
108+Это сделано из-за того что при архивировании развертываний удаляются только вычислительные его части, _persistent volumes_ не удаляются, и при необходимости развертывание возможно восстановить в том виде, в котором он был до архивирования. Такая возможность пропадает по истечению 14 дней с момент его архивирования.
109+
110+### Kube API Server
111+
112+[Kube API Server](https://kubernetes.io/docs/concepts/overview/kubernetes-api/) – сервис на стороне [_Kubernetes_][kube], к которому должны обращаться [_скрипты управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
113+
114+## Octopod Distribution model
115+
116+[_octo CLI_](#octo-cli) в виде статически слинкованного исполняемого файла. Собранные исполняемые файлы доступны во вкладке "Releases" этого проекта на GitHub.
117+
118+[_Octopod Server_](#octopod-server) и [_UI_](#ui) поставляются упаковынными в один Docker образ. Для [развертывания](Octopod_deployment_with_K8S.md) в [_Kubernetes_][kube] используется набор [_Chart_][chart]-ов. Образы доступны в [нашем registry на _Docker Hub_](https://hub.docker.com/orgs/typeable/repositories).
119+
120+Docker образ [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts) пользователь _Octopod_ собирает сам.
121+
122+## Process view
123+
124+Далее здесь для справки приводятся схемы последовательностей для каждой из базовых операция работы с развертываниями. При их исполнении вызываются [_скрипты управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts). На схеме они помечены как _ControlScripts_.
125+
126+### Create
127+
128+_create_ – создание нового развертывания.
129+ В качестве аргументов принимаются `name`, `tag` и опциональные `overrides` (уровня App или Deployment, открытие или секретные).
130+
131+ Переданные аргументы вместе с `project-name`, `base-domain`, `namespace` передаются в качестве аргументов в `create` из [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
132+ `create` создает новое развертывание в кластере используя полученные аргументы.
133+ Например, он может выполнять:
134+
135+```bash
136+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
137+ --set "global.project-name=$project_name" \
138+ --set "global.base-domain=$base-domain" \
139+ --set "app.tag=$tag" \
140+ --set "app.env.foo=$app_env_override_1" \
141+ --set "app.bar=$deployment_override_1" \
142+ --wait \
143+ --timeout 300
144+```
145+
146+#### Create via CLI
147+![Create](../diagrams/images/technical-architecture-create-via-cli.png)
148+
149+#### Create via UI
150+![Create](../diagrams/images/technical-architecture-create-via-ui.png)
151+
152+### Update
153+
154+_update_ – обновление существующего развертывания.
155+ В качестве аргументов принимаются `name`, `tag` и опционально изменения `overrides` (уровня App или Deployment, открытие или секретные).
156+
157+ Затем из базы достаются `overrides` (уровня App или Deployment, открытие или секретные), они мерджатся с изменениями `overrides`
158+ и передаются вместе с `project-name`, `base-domain`, `namespace` в качестве аргументов в `update` из [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
159+ `update` обновляет существующее развертывание в кластере используя полученные аргументы.
160+ Например, он может выполнять:
161+
162+```bash
163+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
164+ --set "global.project-name=$project_name" \
165+ --set "global.base-domain=$base-domain" \
166+ --set "app.tag=$tag" \
167+ --set "app.env.foo=$app_env_override_1" \
168+ --set "app.bar=$deployment_override_1" \
169+ --wait \
170+ --timeout 300
171+```
172+
173+
174+#### Update via CLI
175+![Update](../diagrams/images/technical-architecture-update-via-cli.png)
176+
177+#### Update via UI
178+![Update](../diagrams/images/technical-architecture-update-via-ui.png)
179+
180+### Archive
181+
182+_delete_ – архивирование существующего развертывания.
183+ Производится удаление только подов, Persistent Volumes (диски) сохраняются. Отменить действие этой команды можно с помощью команды restore.
184+ В качестве аргументов принимается `name`.
185+
186+ `name` вместе с `project-name`, `base-domain`, `namespace` передаются в качестве аргументов в `archive` из [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
187+ `archive` удаляет существующее развертывание в кластере, используя полученные аргументы.
188+ Например он может выполнять:
189+
190+```bash
191+helm delete "$name" --purge
192+```
193+
194+
195+#### Archive via CLI
196+![Archive](../diagrams/images/technical-architecture-archive-via-cli.png)
197+
198+#### Archive via UI
199+![Archive](../diagrams/images/technical-architecture-archive-via-ui.png)
200+
201+### Cleanup
202+
203+_cleanup_ – полная очистка развертывания.
204+ В качестве аргументов принимаeтся `name`.
205+
206+ Например, можно удалять сертификаты, Persistent Volume Claim и Persistent Volumes.
207+
208+ `name` вместе с `project-name`, `base-domain`, `namespace` передаются в качестве аргументов в `cleanup` из [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
209+ `cleanup` выполняет освобождение ресурсов в кластере, которые ранее использовались одним из удаленных развертываний.
210+ Например, он может выполнять:
211+
212+```bash
213+kubectl delete pvc -n "$namespace" "$name-postgres-pvc"
214+kubectl delete certificate -n "$namespace" "$name-postgres-cert"
215+```
216+
217+#### Cleanup via CLI
218+![Cleanup](../diagrams/images/technical-architecture-cleanup-via-cli.png)
219+
220+#### Cleanup via UI
221+![Cleanup](../diagrams/images/technical-architecture-cleanup-via-ui.png)
222+
223+### Restore
224+
225+_restore_ – восстановление заархивированного развертывания с последними настройками.
226+ В качестве аргументов принимается `name`.
227+
228+ Из базы достаются недостающие для восстановления `tag` и опциональные `overrides` (уровня App или Deployment, открытие или секретные). Они передаются вместе с `project-name`, `base-domain`, `namespace` в качестве аргументов в `restore` из [_скриптов управления развертываниями (control scripts)_](#скрипты-управления-развертываниями-control-scripts).
229+ `restore` выполняет восстановление в кластере ранее удаленного развертывания используя полученные аргументы.
230+ Например, он может выполнять:
231+
232+```bash
233+helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
234+ --set "global.project-name=$project_name" \
235+ --set "global.base-domain=$base-domain" \
236+ --set "app.tag=$tag" \
237+ --set "app.env.foo=$app_env_override_1" \
238+ --set "app.bar=$deployment_override_1" \
239+ --wait \
240+ --timeout 300
241+```
242+
243+#### Restore via CLI
244+![Restore](../diagrams/images/technical-architecture-restore-via-cli.png)
245+
246+#### Restore via UI
247+![Restore](../diagrams/images/technical-architecture-restore-via-ui.png)
248+
249+## How we use it
250+
251+Мы используем несколько кластеров [_Kubernetes_][kube]: отдельный кластер для каждого приложения, а так же разделяем по кластерам _production_ и _deployment_ окружения.
252+
253+В каждый _deployment_ кластер мы устанавливаем _Octopod_, через _Octopod_ осуществляем разворачивание различных версий deployment'ов необходимых для QA.
254+
255+## Deployment state transitions
256+
257+Существует 6 статусов развертываний:
258+1. *Running*
259+2. *Failure*
260+3. *CreatePending*
261+4. *UpdatePending*
262+5. *DeletePending*
263+6. *Archived*
264+
265+_Running_, _Failure_, _Archived_ являются постоянными, т.е. развертывание уже не находится в режиме выполнения команды.
266+
267+*CreatePending*, *UpdatePending*, *DeletePending* являются переходными, т.е. развертывание находится в режиме выполнения команды.
268+
269+![Deployment Statuses](../diagrams/images/technical-architecture-deployment-states-fsm.png)
270+
271+<!-- [kubectl]: https://kubernetes.io/docs/reference/kubectl/ -->
272+<!-- [helm]: https://helm.sh -->
273+<!-- [kubedog]: https://github.com/werf/kubedog -->
274+[kube]: https://kubernetes.io
275+[chart]: https://helm.sh/docs/topics/charts/
docs/style.cssnew file+90
@@ -0,0 +1,90 @@
1+body {
2+ background: white;
3+}
4+
5+.actor {
6+ stroke: #ccccff;
7+ fill: #ececff;
8+}
9+
10+text.actor {
11+ fill: black;
12+ stroke: none;
13+ font-family: Helvetica, Helvetica;
14+}
15+
16+.actor-line {
17+ stroke: grey;
18+}
19+
20+.messageLine0 {
21+ stroke-width: 1.5;
22+ stroke-dasharray: '2 2';
23+ marker-end: 'url(#arrowhead)';
24+ stroke: black;
25+}
26+
27+.messageLine1 {
28+ stroke-width: 1.5;
29+ stroke-dasharray: '2 2';
30+ stroke: black;
31+}
32+
33+#arrowhead {
34+ fill: black;
35+}
36+
37+.messageText {
38+ fill: black;
39+ stroke: none;
40+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
41+ font-size: 19px;
42+}
43+
44+.labelBox {
45+ stroke: #ccccff;
46+ fill: #ececff;
47+}
48+
49+.labelText {
50+ fill: black;
51+ stroke: none;
52+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
53+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
54+ font-size: 19px;
55+}
56+
57+.loopText {
58+ fill: black;
59+ stroke: none;
60+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
61+}
62+
63+.loopLine {
64+ stroke-width: 2;
65+ stroke-dasharray: '2 2';
66+ marker-end: 'url(#arrowhead)';
67+ stroke: #ccccff;
68+}
69+
70+.note {
71+ stroke: #decc93;
72+ fill: #fff5ad;
73+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
74+ font-size: 19px;
75+}
76+
77+.noteText {
78+ font-size: 19px;
79+ fill: black;
80+ stroke: none;
81+}
82+
83+.sectionTitle,
84+.noteText,
85+.taskText,
86+.titleText,
87+.taskTextOutsideRight {
88+ font-size: 19px;
89+ font-family: Helvetica, 'trebuchet ms', verdana, arial;
90+}
examples/helm-based-control-scripts/.gitignorenew file+1
@@ -0,0 +1 @@
1+/target
examples/helm-based-control-scripts/Cargo.locknew file+1244
@@ -0,0 +1,1244 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+[[package]]
4+name = "ansi_term"
5+version = "0.11.0"
6+source = "registry+https://github.com/rust-lang/crates.io-index"
7+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
8+dependencies = [
9+ "winapi 0.3.9",
10+]
11+
12+[[package]]
13+name = "atty"
14+version = "0.2.14"
15+source = "registry+https://github.com/rust-lang/crates.io-index"
16+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
17+dependencies = [
18+ "hermit-abi",
19+ "libc",
20+ "winapi 0.3.9",
21+]
22+
23+[[package]]
24+name = "autocfg"
25+version = "1.0.1"
26+source = "registry+https://github.com/rust-lang/crates.io-index"
27+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
28+
29+[[package]]
30+name = "base64"
31+version = "0.12.3"
32+source = "registry+https://github.com/rust-lang/crates.io-index"
33+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
34+
35+[[package]]
36+name = "base64"
37+version = "0.13.0"
38+source = "registry+https://github.com/rust-lang/crates.io-index"
39+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
40+
41+[[package]]
42+name = "bitflags"
43+version = "1.2.1"
44+source = "registry+https://github.com/rust-lang/crates.io-index"
45+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
46+
47+[[package]]
48+name = "bumpalo"
49+version = "3.4.0"
50+source = "registry+https://github.com/rust-lang/crates.io-index"
51+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
52+
53+[[package]]
54+name = "bytes"
55+version = "0.5.6"
56+source = "registry+https://github.com/rust-lang/crates.io-index"
57+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
58+
59+[[package]]
60+name = "cc"
61+version = "1.0.65"
62+source = "registry+https://github.com/rust-lang/crates.io-index"
63+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
64+
65+[[package]]
66+name = "cfg-if"
67+version = "0.1.10"
68+source = "registry+https://github.com/rust-lang/crates.io-index"
69+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
70+
71+[[package]]
72+name = "cfg-if"
73+version = "1.0.0"
74+source = "registry+https://github.com/rust-lang/crates.io-index"
75+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
76+
77+[[package]]
78+name = "clap"
79+version = "2.33.3"
80+source = "registry+https://github.com/rust-lang/crates.io-index"
81+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
82+dependencies = [
83+ "ansi_term",
84+ "atty",
85+ "bitflags",
86+ "strsim",
87+ "textwrap",
88+ "unicode-width",
89+ "vec_map",
90+]
91+
92+[[package]]
93+name = "console_error_panic_hook"
94+version = "0.1.6"
95+source = "registry+https://github.com/rust-lang/crates.io-index"
96+checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
97+dependencies = [
98+ "cfg-if 0.1.10",
99+ "wasm-bindgen",
100+]
101+
102+[[package]]
103+name = "encoding_rs"
104+version = "0.8.26"
105+source = "registry+https://github.com/rust-lang/crates.io-index"
106+checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283"
107+dependencies = [
108+ "cfg-if 1.0.0",
109+]
110+
111+[[package]]
112+name = "fnv"
113+version = "1.0.7"
114+source = "registry+https://github.com/rust-lang/crates.io-index"
115+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
116+
117+[[package]]
118+name = "form_urlencoded"
119+version = "1.0.0"
120+source = "registry+https://github.com/rust-lang/crates.io-index"
121+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
122+dependencies = [
123+ "matches",
124+ "percent-encoding",
125+]
126+
127+[[package]]
128+name = "fuchsia-zircon"
129+version = "0.3.3"
130+source = "registry+https://github.com/rust-lang/crates.io-index"
131+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
132+dependencies = [
133+ "bitflags",
134+ "fuchsia-zircon-sys",
135+]
136+
137+[[package]]
138+name = "fuchsia-zircon-sys"
139+version = "0.3.3"
140+source = "registry+https://github.com/rust-lang/crates.io-index"
141+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
142+
143+[[package]]
144+name = "futures-channel"
145+version = "0.3.8"
146+source = "registry+https://github.com/rust-lang/crates.io-index"
147+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
148+dependencies = [
149+ "futures-core",
150+]
151+
152+[[package]]
153+name = "futures-core"
154+version = "0.3.8"
155+source = "registry+https://github.com/rust-lang/crates.io-index"
156+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
157+
158+[[package]]
159+name = "futures-macro"
160+version = "0.3.8"
161+source = "registry+https://github.com/rust-lang/crates.io-index"
162+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
163+dependencies = [
164+ "proc-macro-hack",
165+ "proc-macro2",
166+ "quote",
167+ "syn",
168+]
169+
170+[[package]]
171+name = "futures-sink"
172+version = "0.3.8"
173+source = "registry+https://github.com/rust-lang/crates.io-index"
174+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
175+
176+[[package]]
177+name = "futures-task"
178+version = "0.3.8"
179+source = "registry+https://github.com/rust-lang/crates.io-index"
180+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
181+dependencies = [
182+ "once_cell",
183+]
184+
185+[[package]]
186+name = "futures-util"
187+version = "0.3.8"
188+source = "registry+https://github.com/rust-lang/crates.io-index"
189+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
190+dependencies = [
191+ "futures-core",
192+ "futures-macro",
193+ "futures-task",
194+ "pin-project 1.0.2",
195+ "pin-utils",
196+ "proc-macro-hack",
197+ "proc-macro-nested",
198+ "slab",
199+]
200+
201+[[package]]
202+name = "getrandom"
203+version = "0.1.15"
204+source = "registry+https://github.com/rust-lang/crates.io-index"
205+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
206+dependencies = [
207+ "cfg-if 0.1.10",
208+ "libc",
209+ "wasi",
210+]
211+
212+[[package]]
213+name = "h2"
214+version = "0.2.7"
215+source = "registry+https://github.com/rust-lang/crates.io-index"
216+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
217+dependencies = [
218+ "bytes",
219+ "fnv",
220+ "futures-core",
221+ "futures-sink",
222+ "futures-util",
223+ "http",
224+ "indexmap",
225+ "slab",
226+ "tokio",
227+ "tokio-util",
228+ "tracing",
229+ "tracing-futures",
230+]
231+
232+[[package]]
233+name = "hashbrown"
234+version = "0.9.1"
235+source = "registry+https://github.com/rust-lang/crates.io-index"
236+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
237+
238+[[package]]
239+name = "helm-based-control-scripts"
240+version = "0.1.0"
241+dependencies = [
242+ "clap",
243+ "rand",
244+ "reqwest",
245+ "serde",
246+ "serde_derive",
247+ "serde_json",
248+ "tokio",
249+]
250+
251+[[package]]
252+name = "hermit-abi"
253+version = "0.1.17"
254+source = "registry+https://github.com/rust-lang/crates.io-index"
255+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
256+dependencies = [
257+ "libc",
258+]
259+
260+[[package]]
261+name = "http"
262+version = "0.2.1"
263+source = "registry+https://github.com/rust-lang/crates.io-index"
264+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
265+dependencies = [
266+ "bytes",
267+ "fnv",
268+ "itoa",
269+]
270+
271+[[package]]
272+name = "http-body"
273+version = "0.3.1"
274+source = "registry+https://github.com/rust-lang/crates.io-index"
275+checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
276+dependencies = [
277+ "bytes",
278+ "http",
279+]
280+
281+[[package]]
282+name = "httparse"
283+version = "1.3.4"
284+source = "registry+https://github.com/rust-lang/crates.io-index"
285+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
286+
287+[[package]]
288+name = "httpdate"
289+version = "0.3.2"
290+source = "registry+https://github.com/rust-lang/crates.io-index"
291+checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
292+
293+[[package]]
294+name = "hyper"
295+version = "0.13.9"
296+source = "registry+https://github.com/rust-lang/crates.io-index"
297+checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf"
298+dependencies = [
299+ "bytes",
300+ "futures-channel",
301+ "futures-core",
302+ "futures-util",
303+ "h2",
304+ "http",
305+ "http-body",
306+ "httparse",
307+ "httpdate",
308+ "itoa",
309+ "pin-project 1.0.2",
310+ "socket2",
311+ "tokio",
312+ "tower-service",
313+ "tracing",
314+ "want",
315+]
316+
317+[[package]]
318+name = "hyper-rustls"
319+version = "0.21.0"
320+source = "registry+https://github.com/rust-lang/crates.io-index"
321+checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6"
322+dependencies = [
323+ "bytes",
324+ "futures-util",
325+ "hyper",
326+ "log",
327+ "rustls",
328+ "tokio",
329+ "tokio-rustls",
330+ "webpki",
331+]
332+
333+[[package]]
334+name = "idna"
335+version = "0.2.0"
336+source = "registry+https://github.com/rust-lang/crates.io-index"
337+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
338+dependencies = [
339+ "matches",
340+ "unicode-bidi",
341+ "unicode-normalization",
342+]
343+
344+[[package]]
345+name = "indexmap"
346+version = "1.6.0"
347+source = "registry+https://github.com/rust-lang/crates.io-index"
348+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
349+dependencies = [
350+ "autocfg",
351+ "hashbrown",
352+]
353+
354+[[package]]
355+name = "iovec"
356+version = "0.1.4"
357+source = "registry+https://github.com/rust-lang/crates.io-index"
358+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
359+dependencies = [
360+ "libc",
361+]
362+
363+[[package]]
364+name = "ipnet"
365+version = "2.3.0"
366+source = "registry+https://github.com/rust-lang/crates.io-index"
367+checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
368+
369+[[package]]
370+name = "itoa"
371+version = "0.4.6"
372+source = "registry+https://github.com/rust-lang/crates.io-index"
373+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
374+
375+[[package]]
376+name = "js-sys"
377+version = "0.3.46"
378+source = "registry+https://github.com/rust-lang/crates.io-index"
379+checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
380+dependencies = [
381+ "wasm-bindgen",
382+]
383+
384+[[package]]
385+name = "kernel32-sys"
386+version = "0.2.2"
387+source = "registry+https://github.com/rust-lang/crates.io-index"
388+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
389+dependencies = [
390+ "winapi 0.2.8",
391+ "winapi-build",
392+]
393+
394+[[package]]
395+name = "lazy_static"
396+version = "1.4.0"
397+source = "registry+https://github.com/rust-lang/crates.io-index"
398+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
399+
400+[[package]]
401+name = "libc"
402+version = "0.2.80"
403+source = "registry+https://github.com/rust-lang/crates.io-index"
404+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
405+
406+[[package]]
407+name = "log"
408+version = "0.4.11"
409+source = "registry+https://github.com/rust-lang/crates.io-index"
410+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
411+dependencies = [
412+ "cfg-if 0.1.10",
413+]
414+
415+[[package]]
416+name = "matches"
417+version = "0.1.8"
418+source = "registry+https://github.com/rust-lang/crates.io-index"
419+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
420+
421+[[package]]
422+name = "memchr"
423+version = "2.3.4"
424+source = "registry+https://github.com/rust-lang/crates.io-index"
425+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
426+
427+[[package]]
428+name = "mime"
429+version = "0.3.16"
430+source = "registry+https://github.com/rust-lang/crates.io-index"
431+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
432+
433+[[package]]
434+name = "mime_guess"
435+version = "2.0.3"
436+source = "registry+https://github.com/rust-lang/crates.io-index"
437+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
438+dependencies = [
439+ "mime",
440+ "unicase",
441+]
442+
443+[[package]]
444+name = "mio"
445+version = "0.6.23"
446+source = "registry+https://github.com/rust-lang/crates.io-index"
447+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
448+dependencies = [
449+ "cfg-if 0.1.10",
450+ "fuchsia-zircon",
451+ "fuchsia-zircon-sys",
452+ "iovec",
453+ "kernel32-sys",
454+ "libc",
455+ "log",
456+ "miow 0.2.2",
457+ "net2",
458+ "slab",
459+ "winapi 0.2.8",
460+]
461+
462+[[package]]
463+name = "mio-named-pipes"
464+version = "0.1.7"
465+source = "registry+https://github.com/rust-lang/crates.io-index"
466+checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
467+dependencies = [
468+ "log",
469+ "mio",
470+ "miow 0.3.6",
471+ "winapi 0.3.9",
472+]
473+
474+[[package]]
475+name = "mio-uds"
476+version = "0.6.8"
477+source = "registry+https://github.com/rust-lang/crates.io-index"
478+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
479+dependencies = [
480+ "iovec",
481+ "libc",
482+ "mio",
483+]
484+
485+[[package]]
486+name = "miow"
487+version = "0.2.2"
488+source = "registry+https://github.com/rust-lang/crates.io-index"
489+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
490+dependencies = [
491+ "kernel32-sys",
492+ "net2",
493+ "winapi 0.2.8",
494+ "ws2_32-sys",
495+]
496+
497+[[package]]
498+name = "miow"
499+version = "0.3.6"
500+source = "registry+https://github.com/rust-lang/crates.io-index"
501+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
502+dependencies = [
503+ "socket2",
504+ "winapi 0.3.9",
505+]
506+
507+[[package]]
508+name = "net2"
509+version = "0.2.36"
510+source = "registry+https://github.com/rust-lang/crates.io-index"
511+checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
512+dependencies = [
513+ "cfg-if 0.1.10",
514+ "libc",
515+ "winapi 0.3.9",
516+]
517+
518+[[package]]
519+name = "num_cpus"
520+version = "1.13.0"
521+source = "registry+https://github.com/rust-lang/crates.io-index"
522+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
523+dependencies = [
524+ "hermit-abi",
525+ "libc",
526+]
527+
528+[[package]]
529+name = "once_cell"
530+version = "1.5.2"
531+source = "registry+https://github.com/rust-lang/crates.io-index"
532+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
533+
534+[[package]]
535+name = "percent-encoding"
536+version = "2.1.0"
537+source = "registry+https://github.com/rust-lang/crates.io-index"
538+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
539+
540+[[package]]
541+name = "pin-project"
542+version = "0.4.27"
543+source = "registry+https://github.com/rust-lang/crates.io-index"
544+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
545+dependencies = [
546+ "pin-project-internal 0.4.27",
547+]
548+
549+[[package]]
550+name = "pin-project"
551+version = "1.0.2"
552+source = "registry+https://github.com/rust-lang/crates.io-index"
553+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
554+dependencies = [
555+ "pin-project-internal 1.0.2",
556+]
557+
558+[[package]]
559+name = "pin-project-internal"
560+version = "0.4.27"
561+source = "registry+https://github.com/rust-lang/crates.io-index"
562+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
563+dependencies = [
564+ "proc-macro2",
565+ "quote",
566+ "syn",
567+]
568+
569+[[package]]
570+name = "pin-project-internal"
571+version = "1.0.2"
572+source = "registry+https://github.com/rust-lang/crates.io-index"
573+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
574+dependencies = [
575+ "proc-macro2",
576+ "quote",
577+ "syn",
578+]
579+
580+[[package]]
581+name = "pin-project-lite"
582+version = "0.1.11"
583+source = "registry+https://github.com/rust-lang/crates.io-index"
584+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
585+
586+[[package]]
587+name = "pin-project-lite"
588+version = "0.2.0"
589+source = "registry+https://github.com/rust-lang/crates.io-index"
590+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
591+
592+[[package]]
593+name = "pin-utils"
594+version = "0.1.0"
595+source = "registry+https://github.com/rust-lang/crates.io-index"
596+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
597+
598+[[package]]
599+name = "ppv-lite86"
600+version = "0.2.10"
601+source = "registry+https://github.com/rust-lang/crates.io-index"
602+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
603+
604+[[package]]
605+name = "proc-macro-hack"
606+version = "0.5.19"
607+source = "registry+https://github.com/rust-lang/crates.io-index"
608+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
609+
610+[[package]]
611+name = "proc-macro-nested"
612+version = "0.1.6"
613+source = "registry+https://github.com/rust-lang/crates.io-index"
614+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
615+
616+[[package]]
617+name = "proc-macro2"
618+version = "1.0.24"
619+source = "registry+https://github.com/rust-lang/crates.io-index"
620+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
621+dependencies = [
622+ "unicode-xid",
623+]
624+
625+[[package]]
626+name = "quote"
627+version = "1.0.7"
628+source = "registry+https://github.com/rust-lang/crates.io-index"
629+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
630+dependencies = [
631+ "proc-macro2",
632+]
633+
634+[[package]]
635+name = "rand"
636+version = "0.7.3"
637+source = "registry+https://github.com/rust-lang/crates.io-index"
638+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
639+dependencies = [
640+ "getrandom",
641+ "libc",
642+ "rand_chacha",
643+ "rand_core",
644+ "rand_hc",
645+]
646+
647+[[package]]
648+name = "rand_chacha"
649+version = "0.2.2"
650+source = "registry+https://github.com/rust-lang/crates.io-index"
651+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
652+dependencies = [
653+ "ppv-lite86",
654+ "rand_core",
655+]
656+
657+[[package]]
658+name = "rand_core"
659+version = "0.5.1"
660+source = "registry+https://github.com/rust-lang/crates.io-index"
661+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
662+dependencies = [
663+ "getrandom",
664+]
665+
666+[[package]]
667+name = "rand_hc"
668+version = "0.2.0"
669+source = "registry+https://github.com/rust-lang/crates.io-index"
670+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
671+dependencies = [
672+ "rand_core",
673+]
674+
675+[[package]]
676+name = "redox_syscall"
677+version = "0.1.57"
678+source = "registry+https://github.com/rust-lang/crates.io-index"
679+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
680+
681+[[package]]
682+name = "reqwest"
683+version = "0.10.9"
684+source = "registry+https://github.com/rust-lang/crates.io-index"
685+checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
686+dependencies = [
687+ "base64 0.13.0",
688+ "bytes",
689+ "encoding_rs",
690+ "futures-core",
691+ "futures-util",
692+ "http",
693+ "http-body",
694+ "hyper",
695+ "hyper-rustls",
696+ "ipnet",
697+ "js-sys",
698+ "lazy_static",
699+ "log",
700+ "mime",
701+ "mime_guess",
702+ "percent-encoding",
703+ "pin-project-lite 0.2.0",
704+ "rustls",
705+ "serde",
706+ "serde_json",
707+ "serde_urlencoded",
708+ "tokio",
709+ "tokio-rustls",
710+ "url",
711+ "wasm-bindgen",
712+ "wasm-bindgen-futures",
713+ "wasm-bindgen-test",
714+ "web-sys",
715+ "webpki-roots",
716+ "winreg",
717+]
718+
719+[[package]]
720+name = "ring"
721+version = "0.16.19"
722+source = "registry+https://github.com/rust-lang/crates.io-index"
723+checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
724+dependencies = [
725+ "cc",
726+ "libc",
727+ "once_cell",
728+ "spin",
729+ "untrusted",
730+ "web-sys",
731+ "winapi 0.3.9",
732+]
733+
734+[[package]]
735+name = "rustls"
736+version = "0.18.1"
737+source = "registry+https://github.com/rust-lang/crates.io-index"
738+checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
739+dependencies = [
740+ "base64 0.12.3",
741+ "log",
742+ "ring",
743+ "sct",
744+ "webpki",
745+]
746+
747+[[package]]
748+name = "ryu"
749+version = "1.0.5"
750+source = "registry+https://github.com/rust-lang/crates.io-index"
751+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
752+
753+[[package]]
754+name = "scoped-tls"
755+version = "1.0.0"
756+source = "registry+https://github.com/rust-lang/crates.io-index"
757+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
758+
759+[[package]]
760+name = "sct"
761+version = "0.6.0"
762+source = "registry+https://github.com/rust-lang/crates.io-index"
763+checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
764+dependencies = [
765+ "ring",
766+ "untrusted",
767+]
768+
769+[[package]]
770+name = "serde"
771+version = "1.0.117"
772+source = "registry+https://github.com/rust-lang/crates.io-index"
773+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
774+
775+[[package]]
776+name = "serde_derive"
777+version = "1.0.117"
778+source = "registry+https://github.com/rust-lang/crates.io-index"
779+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
780+dependencies = [
781+ "proc-macro2",
782+ "quote",
783+ "syn",
784+]
785+
786+[[package]]
787+name = "serde_json"
788+version = "1.0.60"
789+source = "registry+https://github.com/rust-lang/crates.io-index"
790+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
791+dependencies = [
792+ "itoa",
793+ "ryu",
794+ "serde",
795+]
796+
797+[[package]]
798+name = "serde_urlencoded"
799+version = "0.7.0"
800+source = "registry+https://github.com/rust-lang/crates.io-index"
801+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
802+dependencies = [
803+ "form_urlencoded",
804+ "itoa",
805+ "ryu",
806+ "serde",
807+]
808+
809+[[package]]
810+name = "signal-hook-registry"
811+version = "1.2.2"
812+source = "registry+https://github.com/rust-lang/crates.io-index"
813+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
814+dependencies = [
815+ "libc",
816+]
817+
818+[[package]]
819+name = "slab"
820+version = "0.4.2"
821+source = "registry+https://github.com/rust-lang/crates.io-index"
822+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
823+
824+[[package]]
825+name = "socket2"
826+version = "0.3.17"
827+source = "registry+https://github.com/rust-lang/crates.io-index"
828+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
829+dependencies = [
830+ "cfg-if 1.0.0",
831+ "libc",
832+ "redox_syscall",
833+ "winapi 0.3.9",
834+]
835+
836+[[package]]
837+name = "spin"
838+version = "0.5.2"
839+source = "registry+https://github.com/rust-lang/crates.io-index"
840+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
841+
842+[[package]]
843+name = "strsim"
844+version = "0.8.0"
845+source = "registry+https://github.com/rust-lang/crates.io-index"
846+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
847+
848+[[package]]
849+name = "syn"
850+version = "1.0.53"
851+source = "registry+https://github.com/rust-lang/crates.io-index"
852+checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
853+dependencies = [
854+ "proc-macro2",
855+ "quote",
856+ "unicode-xid",
857+]
858+
859+[[package]]
860+name = "textwrap"
861+version = "0.11.0"
862+source = "registry+https://github.com/rust-lang/crates.io-index"
863+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
864+dependencies = [
865+ "unicode-width",
866+]
867+
868+[[package]]
869+name = "tinyvec"
870+version = "1.1.0"
871+source = "registry+https://github.com/rust-lang/crates.io-index"
872+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
873+dependencies = [
874+ "tinyvec_macros",
875+]
876+
877+[[package]]
878+name = "tinyvec_macros"
879+version = "0.1.0"
880+source = "registry+https://github.com/rust-lang/crates.io-index"
881+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
882+
883+[[package]]
884+name = "tokio"
885+version = "0.2.23"
886+source = "registry+https://github.com/rust-lang/crates.io-index"
887+checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff"
888+dependencies = [
889+ "bytes",
890+ "fnv",
891+ "futures-core",
892+ "iovec",
893+ "lazy_static",
894+ "libc",
895+ "memchr",
896+ "mio",
897+ "mio-named-pipes",
898+ "mio-uds",
899+ "num_cpus",
900+ "pin-project-lite 0.1.11",
901+ "signal-hook-registry",
902+ "slab",
903+ "tokio-macros",
904+ "winapi 0.3.9",
905+]
906+
907+[[package]]
908+name = "tokio-macros"
909+version = "0.2.6"
910+source = "registry+https://github.com/rust-lang/crates.io-index"
911+checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
912+dependencies = [
913+ "proc-macro2",
914+ "quote",
915+ "syn",
916+]
917+
918+[[package]]
919+name = "tokio-rustls"
920+version = "0.14.1"
921+source = "registry+https://github.com/rust-lang/crates.io-index"
922+checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
923+dependencies = [
924+ "futures-core",
925+ "rustls",
926+ "tokio",
927+ "webpki",
928+]
929+
930+[[package]]
931+name = "tokio-util"
932+version = "0.3.1"
933+source = "registry+https://github.com/rust-lang/crates.io-index"
934+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
935+dependencies = [
936+ "bytes",
937+ "futures-core",
938+ "futures-sink",
939+ "log",
940+ "pin-project-lite 0.1.11",
941+ "tokio",
942+]
943+
944+[[package]]
945+name = "tower-service"
946+version = "0.3.0"
947+source = "registry+https://github.com/rust-lang/crates.io-index"
948+checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
949+
950+[[package]]
951+name = "tracing"
952+version = "0.1.22"
953+source = "registry+https://github.com/rust-lang/crates.io-index"
954+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
955+dependencies = [
956+ "cfg-if 1.0.0",
957+ "log",
958+ "pin-project-lite 0.2.0",
959+ "tracing-core",
960+]
961+
962+[[package]]
963+name = "tracing-core"
964+version = "0.1.17"
965+source = "registry+https://github.com/rust-lang/crates.io-index"
966+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
967+dependencies = [
968+ "lazy_static",
969+]
970+
971+[[package]]
972+name = "tracing-futures"
973+version = "0.2.4"
974+source = "registry+https://github.com/rust-lang/crates.io-index"
975+checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c"
976+dependencies = [
977+ "pin-project 0.4.27",
978+ "tracing",
979+]
980+
981+[[package]]
982+name = "try-lock"
983+version = "0.2.3"
984+source = "registry+https://github.com/rust-lang/crates.io-index"
985+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
986+
987+[[package]]
988+name = "unicase"
989+version = "2.6.0"
990+source = "registry+https://github.com/rust-lang/crates.io-index"
991+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
992+dependencies = [
993+ "version_check",
994+]
995+
996+[[package]]
997+name = "unicode-bidi"
998+version = "0.3.4"
999+source = "registry+https://github.com/rust-lang/crates.io-index"
1000+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
1001+dependencies = [
1002+ "matches",
1003+]
1004+
1005+[[package]]
1006+name = "unicode-normalization"
1007+version = "0.1.16"
1008+source = "registry+https://github.com/rust-lang/crates.io-index"
1009+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
1010+dependencies = [
1011+ "tinyvec",
1012+]
1013+
1014+[[package]]
1015+name = "unicode-width"
1016+version = "0.1.8"
1017+source = "registry+https://github.com/rust-lang/crates.io-index"
1018+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1019+
1020+[[package]]
1021+name = "unicode-xid"
1022+version = "0.2.1"
1023+source = "registry+https://github.com/rust-lang/crates.io-index"
1024+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1025+
1026+[[package]]
1027+name = "untrusted"
1028+version = "0.7.1"
1029+source = "registry+https://github.com/rust-lang/crates.io-index"
1030+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
1031+
1032+[[package]]
1033+name = "url"
1034+version = "2.2.0"
1035+source = "registry+https://github.com/rust-lang/crates.io-index"
1036+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
1037+dependencies = [
1038+ "form_urlencoded",
1039+ "idna",
1040+ "matches",
1041+ "percent-encoding",
1042+]
1043+
1044+[[package]]
1045+name = "vec_map"
1046+version = "0.8.2"
1047+source = "registry+https://github.com/rust-lang/crates.io-index"
1048+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1049+
1050+[[package]]
1051+name = "version_check"
1052+version = "0.9.2"
1053+source = "registry+https://github.com/rust-lang/crates.io-index"
1054+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1055+
1056+[[package]]
1057+name = "want"
1058+version = "0.3.0"
1059+source = "registry+https://github.com/rust-lang/crates.io-index"
1060+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
1061+dependencies = [
1062+ "log",
1063+ "try-lock",
1064+]
1065+
1066+[[package]]
1067+name = "wasi"
1068+version = "0.9.0+wasi-snapshot-preview1"
1069+source = "registry+https://github.com/rust-lang/crates.io-index"
1070+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1071+
1072+[[package]]
1073+name = "wasm-bindgen"
1074+version = "0.2.69"
1075+source = "registry+https://github.com/rust-lang/crates.io-index"
1076+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
1077+dependencies = [
1078+ "cfg-if 1.0.0",
1079+ "serde",
1080+ "serde_json",
1081+ "wasm-bindgen-macro",
1082+]
1083+
1084+[[package]]
1085+name = "wasm-bindgen-backend"
1086+version = "0.2.69"
1087+source = "registry+https://github.com/rust-lang/crates.io-index"
1088+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
1089+dependencies = [
1090+ "bumpalo",
1091+ "lazy_static",
1092+ "log",
1093+ "proc-macro2",
1094+ "quote",
1095+ "syn",
1096+ "wasm-bindgen-shared",
1097+]
1098+
1099+[[package]]
1100+name = "wasm-bindgen-futures"
1101+version = "0.4.19"
1102+source = "registry+https://github.com/rust-lang/crates.io-index"
1103+checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
1104+dependencies = [
1105+ "cfg-if 1.0.0",
1106+ "js-sys",
1107+ "wasm-bindgen",
1108+ "web-sys",
1109+]
1110+
1111+[[package]]
1112+name = "wasm-bindgen-macro"
1113+version = "0.2.69"
1114+source = "registry+https://github.com/rust-lang/crates.io-index"
1115+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
1116+dependencies = [
1117+ "quote",
1118+ "wasm-bindgen-macro-support",
1119+]
1120+
1121+[[package]]
1122+name = "wasm-bindgen-macro-support"
1123+version = "0.2.69"
1124+source = "registry+https://github.com/rust-lang/crates.io-index"
1125+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
1126+dependencies = [
1127+ "proc-macro2",
1128+ "quote",
1129+ "syn",
1130+ "wasm-bindgen-backend",
1131+ "wasm-bindgen-shared",
1132+]
1133+
1134+[[package]]
1135+name = "wasm-bindgen-shared"
1136+version = "0.2.69"
1137+source = "registry+https://github.com/rust-lang/crates.io-index"
1138+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
1139+
1140+[[package]]
1141+name = "wasm-bindgen-test"
1142+version = "0.3.19"
1143+source = "registry+https://github.com/rust-lang/crates.io-index"
1144+checksum = "0355fa0c1f9b792a09b6dcb6a8be24d51e71e6d74972f9eb4a44c4c004d24a25"
1145+dependencies = [
1146+ "console_error_panic_hook",
1147+ "js-sys",
1148+ "scoped-tls",
1149+ "wasm-bindgen",
1150+ "wasm-bindgen-futures",
1151+ "wasm-bindgen-test-macro",
1152+]
1153+
1154+[[package]]
1155+name = "wasm-bindgen-test-macro"
1156+version = "0.3.19"
1157+source = "registry+https://github.com/rust-lang/crates.io-index"
1158+checksum = "27e07b46b98024c2ba2f9e83a10c2ef0515f057f2da299c1762a2017de80438b"
1159+dependencies = [
1160+ "proc-macro2",
1161+ "quote",
1162+]
1163+
1164+[[package]]
1165+name = "web-sys"
1166+version = "0.3.46"
1167+source = "registry+https://github.com/rust-lang/crates.io-index"
1168+checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
1169+dependencies = [
1170+ "js-sys",
1171+ "wasm-bindgen",
1172+]
1173+
1174+[[package]]
1175+name = "webpki"
1176+version = "0.21.4"
1177+source = "registry+https://github.com/rust-lang/crates.io-index"
1178+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
1179+dependencies = [
1180+ "ring",
1181+ "untrusted",
1182+]
1183+
1184+[[package]]
1185+name = "webpki-roots"
1186+version = "0.20.0"
1187+source = "registry+https://github.com/rust-lang/crates.io-index"
1188+checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
1189+dependencies = [
1190+ "webpki",
1191+]
1192+
1193+[[package]]
1194+name = "winapi"
1195+version = "0.2.8"
1196+source = "registry+https://github.com/rust-lang/crates.io-index"
1197+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
1198+
1199+[[package]]
1200+name = "winapi"
1201+version = "0.3.9"
1202+source = "registry+https://github.com/rust-lang/crates.io-index"
1203+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1204+dependencies = [
1205+ "winapi-i686-pc-windows-gnu",
1206+ "winapi-x86_64-pc-windows-gnu",
1207+]
1208+
1209+[[package]]
1210+name = "winapi-build"
1211+version = "0.1.1"
1212+source = "registry+https://github.com/rust-lang/crates.io-index"
1213+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
1214+
1215+[[package]]
1216+name = "winapi-i686-pc-windows-gnu"
1217+version = "0.4.0"
1218+source = "registry+https://github.com/rust-lang/crates.io-index"
1219+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1220+
1221+[[package]]
1222+name = "winapi-x86_64-pc-windows-gnu"
1223+version = "0.4.0"
1224+source = "registry+https://github.com/rust-lang/crates.io-index"
1225+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1226+
1227+[[package]]
1228+name = "winreg"
1229+version = "0.7.0"
1230+source = "registry+https://github.com/rust-lang/crates.io-index"
1231+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
1232+dependencies = [
1233+ "winapi 0.3.9",
1234+]
1235+
1236+[[package]]
1237+name = "ws2_32-sys"
1238+version = "0.2.1"
1239+source = "registry+https://github.com/rust-lang/crates.io-index"
1240+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
1241+dependencies = [
1242+ "winapi 0.2.8",
1243+ "winapi-build",
1244+]
examples/helm-based-control-scripts/Cargo.tomlnew file+46
@@ -0,0 +1,46 @@
1+[package]
2+name = "helm-based-control-scripts"
3+version = "0.1.0"
4+authors = ["Typeable LLC <octopod@typeable.io>"]
5+edition = "2018"
6+
7+[dependencies]
8+clap = "2.33.3"
9+rand = "0.7.3"
10+serde = "1.0.117"
11+serde_derive = "1.0.117"
12+serde_json = "1.0.60"
13+tokio = { version = "0.2.22", default_features = false, features = ["full"] }
14+reqwest = { version = "0.10.9", default_features = false, features = ["json", "rustls-tls"] }
15+
16+[[bin]]
17+name = "create"
18+path = "src/bin/create.rs"
19+
20+[[bin]]
21+name = "archive"
22+path = "src/bin/archive.rs"
23+
24+[[bin]]
25+name = "check"
26+path = "src/bin/check.rs"
27+
28+[[bin]]
29+name = "cleanup"
30+path = "src/bin/cleanup.rs"
31+
32+[[bin]]
33+name = "archive_check"
34+path = "src/bin/archive_check.rs"
35+
36+[[bin]]
37+name = "tag_check"
38+path = "src/bin/tag_check.rs"
39+
40+[[bin]]
41+name = "init"
42+path = "src/bin/init.rs"
43+
44+[[bin]]
45+name = "info"
46+path = "src/bin/info.rs"
examples/helm-based-control-scripts/README.mdnew file+9
@@ -0,0 +1,9 @@
1+# helm-based-control-scripts
2+
3+An Example of Helm-based control scripts.
4+
5+## Build Docker image
6+
7+```bash
8+docker build -f docker/Dockerfile -t typeable/octopod-helm-example:1.0 .
9+```
examples/helm-based-control-scripts/docker/Dockerfilenew file+29
@@ -0,0 +1,29 @@
1+FROM ekidd/rust-musl-builder:1.46.0 as builder
2+
3+RUN sudo mkdir /app && \
4+ sudo chown -R rust:rust /app
5+WORKDIR /app
6+RUN USER=root cargo new helm-based-control-scripts
7+COPY Cargo.toml /app/helm-based-control-scripts/
8+COPY Cargo.lock /app/helm-based-control-scripts/
9+COPY src /app/helm-based-control-scripts/src
10+WORKDIR /app/helm-based-control-scripts
11+RUN cargo build --release
12+
13+FROM typeable/kube-things as base-utils
14+
15+FROM busybox
16+
17+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/create /utils/create
18+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/create /utils/update
19+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/archive /utils/archive
20+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/check /utils/check
21+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/cleanup /utils/cleanup
22+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/archive_check /utils/archive_check
23+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/tag_check /utils/tag_check
24+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/init /utils/init
25+COPY --from=builder /app/helm-based-control-scripts/target/x86_64-unknown-linux-musl/release/info /utils/info
26+
27+COPY --from=base-utils /utils/kubectl /utils/kubectl
28+COPY --from=base-utils /utils/helm2 /utils/helm
29+COPY --from=base-utils /utils/kubedog /utils/kubedog
examples/helm-based-control-scripts/src/bin/archive.rsnew file+59
@@ -0,0 +1,59 @@
1+use clap::{App, Arg};
2+use std::process::{exit, Command};
3+
4+fn main() -> std::io::Result<()> {
5+ let matches = App::new("archive")
6+ .version("0.1")
7+ .arg(
8+ Arg::with_name("project-name")
9+ .long("project-name")
10+ .short("p")
11+ .required(true)
12+ .takes_value(true),
13+ )
14+ .arg(
15+ Arg::with_name("base-domain")
16+ .long("base-domain")
17+ .short("d")
18+ .required(true)
19+ .takes_value(true),
20+ )
21+ .arg(
22+ Arg::with_name("namespace")
23+ .long("namespace")
24+ .short("s")
25+ .required(true)
26+ .takes_value(true),
27+ )
28+ .arg(
29+ Arg::with_name("name")
30+ .long("name")
31+ .short("n")
32+ .required(true)
33+ .takes_value(true),
34+ )
35+ .get_matches();
36+
37+ let _project_name = matches
38+ .value_of("project-name")
39+ .expect("could not get project-name");
40+ let _base_domain = matches
41+ .value_of("base-domain")
42+ .expect("could not get base-domain");
43+ let _namespace = matches
44+ .value_of("namespace")
45+ .expect("could not get namepace");
46+ let name = matches.value_of("name").expect("could not get name");
47+
48+ let output = Command::new("helm")
49+ .args(&["delete", &format!("app-{}", name), "--purge"])
50+ .output()
51+ .expect("could not delete app");
52+ let success = output.status.success();
53+
54+ if !success {
55+ exit(1)
56+ }
57+
58+ Ok(())
59+}
examples/helm-based-control-scripts/src/bin/archive_check.rsnew file+59
@@ -0,0 +1,59 @@
1+use clap::{App, Arg};
2+use std::process::{exit, Command};
3+
4+fn main() -> std::io::Result<()> {
5+ let matches = App::new("archive_check")
6+ .version("0.1")
7+ .arg(
8+ Arg::with_name("project-name")
9+ .long("project-name")
10+ .short("p")
11+ .required(true)
12+ .takes_value(true),
13+ )
14+ .arg(
15+ Arg::with_name("base-domain")
16+ .long("base-domain")
17+ .short("d")
18+ .required(true)
19+ .takes_value(true),
20+ )
21+ .arg(
22+ Arg::with_name("namespace")
23+ .long("namespace")
24+ .short("s")
25+ .required(true)
26+ .takes_value(true),
27+ )
28+ .arg(
29+ Arg::with_name("name")
30+ .long("name")
31+ .short("n")
32+ .required(true)
33+ .takes_value(true),
34+ )
35+ .get_matches();
36+
37+ let _project_name = matches
38+ .value_of("project-name")
39+ .expect("could not get project-name");
40+ let _base_domain = matches
41+ .value_of("base-domain")
42+ .expect("could not get base-domain");
43+ let _namespace = matches
44+ .value_of("namespace")
45+ .expect("could not get namepace");
46+ let name = matches.value_of("name").expect("could not get name");
47+
48+ let output = Command::new("helm")
49+ .args(&["status", name])
50+ .output()
51+ .expect("could not get status");
52+ let success = output.status.success();
53+
54+ if success {
55+ exit(1)
56+ }
57+
58+ Ok(())
59+}
examples/helm-based-control-scripts/src/bin/check.rsnew file+78
@@ -0,0 +1,78 @@
1+use clap::{App, Arg};
2+use serde_json::json;
3+use std::io::Write;
4+use std::process::{exit, Command, Stdio};
5+
6+const KUBEDOG_TIMEOUT: usize = 3;
7+
8+fn main() -> std::io::Result<()> {
9+ let matches = App::new("check")
10+ .version("0.1")
11+ .arg(
12+ Arg::with_name("project-name")
13+ .long("project-name")
14+ .short("p")
15+ .required(true)
16+ .takes_value(true),
17+ )
18+ .arg(
19+ Arg::with_name("base-domain")
20+ .long("base-domain")
21+ .short("d")
22+ .required(true)
23+ .takes_value(true),
24+ )
25+ .arg(
26+ Arg::with_name("namespace")
27+ .long("namespace")
28+ .short("s")
29+ .required(true)
30+ .takes_value(true),
31+ )
32+ .arg(
33+ Arg::with_name("name")
34+ .long("name")
35+ .short("n")
36+ .required(true)
37+ .takes_value(true),
38+ )
39+ .get_matches();
40+
41+ let _project_name = matches
42+ .value_of("project-name")
43+ .expect("could not get project-name");
44+ let _base_domain = matches
45+ .value_of("base-domain")
46+ .expect("could not get base-domain");
47+ let namespace = matches
48+ .value_of("namespace")
49+ .expect("could not get namepace");
50+ let name = matches.value_of("name").expect("could not get name");
51+
52+ let kubedog_stdin = json!({
53+ "Deployments": [{"ResourceName": format!("app-{}", name), "Namespace": namespace}]
54+ })
55+ .to_string();
56+
57+ let mut child = Command::new("kubedog")
58+ .args(&["multitrack", "-t", &KUBEDOG_TIMEOUT.to_string()])
59+ .stdin(Stdio::piped())
60+ .spawn()
61+ .expect("failed to call kubedog");
62+
63+ {
64+ let stdin = child.stdin.as_mut().expect("failed to open stdin");
65+ stdin
66+ .write_all(kubedog_stdin.as_bytes())
67+ .expect("failed to write to stdin");
68+ }
69+
70+ let output = child.wait_with_output().expect("failed to read stdout");
71+ let success = output.status.success();
72+
73+ if !success {
74+ exit(1)
75+ }
76+
77+ Ok(())
78+}
examples/helm-based-control-scripts/src/bin/cleanup.rsnew file+50
@@ -0,0 +1,50 @@
1+use clap::{App, Arg};
2+
3+fn main() -> std::io::Result<()> {
4+ let matches = App::new("cleanup")
5+ .version("0.1")
6+ .arg(
7+ Arg::with_name("project-name")
8+ .long("project-name")
9+ .short("p")
10+ .required(true)
11+ .takes_value(true),
12+ )
13+ .arg(
14+ Arg::with_name("base-domain")
15+ .long("base-domain")
16+ .short("d")
17+ .required(true)
18+ .takes_value(true),
19+ )
20+ .arg(
21+ Arg::with_name("namespace")
22+ .long("namespace")
23+ .short("s")
24+ .required(true)
25+ .takes_value(true),
26+ )
27+ .arg(
28+ Arg::with_name("name")
29+ .long("name")
30+ .short("n")
31+ .required(true)
32+ .takes_value(true),
33+ )
34+ .get_matches();
35+
36+ let _project_name = matches
37+ .value_of("project-name")
38+ .expect("could not get project-name");
39+ let _base_domain = matches
40+ .value_of("base-domain")
41+ .expect("could not get base-domain");
42+ let _namespace = matches
43+ .value_of("namespace")
44+ .expect("could not get namepace");
45+ let _name = matches.value_of("name").expect("could not get name");
46+
47+ // nop
48+
49+ Ok(())
50+}
examples/helm-based-control-scripts/src/bin/create.rsnew file+237
@@ -0,0 +1,237 @@
1+use clap::{App, Arg};
2+use rand::{
3+ distributions::{Alphanumeric, Distribution},
4+ thread_rng,
5+};
6+use std::convert::TryFrom;
7+use std::convert::TryInto;
8+use std::fs;
9+use std::path::Path;
10+use std::path::PathBuf;
11+use std::process::{exit, Command};
12+
13+static REGISTRY: &str = "typeable";
14+static IMAGE: &str = "octopod-web-app-example";
15+static GIT_REPOSITORY: &str = "https://github.com/typeable/octopod.git";
16+
17+struct TmpDirGuard(PathBuf);
18+
19+impl TmpDirGuard {
20+ pub fn new(tmp_dir: &PathBuf) -> Self {
21+ TmpDirGuard(tmp_dir.clone())
22+ }
23+}
24+
25+impl Drop for TmpDirGuard {
26+ fn drop(&mut self) {
27+ match fs::remove_dir_all(&self.0) {
28+ Ok(_) => (),
29+ Err(err) => eprintln!("remove_dir_all error: {:?}", err),
30+ };
31+ }
32+}
33+
34+#[derive(Debug, Clone)]
35+struct Override {
36+ pub key: String,
37+ pub value: String,
38+}
39+
40+impl TryFrom<&str> for Override {
41+ type Error = &'static str;
42+ fn try_from(s: &str) -> Result<Self, Self::Error> {
43+ let parts = s.split('=').collect::<Vec<_>>();
44+ if parts.len() == 2 {
45+ Ok(Override {
46+ key: parts[0].to_string(),
47+ value: parts[1].to_string(),
48+ })
49+ } else {
50+ Err("Malformed environment key-value pair, should be similar to FOO=bar")
51+ }
52+ }
53+}
54+
55+impl ToString for Override {
56+ fn to_string(&self) -> String {
57+ format!("{}={}", self.key, self.value)
58+ }
59+}
60+
61+fn main() -> std::io::Result<()> {
62+ let matches = App::new("create")
63+ .version("0.1")
64+ .arg(
65+ Arg::with_name("project-name")
66+ .long("project-name")
67+ .short("p")
68+ .required(true)
69+ .takes_value(true),
70+ )
71+ .arg(
72+ Arg::with_name("base-domain")
73+ .long("base-domain")
74+ .short("d")
75+ .required(true)
76+ .takes_value(true),
77+ )
78+ .arg(
79+ Arg::with_name("namespace")
80+ .long("namespace")
81+ .short("s")
82+ .required(true)
83+ .takes_value(true),
84+ )
85+ .arg(
86+ Arg::with_name("name")
87+ .long("name")
88+ .short("n")
89+ .required(true)
90+ .takes_value(true),
91+ )
92+ .arg(
93+ Arg::with_name("tag")
94+ .long("tag")
95+ .short("t")
96+ .required(true)
97+ .takes_value(true),
98+ )
99+ .arg(
100+ Arg::with_name("app-env-override")
101+ .long("app-env-override")
102+ .short("e")
103+ .required(false)
104+ .multiple(true)
105+ .takes_value(true),
106+ )
107+ .arg(
108+ Arg::with_name("deployment-override")
109+ .long("deployment-override")
110+ .short("o")
111+ .required(false)
112+ .multiple(true)
113+ .takes_value(true),
114+ )
115+ .get_matches();
116+
117+ let _project_name = matches
118+ .value_of("project-name")
119+ .expect("could not get project-name");
120+ let base_domain = matches
121+ .value_of("base-domain")
122+ .expect("could not get base-domain");
123+ let namespace = matches
124+ .value_of("namespace")
125+ .expect("could not get namepace");
126+ let name = matches.value_of("name").expect("could not get name");
127+ let tag = matches.value_of("tag").expect("could not get tag");
128+ let app_env_overrides = matches
129+ .values_of("app-env-override")
130+ .unwrap_or_else(Default::default)
131+ .map(|e| e.try_into().expect("could not get valid key=value"))
132+ .collect::<Vec<Override>>();
133+ let deployment_overrides = matches
134+ .values_of("deployment-override")
135+ .unwrap_or_else(Default::default)
136+ .map(|e| e.try_into().expect("could not get valid key=value"))
137+ .collect::<Vec<Override>>();
138+
139+ let tmp_dir = tmp_dir();
140+ let work_dir = Path::new("/tmp").join(tmp_dir);
141+
142+ let _guard = TmpDirGuard::new(&work_dir);
143+ fs::create_dir(&work_dir)?;
144+
145+ let success = clone_and_prepare_repo(&work_dir);
146+
147+ let output = Command::new("helm")
148+ .args(command_args(
149+ base_domain,
150+ namespace,
151+ name,
152+ tag,
153+ app_env_overrides,
154+ deployment_overrides,
155+ ))
156+ .current_dir(&work_dir)
157+ .output()
158+ .expect("could not create app");
159+ let success2 = output.status.success();
160+
161+ if !(success && success2) {
162+ exit(1)
163+ }
164+
165+ Ok(())
166+}
167+
168+fn tmp_dir() -> String {
169+ const LENGTH: usize = 10;
170+
171+ let rng = thread_rng();
172+ let random_string: String = Alphanumeric.sample_iter(rng).take(LENGTH).collect();
173+ format!("octopod-{}", random_string)
174+}
175+
176+fn clone_and_prepare_repo(work_dir: &PathBuf) -> bool {
177+ let output = Command::new("git")
178+ .args(&["clone", "--recursive", "--depth=1", GIT_REPOSITORY, "."])
179+ .current_dir(work_dir)
180+ .output()
181+ .expect("could not clone repo");
182+ output.status.success()
183+}
184+
185+fn command_args(
186+ domain: &str,
187+ namespace: &str,
188+ name: &str,
189+ tag: &str,
190+ app_env_overrides: Vec<Override>,
191+ deployment_overrides: Vec<Override>,
192+) -> Vec<String> {
193+ let mut args = vec![
194+ "upgrade",
195+ "--install",
196+ "--namespace",
197+ namespace,
198+ &format!("app-{}", name),
199+ "examples/web-app/charts/web-app",
200+ "--set",
201+ &format!("image_prefix={}", REGISTRY),
202+ "--set",
203+ &format!("image={}", IMAGE),
204+ "--set",
205+ &format!("image_tag={}", tag),
206+ "--set",
207+ &format!("domain={}.{}", name, domain),
208+ "--wait",
209+ "--timeout",
210+ "300",
211+ "--debug",
212+ ]
213+ .iter()
214+ .map(ToString::to_string)
215+ .collect::<Vec<_>>();
216+
217+ let mut app_args = app_env_overrides
218+ .into_iter()
219+ .map(|e| {
220+ vec![
221+ "--set".to_string(),
222+ format!("env.{}", e.to_string()),
223+ ]
224+ })
225+ .flatten()
226+ .collect::<Vec<_>>();
227+ args.append(&mut app_args);
228+
229+ let mut deployment_args = deployment_overrides
230+ .into_iter()
231+ .map(|e| vec!["--set".to_string(), e.to_string()])
232+ .flatten()
233+ .collect::<Vec<_>>();
234+ args.append(&mut deployment_args);
235+
236+ args
237+}
examples/helm-based-control-scripts/src/bin/info.rsnew file+52
@@ -0,0 +1,52 @@
1+use clap::{App, Arg};
2+
3+fn main() -> std::io::Result<()> {
4+ let matches = App::new("cleanup")
5+ .version("0.1")
6+ .arg(
7+ Arg::with_name("project-name")
8+ .long("project-name")
9+ .short("p")
10+ .required(true)
11+ .takes_value(true),
12+ )
13+ .arg(
14+ Arg::with_name("base-domain")
15+ .long("base-domain")
16+ .short("d")
17+ .required(true)
18+ .takes_value(true),
19+ )
20+ .arg(
21+ Arg::with_name("namespace")
22+ .long("namespace")
23+ .short("s")
24+ .required(true)
25+ .takes_value(true),
26+ )
27+ .arg(
28+ Arg::with_name("name")
29+ .long("name")
30+ .short("n")
31+ .required(true)
32+ .takes_value(true),
33+ )
34+ .get_matches();
35+
36+ let _project_name = matches
37+ .value_of("project-name")
38+ .expect("could not get project-name");
39+ let base_domain = matches
40+ .value_of("base-domain")
41+ .expect("could not get base-domain");
42+ let _namespace = matches
43+ .value_of("namespace")
44+ .expect("could not get namepace");
45+ let name = matches.value_of("name").expect("could not get name");
46+
47+ [("app", format!("https://{}.{}", name, base_domain))]
48+ .iter()
49+ .for_each(|(key, value)| println!("{},{}", key, value));
50+
51+ Ok(())
52+}
examples/helm-based-control-scripts/src/bin/init.rsnew file+9
@@ -0,0 +1,9 @@
1+use std::env;
2+
3+fn main() -> std::io::Result<()> {
4+ let _home = env::var("HOME").expect("could not get $HOME");
5+
6+ // nop
7+
8+ Ok(())
9+}
examples/helm-based-control-scripts/src/bin/tag_check.rsnew file+91
@@ -0,0 +1,91 @@
1+use clap::{App, Arg};
2+use serde_derive::Deserialize;
3+use std::process::exit;
4+
5+static REGISTRY: &str = "typeable";
6+static REPOSITORY: &str = "octopod-web-app-example";
7+
8+#[derive(Debug, Deserialize)]
9+struct Resp {
10+ results: Vec<Tag>,
11+}
12+
13+#[derive(Debug, Deserialize)]
14+struct Tag {
15+ name: String,
16+}
17+
18+async fn do_request() -> reqwest::Result<Resp> {
19+ let url = format!(
20+ "https://hub.docker.com/v2/repositories/{}/{}/tags",
21+ REGISTRY, REPOSITORY
22+ );
23+ let body = reqwest::get(&url).await?.json::<Resp>().await?;
24+ Ok(body)
25+}
26+
27+#[tokio::main]
28+async fn main() -> std::io::Result<()> {
29+ let matches = App::new("tag_check")
30+ .version("0.1")
31+ .arg(
32+ Arg::with_name("project-name")
33+ .long("project-name")
34+ .short("p")
35+ .required(true)
36+ .takes_value(true),
37+ )
38+ .arg(
39+ Arg::with_name("base-domain")
40+ .long("base-domain")
41+ .short("d")
42+ .required(true)
43+ .takes_value(true),
44+ )
45+ .arg(
46+ Arg::with_name("namespace")
47+ .long("namespace")
48+ .short("s")
49+ .required(true)
50+ .takes_value(true),
51+ )
52+ .arg(
53+ Arg::with_name("name")
54+ .long("name")
55+ .short("n")
56+ .required(true)
57+ .takes_value(true),
58+ )
59+ .arg(
60+ Arg::with_name("tag")
61+ .long("tag")
62+ .short("t")
63+ .required(true)
64+ .takes_value(true),
65+ )
66+ .get_matches();
67+
68+ let _project_name = matches
69+ .value_of("project-name")
70+ .expect("could not get project-name");
71+ let _base_domain = matches
72+ .value_of("base-domain")
73+ .expect("could not get base-domain");
74+ let _namespace = matches
75+ .value_of("namespace")
76+ .expect("could not get namepace");
77+ let _name = matches.value_of("name").expect("could not get name");
78+ let tag = matches.value_of("tag").expect("could not get tag");
79+
80+ let mut tag_found = false;
81+ match do_request().await {
82+ Ok(resp) => tag_found = resp.results.iter().any(|t| t.name == tag),
83+ Err(err) => eprintln!("could not get tags, reason: {:?}", err),
84+ }
85+
86+ if !tag_found {
87+ exit(1)
88+ }
89+
90+ Ok(())
91+}
examples/web-app-v2/.gitignorenew file+1
@@ -0,0 +1 @@
1+/target
examples/web-app-v2/Cargo.locknew file+1935
@@ -0,0 +1,1935 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+[[package]]
4+name = "actix-codec"
5+version = "0.3.0"
6+source = "registry+https://github.com/rust-lang/crates.io-index"
7+checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
8+dependencies = [
9+ "bitflags",
10+ "bytes",
11+ "futures-core",
12+ "futures-sink",
13+ "log",
14+ "pin-project 0.4.27",
15+ "tokio",
16+ "tokio-util",
17+]
18+
19+[[package]]
20+name = "actix-connect"
21+version = "2.0.0"
22+source = "registry+https://github.com/rust-lang/crates.io-index"
23+checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
24+dependencies = [
25+ "actix-codec",
26+ "actix-rt",
27+ "actix-service",
28+ "actix-utils",
29+ "derive_more",
30+ "either",
31+ "futures-util",
32+ "http",
33+ "log",
34+ "trust-dns-proto",
35+ "trust-dns-resolver",
36+]
37+
38+[[package]]
39+name = "actix-http"
40+version = "2.2.0"
41+source = "registry+https://github.com/rust-lang/crates.io-index"
42+checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874"
43+dependencies = [
44+ "actix-codec",
45+ "actix-connect",
46+ "actix-rt",
47+ "actix-service",
48+ "actix-threadpool",
49+ "actix-utils",
50+ "base64",
51+ "bitflags",
52+ "brotli2",
53+ "bytes",
54+ "cookie",
55+ "copyless",
56+ "derive_more",
57+ "either",
58+ "encoding_rs",
59+ "flate2",
60+ "futures-channel",
61+ "futures-core",
62+ "futures-util",
63+ "fxhash",
64+ "h2",
65+ "http",
66+ "httparse",
67+ "indexmap",
68+ "itoa",
69+ "language-tags",
70+ "lazy_static",
71+ "log",
72+ "mime",
73+ "percent-encoding",
74+ "pin-project 1.0.2",
75+ "rand",
76+ "regex",
77+ "serde",
78+ "serde_json",
79+ "serde_urlencoded",
80+ "sha-1",
81+ "slab",
82+ "time",
83+]
84+
85+[[package]]
86+name = "actix-macros"
87+version = "0.1.3"
88+source = "registry+https://github.com/rust-lang/crates.io-index"
89+checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655"
90+dependencies = [
91+ "quote",
92+ "syn",
93+]
94+
95+[[package]]
96+name = "actix-router"
97+version = "0.2.5"
98+source = "registry+https://github.com/rust-lang/crates.io-index"
99+checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235"
100+dependencies = [
101+ "bytestring",
102+ "http",
103+ "log",
104+ "regex",
105+ "serde",
106+]
107+
108+[[package]]
109+name = "actix-rt"
110+version = "1.1.1"
111+source = "registry+https://github.com/rust-lang/crates.io-index"
112+checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
113+dependencies = [
114+ "actix-macros",
115+ "actix-threadpool",
116+ "copyless",
117+ "futures-channel",
118+ "futures-util",
119+ "smallvec",
120+ "tokio",
121+]
122+
123+[[package]]
124+name = "actix-server"
125+version = "1.0.4"
126+source = "registry+https://github.com/rust-lang/crates.io-index"
127+checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
128+dependencies = [
129+ "actix-codec",
130+ "actix-rt",
131+ "actix-service",
132+ "actix-utils",
133+ "futures-channel",
134+ "futures-util",
135+ "log",
136+ "mio",
137+ "mio-uds",
138+ "num_cpus",
139+ "slab",
140+ "socket2",
141+]
142+
143+[[package]]
144+name = "actix-service"
145+version = "1.0.6"
146+source = "registry+https://github.com/rust-lang/crates.io-index"
147+checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb"
148+dependencies = [
149+ "futures-util",
150+ "pin-project 0.4.27",
151+]
152+
153+[[package]]
154+name = "actix-testing"
155+version = "1.0.1"
156+source = "registry+https://github.com/rust-lang/crates.io-index"
157+checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
158+dependencies = [
159+ "actix-macros",
160+ "actix-rt",
161+ "actix-server",
162+ "actix-service",
163+ "log",
164+ "socket2",
165+]
166+
167+[[package]]
168+name = "actix-threadpool"
169+version = "0.3.3"
170+source = "registry+https://github.com/rust-lang/crates.io-index"
171+checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
172+dependencies = [
173+ "derive_more",
174+ "futures-channel",
175+ "lazy_static",
176+ "log",
177+ "num_cpus",
178+ "parking_lot",
179+ "threadpool",
180+]
181+
182+[[package]]
183+name = "actix-tls"
184+version = "2.0.0"
185+source = "registry+https://github.com/rust-lang/crates.io-index"
186+checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
187+dependencies = [
188+ "actix-codec",
189+ "actix-service",
190+ "actix-utils",
191+ "futures-util",
192+]
193+
194+[[package]]
195+name = "actix-utils"
196+version = "2.0.0"
197+source = "registry+https://github.com/rust-lang/crates.io-index"
198+checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
199+dependencies = [
200+ "actix-codec",
201+ "actix-rt",
202+ "actix-service",
203+ "bitflags",
204+ "bytes",
205+ "either",
206+ "futures-channel",
207+ "futures-sink",
208+ "futures-util",
209+ "log",
210+ "pin-project 0.4.27",
211+ "slab",
212+]
213+
214+[[package]]
215+name = "actix-web"
216+version = "3.3.2"
217+source = "registry+https://github.com/rust-lang/crates.io-index"
218+checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86"
219+dependencies = [
220+ "actix-codec",
221+ "actix-http",
222+ "actix-macros",
223+ "actix-router",
224+ "actix-rt",
225+ "actix-server",
226+ "actix-service",
227+ "actix-testing",
228+ "actix-threadpool",
229+ "actix-tls",
230+ "actix-utils",
231+ "actix-web-codegen",
232+ "awc",
233+ "bytes",
234+ "derive_more",
235+ "encoding_rs",
236+ "futures-channel",
237+ "futures-core",
238+ "futures-util",
239+ "fxhash",
240+ "log",
241+ "mime",
242+ "pin-project 1.0.2",
243+ "regex",
244+ "serde",
245+ "serde_json",
246+ "serde_urlencoded",
247+ "socket2",
248+ "time",
249+ "tinyvec",
250+ "url",
251+]
252+
253+[[package]]
254+name = "actix-web-codegen"
255+version = "0.4.0"
256+source = "registry+https://github.com/rust-lang/crates.io-index"
257+checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
258+dependencies = [
259+ "proc-macro2",
260+ "quote",
261+ "syn",
262+]
263+
264+[[package]]
265+name = "addr2line"
266+version = "0.14.0"
267+source = "registry+https://github.com/rust-lang/crates.io-index"
268+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
269+dependencies = [
270+ "gimli",
271+]
272+
273+[[package]]
274+name = "adler"
275+version = "0.2.3"
276+source = "registry+https://github.com/rust-lang/crates.io-index"
277+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
278+
279+[[package]]
280+name = "aho-corasick"
281+version = "0.7.15"
282+source = "registry+https://github.com/rust-lang/crates.io-index"
283+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
284+dependencies = [
285+ "memchr",
286+]
287+
288+[[package]]
289+name = "arrayvec"
290+version = "0.5.2"
291+source = "registry+https://github.com/rust-lang/crates.io-index"
292+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
293+
294+[[package]]
295+name = "askama"
296+version = "0.10.5"
297+source = "registry+https://github.com/rust-lang/crates.io-index"
298+checksum = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134"
299+dependencies = [
300+ "askama_derive",
301+ "askama_escape",
302+ "askama_shared",
303+]
304+
305+[[package]]
306+name = "askama_derive"
307+version = "0.10.5"
308+source = "registry+https://github.com/rust-lang/crates.io-index"
309+checksum = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522"
310+dependencies = [
311+ "askama_shared",
312+ "proc-macro2",
313+ "syn",
314+]
315+
316+[[package]]
317+name = "askama_escape"
318+version = "0.10.1"
319+source = "registry+https://github.com/rust-lang/crates.io-index"
320+checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb"
321+
322+[[package]]
323+name = "askama_shared"
324+version = "0.11.1"
325+source = "registry+https://github.com/rust-lang/crates.io-index"
326+checksum = "2582b77e0f3c506ec4838a25fa8a5f97b9bed72bb6d3d272ea1c031d8bd373bc"
327+dependencies = [
328+ "askama_escape",
329+ "humansize",
330+ "nom",
331+ "num-traits",
332+ "percent-encoding",
333+ "proc-macro2",
334+ "quote",
335+ "serde",
336+ "syn",
337+ "toml",
338+]
339+
340+[[package]]
341+name = "async-trait"
342+version = "0.1.42"
343+source = "registry+https://github.com/rust-lang/crates.io-index"
344+checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
345+dependencies = [
346+ "proc-macro2",
347+ "quote",
348+ "syn",
349+]
350+
351+[[package]]
352+name = "autocfg"
353+version = "1.0.1"
354+source = "registry+https://github.com/rust-lang/crates.io-index"
355+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
356+
357+[[package]]
358+name = "awc"
359+version = "2.0.3"
360+source = "registry+https://github.com/rust-lang/crates.io-index"
361+checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
362+dependencies = [
363+ "actix-codec",
364+ "actix-http",
365+ "actix-rt",
366+ "actix-service",
367+ "base64",
368+ "bytes",
369+ "cfg-if 1.0.0",
370+ "derive_more",
371+ "futures-core",
372+ "log",
373+ "mime",
374+ "percent-encoding",
375+ "rand",
376+ "serde",
377+ "serde_json",
378+ "serde_urlencoded",
379+]
380+
381+[[package]]
382+name = "backtrace"
383+version = "0.3.55"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
386+dependencies = [
387+ "addr2line",
388+ "cfg-if 1.0.0",
389+ "libc",
390+ "miniz_oxide",
391+ "object",
392+ "rustc-demangle",
393+]
394+
395+[[package]]
396+name = "base-x"
397+version = "0.2.8"
398+source = "registry+https://github.com/rust-lang/crates.io-index"
399+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
400+
401+[[package]]
402+name = "base64"
403+version = "0.13.0"
404+source = "registry+https://github.com/rust-lang/crates.io-index"
405+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
406+
407+[[package]]
408+name = "bitflags"
409+version = "1.2.1"
410+source = "registry+https://github.com/rust-lang/crates.io-index"
411+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
412+
413+[[package]]
414+name = "bitvec"
415+version = "0.19.4"
416+source = "registry+https://github.com/rust-lang/crates.io-index"
417+checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
418+dependencies = [
419+ "funty",
420+ "radium",
421+ "tap",
422+ "wyz",
423+]
424+
425+[[package]]
426+name = "block-buffer"
427+version = "0.9.0"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
430+dependencies = [
431+ "generic-array",
432+]
433+
434+[[package]]
435+name = "brotli-sys"
436+version = "0.3.2"
437+source = "registry+https://github.com/rust-lang/crates.io-index"
438+checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
439+dependencies = [
440+ "cc",
441+ "libc",
442+]
443+
444+[[package]]
445+name = "brotli2"
446+version = "0.3.2"
447+source = "registry+https://github.com/rust-lang/crates.io-index"
448+checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
449+dependencies = [
450+ "brotli-sys",
451+ "libc",
452+]
453+
454+[[package]]
455+name = "bumpalo"
456+version = "3.4.0"
457+source = "registry+https://github.com/rust-lang/crates.io-index"
458+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
459+
460+[[package]]
461+name = "byteorder"
462+version = "1.3.4"
463+source = "registry+https://github.com/rust-lang/crates.io-index"
464+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
465+
466+[[package]]
467+name = "bytes"
468+version = "0.5.6"
469+source = "registry+https://github.com/rust-lang/crates.io-index"
470+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
471+
472+[[package]]
473+name = "bytestring"
474+version = "0.1.5"
475+source = "registry+https://github.com/rust-lang/crates.io-index"
476+checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363"
477+dependencies = [
478+ "bytes",
479+]
480+
481+[[package]]
482+name = "cc"
483+version = "1.0.65"
484+source = "registry+https://github.com/rust-lang/crates.io-index"
485+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
486+
487+[[package]]
488+name = "cfg-if"
489+version = "0.1.10"
490+source = "registry+https://github.com/rust-lang/crates.io-index"
491+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
492+
493+[[package]]
494+name = "cfg-if"
495+version = "1.0.0"
496+source = "registry+https://github.com/rust-lang/crates.io-index"
497+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
498+
499+[[package]]
500+name = "cloudabi"
501+version = "0.1.0"
502+source = "registry+https://github.com/rust-lang/crates.io-index"
503+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
504+dependencies = [
505+ "bitflags",
506+]
507+
508+[[package]]
509+name = "const_fn"
510+version = "0.4.3"
511+source = "registry+https://github.com/rust-lang/crates.io-index"
512+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
513+
514+[[package]]
515+name = "cookie"
516+version = "0.14.3"
517+source = "registry+https://github.com/rust-lang/crates.io-index"
518+checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
519+dependencies = [
520+ "percent-encoding",
521+ "time",
522+ "version_check",
523+]
524+
525+[[package]]
526+name = "copyless"
527+version = "0.1.5"
528+source = "registry+https://github.com/rust-lang/crates.io-index"
529+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
530+
531+[[package]]
532+name = "cpuid-bool"
533+version = "0.1.2"
534+source = "registry+https://github.com/rust-lang/crates.io-index"
535+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
536+
537+[[package]]
538+name = "crc32fast"
539+version = "1.2.1"
540+source = "registry+https://github.com/rust-lang/crates.io-index"
541+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
542+dependencies = [
543+ "cfg-if 1.0.0",
544+]
545+
546+[[package]]
547+name = "derive_more"
548+version = "0.99.11"
549+source = "registry+https://github.com/rust-lang/crates.io-index"
550+checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
551+dependencies = [
552+ "proc-macro2",
553+ "quote",
554+ "syn",
555+]
556+
557+[[package]]
558+name = "digest"
559+version = "0.9.0"
560+source = "registry+https://github.com/rust-lang/crates.io-index"
561+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
562+dependencies = [
563+ "generic-array",
564+]
565+
566+[[package]]
567+name = "discard"
568+version = "1.0.4"
569+source = "registry+https://github.com/rust-lang/crates.io-index"
570+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
571+
572+[[package]]
573+name = "either"
574+version = "1.6.1"
575+source = "registry+https://github.com/rust-lang/crates.io-index"
576+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
577+
578+[[package]]
579+name = "encoding_rs"
580+version = "0.8.26"
581+source = "registry+https://github.com/rust-lang/crates.io-index"
582+checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283"
583+dependencies = [
584+ "cfg-if 1.0.0",
585+]
586+
587+[[package]]
588+name = "enum-as-inner"
589+version = "0.3.3"
590+source = "registry+https://github.com/rust-lang/crates.io-index"
591+checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
592+dependencies = [
593+ "heck",
594+ "proc-macro2",
595+ "quote",
596+ "syn",
597+]
598+
599+[[package]]
600+name = "flate2"
601+version = "1.0.19"
602+source = "registry+https://github.com/rust-lang/crates.io-index"
603+checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
604+dependencies = [
605+ "cfg-if 1.0.0",
606+ "crc32fast",
607+ "libc",
608+ "miniz_oxide",
609+]
610+
611+[[package]]
612+name = "fnv"
613+version = "1.0.7"
614+source = "registry+https://github.com/rust-lang/crates.io-index"
615+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
616+
617+[[package]]
618+name = "form_urlencoded"
619+version = "1.0.0"
620+source = "registry+https://github.com/rust-lang/crates.io-index"
621+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
622+dependencies = [
623+ "matches",
624+ "percent-encoding",
625+]
626+
627+[[package]]
628+name = "fuchsia-zircon"
629+version = "0.3.3"
630+source = "registry+https://github.com/rust-lang/crates.io-index"
631+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
632+dependencies = [
633+ "bitflags",
634+ "fuchsia-zircon-sys",
635+]
636+
637+[[package]]
638+name = "fuchsia-zircon-sys"
639+version = "0.3.3"
640+source = "registry+https://github.com/rust-lang/crates.io-index"
641+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
642+
643+[[package]]
644+name = "funty"
645+version = "1.0.1"
646+source = "registry+https://github.com/rust-lang/crates.io-index"
647+checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
648+
649+[[package]]
650+name = "futures"
651+version = "0.3.8"
652+source = "registry+https://github.com/rust-lang/crates.io-index"
653+checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
654+dependencies = [
655+ "futures-channel",
656+ "futures-core",
657+ "futures-io",
658+ "futures-sink",
659+ "futures-task",
660+ "futures-util",
661+]
662+
663+[[package]]
664+name = "futures-channel"
665+version = "0.3.8"
666+source = "registry+https://github.com/rust-lang/crates.io-index"
667+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
668+dependencies = [
669+ "futures-core",
670+ "futures-sink",
671+]
672+
673+[[package]]
674+name = "futures-core"
675+version = "0.3.8"
676+source = "registry+https://github.com/rust-lang/crates.io-index"
677+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
678+
679+[[package]]
680+name = "futures-io"
681+version = "0.3.8"
682+source = "registry+https://github.com/rust-lang/crates.io-index"
683+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
684+
685+[[package]]
686+name = "futures-macro"
687+version = "0.3.8"
688+source = "registry+https://github.com/rust-lang/crates.io-index"
689+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
690+dependencies = [
691+ "proc-macro-hack",
692+ "proc-macro2",
693+ "quote",
694+ "syn",
695+]
696+
697+[[package]]
698+name = "futures-sink"
699+version = "0.3.8"
700+source = "registry+https://github.com/rust-lang/crates.io-index"
701+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
702+
703+[[package]]
704+name = "futures-task"
705+version = "0.3.8"
706+source = "registry+https://github.com/rust-lang/crates.io-index"
707+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
708+dependencies = [
709+ "once_cell",
710+]
711+
712+[[package]]
713+name = "futures-util"
714+version = "0.3.8"
715+source = "registry+https://github.com/rust-lang/crates.io-index"
716+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
717+dependencies = [
718+ "futures-channel",
719+ "futures-core",
720+ "futures-io",
721+ "futures-macro",
722+ "futures-sink",
723+ "futures-task",
724+ "memchr",
725+ "pin-project 1.0.2",
726+ "pin-utils",
727+ "proc-macro-hack",
728+ "proc-macro-nested",
729+ "slab",
730+]
731+
732+[[package]]
733+name = "fxhash"
734+version = "0.2.1"
735+source = "registry+https://github.com/rust-lang/crates.io-index"
736+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
737+dependencies = [
738+ "byteorder",
739+]
740+
741+[[package]]
742+name = "generic-array"
743+version = "0.14.4"
744+source = "registry+https://github.com/rust-lang/crates.io-index"
745+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
746+dependencies = [
747+ "typenum",
748+ "version_check",
749+]
750+
751+[[package]]
752+name = "getrandom"
753+version = "0.1.15"
754+source = "registry+https://github.com/rust-lang/crates.io-index"
755+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
756+dependencies = [
757+ "cfg-if 0.1.10",
758+ "libc",
759+ "wasi",
760+]
761+
762+[[package]]
763+name = "gimli"
764+version = "0.23.0"
765+source = "registry+https://github.com/rust-lang/crates.io-index"
766+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
767+
768+[[package]]
769+name = "h2"
770+version = "0.2.7"
771+source = "registry+https://github.com/rust-lang/crates.io-index"
772+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
773+dependencies = [
774+ "bytes",
775+ "fnv",
776+ "futures-core",
777+ "futures-sink",
778+ "futures-util",
779+ "http",
780+ "indexmap",
781+ "slab",
782+ "tokio",
783+ "tokio-util",
784+ "tracing",
785+ "tracing-futures",
786+]
787+
788+[[package]]
789+name = "hashbrown"
790+version = "0.9.1"
791+source = "registry+https://github.com/rust-lang/crates.io-index"
792+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
793+
794+[[package]]
795+name = "heck"
796+version = "0.3.1"
797+source = "registry+https://github.com/rust-lang/crates.io-index"
798+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
799+dependencies = [
800+ "unicode-segmentation",
801+]
802+
803+[[package]]
804+name = "hermit-abi"
805+version = "0.1.17"
806+source = "registry+https://github.com/rust-lang/crates.io-index"
807+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
808+dependencies = [
809+ "libc",
810+]
811+
812+[[package]]
813+name = "hostname"
814+version = "0.3.1"
815+source = "registry+https://github.com/rust-lang/crates.io-index"
816+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
817+dependencies = [
818+ "libc",
819+ "match_cfg",
820+ "winapi 0.3.9",
821+]
822+
823+[[package]]
824+name = "http"
825+version = "0.2.1"
826+source = "registry+https://github.com/rust-lang/crates.io-index"
827+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
828+dependencies = [
829+ "bytes",
830+ "fnv",
831+ "itoa",
832+]
833+
834+[[package]]
835+name = "httparse"
836+version = "1.3.4"
837+source = "registry+https://github.com/rust-lang/crates.io-index"
838+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
839+
840+[[package]]
841+name = "humansize"
842+version = "1.1.0"
843+source = "registry+https://github.com/rust-lang/crates.io-index"
844+checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
845+
846+[[package]]
847+name = "idna"
848+version = "0.2.0"
849+source = "registry+https://github.com/rust-lang/crates.io-index"
850+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
851+dependencies = [
852+ "matches",
853+ "unicode-bidi",
854+ "unicode-normalization",
855+]
856+
857+[[package]]
858+name = "indexmap"
859+version = "1.6.0"
860+source = "registry+https://github.com/rust-lang/crates.io-index"
861+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
862+dependencies = [
863+ "autocfg",
864+ "hashbrown",
865+]
866+
867+[[package]]
868+name = "instant"
869+version = "0.1.9"
870+source = "registry+https://github.com/rust-lang/crates.io-index"
871+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
872+dependencies = [
873+ "cfg-if 1.0.0",
874+]
875+
876+[[package]]
877+name = "iovec"
878+version = "0.1.4"
879+source = "registry+https://github.com/rust-lang/crates.io-index"
880+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
881+dependencies = [
882+ "libc",
883+]
884+
885+[[package]]
886+name = "ipconfig"
887+version = "0.2.2"
888+source = "registry+https://github.com/rust-lang/crates.io-index"
889+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
890+dependencies = [
891+ "socket2",
892+ "widestring",
893+ "winapi 0.3.9",
894+ "winreg",
895+]
896+
897+[[package]]
898+name = "itoa"
899+version = "0.4.6"
900+source = "registry+https://github.com/rust-lang/crates.io-index"
901+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
902+
903+[[package]]
904+name = "kernel32-sys"
905+version = "0.2.2"
906+source = "registry+https://github.com/rust-lang/crates.io-index"
907+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
908+dependencies = [
909+ "winapi 0.2.8",
910+ "winapi-build",
911+]
912+
913+[[package]]
914+name = "language-tags"
915+version = "0.2.2"
916+source = "registry+https://github.com/rust-lang/crates.io-index"
917+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
918+
919+[[package]]
920+name = "lazy_static"
921+version = "1.4.0"
922+source = "registry+https://github.com/rust-lang/crates.io-index"
923+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
924+
925+[[package]]
926+name = "lexical-core"
927+version = "0.7.4"
928+source = "registry+https://github.com/rust-lang/crates.io-index"
929+checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
930+dependencies = [
931+ "arrayvec",
932+ "bitflags",
933+ "cfg-if 0.1.10",
934+ "ryu",
935+ "static_assertions",
936+]
937+
938+[[package]]
939+name = "libc"
940+version = "0.2.80"
941+source = "registry+https://github.com/rust-lang/crates.io-index"
942+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
943+
944+[[package]]
945+name = "linked-hash-map"
946+version = "0.5.3"
947+source = "registry+https://github.com/rust-lang/crates.io-index"
948+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
949+
950+[[package]]
951+name = "lock_api"
952+version = "0.4.2"
953+source = "registry+https://github.com/rust-lang/crates.io-index"
954+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
955+dependencies = [
956+ "scopeguard",
957+]
958+
959+[[package]]
960+name = "log"
961+version = "0.4.11"
962+source = "registry+https://github.com/rust-lang/crates.io-index"
963+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
964+dependencies = [
965+ "cfg-if 0.1.10",
966+]
967+
968+[[package]]
969+name = "lru-cache"
970+version = "0.1.2"
971+source = "registry+https://github.com/rust-lang/crates.io-index"
972+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
973+dependencies = [
974+ "linked-hash-map",
975+]
976+
977+[[package]]
978+name = "match_cfg"
979+version = "0.1.0"
980+source = "registry+https://github.com/rust-lang/crates.io-index"
981+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
982+
983+[[package]]
984+name = "matches"
985+version = "0.1.8"
986+source = "registry+https://github.com/rust-lang/crates.io-index"
987+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
988+
989+[[package]]
990+name = "memchr"
991+version = "2.3.4"
992+source = "registry+https://github.com/rust-lang/crates.io-index"
993+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
994+
995+[[package]]
996+name = "mime"
997+version = "0.3.16"
998+source = "registry+https://github.com/rust-lang/crates.io-index"
999+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
1000+
1001+[[package]]
1002+name = "miniz_oxide"
1003+version = "0.4.3"
1004+source = "registry+https://github.com/rust-lang/crates.io-index"
1005+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
1006+dependencies = [
1007+ "adler",
1008+ "autocfg",
1009+]
1010+
1011+[[package]]
1012+name = "mio"
1013+version = "0.6.23"
1014+source = "registry+https://github.com/rust-lang/crates.io-index"
1015+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
1016+dependencies = [
1017+ "cfg-if 0.1.10",
1018+ "fuchsia-zircon",
1019+ "fuchsia-zircon-sys",
1020+ "iovec",
1021+ "kernel32-sys",
1022+ "libc",
1023+ "log",
1024+ "miow",
1025+ "net2",
1026+ "slab",
1027+ "winapi 0.2.8",
1028+]
1029+
1030+[[package]]
1031+name = "mio-uds"
1032+version = "0.6.8"
1033+source = "registry+https://github.com/rust-lang/crates.io-index"
1034+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
1035+dependencies = [
1036+ "iovec",
1037+ "libc",
1038+ "mio",
1039+]
1040+
1041+[[package]]
1042+name = "miow"
1043+version = "0.2.2"
1044+source = "registry+https://github.com/rust-lang/crates.io-index"
1045+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
1046+dependencies = [
1047+ "kernel32-sys",
1048+ "net2",
1049+ "winapi 0.2.8",
1050+ "ws2_32-sys",
1051+]
1052+
1053+[[package]]
1054+name = "net2"
1055+version = "0.2.36"
1056+source = "registry+https://github.com/rust-lang/crates.io-index"
1057+checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
1058+dependencies = [
1059+ "cfg-if 0.1.10",
1060+ "libc",
1061+ "winapi 0.3.9",
1062+]
1063+
1064+[[package]]
1065+name = "nom"
1066+version = "6.0.1"
1067+source = "registry+https://github.com/rust-lang/crates.io-index"
1068+checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0"
1069+dependencies = [
1070+ "bitvec",
1071+ "lexical-core",
1072+ "memchr",
1073+ "version_check",
1074+]
1075+
1076+[[package]]
1077+name = "num-traits"
1078+version = "0.2.14"
1079+source = "registry+https://github.com/rust-lang/crates.io-index"
1080+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
1081+dependencies = [
1082+ "autocfg",
1083+]
1084+
1085+[[package]]
1086+name = "num_cpus"
1087+version = "1.13.0"
1088+source = "registry+https://github.com/rust-lang/crates.io-index"
1089+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
1090+dependencies = [
1091+ "hermit-abi",
1092+ "libc",
1093+]
1094+
1095+[[package]]
1096+name = "object"
1097+version = "0.22.0"
1098+source = "registry+https://github.com/rust-lang/crates.io-index"
1099+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
1100+
1101+[[package]]
1102+name = "once_cell"
1103+version = "1.5.2"
1104+source = "registry+https://github.com/rust-lang/crates.io-index"
1105+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
1106+
1107+[[package]]
1108+name = "opaque-debug"
1109+version = "0.3.0"
1110+source = "registry+https://github.com/rust-lang/crates.io-index"
1111+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
1112+
1113+[[package]]
1114+name = "parking_lot"
1115+version = "0.11.1"
1116+source = "registry+https://github.com/rust-lang/crates.io-index"
1117+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
1118+dependencies = [
1119+ "instant",
1120+ "lock_api",
1121+ "parking_lot_core",
1122+]
1123+
1124+[[package]]
1125+name = "parking_lot_core"
1126+version = "0.8.0"
1127+source = "registry+https://github.com/rust-lang/crates.io-index"
1128+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
1129+dependencies = [
1130+ "cfg-if 0.1.10",
1131+ "cloudabi",
1132+ "instant",
1133+ "libc",
1134+ "redox_syscall",
1135+ "smallvec",
1136+ "winapi 0.3.9",
1137+]
1138+
1139+[[package]]
1140+name = "percent-encoding"
1141+version = "2.1.0"
1142+source = "registry+https://github.com/rust-lang/crates.io-index"
1143+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
1144+
1145+[[package]]
1146+name = "pin-project"
1147+version = "0.4.27"
1148+source = "registry+https://github.com/rust-lang/crates.io-index"
1149+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
1150+dependencies = [
1151+ "pin-project-internal 0.4.27",
1152+]
1153+
1154+[[package]]
1155+name = "pin-project"
1156+version = "1.0.2"
1157+source = "registry+https://github.com/rust-lang/crates.io-index"
1158+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
1159+dependencies = [
1160+ "pin-project-internal 1.0.2",
1161+]
1162+
1163+[[package]]
1164+name = "pin-project-internal"
1165+version = "0.4.27"
1166+source = "registry+https://github.com/rust-lang/crates.io-index"
1167+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
1168+dependencies = [
1169+ "proc-macro2",
1170+ "quote",
1171+ "syn",
1172+]
1173+
1174+[[package]]
1175+name = "pin-project-internal"
1176+version = "1.0.2"
1177+source = "registry+https://github.com/rust-lang/crates.io-index"
1178+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
1179+dependencies = [
1180+ "proc-macro2",
1181+ "quote",
1182+ "syn",
1183+]
1184+
1185+[[package]]
1186+name = "pin-project-lite"
1187+version = "0.1.11"
1188+source = "registry+https://github.com/rust-lang/crates.io-index"
1189+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
1190+
1191+[[package]]
1192+name = "pin-project-lite"
1193+version = "0.2.0"
1194+source = "registry+https://github.com/rust-lang/crates.io-index"
1195+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
1196+
1197+[[package]]
1198+name = "pin-utils"
1199+version = "0.1.0"
1200+source = "registry+https://github.com/rust-lang/crates.io-index"
1201+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1202+
1203+[[package]]
1204+name = "ppv-lite86"
1205+version = "0.2.10"
1206+source = "registry+https://github.com/rust-lang/crates.io-index"
1207+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
1208+
1209+[[package]]
1210+name = "proc-macro-hack"
1211+version = "0.5.19"
1212+source = "registry+https://github.com/rust-lang/crates.io-index"
1213+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
1214+
1215+[[package]]
1216+name = "proc-macro-nested"
1217+version = "0.1.6"
1218+source = "registry+https://github.com/rust-lang/crates.io-index"
1219+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
1220+
1221+[[package]]
1222+name = "proc-macro2"
1223+version = "1.0.24"
1224+source = "registry+https://github.com/rust-lang/crates.io-index"
1225+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
1226+dependencies = [
1227+ "unicode-xid",
1228+]
1229+
1230+[[package]]
1231+name = "quick-error"
1232+version = "1.2.3"
1233+source = "registry+https://github.com/rust-lang/crates.io-index"
1234+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
1235+
1236+[[package]]
1237+name = "quote"
1238+version = "1.0.7"
1239+source = "registry+https://github.com/rust-lang/crates.io-index"
1240+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
1241+dependencies = [
1242+ "proc-macro2",
1243+]
1244+
1245+[[package]]
1246+name = "radium"
1247+version = "0.5.3"
1248+source = "registry+https://github.com/rust-lang/crates.io-index"
1249+checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
1250+
1251+[[package]]
1252+name = "rand"
1253+version = "0.7.3"
1254+source = "registry+https://github.com/rust-lang/crates.io-index"
1255+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
1256+dependencies = [
1257+ "getrandom",
1258+ "libc",
1259+ "rand_chacha",
1260+ "rand_core",
1261+ "rand_hc",
1262+]
1263+
1264+[[package]]
1265+name = "rand_chacha"
1266+version = "0.2.2"
1267+source = "registry+https://github.com/rust-lang/crates.io-index"
1268+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
1269+dependencies = [
1270+ "ppv-lite86",
1271+ "rand_core",
1272+]
1273+
1274+[[package]]
1275+name = "rand_core"
1276+version = "0.5.1"
1277+source = "registry+https://github.com/rust-lang/crates.io-index"
1278+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
1279+dependencies = [
1280+ "getrandom",
1281+]
1282+
1283+[[package]]
1284+name = "rand_hc"
1285+version = "0.2.0"
1286+source = "registry+https://github.com/rust-lang/crates.io-index"
1287+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1288+dependencies = [
1289+ "rand_core",
1290+]
1291+
1292+[[package]]
1293+name = "redox_syscall"
1294+version = "0.1.57"
1295+source = "registry+https://github.com/rust-lang/crates.io-index"
1296+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
1297+
1298+[[package]]
1299+name = "regex"
1300+version = "1.4.2"
1301+source = "registry+https://github.com/rust-lang/crates.io-index"
1302+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
1303+dependencies = [
1304+ "aho-corasick",
1305+ "memchr",
1306+ "regex-syntax",
1307+ "thread_local",
1308+]
1309+
1310+[[package]]
1311+name = "regex-syntax"
1312+version = "0.6.21"
1313+source = "registry+https://github.com/rust-lang/crates.io-index"
1314+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
1315+
1316+[[package]]
1317+name = "resolv-conf"
1318+version = "0.7.0"
1319+source = "registry+https://github.com/rust-lang/crates.io-index"
1320+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
1321+dependencies = [
1322+ "hostname",
1323+ "quick-error",
1324+]
1325+
1326+[[package]]
1327+name = "rustc-demangle"
1328+version = "0.1.18"
1329+source = "registry+https://github.com/rust-lang/crates.io-index"
1330+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
1331+
1332+[[package]]
1333+name = "rustc_version"
1334+version = "0.2.3"
1335+source = "registry+https://github.com/rust-lang/crates.io-index"
1336+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
1337+dependencies = [
1338+ "semver",
1339+]
1340+
1341+[[package]]
1342+name = "ryu"
1343+version = "1.0.5"
1344+source = "registry+https://github.com/rust-lang/crates.io-index"
1345+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1346+
1347+[[package]]
1348+name = "scopeguard"
1349+version = "1.1.0"
1350+source = "registry+https://github.com/rust-lang/crates.io-index"
1351+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1352+
1353+[[package]]
1354+name = "semver"
1355+version = "0.9.0"
1356+source = "registry+https://github.com/rust-lang/crates.io-index"
1357+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
1358+dependencies = [
1359+ "semver-parser",
1360+]
1361+
1362+[[package]]
1363+name = "semver-parser"
1364+version = "0.7.0"
1365+source = "registry+https://github.com/rust-lang/crates.io-index"
1366+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1367+
1368+[[package]]
1369+name = "serde"
1370+version = "1.0.117"
1371+source = "registry+https://github.com/rust-lang/crates.io-index"
1372+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
1373+dependencies = [
1374+ "serde_derive",
1375+]
1376+
1377+[[package]]
1378+name = "serde_derive"
1379+version = "1.0.117"
1380+source = "registry+https://github.com/rust-lang/crates.io-index"
1381+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
1382+dependencies = [
1383+ "proc-macro2",
1384+ "quote",
1385+ "syn",
1386+]
1387+
1388+[[package]]
1389+name = "serde_json"
1390+version = "1.0.60"
1391+source = "registry+https://github.com/rust-lang/crates.io-index"
1392+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
1393+dependencies = [
1394+ "itoa",
1395+ "ryu",
1396+ "serde",
1397+]
1398+
1399+[[package]]
1400+name = "serde_urlencoded"
1401+version = "0.7.0"
1402+source = "registry+https://github.com/rust-lang/crates.io-index"
1403+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
1404+dependencies = [
1405+ "form_urlencoded",
1406+ "itoa",
1407+ "ryu",
1408+ "serde",
1409+]
1410+
1411+[[package]]
1412+name = "sha-1"
1413+version = "0.9.2"
1414+source = "registry+https://github.com/rust-lang/crates.io-index"
1415+checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
1416+dependencies = [
1417+ "block-buffer",
1418+ "cfg-if 1.0.0",
1419+ "cpuid-bool",
1420+ "digest",
1421+ "opaque-debug",
1422+]
1423+
1424+[[package]]
1425+name = "sha1"
1426+version = "0.6.0"
1427+source = "registry+https://github.com/rust-lang/crates.io-index"
1428+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
1429+
1430+[[package]]
1431+name = "signal-hook-registry"
1432+version = "1.2.2"
1433+source = "registry+https://github.com/rust-lang/crates.io-index"
1434+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
1435+dependencies = [
1436+ "libc",
1437+]
1438+
1439+[[package]]
1440+name = "slab"
1441+version = "0.4.2"
1442+source = "registry+https://github.com/rust-lang/crates.io-index"
1443+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1444+
1445+[[package]]
1446+name = "smallvec"
1447+version = "1.5.0"
1448+source = "registry+https://github.com/rust-lang/crates.io-index"
1449+checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
1450+
1451+[[package]]
1452+name = "socket2"
1453+version = "0.3.17"
1454+source = "registry+https://github.com/rust-lang/crates.io-index"
1455+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
1456+dependencies = [
1457+ "cfg-if 1.0.0",
1458+ "libc",
1459+ "redox_syscall",
1460+ "winapi 0.3.9",
1461+]
1462+
1463+[[package]]
1464+name = "standback"
1465+version = "0.2.13"
1466+source = "registry+https://github.com/rust-lang/crates.io-index"
1467+checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8"
1468+dependencies = [
1469+ "version_check",
1470+]
1471+
1472+[[package]]
1473+name = "static_assertions"
1474+version = "1.1.0"
1475+source = "registry+https://github.com/rust-lang/crates.io-index"
1476+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1477+
1478+[[package]]
1479+name = "stdweb"
1480+version = "0.4.20"
1481+source = "registry+https://github.com/rust-lang/crates.io-index"
1482+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
1483+dependencies = [
1484+ "discard",
1485+ "rustc_version",
1486+ "stdweb-derive",
1487+ "stdweb-internal-macros",
1488+ "stdweb-internal-runtime",
1489+ "wasm-bindgen",
1490+]
1491+
1492+[[package]]
1493+name = "stdweb-derive"
1494+version = "0.5.3"
1495+source = "registry+https://github.com/rust-lang/crates.io-index"
1496+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
1497+dependencies = [
1498+ "proc-macro2",
1499+ "quote",
1500+ "serde",
1501+ "serde_derive",
1502+ "syn",
1503+]
1504+
1505+[[package]]
1506+name = "stdweb-internal-macros"
1507+version = "0.2.9"
1508+source = "registry+https://github.com/rust-lang/crates.io-index"
1509+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
1510+dependencies = [
1511+ "base-x",
1512+ "proc-macro2",
1513+ "quote",
1514+ "serde",
1515+ "serde_derive",
1516+ "serde_json",
1517+ "sha1",
1518+ "syn",
1519+]
1520+
1521+[[package]]
1522+name = "stdweb-internal-runtime"
1523+version = "0.1.5"
1524+source = "registry+https://github.com/rust-lang/crates.io-index"
1525+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
1526+
1527+[[package]]
1528+name = "syn"
1529+version = "1.0.53"
1530+source = "registry+https://github.com/rust-lang/crates.io-index"
1531+checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
1532+dependencies = [
1533+ "proc-macro2",
1534+ "quote",
1535+ "unicode-xid",
1536+]
1537+
1538+[[package]]
1539+name = "tap"
1540+version = "1.0.0"
1541+source = "registry+https://github.com/rust-lang/crates.io-index"
1542+checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
1543+
1544+[[package]]
1545+name = "thiserror"
1546+version = "1.0.22"
1547+source = "registry+https://github.com/rust-lang/crates.io-index"
1548+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
1549+dependencies = [
1550+ "thiserror-impl",
1551+]
1552+
1553+[[package]]
1554+name = "thiserror-impl"
1555+version = "1.0.22"
1556+source = "registry+https://github.com/rust-lang/crates.io-index"
1557+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
1558+dependencies = [
1559+ "proc-macro2",
1560+ "quote",
1561+ "syn",
1562+]
1563+
1564+[[package]]
1565+name = "thread_local"
1566+version = "1.0.1"
1567+source = "registry+https://github.com/rust-lang/crates.io-index"
1568+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
1569+dependencies = [
1570+ "lazy_static",
1571+]
1572+
1573+[[package]]
1574+name = "threadpool"
1575+version = "1.8.1"
1576+source = "registry+https://github.com/rust-lang/crates.io-index"
1577+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
1578+dependencies = [
1579+ "num_cpus",
1580+]
1581+
1582+[[package]]
1583+name = "time"
1584+version = "0.2.23"
1585+source = "registry+https://github.com/rust-lang/crates.io-index"
1586+checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b"
1587+dependencies = [
1588+ "const_fn",
1589+ "libc",
1590+ "standback",
1591+ "stdweb",
1592+ "time-macros",
1593+ "version_check",
1594+ "winapi 0.3.9",
1595+]
1596+
1597+[[package]]
1598+name = "time-macros"
1599+version = "0.1.1"
1600+source = "registry+https://github.com/rust-lang/crates.io-index"
1601+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
1602+dependencies = [
1603+ "proc-macro-hack",
1604+ "time-macros-impl",
1605+]
1606+
1607+[[package]]
1608+name = "time-macros-impl"
1609+version = "0.1.1"
1610+source = "registry+https://github.com/rust-lang/crates.io-index"
1611+checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
1612+dependencies = [
1613+ "proc-macro-hack",
1614+ "proc-macro2",
1615+ "quote",
1616+ "standback",
1617+ "syn",
1618+]
1619+
1620+[[package]]
1621+name = "tinyvec"
1622+version = "1.1.0"
1623+source = "registry+https://github.com/rust-lang/crates.io-index"
1624+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
1625+dependencies = [
1626+ "tinyvec_macros",
1627+]
1628+
1629+[[package]]
1630+name = "tinyvec_macros"
1631+version = "0.1.0"
1632+source = "registry+https://github.com/rust-lang/crates.io-index"
1633+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1634+
1635+[[package]]
1636+name = "tokio"
1637+version = "0.2.23"
1638+source = "registry+https://github.com/rust-lang/crates.io-index"
1639+checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff"
1640+dependencies = [
1641+ "bytes",
1642+ "futures-core",
1643+ "iovec",
1644+ "lazy_static",
1645+ "libc",
1646+ "memchr",
1647+ "mio",
1648+ "mio-uds",
1649+ "pin-project-lite 0.1.11",
1650+ "signal-hook-registry",
1651+ "slab",
1652+ "winapi 0.3.9",
1653+]
1654+
1655+[[package]]
1656+name = "tokio-util"
1657+version = "0.3.1"
1658+source = "registry+https://github.com/rust-lang/crates.io-index"
1659+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
1660+dependencies = [
1661+ "bytes",
1662+ "futures-core",
1663+ "futures-sink",
1664+ "log",
1665+ "pin-project-lite 0.1.11",
1666+ "tokio",
1667+]
1668+
1669+[[package]]
1670+name = "toml"
1671+version = "0.5.7"
1672+source = "registry+https://github.com/rust-lang/crates.io-index"
1673+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
1674+dependencies = [
1675+ "serde",
1676+]
1677+
1678+[[package]]
1679+name = "tracing"
1680+version = "0.1.22"
1681+source = "registry+https://github.com/rust-lang/crates.io-index"
1682+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
1683+dependencies = [
1684+ "cfg-if 1.0.0",
1685+ "log",
1686+ "pin-project-lite 0.2.0",
1687+ "tracing-core",
1688+]
1689+
1690+[[package]]
1691+name = "tracing-core"
1692+version = "0.1.17"
1693+source = "registry+https://github.com/rust-lang/crates.io-index"
1694+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
1695+dependencies = [
1696+ "lazy_static",
1697+]
1698+
1699+[[package]]
1700+name = "tracing-futures"
1701+version = "0.2.4"
1702+source = "registry+https://github.com/rust-lang/crates.io-index"
1703+checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c"
1704+dependencies = [
1705+ "pin-project 0.4.27",
1706+ "tracing",
1707+]
1708+
1709+[[package]]
1710+name = "trust-dns-proto"
1711+version = "0.19.6"
1712+source = "registry+https://github.com/rust-lang/crates.io-index"
1713+checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b"
1714+dependencies = [
1715+ "async-trait",
1716+ "backtrace",
1717+ "enum-as-inner",
1718+ "futures",
1719+ "idna",
1720+ "lazy_static",
1721+ "log",
1722+ "rand",
1723+ "smallvec",
1724+ "thiserror",
1725+ "tokio",
1726+ "url",
1727+]
1728+
1729+[[package]]
1730+name = "trust-dns-resolver"
1731+version = "0.19.6"
1732+source = "registry+https://github.com/rust-lang/crates.io-index"
1733+checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e"
1734+dependencies = [
1735+ "backtrace",
1736+ "cfg-if 0.1.10",
1737+ "futures",
1738+ "ipconfig",
1739+ "lazy_static",
1740+ "log",
1741+ "lru-cache",
1742+ "resolv-conf",
1743+ "smallvec",
1744+ "thiserror",
1745+ "tokio",
1746+ "trust-dns-proto",
1747+]
1748+
1749+[[package]]
1750+name = "typenum"
1751+version = "1.12.0"
1752+source = "registry+https://github.com/rust-lang/crates.io-index"
1753+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1754+
1755+[[package]]
1756+name = "unicode-bidi"
1757+version = "0.3.4"
1758+source = "registry+https://github.com/rust-lang/crates.io-index"
1759+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
1760+dependencies = [
1761+ "matches",
1762+]
1763+
1764+[[package]]
1765+name = "unicode-normalization"
1766+version = "0.1.16"
1767+source = "registry+https://github.com/rust-lang/crates.io-index"
1768+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
1769+dependencies = [
1770+ "tinyvec",
1771+]
1772+
1773+[[package]]
1774+name = "unicode-segmentation"
1775+version = "1.7.1"
1776+source = "registry+https://github.com/rust-lang/crates.io-index"
1777+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1778+
1779+[[package]]
1780+name = "unicode-xid"
1781+version = "0.2.1"
1782+source = "registry+https://github.com/rust-lang/crates.io-index"
1783+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1784+
1785+[[package]]
1786+name = "url"
1787+version = "2.2.0"
1788+source = "registry+https://github.com/rust-lang/crates.io-index"
1789+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
1790+dependencies = [
1791+ "form_urlencoded",
1792+ "idna",
1793+ "matches",
1794+ "percent-encoding",
1795+]
1796+
1797+[[package]]
1798+name = "version_check"
1799+version = "0.9.2"
1800+source = "registry+https://github.com/rust-lang/crates.io-index"
1801+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1802+
1803+[[package]]
1804+name = "wasi"
1805+version = "0.9.0+wasi-snapshot-preview1"
1806+source = "registry+https://github.com/rust-lang/crates.io-index"
1807+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1808+
1809+[[package]]
1810+name = "wasm-bindgen"
1811+version = "0.2.69"
1812+source = "registry+https://github.com/rust-lang/crates.io-index"
1813+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
1814+dependencies = [
1815+ "cfg-if 1.0.0",
1816+ "wasm-bindgen-macro",
1817+]
1818+
1819+[[package]]
1820+name = "wasm-bindgen-backend"
1821+version = "0.2.69"
1822+source = "registry+https://github.com/rust-lang/crates.io-index"
1823+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
1824+dependencies = [
1825+ "bumpalo",
1826+ "lazy_static",
1827+ "log",
1828+ "proc-macro2",
1829+ "quote",
1830+ "syn",
1831+ "wasm-bindgen-shared",
1832+]
1833+
1834+[[package]]
1835+name = "wasm-bindgen-macro"
1836+version = "0.2.69"
1837+source = "registry+https://github.com/rust-lang/crates.io-index"
1838+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
1839+dependencies = [
1840+ "quote",
1841+ "wasm-bindgen-macro-support",
1842+]
1843+
1844+[[package]]
1845+name = "wasm-bindgen-macro-support"
1846+version = "0.2.69"
1847+source = "registry+https://github.com/rust-lang/crates.io-index"
1848+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
1849+dependencies = [
1850+ "proc-macro2",
1851+ "quote",
1852+ "syn",
1853+ "wasm-bindgen-backend",
1854+ "wasm-bindgen-shared",
1855+]
1856+
1857+[[package]]
1858+name = "wasm-bindgen-shared"
1859+version = "0.2.69"
1860+source = "registry+https://github.com/rust-lang/crates.io-index"
1861+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
1862+
1863+[[package]]
1864+name = "web-app"
1865+version = "0.2.0"
1866+dependencies = [
1867+ "actix-rt",
1868+ "actix-web",
1869+ "askama",
1870+]
1871+
1872+[[package]]
1873+name = "widestring"
1874+version = "0.4.3"
1875+source = "registry+https://github.com/rust-lang/crates.io-index"
1876+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
1877+
1878+[[package]]
1879+name = "winapi"
1880+version = "0.2.8"
1881+source = "registry+https://github.com/rust-lang/crates.io-index"
1882+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
1883+
1884+[[package]]
1885+name = "winapi"
1886+version = "0.3.9"
1887+source = "registry+https://github.com/rust-lang/crates.io-index"
1888+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1889+dependencies = [
1890+ "winapi-i686-pc-windows-gnu",
1891+ "winapi-x86_64-pc-windows-gnu",
1892+]
1893+
1894+[[package]]
1895+name = "winapi-build"
1896+version = "0.1.1"
1897+source = "registry+https://github.com/rust-lang/crates.io-index"
1898+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
1899+
1900+[[package]]
1901+name = "winapi-i686-pc-windows-gnu"
1902+version = "0.4.0"
1903+source = "registry+https://github.com/rust-lang/crates.io-index"
1904+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1905+
1906+[[package]]
1907+name = "winapi-x86_64-pc-windows-gnu"
1908+version = "0.4.0"
1909+source = "registry+https://github.com/rust-lang/crates.io-index"
1910+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1911+
1912+[[package]]
1913+name = "winreg"
1914+version = "0.6.2"
1915+source = "registry+https://github.com/rust-lang/crates.io-index"
1916+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
1917+dependencies = [
1918+ "winapi 0.3.9",
1919+]
1920+
1921+[[package]]
1922+name = "ws2_32-sys"
1923+version = "0.2.1"
1924+source = "registry+https://github.com/rust-lang/crates.io-index"
1925+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
1926+dependencies = [
1927+ "winapi 0.2.8",
1928+ "winapi-build",
1929+]
1930+
1931+[[package]]
1932+name = "wyz"
1933+version = "0.2.0"
1934+source = "registry+https://github.com/rust-lang/crates.io-index"
1935+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
examples/web-app-v2/Cargo.tomlnew file+12
@@ -0,0 +1,12 @@
1+[package]
2+name = "web-app"
3+version = "0.2.0"
4+authors = ["Typeable LLC <octopod@typeable.io>"]
5+edition = "2018"
6+
7+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+[dependencies]
10+actix-web = "3.3.2"
11+actix-rt = "1.1.1"
12+askama = "0.10.5"
examples/web-app-v2/README.mdnew file+5
@@ -0,0 +1,5 @@
1+# Build Docker image
2+
3+```bash
4+docker build -f docker/Dockerfile -t typeable/octopod-web-app-example:v2 .
5+```
examples/web-app-v2/charts/web-app/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
examples/web-app-v2/charts/web-app/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: web-app
5+version: 0.1.0
examples/web-app-v2/charts/web-app/templates/deployment.yamlnew file+48
@@ -0,0 +1,48 @@
1+apiVersion: apps/v1
2+kind: Deployment
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+spec:
7+ replicas: {{ .Values.replicas }}
8+ selector:
9+ matchLabels:
10+ app: {{ .Release.Name }}
11+ template:
12+ metadata:
13+ name: {{ .Release.Name }}
14+ labels:
15+ app: {{ .Release.Name }}
16+ spec:
17+ nodeSelector:
18+ role: {{ .Values.nodeselector }}
19+ terminationGracePeriodSeconds: 60
20+ containers:
21+ - name: main
22+ image: {{ .Values.image_prefix }}/{{ .Values.image }}:{{ .Values.image_tag }}
23+ env:
24+ {{- range $key, $value := .Values.env }}
25+ - name: {{ $key | quote }}
26+ value: {{ $value | quote }}
27+ {{- end }}
28+ ports:
29+ - containerPort: 8080
30+ protocol: TCP
31+ resources:
32+ requests:
33+ cpu: 0.1
34+ memory: 64Mi
35+ limits:
36+ cpu: 0.1
37+ memory: 128Mi
38+ readinessProbe:
39+ httpGet:
40+ port: 8080
41+ path: /
42+ periodSeconds: 20
43+ livenessProbe:
44+ httpGet:
45+ port: 8080
46+ path: /
47+ initialDelaySeconds: 15
48+ periodSeconds: 5
examples/web-app-v2/charts/web-app/templates/ingress.yamlnew file+26
@@ -0,0 +1,26 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ kubernetes.io/tls-acme: "true"
9+ cert-manager.io/issuer: "{{ .Release.Name }}-certs"
10+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
12+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
13+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
14+spec:
15+ tls:
16+ - hosts:
17+ - {{ .Values.domain }}
18+ secretName: {{ .Release.Name }}-tls
19+ rules:
20+ - host: {{ .Values.domain }}
21+ http:
22+ paths:
23+ - path: /
24+ backend:
25+ serviceName: {{ .Release.Name }}
26+ servicePort: 80
examples/web-app-v2/charts/web-app/templates/issuer.yamlnew file+18
@@ -0,0 +1,18 @@
1+apiVersion: cert-manager.io/v1
2+kind: Issuer
3+metadata:
4+ name: {{ .Release.Name }}-certs
5+ namespace: {{ .Release.Namespace }}
6+spec:
7+ acme:
8+ email: {{ .Values.acme_registration_email }}
9+ server: https://acme-v02.api.letsencrypt.org/directory
10+ privateKeySecretRef:
11+ name: {{ .Release.Name }}-letsencrypt
12+ # ACME HTTP-01 provider configurations
13+ solvers:
14+ # An empty 'selector' means that this solver matches all domains
15+ - selector: {}
16+ http01:
17+ ingress:
18+ class: nginx
examples/web-app-v2/charts/web-app/templates/service.yamlnew file+14
@@ -0,0 +1,14 @@
1+apiVersion: v1
2+kind: Service
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+ labels:
7+ app: {{ .Release.Name }}
8+spec:
9+ selector:
10+ app: {{ .Release.Name }}
11+ ports:
12+ - name: app
13+ port: 80
14+ targetPort: 8080
examples/web-app-v2/charts/web-app/values.yamlnew file+8
@@ -0,0 +1,8 @@
1+image_prefix:
2+image: web-app-example
3+image_tag:
4+namespace: deployment
5+domain:
6+nodeselector: stand
7+replicas: 1
8+env:
examples/web-app-v2/docker/Dockerfilenew file+22
@@ -0,0 +1,22 @@
1+FROM ekidd/rust-musl-builder:1.48.0 as builder
2+
3+RUN sudo mkdir /app && \
4+ sudo chown -R rust:rust /app
5+WORKDIR /app
6+RUN USER=root cargo new web-app
7+WORKDIR /app/web-app
8+COPY Cargo.toml /app/web-app/
9+COPY Cargo.lock /app/web-app/
10+RUN cargo build --release
11+COPY src /app/web-app/src
12+COPY templates /app/web-app/templates
13+RUN rm -r ./target/x86_64-unknown-linux-musl/release/deps/web_app* && \
14+ cargo build --release --frozen
15+
16+FROM alpine:3.12.1
17+
18+COPY --from=builder /app/web-app/target/x86_64-unknown-linux-musl/release/web-app /app/web-app
19+RUN addgroup -g 1000 user && \
20+ adduser -D -s /bin/sh -g 1000 -G user user
21+
22+ENTRYPOINT ["/app/web-app"]
examples/web-app-v2/src/main.rsnew file+43
@@ -0,0 +1,43 @@
1+use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
2+use askama::Template;
3+
4+#[derive(Template)]
5+#[template(path = "index.html")]
6+struct IndexTemplate {
7+ envs: Vec<Env>,
8+}
9+
10+#[derive(Debug, Clone, Template)]
11+#[template(source = "{{ key }} = {{ value }}", ext = "txt")]
12+struct Env {
13+ key: String,
14+ value: String,
15+}
16+
17+#[get("/")]
18+async fn index(state: web::Data<Vec<Env>>) -> impl Responder {
19+ let body = IndexTemplate {
20+ envs: state.to_vec(),
21+ }
22+ .render()
23+ .expect("could not render template");
24+ HttpResponse::Ok().body(body)
25+}
26+
27+#[actix_rt::main]
28+async fn main() -> std::io::Result<()> {
29+ HttpServer::new(|| App::new().data(get_app_envs()).service(index))
30+ .bind("0.0.0.0:8080")?
31+ .run()
32+ .await
33+}
34+
35+fn get_app_envs() -> Vec<Env> {
36+ std::env::vars()
37+ .filter(|env| env.0.starts_with("APP_ENV"))
38+ .map(|env| Env {
39+ key: env.0,
40+ value: env.1,
41+ })
42+ .collect::<Vec<_>>()
43+}
examples/web-app-v2/templates/index.htmlnew file+16
@@ -0,0 +1,16 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta name="title" content="Web App" />
5+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6+ </head>
7+ <body>
8+ <div>
9+ <ul>
10+ {% for env in envs %}
11+ <li>{{ env }}</li>
12+ {% endfor %}
13+ </ul>
14+ </div>
15+ </body>
16+</html>
examples/web-app/.gitignorenew file+1
@@ -0,0 +1 @@
1+/target
examples/web-app/Cargo.locknew file+1935
@@ -0,0 +1,1935 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+[[package]]
4+name = "actix-codec"
5+version = "0.3.0"
6+source = "registry+https://github.com/rust-lang/crates.io-index"
7+checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
8+dependencies = [
9+ "bitflags",
10+ "bytes",
11+ "futures-core",
12+ "futures-sink",
13+ "log",
14+ "pin-project 0.4.27",
15+ "tokio",
16+ "tokio-util",
17+]
18+
19+[[package]]
20+name = "actix-connect"
21+version = "2.0.0"
22+source = "registry+https://github.com/rust-lang/crates.io-index"
23+checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
24+dependencies = [
25+ "actix-codec",
26+ "actix-rt",
27+ "actix-service",
28+ "actix-utils",
29+ "derive_more",
30+ "either",
31+ "futures-util",
32+ "http",
33+ "log",
34+ "trust-dns-proto",
35+ "trust-dns-resolver",
36+]
37+
38+[[package]]
39+name = "actix-http"
40+version = "2.2.0"
41+source = "registry+https://github.com/rust-lang/crates.io-index"
42+checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874"
43+dependencies = [
44+ "actix-codec",
45+ "actix-connect",
46+ "actix-rt",
47+ "actix-service",
48+ "actix-threadpool",
49+ "actix-utils",
50+ "base64",
51+ "bitflags",
52+ "brotli2",
53+ "bytes",
54+ "cookie",
55+ "copyless",
56+ "derive_more",
57+ "either",
58+ "encoding_rs",
59+ "flate2",
60+ "futures-channel",
61+ "futures-core",
62+ "futures-util",
63+ "fxhash",
64+ "h2",
65+ "http",
66+ "httparse",
67+ "indexmap",
68+ "itoa",
69+ "language-tags",
70+ "lazy_static",
71+ "log",
72+ "mime",
73+ "percent-encoding",
74+ "pin-project 1.0.2",
75+ "rand",
76+ "regex",
77+ "serde",
78+ "serde_json",
79+ "serde_urlencoded",
80+ "sha-1",
81+ "slab",
82+ "time",
83+]
84+
85+[[package]]
86+name = "actix-macros"
87+version = "0.1.2"
88+source = "registry+https://github.com/rust-lang/crates.io-index"
89+checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2"
90+dependencies = [
91+ "quote",
92+ "syn",
93+]
94+
95+[[package]]
96+name = "actix-router"
97+version = "0.2.5"
98+source = "registry+https://github.com/rust-lang/crates.io-index"
99+checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235"
100+dependencies = [
101+ "bytestring",
102+ "http",
103+ "log",
104+ "regex",
105+ "serde",
106+]
107+
108+[[package]]
109+name = "actix-rt"
110+version = "1.1.1"
111+source = "registry+https://github.com/rust-lang/crates.io-index"
112+checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
113+dependencies = [
114+ "actix-macros",
115+ "actix-threadpool",
116+ "copyless",
117+ "futures-channel",
118+ "futures-util",
119+ "smallvec",
120+ "tokio",
121+]
122+
123+[[package]]
124+name = "actix-server"
125+version = "1.0.4"
126+source = "registry+https://github.com/rust-lang/crates.io-index"
127+checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
128+dependencies = [
129+ "actix-codec",
130+ "actix-rt",
131+ "actix-service",
132+ "actix-utils",
133+ "futures-channel",
134+ "futures-util",
135+ "log",
136+ "mio",
137+ "mio-uds",
138+ "num_cpus",
139+ "slab",
140+ "socket2",
141+]
142+
143+[[package]]
144+name = "actix-service"
145+version = "1.0.6"
146+source = "registry+https://github.com/rust-lang/crates.io-index"
147+checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb"
148+dependencies = [
149+ "futures-util",
150+ "pin-project 0.4.27",
151+]
152+
153+[[package]]
154+name = "actix-testing"
155+version = "1.0.1"
156+source = "registry+https://github.com/rust-lang/crates.io-index"
157+checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
158+dependencies = [
159+ "actix-macros",
160+ "actix-rt",
161+ "actix-server",
162+ "actix-service",
163+ "log",
164+ "socket2",
165+]
166+
167+[[package]]
168+name = "actix-threadpool"
169+version = "0.3.3"
170+source = "registry+https://github.com/rust-lang/crates.io-index"
171+checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30"
172+dependencies = [
173+ "derive_more",
174+ "futures-channel",
175+ "lazy_static",
176+ "log",
177+ "num_cpus",
178+ "parking_lot",
179+ "threadpool",
180+]
181+
182+[[package]]
183+name = "actix-tls"
184+version = "2.0.0"
185+source = "registry+https://github.com/rust-lang/crates.io-index"
186+checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
187+dependencies = [
188+ "actix-codec",
189+ "actix-service",
190+ "actix-utils",
191+ "futures-util",
192+]
193+
194+[[package]]
195+name = "actix-utils"
196+version = "2.0.0"
197+source = "registry+https://github.com/rust-lang/crates.io-index"
198+checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
199+dependencies = [
200+ "actix-codec",
201+ "actix-rt",
202+ "actix-service",
203+ "bitflags",
204+ "bytes",
205+ "either",
206+ "futures-channel",
207+ "futures-sink",
208+ "futures-util",
209+ "log",
210+ "pin-project 0.4.27",
211+ "slab",
212+]
213+
214+[[package]]
215+name = "actix-web"
216+version = "3.3.2"
217+source = "registry+https://github.com/rust-lang/crates.io-index"
218+checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86"
219+dependencies = [
220+ "actix-codec",
221+ "actix-http",
222+ "actix-macros",
223+ "actix-router",
224+ "actix-rt",
225+ "actix-server",
226+ "actix-service",
227+ "actix-testing",
228+ "actix-threadpool",
229+ "actix-tls",
230+ "actix-utils",
231+ "actix-web-codegen",
232+ "awc",
233+ "bytes",
234+ "derive_more",
235+ "encoding_rs",
236+ "futures-channel",
237+ "futures-core",
238+ "futures-util",
239+ "fxhash",
240+ "log",
241+ "mime",
242+ "pin-project 1.0.2",
243+ "regex",
244+ "serde",
245+ "serde_json",
246+ "serde_urlencoded",
247+ "socket2",
248+ "time",
249+ "tinyvec",
250+ "url",
251+]
252+
253+[[package]]
254+name = "actix-web-codegen"
255+version = "0.4.0"
256+source = "registry+https://github.com/rust-lang/crates.io-index"
257+checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb"
258+dependencies = [
259+ "proc-macro2",
260+ "quote",
261+ "syn",
262+]
263+
264+[[package]]
265+name = "addr2line"
266+version = "0.14.0"
267+source = "registry+https://github.com/rust-lang/crates.io-index"
268+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
269+dependencies = [
270+ "gimli",
271+]
272+
273+[[package]]
274+name = "adler"
275+version = "0.2.3"
276+source = "registry+https://github.com/rust-lang/crates.io-index"
277+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
278+
279+[[package]]
280+name = "aho-corasick"
281+version = "0.7.15"
282+source = "registry+https://github.com/rust-lang/crates.io-index"
283+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
284+dependencies = [
285+ "memchr",
286+]
287+
288+[[package]]
289+name = "arrayvec"
290+version = "0.5.2"
291+source = "registry+https://github.com/rust-lang/crates.io-index"
292+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
293+
294+[[package]]
295+name = "askama"
296+version = "0.10.5"
297+source = "registry+https://github.com/rust-lang/crates.io-index"
298+checksum = "d298738b6e47e1034e560e5afe63aa488fea34e25ec11b855a76f0d7b8e73134"
299+dependencies = [
300+ "askama_derive",
301+ "askama_escape",
302+ "askama_shared",
303+]
304+
305+[[package]]
306+name = "askama_derive"
307+version = "0.10.5"
308+source = "registry+https://github.com/rust-lang/crates.io-index"
309+checksum = "ca2925c4c290382f9d2fa3d1c1b6a63fa1427099721ecca4749b154cc9c25522"
310+dependencies = [
311+ "askama_shared",
312+ "proc-macro2",
313+ "syn",
314+]
315+
316+[[package]]
317+name = "askama_escape"
318+version = "0.10.1"
319+source = "registry+https://github.com/rust-lang/crates.io-index"
320+checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb"
321+
322+[[package]]
323+name = "askama_shared"
324+version = "0.11.1"
325+source = "registry+https://github.com/rust-lang/crates.io-index"
326+checksum = "2582b77e0f3c506ec4838a25fa8a5f97b9bed72bb6d3d272ea1c031d8bd373bc"
327+dependencies = [
328+ "askama_escape",
329+ "humansize",
330+ "nom",
331+ "num-traits",
332+ "percent-encoding",
333+ "proc-macro2",
334+ "quote",
335+ "serde",
336+ "syn",
337+ "toml",
338+]
339+
340+[[package]]
341+name = "async-trait"
342+version = "0.1.42"
343+source = "registry+https://github.com/rust-lang/crates.io-index"
344+checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
345+dependencies = [
346+ "proc-macro2",
347+ "quote",
348+ "syn",
349+]
350+
351+[[package]]
352+name = "autocfg"
353+version = "1.0.1"
354+source = "registry+https://github.com/rust-lang/crates.io-index"
355+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
356+
357+[[package]]
358+name = "awc"
359+version = "2.0.3"
360+source = "registry+https://github.com/rust-lang/crates.io-index"
361+checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
362+dependencies = [
363+ "actix-codec",
364+ "actix-http",
365+ "actix-rt",
366+ "actix-service",
367+ "base64",
368+ "bytes",
369+ "cfg-if 1.0.0",
370+ "derive_more",
371+ "futures-core",
372+ "log",
373+ "mime",
374+ "percent-encoding",
375+ "rand",
376+ "serde",
377+ "serde_json",
378+ "serde_urlencoded",
379+]
380+
381+[[package]]
382+name = "backtrace"
383+version = "0.3.55"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
386+dependencies = [
387+ "addr2line",
388+ "cfg-if 1.0.0",
389+ "libc",
390+ "miniz_oxide",
391+ "object",
392+ "rustc-demangle",
393+]
394+
395+[[package]]
396+name = "base-x"
397+version = "0.2.8"
398+source = "registry+https://github.com/rust-lang/crates.io-index"
399+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
400+
401+[[package]]
402+name = "base64"
403+version = "0.13.0"
404+source = "registry+https://github.com/rust-lang/crates.io-index"
405+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
406+
407+[[package]]
408+name = "bitflags"
409+version = "1.2.1"
410+source = "registry+https://github.com/rust-lang/crates.io-index"
411+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
412+
413+[[package]]
414+name = "bitvec"
415+version = "0.19.4"
416+source = "registry+https://github.com/rust-lang/crates.io-index"
417+checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
418+dependencies = [
419+ "funty",
420+ "radium",
421+ "tap",
422+ "wyz",
423+]
424+
425+[[package]]
426+name = "block-buffer"
427+version = "0.9.0"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
430+dependencies = [
431+ "generic-array",
432+]
433+
434+[[package]]
435+name = "brotli-sys"
436+version = "0.3.2"
437+source = "registry+https://github.com/rust-lang/crates.io-index"
438+checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
439+dependencies = [
440+ "cc",
441+ "libc",
442+]
443+
444+[[package]]
445+name = "brotli2"
446+version = "0.3.2"
447+source = "registry+https://github.com/rust-lang/crates.io-index"
448+checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
449+dependencies = [
450+ "brotli-sys",
451+ "libc",
452+]
453+
454+[[package]]
455+name = "bumpalo"
456+version = "3.4.0"
457+source = "registry+https://github.com/rust-lang/crates.io-index"
458+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
459+
460+[[package]]
461+name = "byteorder"
462+version = "1.3.4"
463+source = "registry+https://github.com/rust-lang/crates.io-index"
464+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
465+
466+[[package]]
467+name = "bytes"
468+version = "0.5.6"
469+source = "registry+https://github.com/rust-lang/crates.io-index"
470+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
471+
472+[[package]]
473+name = "bytestring"
474+version = "0.1.5"
475+source = "registry+https://github.com/rust-lang/crates.io-index"
476+checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363"
477+dependencies = [
478+ "bytes",
479+]
480+
481+[[package]]
482+name = "cc"
483+version = "1.0.65"
484+source = "registry+https://github.com/rust-lang/crates.io-index"
485+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
486+
487+[[package]]
488+name = "cfg-if"
489+version = "0.1.10"
490+source = "registry+https://github.com/rust-lang/crates.io-index"
491+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
492+
493+[[package]]
494+name = "cfg-if"
495+version = "1.0.0"
496+source = "registry+https://github.com/rust-lang/crates.io-index"
497+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
498+
499+[[package]]
500+name = "cloudabi"
501+version = "0.1.0"
502+source = "registry+https://github.com/rust-lang/crates.io-index"
503+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
504+dependencies = [
505+ "bitflags",
506+]
507+
508+[[package]]
509+name = "const_fn"
510+version = "0.4.3"
511+source = "registry+https://github.com/rust-lang/crates.io-index"
512+checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
513+
514+[[package]]
515+name = "cookie"
516+version = "0.14.3"
517+source = "registry+https://github.com/rust-lang/crates.io-index"
518+checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
519+dependencies = [
520+ "percent-encoding",
521+ "time",
522+ "version_check",
523+]
524+
525+[[package]]
526+name = "copyless"
527+version = "0.1.5"
528+source = "registry+https://github.com/rust-lang/crates.io-index"
529+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
530+
531+[[package]]
532+name = "cpuid-bool"
533+version = "0.1.2"
534+source = "registry+https://github.com/rust-lang/crates.io-index"
535+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
536+
537+[[package]]
538+name = "crc32fast"
539+version = "1.2.1"
540+source = "registry+https://github.com/rust-lang/crates.io-index"
541+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
542+dependencies = [
543+ "cfg-if 1.0.0",
544+]
545+
546+[[package]]
547+name = "derive_more"
548+version = "0.99.11"
549+source = "registry+https://github.com/rust-lang/crates.io-index"
550+checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
551+dependencies = [
552+ "proc-macro2",
553+ "quote",
554+ "syn",
555+]
556+
557+[[package]]
558+name = "digest"
559+version = "0.9.0"
560+source = "registry+https://github.com/rust-lang/crates.io-index"
561+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
562+dependencies = [
563+ "generic-array",
564+]
565+
566+[[package]]
567+name = "discard"
568+version = "1.0.4"
569+source = "registry+https://github.com/rust-lang/crates.io-index"
570+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
571+
572+[[package]]
573+name = "either"
574+version = "1.6.1"
575+source = "registry+https://github.com/rust-lang/crates.io-index"
576+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
577+
578+[[package]]
579+name = "encoding_rs"
580+version = "0.8.26"
581+source = "registry+https://github.com/rust-lang/crates.io-index"
582+checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283"
583+dependencies = [
584+ "cfg-if 1.0.0",
585+]
586+
587+[[package]]
588+name = "enum-as-inner"
589+version = "0.3.3"
590+source = "registry+https://github.com/rust-lang/crates.io-index"
591+checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
592+dependencies = [
593+ "heck",
594+ "proc-macro2",
595+ "quote",
596+ "syn",
597+]
598+
599+[[package]]
600+name = "flate2"
601+version = "1.0.19"
602+source = "registry+https://github.com/rust-lang/crates.io-index"
603+checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
604+dependencies = [
605+ "cfg-if 1.0.0",
606+ "crc32fast",
607+ "libc",
608+ "miniz_oxide",
609+]
610+
611+[[package]]
612+name = "fnv"
613+version = "1.0.7"
614+source = "registry+https://github.com/rust-lang/crates.io-index"
615+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
616+
617+[[package]]
618+name = "form_urlencoded"
619+version = "1.0.0"
620+source = "registry+https://github.com/rust-lang/crates.io-index"
621+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
622+dependencies = [
623+ "matches",
624+ "percent-encoding",
625+]
626+
627+[[package]]
628+name = "fuchsia-zircon"
629+version = "0.3.3"
630+source = "registry+https://github.com/rust-lang/crates.io-index"
631+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
632+dependencies = [
633+ "bitflags",
634+ "fuchsia-zircon-sys",
635+]
636+
637+[[package]]
638+name = "fuchsia-zircon-sys"
639+version = "0.3.3"
640+source = "registry+https://github.com/rust-lang/crates.io-index"
641+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
642+
643+[[package]]
644+name = "funty"
645+version = "1.0.1"
646+source = "registry+https://github.com/rust-lang/crates.io-index"
647+checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
648+
649+[[package]]
650+name = "futures"
651+version = "0.3.8"
652+source = "registry+https://github.com/rust-lang/crates.io-index"
653+checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
654+dependencies = [
655+ "futures-channel",
656+ "futures-core",
657+ "futures-io",
658+ "futures-sink",
659+ "futures-task",
660+ "futures-util",
661+]
662+
663+[[package]]
664+name = "futures-channel"
665+version = "0.3.8"
666+source = "registry+https://github.com/rust-lang/crates.io-index"
667+checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
668+dependencies = [
669+ "futures-core",
670+ "futures-sink",
671+]
672+
673+[[package]]
674+name = "futures-core"
675+version = "0.3.8"
676+source = "registry+https://github.com/rust-lang/crates.io-index"
677+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
678+
679+[[package]]
680+name = "futures-io"
681+version = "0.3.8"
682+source = "registry+https://github.com/rust-lang/crates.io-index"
683+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
684+
685+[[package]]
686+name = "futures-macro"
687+version = "0.3.8"
688+source = "registry+https://github.com/rust-lang/crates.io-index"
689+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
690+dependencies = [
691+ "proc-macro-hack",
692+ "proc-macro2",
693+ "quote",
694+ "syn",
695+]
696+
697+[[package]]
698+name = "futures-sink"
699+version = "0.3.8"
700+source = "registry+https://github.com/rust-lang/crates.io-index"
701+checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
702+
703+[[package]]
704+name = "futures-task"
705+version = "0.3.8"
706+source = "registry+https://github.com/rust-lang/crates.io-index"
707+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
708+dependencies = [
709+ "once_cell",
710+]
711+
712+[[package]]
713+name = "futures-util"
714+version = "0.3.8"
715+source = "registry+https://github.com/rust-lang/crates.io-index"
716+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
717+dependencies = [
718+ "futures-channel",
719+ "futures-core",
720+ "futures-io",
721+ "futures-macro",
722+ "futures-sink",
723+ "futures-task",
724+ "memchr",
725+ "pin-project 1.0.2",
726+ "pin-utils",
727+ "proc-macro-hack",
728+ "proc-macro-nested",
729+ "slab",
730+]
731+
732+[[package]]
733+name = "fxhash"
734+version = "0.2.1"
735+source = "registry+https://github.com/rust-lang/crates.io-index"
736+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
737+dependencies = [
738+ "byteorder",
739+]
740+
741+[[package]]
742+name = "generic-array"
743+version = "0.14.4"
744+source = "registry+https://github.com/rust-lang/crates.io-index"
745+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
746+dependencies = [
747+ "typenum",
748+ "version_check",
749+]
750+
751+[[package]]
752+name = "getrandom"
753+version = "0.1.15"
754+source = "registry+https://github.com/rust-lang/crates.io-index"
755+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
756+dependencies = [
757+ "cfg-if 0.1.10",
758+ "libc",
759+ "wasi",
760+]
761+
762+[[package]]
763+name = "gimli"
764+version = "0.23.0"
765+source = "registry+https://github.com/rust-lang/crates.io-index"
766+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
767+
768+[[package]]
769+name = "h2"
770+version = "0.2.7"
771+source = "registry+https://github.com/rust-lang/crates.io-index"
772+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
773+dependencies = [
774+ "bytes",
775+ "fnv",
776+ "futures-core",
777+ "futures-sink",
778+ "futures-util",
779+ "http",
780+ "indexmap",
781+ "slab",
782+ "tokio",
783+ "tokio-util",
784+ "tracing",
785+ "tracing-futures",
786+]
787+
788+[[package]]
789+name = "hashbrown"
790+version = "0.9.1"
791+source = "registry+https://github.com/rust-lang/crates.io-index"
792+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
793+
794+[[package]]
795+name = "heck"
796+version = "0.3.1"
797+source = "registry+https://github.com/rust-lang/crates.io-index"
798+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
799+dependencies = [
800+ "unicode-segmentation",
801+]
802+
803+[[package]]
804+name = "hermit-abi"
805+version = "0.1.17"
806+source = "registry+https://github.com/rust-lang/crates.io-index"
807+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
808+dependencies = [
809+ "libc",
810+]
811+
812+[[package]]
813+name = "hostname"
814+version = "0.3.1"
815+source = "registry+https://github.com/rust-lang/crates.io-index"
816+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
817+dependencies = [
818+ "libc",
819+ "match_cfg",
820+ "winapi 0.3.9",
821+]
822+
823+[[package]]
824+name = "http"
825+version = "0.2.1"
826+source = "registry+https://github.com/rust-lang/crates.io-index"
827+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
828+dependencies = [
829+ "bytes",
830+ "fnv",
831+ "itoa",
832+]
833+
834+[[package]]
835+name = "httparse"
836+version = "1.3.4"
837+source = "registry+https://github.com/rust-lang/crates.io-index"
838+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
839+
840+[[package]]
841+name = "humansize"
842+version = "1.1.0"
843+source = "registry+https://github.com/rust-lang/crates.io-index"
844+checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
845+
846+[[package]]
847+name = "idna"
848+version = "0.2.0"
849+source = "registry+https://github.com/rust-lang/crates.io-index"
850+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
851+dependencies = [
852+ "matches",
853+ "unicode-bidi",
854+ "unicode-normalization",
855+]
856+
857+[[package]]
858+name = "indexmap"
859+version = "1.6.0"
860+source = "registry+https://github.com/rust-lang/crates.io-index"
861+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
862+dependencies = [
863+ "autocfg",
864+ "hashbrown",
865+]
866+
867+[[package]]
868+name = "instant"
869+version = "0.1.9"
870+source = "registry+https://github.com/rust-lang/crates.io-index"
871+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
872+dependencies = [
873+ "cfg-if 1.0.0",
874+]
875+
876+[[package]]
877+name = "iovec"
878+version = "0.1.4"
879+source = "registry+https://github.com/rust-lang/crates.io-index"
880+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
881+dependencies = [
882+ "libc",
883+]
884+
885+[[package]]
886+name = "ipconfig"
887+version = "0.2.2"
888+source = "registry+https://github.com/rust-lang/crates.io-index"
889+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
890+dependencies = [
891+ "socket2",
892+ "widestring",
893+ "winapi 0.3.9",
894+ "winreg",
895+]
896+
897+[[package]]
898+name = "itoa"
899+version = "0.4.6"
900+source = "registry+https://github.com/rust-lang/crates.io-index"
901+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
902+
903+[[package]]
904+name = "kernel32-sys"
905+version = "0.2.2"
906+source = "registry+https://github.com/rust-lang/crates.io-index"
907+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
908+dependencies = [
909+ "winapi 0.2.8",
910+ "winapi-build",
911+]
912+
913+[[package]]
914+name = "language-tags"
915+version = "0.2.2"
916+source = "registry+https://github.com/rust-lang/crates.io-index"
917+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
918+
919+[[package]]
920+name = "lazy_static"
921+version = "1.4.0"
922+source = "registry+https://github.com/rust-lang/crates.io-index"
923+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
924+
925+[[package]]
926+name = "lexical-core"
927+version = "0.7.4"
928+source = "registry+https://github.com/rust-lang/crates.io-index"
929+checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
930+dependencies = [
931+ "arrayvec",
932+ "bitflags",
933+ "cfg-if 0.1.10",
934+ "ryu",
935+ "static_assertions",
936+]
937+
938+[[package]]
939+name = "libc"
940+version = "0.2.80"
941+source = "registry+https://github.com/rust-lang/crates.io-index"
942+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
943+
944+[[package]]
945+name = "linked-hash-map"
946+version = "0.5.3"
947+source = "registry+https://github.com/rust-lang/crates.io-index"
948+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
949+
950+[[package]]
951+name = "lock_api"
952+version = "0.4.2"
953+source = "registry+https://github.com/rust-lang/crates.io-index"
954+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
955+dependencies = [
956+ "scopeguard",
957+]
958+
959+[[package]]
960+name = "log"
961+version = "0.4.11"
962+source = "registry+https://github.com/rust-lang/crates.io-index"
963+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
964+dependencies = [
965+ "cfg-if 0.1.10",
966+]
967+
968+[[package]]
969+name = "lru-cache"
970+version = "0.1.2"
971+source = "registry+https://github.com/rust-lang/crates.io-index"
972+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
973+dependencies = [
974+ "linked-hash-map",
975+]
976+
977+[[package]]
978+name = "match_cfg"
979+version = "0.1.0"
980+source = "registry+https://github.com/rust-lang/crates.io-index"
981+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
982+
983+[[package]]
984+name = "matches"
985+version = "0.1.8"
986+source = "registry+https://github.com/rust-lang/crates.io-index"
987+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
988+
989+[[package]]
990+name = "memchr"
991+version = "2.3.4"
992+source = "registry+https://github.com/rust-lang/crates.io-index"
993+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
994+
995+[[package]]
996+name = "mime"
997+version = "0.3.16"
998+source = "registry+https://github.com/rust-lang/crates.io-index"
999+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
1000+
1001+[[package]]
1002+name = "miniz_oxide"
1003+version = "0.4.3"
1004+source = "registry+https://github.com/rust-lang/crates.io-index"
1005+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
1006+dependencies = [
1007+ "adler",
1008+ "autocfg",
1009+]
1010+
1011+[[package]]
1012+name = "mio"
1013+version = "0.6.23"
1014+source = "registry+https://github.com/rust-lang/crates.io-index"
1015+checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
1016+dependencies = [
1017+ "cfg-if 0.1.10",
1018+ "fuchsia-zircon",
1019+ "fuchsia-zircon-sys",
1020+ "iovec",
1021+ "kernel32-sys",
1022+ "libc",
1023+ "log",
1024+ "miow",
1025+ "net2",
1026+ "slab",
1027+ "winapi 0.2.8",
1028+]
1029+
1030+[[package]]
1031+name = "mio-uds"
1032+version = "0.6.8"
1033+source = "registry+https://github.com/rust-lang/crates.io-index"
1034+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
1035+dependencies = [
1036+ "iovec",
1037+ "libc",
1038+ "mio",
1039+]
1040+
1041+[[package]]
1042+name = "miow"
1043+version = "0.2.2"
1044+source = "registry+https://github.com/rust-lang/crates.io-index"
1045+checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
1046+dependencies = [
1047+ "kernel32-sys",
1048+ "net2",
1049+ "winapi 0.2.8",
1050+ "ws2_32-sys",
1051+]
1052+
1053+[[package]]
1054+name = "net2"
1055+version = "0.2.36"
1056+source = "registry+https://github.com/rust-lang/crates.io-index"
1057+checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
1058+dependencies = [
1059+ "cfg-if 0.1.10",
1060+ "libc",
1061+ "winapi 0.3.9",
1062+]
1063+
1064+[[package]]
1065+name = "nom"
1066+version = "6.0.1"
1067+source = "registry+https://github.com/rust-lang/crates.io-index"
1068+checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0"
1069+dependencies = [
1070+ "bitvec",
1071+ "lexical-core",
1072+ "memchr",
1073+ "version_check",
1074+]
1075+
1076+[[package]]
1077+name = "num-traits"
1078+version = "0.2.14"
1079+source = "registry+https://github.com/rust-lang/crates.io-index"
1080+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
1081+dependencies = [
1082+ "autocfg",
1083+]
1084+
1085+[[package]]
1086+name = "num_cpus"
1087+version = "1.13.0"
1088+source = "registry+https://github.com/rust-lang/crates.io-index"
1089+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
1090+dependencies = [
1091+ "hermit-abi",
1092+ "libc",
1093+]
1094+
1095+[[package]]
1096+name = "object"
1097+version = "0.22.0"
1098+source = "registry+https://github.com/rust-lang/crates.io-index"
1099+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
1100+
1101+[[package]]
1102+name = "once_cell"
1103+version = "1.5.2"
1104+source = "registry+https://github.com/rust-lang/crates.io-index"
1105+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
1106+
1107+[[package]]
1108+name = "opaque-debug"
1109+version = "0.3.0"
1110+source = "registry+https://github.com/rust-lang/crates.io-index"
1111+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
1112+
1113+[[package]]
1114+name = "parking_lot"
1115+version = "0.11.1"
1116+source = "registry+https://github.com/rust-lang/crates.io-index"
1117+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
1118+dependencies = [
1119+ "instant",
1120+ "lock_api",
1121+ "parking_lot_core",
1122+]
1123+
1124+[[package]]
1125+name = "parking_lot_core"
1126+version = "0.8.0"
1127+source = "registry+https://github.com/rust-lang/crates.io-index"
1128+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
1129+dependencies = [
1130+ "cfg-if 0.1.10",
1131+ "cloudabi",
1132+ "instant",
1133+ "libc",
1134+ "redox_syscall",
1135+ "smallvec",
1136+ "winapi 0.3.9",
1137+]
1138+
1139+[[package]]
1140+name = "percent-encoding"
1141+version = "2.1.0"
1142+source = "registry+https://github.com/rust-lang/crates.io-index"
1143+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
1144+
1145+[[package]]
1146+name = "pin-project"
1147+version = "0.4.27"
1148+source = "registry+https://github.com/rust-lang/crates.io-index"
1149+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
1150+dependencies = [
1151+ "pin-project-internal 0.4.27",
1152+]
1153+
1154+[[package]]
1155+name = "pin-project"
1156+version = "1.0.2"
1157+source = "registry+https://github.com/rust-lang/crates.io-index"
1158+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
1159+dependencies = [
1160+ "pin-project-internal 1.0.2",
1161+]
1162+
1163+[[package]]
1164+name = "pin-project-internal"
1165+version = "0.4.27"
1166+source = "registry+https://github.com/rust-lang/crates.io-index"
1167+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
1168+dependencies = [
1169+ "proc-macro2",
1170+ "quote",
1171+ "syn",
1172+]
1173+
1174+[[package]]
1175+name = "pin-project-internal"
1176+version = "1.0.2"
1177+source = "registry+https://github.com/rust-lang/crates.io-index"
1178+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
1179+dependencies = [
1180+ "proc-macro2",
1181+ "quote",
1182+ "syn",
1183+]
1184+
1185+[[package]]
1186+name = "pin-project-lite"
1187+version = "0.1.11"
1188+source = "registry+https://github.com/rust-lang/crates.io-index"
1189+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
1190+
1191+[[package]]
1192+name = "pin-project-lite"
1193+version = "0.2.0"
1194+source = "registry+https://github.com/rust-lang/crates.io-index"
1195+checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
1196+
1197+[[package]]
1198+name = "pin-utils"
1199+version = "0.1.0"
1200+source = "registry+https://github.com/rust-lang/crates.io-index"
1201+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1202+
1203+[[package]]
1204+name = "ppv-lite86"
1205+version = "0.2.10"
1206+source = "registry+https://github.com/rust-lang/crates.io-index"
1207+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
1208+
1209+[[package]]
1210+name = "proc-macro-hack"
1211+version = "0.5.19"
1212+source = "registry+https://github.com/rust-lang/crates.io-index"
1213+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
1214+
1215+[[package]]
1216+name = "proc-macro-nested"
1217+version = "0.1.6"
1218+source = "registry+https://github.com/rust-lang/crates.io-index"
1219+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
1220+
1221+[[package]]
1222+name = "proc-macro2"
1223+version = "1.0.24"
1224+source = "registry+https://github.com/rust-lang/crates.io-index"
1225+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
1226+dependencies = [
1227+ "unicode-xid",
1228+]
1229+
1230+[[package]]
1231+name = "quick-error"
1232+version = "1.2.3"
1233+source = "registry+https://github.com/rust-lang/crates.io-index"
1234+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
1235+
1236+[[package]]
1237+name = "quote"
1238+version = "1.0.7"
1239+source = "registry+https://github.com/rust-lang/crates.io-index"
1240+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
1241+dependencies = [
1242+ "proc-macro2",
1243+]
1244+
1245+[[package]]
1246+name = "radium"
1247+version = "0.5.3"
1248+source = "registry+https://github.com/rust-lang/crates.io-index"
1249+checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
1250+
1251+[[package]]
1252+name = "rand"
1253+version = "0.7.3"
1254+source = "registry+https://github.com/rust-lang/crates.io-index"
1255+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
1256+dependencies = [
1257+ "getrandom",
1258+ "libc",
1259+ "rand_chacha",
1260+ "rand_core",
1261+ "rand_hc",
1262+]
1263+
1264+[[package]]
1265+name = "rand_chacha"
1266+version = "0.2.2"
1267+source = "registry+https://github.com/rust-lang/crates.io-index"
1268+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
1269+dependencies = [
1270+ "ppv-lite86",
1271+ "rand_core",
1272+]
1273+
1274+[[package]]
1275+name = "rand_core"
1276+version = "0.5.1"
1277+source = "registry+https://github.com/rust-lang/crates.io-index"
1278+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
1279+dependencies = [
1280+ "getrandom",
1281+]
1282+
1283+[[package]]
1284+name = "rand_hc"
1285+version = "0.2.0"
1286+source = "registry+https://github.com/rust-lang/crates.io-index"
1287+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
1288+dependencies = [
1289+ "rand_core",
1290+]
1291+
1292+[[package]]
1293+name = "redox_syscall"
1294+version = "0.1.57"
1295+source = "registry+https://github.com/rust-lang/crates.io-index"
1296+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
1297+
1298+[[package]]
1299+name = "regex"
1300+version = "1.4.2"
1301+source = "registry+https://github.com/rust-lang/crates.io-index"
1302+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
1303+dependencies = [
1304+ "aho-corasick",
1305+ "memchr",
1306+ "regex-syntax",
1307+ "thread_local",
1308+]
1309+
1310+[[package]]
1311+name = "regex-syntax"
1312+version = "0.6.21"
1313+source = "registry+https://github.com/rust-lang/crates.io-index"
1314+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
1315+
1316+[[package]]
1317+name = "resolv-conf"
1318+version = "0.7.0"
1319+source = "registry+https://github.com/rust-lang/crates.io-index"
1320+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
1321+dependencies = [
1322+ "hostname",
1323+ "quick-error",
1324+]
1325+
1326+[[package]]
1327+name = "rustc-demangle"
1328+version = "0.1.18"
1329+source = "registry+https://github.com/rust-lang/crates.io-index"
1330+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
1331+
1332+[[package]]
1333+name = "rustc_version"
1334+version = "0.2.3"
1335+source = "registry+https://github.com/rust-lang/crates.io-index"
1336+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
1337+dependencies = [
1338+ "semver",
1339+]
1340+
1341+[[package]]
1342+name = "ryu"
1343+version = "1.0.5"
1344+source = "registry+https://github.com/rust-lang/crates.io-index"
1345+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1346+
1347+[[package]]
1348+name = "scopeguard"
1349+version = "1.1.0"
1350+source = "registry+https://github.com/rust-lang/crates.io-index"
1351+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1352+
1353+[[package]]
1354+name = "semver"
1355+version = "0.9.0"
1356+source = "registry+https://github.com/rust-lang/crates.io-index"
1357+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
1358+dependencies = [
1359+ "semver-parser",
1360+]
1361+
1362+[[package]]
1363+name = "semver-parser"
1364+version = "0.7.0"
1365+source = "registry+https://github.com/rust-lang/crates.io-index"
1366+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1367+
1368+[[package]]
1369+name = "serde"
1370+version = "1.0.117"
1371+source = "registry+https://github.com/rust-lang/crates.io-index"
1372+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
1373+dependencies = [
1374+ "serde_derive",
1375+]
1376+
1377+[[package]]
1378+name = "serde_derive"
1379+version = "1.0.117"
1380+source = "registry+https://github.com/rust-lang/crates.io-index"
1381+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
1382+dependencies = [
1383+ "proc-macro2",
1384+ "quote",
1385+ "syn",
1386+]
1387+
1388+[[package]]
1389+name = "serde_json"
1390+version = "1.0.59"
1391+source = "registry+https://github.com/rust-lang/crates.io-index"
1392+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
1393+dependencies = [
1394+ "itoa",
1395+ "ryu",
1396+ "serde",
1397+]
1398+
1399+[[package]]
1400+name = "serde_urlencoded"
1401+version = "0.7.0"
1402+source = "registry+https://github.com/rust-lang/crates.io-index"
1403+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
1404+dependencies = [
1405+ "form_urlencoded",
1406+ "itoa",
1407+ "ryu",
1408+ "serde",
1409+]
1410+
1411+[[package]]
1412+name = "sha-1"
1413+version = "0.9.2"
1414+source = "registry+https://github.com/rust-lang/crates.io-index"
1415+checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
1416+dependencies = [
1417+ "block-buffer",
1418+ "cfg-if 1.0.0",
1419+ "cpuid-bool",
1420+ "digest",
1421+ "opaque-debug",
1422+]
1423+
1424+[[package]]
1425+name = "sha1"
1426+version = "0.6.0"
1427+source = "registry+https://github.com/rust-lang/crates.io-index"
1428+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
1429+
1430+[[package]]
1431+name = "signal-hook-registry"
1432+version = "1.2.2"
1433+source = "registry+https://github.com/rust-lang/crates.io-index"
1434+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
1435+dependencies = [
1436+ "libc",
1437+]
1438+
1439+[[package]]
1440+name = "slab"
1441+version = "0.4.2"
1442+source = "registry+https://github.com/rust-lang/crates.io-index"
1443+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1444+
1445+[[package]]
1446+name = "smallvec"
1447+version = "1.5.0"
1448+source = "registry+https://github.com/rust-lang/crates.io-index"
1449+checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
1450+
1451+[[package]]
1452+name = "socket2"
1453+version = "0.3.17"
1454+source = "registry+https://github.com/rust-lang/crates.io-index"
1455+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
1456+dependencies = [
1457+ "cfg-if 1.0.0",
1458+ "libc",
1459+ "redox_syscall",
1460+ "winapi 0.3.9",
1461+]
1462+
1463+[[package]]
1464+name = "standback"
1465+version = "0.2.13"
1466+source = "registry+https://github.com/rust-lang/crates.io-index"
1467+checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8"
1468+dependencies = [
1469+ "version_check",
1470+]
1471+
1472+[[package]]
1473+name = "static_assertions"
1474+version = "1.1.0"
1475+source = "registry+https://github.com/rust-lang/crates.io-index"
1476+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1477+
1478+[[package]]
1479+name = "stdweb"
1480+version = "0.4.20"
1481+source = "registry+https://github.com/rust-lang/crates.io-index"
1482+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
1483+dependencies = [
1484+ "discard",
1485+ "rustc_version",
1486+ "stdweb-derive",
1487+ "stdweb-internal-macros",
1488+ "stdweb-internal-runtime",
1489+ "wasm-bindgen",
1490+]
1491+
1492+[[package]]
1493+name = "stdweb-derive"
1494+version = "0.5.3"
1495+source = "registry+https://github.com/rust-lang/crates.io-index"
1496+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
1497+dependencies = [
1498+ "proc-macro2",
1499+ "quote",
1500+ "serde",
1501+ "serde_derive",
1502+ "syn",
1503+]
1504+
1505+[[package]]
1506+name = "stdweb-internal-macros"
1507+version = "0.2.9"
1508+source = "registry+https://github.com/rust-lang/crates.io-index"
1509+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
1510+dependencies = [
1511+ "base-x",
1512+ "proc-macro2",
1513+ "quote",
1514+ "serde",
1515+ "serde_derive",
1516+ "serde_json",
1517+ "sha1",
1518+ "syn",
1519+]
1520+
1521+[[package]]
1522+name = "stdweb-internal-runtime"
1523+version = "0.1.5"
1524+source = "registry+https://github.com/rust-lang/crates.io-index"
1525+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
1526+
1527+[[package]]
1528+name = "syn"
1529+version = "1.0.53"
1530+source = "registry+https://github.com/rust-lang/crates.io-index"
1531+checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
1532+dependencies = [
1533+ "proc-macro2",
1534+ "quote",
1535+ "unicode-xid",
1536+]
1537+
1538+[[package]]
1539+name = "tap"
1540+version = "1.0.0"
1541+source = "registry+https://github.com/rust-lang/crates.io-index"
1542+checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
1543+
1544+[[package]]
1545+name = "thiserror"
1546+version = "1.0.22"
1547+source = "registry+https://github.com/rust-lang/crates.io-index"
1548+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
1549+dependencies = [
1550+ "thiserror-impl",
1551+]
1552+
1553+[[package]]
1554+name = "thiserror-impl"
1555+version = "1.0.22"
1556+source = "registry+https://github.com/rust-lang/crates.io-index"
1557+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
1558+dependencies = [
1559+ "proc-macro2",
1560+ "quote",
1561+ "syn",
1562+]
1563+
1564+[[package]]
1565+name = "thread_local"
1566+version = "1.0.1"
1567+source = "registry+https://github.com/rust-lang/crates.io-index"
1568+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
1569+dependencies = [
1570+ "lazy_static",
1571+]
1572+
1573+[[package]]
1574+name = "threadpool"
1575+version = "1.8.1"
1576+source = "registry+https://github.com/rust-lang/crates.io-index"
1577+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
1578+dependencies = [
1579+ "num_cpus",
1580+]
1581+
1582+[[package]]
1583+name = "time"
1584+version = "0.2.23"
1585+source = "registry+https://github.com/rust-lang/crates.io-index"
1586+checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b"
1587+dependencies = [
1588+ "const_fn",
1589+ "libc",
1590+ "standback",
1591+ "stdweb",
1592+ "time-macros",
1593+ "version_check",
1594+ "winapi 0.3.9",
1595+]
1596+
1597+[[package]]
1598+name = "time-macros"
1599+version = "0.1.1"
1600+source = "registry+https://github.com/rust-lang/crates.io-index"
1601+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
1602+dependencies = [
1603+ "proc-macro-hack",
1604+ "time-macros-impl",
1605+]
1606+
1607+[[package]]
1608+name = "time-macros-impl"
1609+version = "0.1.1"
1610+source = "registry+https://github.com/rust-lang/crates.io-index"
1611+checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
1612+dependencies = [
1613+ "proc-macro-hack",
1614+ "proc-macro2",
1615+ "quote",
1616+ "standback",
1617+ "syn",
1618+]
1619+
1620+[[package]]
1621+name = "tinyvec"
1622+version = "1.1.0"
1623+source = "registry+https://github.com/rust-lang/crates.io-index"
1624+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
1625+dependencies = [
1626+ "tinyvec_macros",
1627+]
1628+
1629+[[package]]
1630+name = "tinyvec_macros"
1631+version = "0.1.0"
1632+source = "registry+https://github.com/rust-lang/crates.io-index"
1633+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1634+
1635+[[package]]
1636+name = "tokio"
1637+version = "0.2.23"
1638+source = "registry+https://github.com/rust-lang/crates.io-index"
1639+checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff"
1640+dependencies = [
1641+ "bytes",
1642+ "futures-core",
1643+ "iovec",
1644+ "lazy_static",
1645+ "libc",
1646+ "memchr",
1647+ "mio",
1648+ "mio-uds",
1649+ "pin-project-lite 0.1.11",
1650+ "signal-hook-registry",
1651+ "slab",
1652+ "winapi 0.3.9",
1653+]
1654+
1655+[[package]]
1656+name = "tokio-util"
1657+version = "0.3.1"
1658+source = "registry+https://github.com/rust-lang/crates.io-index"
1659+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
1660+dependencies = [
1661+ "bytes",
1662+ "futures-core",
1663+ "futures-sink",
1664+ "log",
1665+ "pin-project-lite 0.1.11",
1666+ "tokio",
1667+]
1668+
1669+[[package]]
1670+name = "toml"
1671+version = "0.5.7"
1672+source = "registry+https://github.com/rust-lang/crates.io-index"
1673+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
1674+dependencies = [
1675+ "serde",
1676+]
1677+
1678+[[package]]
1679+name = "tracing"
1680+version = "0.1.22"
1681+source = "registry+https://github.com/rust-lang/crates.io-index"
1682+checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
1683+dependencies = [
1684+ "cfg-if 1.0.0",
1685+ "log",
1686+ "pin-project-lite 0.2.0",
1687+ "tracing-core",
1688+]
1689+
1690+[[package]]
1691+name = "tracing-core"
1692+version = "0.1.17"
1693+source = "registry+https://github.com/rust-lang/crates.io-index"
1694+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
1695+dependencies = [
1696+ "lazy_static",
1697+]
1698+
1699+[[package]]
1700+name = "tracing-futures"
1701+version = "0.2.4"
1702+source = "registry+https://github.com/rust-lang/crates.io-index"
1703+checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c"
1704+dependencies = [
1705+ "pin-project 0.4.27",
1706+ "tracing",
1707+]
1708+
1709+[[package]]
1710+name = "trust-dns-proto"
1711+version = "0.19.6"
1712+source = "registry+https://github.com/rust-lang/crates.io-index"
1713+checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b"
1714+dependencies = [
1715+ "async-trait",
1716+ "backtrace",
1717+ "enum-as-inner",
1718+ "futures",
1719+ "idna",
1720+ "lazy_static",
1721+ "log",
1722+ "rand",
1723+ "smallvec",
1724+ "thiserror",
1725+ "tokio",
1726+ "url",
1727+]
1728+
1729+[[package]]
1730+name = "trust-dns-resolver"
1731+version = "0.19.6"
1732+source = "registry+https://github.com/rust-lang/crates.io-index"
1733+checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e"
1734+dependencies = [
1735+ "backtrace",
1736+ "cfg-if 0.1.10",
1737+ "futures",
1738+ "ipconfig",
1739+ "lazy_static",
1740+ "log",
1741+ "lru-cache",
1742+ "resolv-conf",
1743+ "smallvec",
1744+ "thiserror",
1745+ "tokio",
1746+ "trust-dns-proto",
1747+]
1748+
1749+[[package]]
1750+name = "typenum"
1751+version = "1.12.0"
1752+source = "registry+https://github.com/rust-lang/crates.io-index"
1753+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1754+
1755+[[package]]
1756+name = "unicode-bidi"
1757+version = "0.3.4"
1758+source = "registry+https://github.com/rust-lang/crates.io-index"
1759+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
1760+dependencies = [
1761+ "matches",
1762+]
1763+
1764+[[package]]
1765+name = "unicode-normalization"
1766+version = "0.1.16"
1767+source = "registry+https://github.com/rust-lang/crates.io-index"
1768+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
1769+dependencies = [
1770+ "tinyvec",
1771+]
1772+
1773+[[package]]
1774+name = "unicode-segmentation"
1775+version = "1.7.1"
1776+source = "registry+https://github.com/rust-lang/crates.io-index"
1777+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1778+
1779+[[package]]
1780+name = "unicode-xid"
1781+version = "0.2.1"
1782+source = "registry+https://github.com/rust-lang/crates.io-index"
1783+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1784+
1785+[[package]]
1786+name = "url"
1787+version = "2.2.0"
1788+source = "registry+https://github.com/rust-lang/crates.io-index"
1789+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
1790+dependencies = [
1791+ "form_urlencoded",
1792+ "idna",
1793+ "matches",
1794+ "percent-encoding",
1795+]
1796+
1797+[[package]]
1798+name = "version_check"
1799+version = "0.9.2"
1800+source = "registry+https://github.com/rust-lang/crates.io-index"
1801+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1802+
1803+[[package]]
1804+name = "wasi"
1805+version = "0.9.0+wasi-snapshot-preview1"
1806+source = "registry+https://github.com/rust-lang/crates.io-index"
1807+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1808+
1809+[[package]]
1810+name = "wasm-bindgen"
1811+version = "0.2.69"
1812+source = "registry+https://github.com/rust-lang/crates.io-index"
1813+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
1814+dependencies = [
1815+ "cfg-if 1.0.0",
1816+ "wasm-bindgen-macro",
1817+]
1818+
1819+[[package]]
1820+name = "wasm-bindgen-backend"
1821+version = "0.2.69"
1822+source = "registry+https://github.com/rust-lang/crates.io-index"
1823+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
1824+dependencies = [
1825+ "bumpalo",
1826+ "lazy_static",
1827+ "log",
1828+ "proc-macro2",
1829+ "quote",
1830+ "syn",
1831+ "wasm-bindgen-shared",
1832+]
1833+
1834+[[package]]
1835+name = "wasm-bindgen-macro"
1836+version = "0.2.69"
1837+source = "registry+https://github.com/rust-lang/crates.io-index"
1838+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
1839+dependencies = [
1840+ "quote",
1841+ "wasm-bindgen-macro-support",
1842+]
1843+
1844+[[package]]
1845+name = "wasm-bindgen-macro-support"
1846+version = "0.2.69"
1847+source = "registry+https://github.com/rust-lang/crates.io-index"
1848+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
1849+dependencies = [
1850+ "proc-macro2",
1851+ "quote",
1852+ "syn",
1853+ "wasm-bindgen-backend",
1854+ "wasm-bindgen-shared",
1855+]
1856+
1857+[[package]]
1858+name = "wasm-bindgen-shared"
1859+version = "0.2.69"
1860+source = "registry+https://github.com/rust-lang/crates.io-index"
1861+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
1862+
1863+[[package]]
1864+name = "web-app"
1865+version = "0.1.0"
1866+dependencies = [
1867+ "actix-rt",
1868+ "actix-web",
1869+ "askama",
1870+]
1871+
1872+[[package]]
1873+name = "widestring"
1874+version = "0.4.3"
1875+source = "registry+https://github.com/rust-lang/crates.io-index"
1876+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
1877+
1878+[[package]]
1879+name = "winapi"
1880+version = "0.2.8"
1881+source = "registry+https://github.com/rust-lang/crates.io-index"
1882+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
1883+
1884+[[package]]
1885+name = "winapi"
1886+version = "0.3.9"
1887+source = "registry+https://github.com/rust-lang/crates.io-index"
1888+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1889+dependencies = [
1890+ "winapi-i686-pc-windows-gnu",
1891+ "winapi-x86_64-pc-windows-gnu",
1892+]
1893+
1894+[[package]]
1895+name = "winapi-build"
1896+version = "0.1.1"
1897+source = "registry+https://github.com/rust-lang/crates.io-index"
1898+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
1899+
1900+[[package]]
1901+name = "winapi-i686-pc-windows-gnu"
1902+version = "0.4.0"
1903+source = "registry+https://github.com/rust-lang/crates.io-index"
1904+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1905+
1906+[[package]]
1907+name = "winapi-x86_64-pc-windows-gnu"
1908+version = "0.4.0"
1909+source = "registry+https://github.com/rust-lang/crates.io-index"
1910+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1911+
1912+[[package]]
1913+name = "winreg"
1914+version = "0.6.2"
1915+source = "registry+https://github.com/rust-lang/crates.io-index"
1916+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
1917+dependencies = [
1918+ "winapi 0.3.9",
1919+]
1920+
1921+[[package]]
1922+name = "ws2_32-sys"
1923+version = "0.2.1"
1924+source = "registry+https://github.com/rust-lang/crates.io-index"
1925+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
1926+dependencies = [
1927+ "winapi 0.2.8",
1928+ "winapi-build",
1929+]
1930+
1931+[[package]]
1932+name = "wyz"
1933+version = "0.2.0"
1934+source = "registry+https://github.com/rust-lang/crates.io-index"
1935+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
examples/web-app/Cargo.tomlnew file+12
@@ -0,0 +1,12 @@
1+[package]
2+name = "web-app"
3+version = "0.1.0"
4+authors = ["Typeable <octopod@typeable.io>"]
5+edition = "2018"
6+
7+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+[dependencies]
10+actix-web = "3.3.0"
11+actix-rt = "1.1.1"
12+askama = "0.10.5"
examples/web-app/README.mdnew file+5
@@ -0,0 +1,5 @@
1+# Build Docker image
2+
3+```bash
4+docker build -f docker/Dockerfile -t typeable/octopod-web-app-example:v1 .
5+```
examples/web-app/charts/web-app/.helmignorenew file+22
@@ -0,0 +1,22 @@
1+# Patterns to ignore when building packages.
2+# This supports shell glob matching, relative path matching, and
3+# negation (prefixed with !). Only one pattern per line.
4+.DS_Store
5+# Common VCS dirs
6+.git/
7+.gitignore
8+.bzr/
9+.bzrignore
10+.hg/
11+.hgignore
12+.svn/
13+# Common backup files
14+*.swp
15+*.bak
16+*.tmp
17+*~
18+# Various IDEs
19+.project
20+.idea/
21+*.tmproj
22+.vscode/
examples/web-app/charts/web-app/Chart.yamlnew file+5
@@ -0,0 +1,5 @@
1+apiVersion: v1
2+appVersion: "1.0"
3+description: A Helm chart for Kubernetes
4+name: web-app
5+version: 0.1.0
examples/web-app/charts/web-app/templates/deployment.yamlnew file+48
@@ -0,0 +1,48 @@
1+apiVersion: apps/v1
2+kind: Deployment
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+spec:
7+ replicas: {{ .Values.replicas }}
8+ selector:
9+ matchLabels:
10+ app: {{ .Release.Name }}
11+ template:
12+ metadata:
13+ name: {{ .Release.Name }}
14+ labels:
15+ app: {{ .Release.Name }}
16+ spec:
17+ nodeSelector:
18+ role: {{ .Values.nodeselector }}
19+ terminationGracePeriodSeconds: 60
20+ containers:
21+ - name: main
22+ image: {{ .Values.image_prefix }}/{{ .Values.image }}:{{ .Values.image_tag }}
23+ env:
24+ {{- range $key, $value := .Values.env }}
25+ - name: {{ $key | quote }}
26+ value: {{ $value | quote }}
27+ {{- end }}
28+ ports:
29+ - containerPort: 8080
30+ protocol: TCP
31+ resources:
32+ requests:
33+ cpu: 0.1
34+ memory: 64Mi
35+ limits:
36+ cpu: 0.1
37+ memory: 128Mi
38+ readinessProbe:
39+ httpGet:
40+ port: 8080
41+ path: /
42+ periodSeconds: 20
43+ livenessProbe:
44+ httpGet:
45+ port: 8080
46+ path: /
47+ initialDelaySeconds: 15
48+ periodSeconds: 5
examples/web-app/charts/web-app/templates/ingress.yamlnew file+26
@@ -0,0 +1,26 @@
1+apiVersion: extensions/v1beta1
2+kind: Ingress
3+metadata:
4+ name: {{ .Release.Name }}-nginx-ingress
5+ namespace: {{ .Values.namespace }}
6+ annotations:
7+ kubernetes.io/ingress.class: "nginx"
8+ kubernetes.io/tls-acme: "true"
9+ cert-manager.io/issuer: "{{ .Release.Name }}-certs"
10+ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
11+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
12+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
13+ nginx.ingress.kubernetes.io/proxy-next-upstream: "http_502 error timeout"
14+spec:
15+ tls:
16+ - hosts:
17+ - {{ .Values.domain }}
18+ secretName: {{ .Release.Name }}-tls
19+ rules:
20+ - host: {{ .Values.domain }}
21+ http:
22+ paths:
23+ - path: /
24+ backend:
25+ serviceName: {{ .Release.Name }}
26+ servicePort: 80
examples/web-app/charts/web-app/templates/issuer.yamlnew file+18
@@ -0,0 +1,18 @@
1+apiVersion: cert-manager.io/v1
2+kind: Issuer
3+metadata:
4+ name: {{ .Release.Name }}-certs
5+ namespace: {{ .Release.Namespace }}
6+spec:
7+ acme:
8+ email: {{ .Values.acme_registration_email }}
9+ server: https://acme-v02.api.letsencrypt.org/directory
10+ privateKeySecretRef:
11+ name: {{ .Release.Name }}-letsencrypt
12+ # ACME HTTP-01 provider configurations
13+ solvers:
14+ # An empty 'selector' means that this solver matches all domains
15+ - selector: {}
16+ http01:
17+ ingress:
18+ class: nginx
examples/web-app/charts/web-app/templates/service.yamlnew file+14
@@ -0,0 +1,14 @@
1+apiVersion: v1
2+kind: Service
3+metadata:
4+ name: {{ .Release.Name }}
5+ namespace: {{ .Values.namespace }}
6+ labels:
7+ app: {{ .Release.Name }}
8+spec:
9+ selector:
10+ app: {{ .Release.Name }}
11+ ports:
12+ - name: app
13+ port: 80
14+ targetPort: 8080
examples/web-app/charts/web-app/values.yamlnew file+8
@@ -0,0 +1,8 @@
1+image_prefix:
2+image: web-app-example
3+image_tag:
4+namespace: deployment
5+domain:
6+nodeselector: stand
7+replicas: 1
8+env:
examples/web-app/docker/Dockerfilenew file+22
@@ -0,0 +1,22 @@
1+FROM ekidd/rust-musl-builder:1.48.0 as builder
2+
3+RUN sudo mkdir /app && \
4+ sudo chown -R rust:rust /app
5+WORKDIR /app
6+RUN USER=root cargo new web-app
7+WORKDIR /app/web-app
8+COPY Cargo.toml /app/web-app/
9+COPY Cargo.lock /app/web-app/
10+RUN cargo build --release
11+COPY src /app/web-app/src
12+COPY templates /app/web-app/templates
13+RUN rm -r ./target/x86_64-unknown-linux-musl/release/deps/web_app* && \
14+ cargo build --release --frozen
15+
16+FROM alpine:3.12.1
17+
18+COPY --from=builder /app/web-app/target/x86_64-unknown-linux-musl/release/web-app /app/web-app
19+RUN addgroup -g 1000 user && \
20+ adduser -D -s /bin/sh -g 1000 -G user user
21+
22+ENTRYPOINT ["/app/web-app"]
examples/web-app/src/main.rsnew file+43
@@ -0,0 +1,43 @@
1+use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
2+use askama::Template;
3+
4+#[derive(Template)]
5+#[template(path = "index.html")]
6+struct IndexTemplate {
7+ envs: Vec<Env>,
8+}
9+
10+#[derive(Debug, Clone, Template)]
11+#[template(source = "{{ key }} = {{ value }}", ext = "txt")]
12+struct Env {
13+ key: String,
14+ value: String,
15+}
16+
17+#[get("/")]
18+async fn index(state: web::Data<Vec<Env>>) -> impl Responder {
19+ let body = IndexTemplate {
20+ envs: state.to_vec(),
21+ }
22+ .render()
23+ .expect("could not render template");
24+ HttpResponse::Ok().body(body)
25+}
26+
27+#[actix_rt::main]
28+async fn main() -> std::io::Result<()> {
29+ HttpServer::new(|| App::new().data(get_app_envs()).service(index))
30+ .bind("0.0.0.0:8080")?
31+ .run()
32+ .await
33+}
34+
35+fn get_app_envs() -> Vec<Env> {
36+ std::env::vars()
37+ .filter(|env| env.0.starts_with("APP_ENV"))
38+ .map(|env| Env {
39+ key: env.0,
40+ value: env.1,
41+ })
42+ .collect::<Vec<_>>()
43+}
examples/web-app/templates/index.htmlnew file+14
@@ -0,0 +1,14 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta name="title" content="Web App" />
5+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6+ </head>
7+ <body>
8+ <div>
9+ {% for env in envs %}
10+ <div>{{ env }}</div>
11+ {% endfor %}
12+ </div>
13+ </body>
14+</html>
hie.yamlnew file+19
@@ -0,0 +1,19 @@
1+cradle:
2+ stack:
3+ - path: "octo-cli/src"
4+ component: "octo-cli:exe:octo"
5+
6+ - path: "octopod-api/src"
7+ component: "octopod-api:lib"
8+
9+ - path: "octopod-backend/src"
10+ component: "octopod-backend:lib"
11+
12+ - path: "octopod-backend/app"
13+ component: "octopod-backend:exe:octopod-exe"
14+
15+ - path: "octopod-backend/test"
16+ component: "octopod-backend:test:octopod-test"
17+
18+ - path: "octopod-common/src"
19+ component: "octopod-common:lib"
img/demo.gifnew file

Binary file not shown.

img/typeable.pngnew file

Binary file not shown.

migrations/deploy/.gitkeepnew file

No changes to the content.

migrations/deploy/add_archived_status.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Deploy octopod:add_archived_status to pg
2+
3+BEGIN;
4+
5+COMMIT;
6+
7+ALTER TYPE statuses ADD VALUE 'Archived' AFTER 'DeletePending';
migrations/deploy/add_deployment_overrides_and_deployment_log_overrides.sqlnew file+83
@@ -0,0 +1,83 @@
1+-- Deploy octopod:add_deployment_overrides_and_deployment_log_overrides to pg
2+
3+BEGIN;
4+
5+CREATE TYPE scopes AS ENUM ('App', 'Staging');
6+CREATE TYPE visibilities AS ENUM ('Private', 'Public');
7+
8+CREATE TABLE deployment_overrides (
9+ id BIGSERIAL PRIMARY KEY,
10+ key TEXT NOT NULL,
11+ value TEXT NOT NULL,
12+ deployment_id BIGINT REFERENCES deployments(id),
13+ scope scopes NOT NULL,
14+ visibility visibilities NOT NULL,
15+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
16+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
17+);
18+CREATE UNIQUE INDEX deployment_overrides_key_deployment_id_key ON deployment_overrides USING BTREE (key, deployment_id, scope);
19+CREATE INDEX deployment_overrides_deployment_id_idx ON deployment_overrides USING BTREE (deployment_id);
20+
21+CREATE TABLE deployment_log_overrides (
22+ id BIGSERIAL PRIMARY KEY,
23+ key TEXT NOT NULL,
24+ value TEXT NOT NULL,
25+ deployment_log_id BIGINT REFERENCES deployment_logs(id),
26+ scope scopes NOT NULL,
27+ visibility visibilities NOT NULL,
28+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
29+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
30+);
31+CREATE UNIQUE INDEX deployment_log_overrides_key_deployment_log_id_key ON deployment_log_overrides USING BTREE (key, deployment_log_id, scope);
32+CREATE INDEX deployment_log_overrides_deployment_log_id_idx ON deployment_log_overrides USING BTREE (deployment_log_id);
33+
34+DO LANGUAGE 'plpgsql' $$
35+DECLARE
36+ _r RECORD;
37+ _r2 RECORD;
38+ _k TEXT;
39+ _v TEXT;
40+BEGIN
41+ FOR _r IN SELECT id, envs FROM deployments LOOP
42+ FOR _r2 IN SELECT regexp_split_to_table(_r.envs, '\n') as env LOOP
43+ _k = split_part(_r2.env, '=', 1);
44+ _v = split_part(_r2.env, '=', 2);
45+ IF _k != '' THEN
46+ INSERT INTO deployment_overrides (key, value, deployment_id, scope, visibility)
47+ VALUES (_k, _v, _r.id, 'App', 'Public')
48+ ON CONFLICT (key, deployment_id, scope)
49+ DO
50+ UPDATE SET value = _v, updated_at = now();
51+ END IF;
52+ END LOOP;
53+ END LOOP;
54+END
55+$$;
56+
57+DO LANGUAGE 'plpgsql' $$
58+DECLARE
59+ _r RECORD;
60+ _r2 RECORD;
61+ _k TEXT;
62+ _v TEXT;
63+BEGIN
64+ FOR _r IN SELECT id, envs FROM deployment_logs LOOP
65+ FOR _r2 IN SELECT regexp_split_to_table(_r.envs, '\n') as env LOOP
66+ _k = split_part(_r2.env, '=', 1);
67+ _v = split_part(_r2.env, '=', 2);
68+ IF _k != '' THEN
69+ INSERT INTO deployment_log_overrides (key, value, deployment_log_id, scope, visibility)
70+ VALUES (_k, _v, _r.id, 'App', 'Public')
71+ ON CONFLICT (key, deployment_log_id, scope)
72+ DO
73+ UPDATE SET value = _v, updated_at = now();
74+ END IF;
75+ END LOOP;
76+ END LOOP;
77+END
78+$$;
79+
80+ALTER TABLE deployments DROP envs;
81+ALTER TABLE deployment_logs DROP envs;
82+
83+COMMIT;
migrations/deploy/add_duration_and_stdout_and_stderr_to_deployment_logs.sqlnew file+9
@@ -0,0 +1,9 @@
1+-- Deploy octopod:add_duration_and_stdout_and_stderr_to_deployment_logs to pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployment_logs ADD duration INT NOT NULL DEFAULT 0;
6+ALTER TABLE deployment_logs ADD stdout TEXT NOT NULL DEFAULT '';
7+ALTER TABLE deployment_logs ADD stderr TEXT NOT NULL DEFAULT '';
8+
9+COMMIT;
migrations/deploy/archived_flag.sqlnew file+17
@@ -0,0 +1,17 @@
1+-- Deploy octopod:archived_flag to pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployments ADD archived BOOLEAN NOT NULL DEFAULT 'f';
6+ALTER TABLE deployments ADD archived_at TIMESTAMPTZ;
7+
8+CREATE INDEX deployments_archived_archived_at_idx ON deployments USING BTREE (archived, archived_at);
9+
10+
11+ALTER TABLE deployment_logs ADD archived BOOLEAN NOT NULL DEFAULT 'f';
12+
13+
14+COMMIT;
15+
16+ALTER TYPE actions ADD VALUE 'delete' AFTER 'update';
17+ALTER TYPE actions ADD VALUE 'restore' AFTER 'delete';
migrations/deploy/create_deployment_metadata.sqlnew file+16
@@ -0,0 +1,16 @@
1+-- Deploy octopod:create_deployment_metadata to pg
2+
3+BEGIN;
4+
5+CREATE TABLE deployment_metadata (
6+ id BIGSERIAL PRIMARY KEY,
7+ deployment_id BIGINT NOT NULL REFERENCES deployments(id),
8+ key TEXT NOT NULL,
9+ value TEXT NOT NULL,
10+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
11+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
12+);
13+
14+CREATE UNIQUE INDEX deployment_metadata_deployment_id_key_key ON deployment_metadata USING BTREE (deployment_id, key);
15+
16+COMMIT;
migrations/deploy/deployment_logs.sqlnew file+17
@@ -0,0 +1,17 @@
1+-- Deploy octopod:deployment_logs to pg
2+
3+BEGIN;
4+
5+CREATE TYPE actions AS ENUM ('create', 'edit', 'update');
6+
7+CREATE TABLE deployment_logs (
8+ id BIGSERIAL PRIMARY KEY,
9+ deployment_id BIGINT NOT NULL REFERENCES deployments(id),
10+ action actions,
11+ tag TEXT,
12+ envs TEXT,
13+ exit_code SMALLINT NOT NULL,
14+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
15+);
16+
17+COMMIT;
migrations/deploy/deployments.sqlnew file+16
@@ -0,0 +1,16 @@
1+-- Deploy octopod:deployments to pg
2+
3+BEGIN;
4+
5+CREATE TABLE deployments (
6+ id BIGSERIAL PRIMARY KEY,
7+ name TEXT NOT NULL,
8+ tag TEXT NOT NULL,
9+ envs TEXT NOT NULL,
10+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
11+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
12+);
13+
14+CREATE UNIQUE INDEX deployments_name_key ON deployments USING BTREE (name);
15+
16+COMMIT;
migrations/deploy/rename-delete-to-archive.sqlnew file+8
@@ -0,0 +1,8 @@
1+-- Deploy octopod:rename-delete-to-archive to pg
2+BEGIN;
3+
4+
5+ALTER TYPE statuses RENAME VALUE 'DeletePending' TO 'ArchivePending';
6+
7+
8+COMMIT;
migrations/deploy/rename_delete_to_archive_2.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Deploy octopod:rename_delete_to_archive_2 to pg
2+
3+BEGIN;
4+
5+ALTER TYPE actions RENAME VALUE 'delete' TO 'archive';
6+
7+COMMIT;
migrations/deploy/rename_elements_of_scope_enum.sqlnew file+8
@@ -0,0 +1,8 @@
1+-- Deploy octopod:rename_elements_of_scope_enum to pg
2+
3+BEGIN;
4+
5+ALTER TYPE scopes RENAME VALUE 'App' to 'ApplicationScope';
6+ALTER TYPE scopes RENAME VALUE 'Staging' to 'DeploymentScope';
7+
8+COMMIT;
migrations/deploy/status_and_status_updated_at.sqlnew file+13
@@ -0,0 +1,13 @@
1+-- Deploy octopod:status_and_status_updated_at to pg
2+
3+BEGIN;
4+
5+CREATE TYPE statuses as ENUM ('Running', 'Failure', 'CreatePending', 'UpdatePending', 'DeletePending');
6+
7+ALTER TABLE deployments ADD status statuses NOT NULL DEFAULT 'Running';
8+ALTER TABLE deployments ADD status_updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
9+ALTER TABLE deployments ADD checked_at TIMESTAMPTZ NOT NULL DEFAULT now();
10+
11+CREATE INDEX deployments_checked_at_idx ON deployments USING BTREE (checked_at);
12+
13+COMMIT;
migrations/revert/.gitkeepnew file

No changes to the content.

migrations/revert/add_archived_status.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Revert octopod:add_archived_status from pg
2+
3+BEGIN;
4+
5+-- XXX Add DDLs here.
6+
7+COMMIT;
migrations/revert/add_deployment_overrides_and_deployment_log_overrides.sqlnew file+46
@@ -0,0 +1,46 @@
1+-- Revert octopod:add_deployment_overrides_and_deployment_log_overrides from pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployments ADD envs TEXT NOT NULL DEFAULT '';
6+ALTER TABLE deployment_logs ADD envs TEXT NOT NULL DEFAULT '';
7+
8+DO LANGUAGE 'plpgsql' $$
9+DECLARE
10+ _r RECORD;
11+ _r2 RECORD;
12+ _buf TEXT;
13+BEGIN
14+ FOR _r IN SELECT DISTINCT deployment_id AS id FROM deployment_overrides LOOP
15+ _buf = '';
16+ FOR _r2 IN SELECT key || '=' || value AS env FROM deployment_overrides WHERE deployment_id = _r.id ORDER BY id LOOP
17+ _buf = _buf || _r2.env || E'\n';
18+ END LOOP;
19+ UPDATE deployments SET envs = rtrim(_buf, E'\n') WHERE id = _r.id;
20+ END LOOP;
21+END
22+$$;
23+
24+DO LANGUAGE 'plpgsql' $$
25+DECLARE
26+ _r RECORD;
27+ _r2 RECORD;
28+ _buf TEXT;
29+BEGIN
30+ FOR _r IN SELECT DISTINCT deployment_log_id AS id FROM deployment_log_overrides LOOP
31+ _buf = '';
32+ FOR _r2 IN SELECT key || '=' || value AS env FROM deployment_log_overrides WHERE deployment_log_id = _r.id ORDER BY id LOOP
33+ _buf = _buf || _r2.env || E'\n';
34+ END LOOP;
35+ UPDATE deployment_logs SET envs = rtrim(_buf, E'\n') WHERE id = _r.id;
36+ END LOOP;
37+END
38+$$;
39+
40+DROP TABLE deployment_overrides;
41+DROP TABLE deployment_log_overrides;
42+
43+DROP TYPE scopes;
44+DROP TYPE visibilities;
45+
46+COMMIT;
migrations/revert/add_duration_and_stdout_and_stderr_to_deployment_logs.sqlnew file+9
@@ -0,0 +1,9 @@
1+-- Revert octopod:add_duration_and_stdout_and_stderr_to_deployment_logs from pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployment_logs DROP duration;
6+ALTER TABLE deployment_logs DROP stdout;
7+ALTER TABLE deployment_logs DROP stderr;
8+
9+COMMIT;
migrations/revert/archived_flag.sqlnew file+10
@@ -0,0 +1,10 @@
1+-- Revert octopod:archived_flag from pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployments DROP archived;
6+ALTER TABLE deployments DROP archived_at;
7+
8+ALTER TABLE deployment_logs DROP archived;
9+
10+COMMIT;
migrations/revert/create_deployment_metadata.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Revert octopod:create_deployment_metadata from pg
2+
3+BEGIN;
4+
5+DROP TABLE deployment_metadata;
6+
7+COMMIT;
migrations/revert/deployment_logs.sqlnew file+9
@@ -0,0 +1,9 @@
1+-- Revert octopod:deployment_logs from pg
2+
3+BEGIN;
4+
5+DROP TABLE deployment_logs;
6+
7+DROP TYPE actions;
8+
9+COMMIT;
migrations/revert/deployments.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Revert octopod:deployments from pg
2+
3+BEGIN;
4+
5+DROP TABLE deployments;
6+
7+COMMIT;
migrations/revert/rename-delete-to-archive.sqlnew file+8
@@ -0,0 +1,8 @@
1+-- Revert octopod:rename-delete-to-archive from pg
2+BEGIN;
3+
4+
5+ALTER TYPE statuses RENAME VALUE 'ArchivePending' TO 'DeletePending';
6+
7+
8+COMMIT;
migrations/revert/rename_delete_to_archive_2.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Revert octopod:rename_delete_to_archive_2 from pg
2+
3+BEGIN;
4+
5+ALTER TYPE actions RENAME VALUE 'archive' TO 'delete';
6+
7+COMMIT;
migrations/revert/rename_elements_of_scope_enum.sqlnew file+8
@@ -0,0 +1,8 @@
1+-- Revert octopod:rename_elements_of_scope_enum from pg
2+
3+BEGIN;
4+
5+ALTER TYPE scopes RENAME VALUE 'ApplicationScope' to 'App';
6+ALTER TYPE scopes RENAME VALUE 'DeploymentScope' to 'Staging';
7+
8+COMMIT;
migrations/revert/status_and_status_updated_at.sqlnew file+11
@@ -0,0 +1,11 @@
1+-- Revert octopod:status_and_status_updated_at from pg
2+
3+BEGIN;
4+
5+ALTER TABLE deployments DROP status;
6+ALTER TABLE deployments DROP status_updated_at;
7+ALTER TABLE deployments DROP checked_at;
8+
9+DROP TYPE statuses;
10+
11+COMMIT;
migrations/sqitch.confnew file+8
@@ -0,0 +1,8 @@
1+[core]
2+ engine = pg
3+ # plan_file = sqitch.plan
4+ # top_dir = .
5+# [engine "pg"]
6+ # target = db:pg:
7+ # registry = sqitch
8+ # client = psql
migrations/sqitch.plannew file+15
@@ -0,0 +1,15 @@
1+%syntax-version=1.0.0
2+%project=octopod
3+%uri=https://github.com/sqitchers/sqitch-intro/
4+
5+deployments 2020-02-09T15:47:32Z Typeable LLC <octopod@typeable.io> # Creates deployments table.
6+deployment_logs 2020-02-28T21:35:30Z Typeable LLC <octopod@typeable.io> # Creates deployment_logs table.
7+archived_flag 2020-06-09T20:30:45Z Typeable LLC <octopod@typeable.io> # Add archived flag to 'deployments' and 'deployment_logs' tables
8+add_duration_and_stdout_and_stderr_to_deployment_logs 2020-06-26T09:30:35Z Typeable LLC <octopod@typeable.io> # Add 'duration', 'stdout' and 'stderr' fields to 'deployment_logs' table
9+status_and_status_updated_at 2020-07-06T08:08:10Z Typeable LLC <octopod@typeable.io> # Add status, status_updated_at, checked_at to 'deployments' table
10+add_deployment_overrides_and_deployment_log_overrides 2020-07-17T08:53:17Z Typeable LLC <octopod@typeable.io> # Add 'deployment_overrides' and 'deployment_log_overrides' tables
11+add_archived_status 2020-07-28T11:40:30Z Typeable LLC <octopod@typeable.io> # Add 'Archived' to statuses
12+create_deployment_metadata 2020-08-14T12:11:42Z Typeable LLC <octopod@typeable.io> # Create 'deployment_metadata' table
13+rename_elements_of_scope_enum 2020-08-19T09:38:40Z Typeable LLC <octopod@typeable.io> # Rename elements of 'scope' enum
14+rename-delete-to-archive 2020-11-23T10:53:10Z Ilya <octopod@typeable.io> # Renamed delete to archive
15+rename_delete_to_archive_2 2020-11-26T08:28:58Z Typeable LLC <octopod@typeable.io> # Renamed delete to archive
migrations/verify/.gitkeepnew file

No changes to the content.

migrations/verify/add_archived_status.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:add_archived_status on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/add_deployment_overrides_and_deployment_log_overrides.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:add_deployment_overrides_and_deployment_log_overrides on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/add_duration_and_stdout_and_stderr_to_deployment_logs.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:add_duration_and_stdout_and_stderr_to_deployment_logs on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/archived_flag.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:archived_flag on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/create_deployment_metadata.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:create_deployment_metadata on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/deployment_logs.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:deployment_logs on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/deployments.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:deployments on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/rename-delete-to-archive.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:rename-delete-to-archive on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/rename_delete_to_archive_2.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:rename_delete_to_archive_2 on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/rename_elements_of_scope_enum.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:rename_elements_of_scope_enum on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
migrations/verify/status_and_status_updated_at.sqlnew file+7
@@ -0,0 +1,7 @@
1+-- Verify octopod:status_and_status_updated_at on pg
2+
3+BEGIN;
4+
5+-- XXX Add verifications here.
6+
7+ROLLBACK;
nix/default.nixnew file+120
@@ -0,0 +1,120 @@
1+{ sources ? import ./sources.nix
2+
3+, octopod-css ? ../octopod-css
4+
5+, migrations ? "please use '--arg migration <value>'"
6+}:
7+with {
8+ overlay = _: pkgs:
9+ with pkgs; rec {
10+ octo-cli = haskellPackages.octo-cli-static;
11+ octopod-backend = haskellPackages.octopod-server-static;
12+ octopod-frontend = haskellPackages.octopod-frontend-static;
13+
14+ cacert' = cacert.overrideAttrs (o: {
15+ fixupPhase = ''
16+ cat $out/etc/ssl/certs/* > $out/etc/ssl/certs/ca-certificates.crt
17+ '';
18+ });
19+
20+ octopod-server-container = dockerTools.buildImage {
21+ name = "octopod-server-container-slim";
22+ contents = [
23+ octopod-backend
24+ octopod-frontend
25+ git
26+ coreutils
27+ bash
28+ openssh
29+ gnugrep
30+ cacert'
31+ shadow
32+ ];
33+
34+ runAsRoot = ''
35+ mkdir /tmp
36+ chmod 777 /tmp
37+
38+ mkdir -p /home/octopod
39+ useradd octopod -d /home/octopod
40+ chown octopod.octopod /home/octopod
41+
42+ mkdir /app
43+ cp -av ${octopod-backend}/bin/octopod-exe /app/octopod-exe
44+
45+ mkdir -p /migrations/{deploy,revert,verify}
46+ cp -av ${migrations}/* /migrations/
47+
48+ mkdir /tls /tls_store
49+
50+ mkdir -p /www/static/{images,styles,vendors/outline}
51+ cp -av ${octopod-frontend}/bin/frontend.jsexe/* /www/
52+ cp -av ${octopod-css}/production/images/* /www/static/images/
53+ cp -av ${octopod-css}/production/styles/* /www/static/styles/
54+ cp -av ${octopod-css}/production/vendors/outline/* /www/static/vendors/outline/
55+ '';
56+
57+ config = {
58+ Entrypoint = [ "/app/octopod-exe" ];
59+ Cmd = [
60+ "--port"
61+ "4443"
62+ "--ui-port"
63+ "4000"
64+ "--ws-port"
65+ "4020"
66+ "--db"
67+ "host='127.0.0.1' port=5432 user='octopod' password='octopod'"
68+ "--db-pool-size"
69+ "10"
70+ "--tls-cert-path"
71+ "/tls/server_cert.pem"
72+ "--tls-key-path"
73+ "/tls/server_key.pem"
74+ "--tls-store-path"
75+ "/tls_store"
76+ ];
77+ Env = [
78+ "PATH=/utils:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
79+ "USER=octopod"
80+ ];
81+ };
82+ };
83+
84+ octo-cli-container = dockerTools.buildImage {
85+ name = "octo-cli-container-slim";
86+ contents = [ octo-cli coreutils bash ];
87+
88+ runAsRoot = ''
89+ mkdir /app
90+ cp -av ${octo-cli}/bin/octo /app/octo
91+ '';
92+
93+ config = {
94+ Entrypoint = [ "/app/octo" ];
95+ Env = [ "EDITOR=${vim}/bin/vim" ];
96+ };
97+ };
98+
99+ niv = import sources.niv { };
100+
101+ haskellPackages = pkgs.haskellPackages.override {
102+ overrides = hself: hsuper: {
103+ octo-cli-static = haskell.lib.justStaticExecutables
104+ (hsuper.callPackage ../default.nix { }).ghc.octo-cli;
105+
106+ octopod-server-static = haskell.lib.justStaticExecutables
107+ (hsuper.callPackage ../default.nix { }).ghc.octopod-backend;
108+
109+ octopod-frontend-static = haskell.lib.justStaticExecutables
110+ (hsuper.callPackage ../default.nix { }).ghcjs.octopod-frontend;
111+ };
112+ };
113+ };
114+};
115+import
116+ sources.nixpkgs # and use them again!
117+{
118+ overlays = [ overlay ];
119+ config = { };
120+}
nix/octo.nixnew file+40
@@ -0,0 +1,40 @@
1+{ sources ? import ./sources.nix
2+, compiler ? "ghc865"
3+, pkgs-expr ? sources.nixpkgs
4+, static-haskell-nix ? sources.static-haskell-nix
5+}:
6+let
7+ normalPkgs = import pkgs-expr {
8+ overlays = [
9+ (self: super: {
10+ haskell = super.haskell // {
11+ packages = super.haskell.packages // {
12+ "${compiler}" = super.haskell.packages."${compiler}".override {
13+ overrides =
14+ (hself: hsuper: {
15+ octo-cli = hsuper.callCabal2nix "octo-cli" ../octo-cli { };
16+ octopod-api = hsuper.callCabal2nix "octopod-api" ../octopod-api { };
17+ octopod-common = hsuper.callCabal2nix "octopod-common" ../octopod-common { };
18+ hspec-wai = hsuper.callHackage "hspec-wai" "0.9.2" { };
19+ table-layout = hsuper.callHackageDirect
20+ {
21+ pkg = "table-layout";
22+ ver = "0.9.0.1";
23+ sha256 = "12nllfnh6b5mjda9qxfy192v0r0sx181w9zc9j70kvjdn7hgrb0y";
24+ } { };
25+ data-default-instances-base = hsuper.callHackageDirect
26+ {
27+ pkg = "data-default-instances-base";
28+ ver = "0.1.0.1";
29+ sha256 = "18basdy4qjn246phw008ll9zbi3rpdn6bh2dk0i81a60gsmyn58q";
30+ } { };
31+ });
32+ };
33+ };
34+ };
35+ })
36+ ];
37+ };
38+ static-haskell = import "${static-haskell-nix}/survey" { inherit normalPkgs compiler; };
39+in
40+static-haskell.haskellPackages.octo-cli
nix/sources.jsonnew file+86
@@ -0,0 +1,86 @@
1+{
2+ "hspec-webdriver-clone": {
3+ "branch": "master",
4+ "description": "Clone of hspec-webdriver, as the original source is inaccesible",
5+ "homepage": null,
6+ "owner": "dfordivam",
7+ "repo": "hspec-webdriver-clone",
8+ "rev": "0d748b7bb7cd74dce0a55a1ec86b01dbb8a71cd8",
9+ "sha256": "1criynifhvmnqwhrshmzylikqkvlgq98xf72w9cdd2zpjw539qf0",
10+ "type": "tarball",
11+ "url": "https://github.com/dfordivam/hspec-webdriver-clone/archive/0d748b7bb7cd74dce0a55a1ec86b01dbb8a71cd8.tar.gz",
12+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
13+ },
14+ "niv": {
15+ "branch": "master",
16+ "description": "Easy dependency management for Nix projects",
17+ "homepage": "https://github.com/nmattia/niv",
18+ "owner": "nmattia",
19+ "repo": "niv",
20+ "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f",
21+ "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4",
22+ "type": "tarball",
23+ "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz",
24+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
25+ },
26+ "nixpkgs": {
27+ "branch": "63ad5ab28ee380a4c6a974c9f70639f81a15f6ad",
28+ "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
29+ "homepage": "https://github.com/NixOS/nixpkgs",
30+ "owner": "NixOS",
31+ "repo": "nixpkgs",
32+ "rev": "63ad5ab28ee380a4c6a974c9f70639f81a15f6ad",
33+ "sha256": "1lppx6rsr52hdd1bp25ni4d6c8b76ip6lvr0iphfv0d1jjdf9r4h",
34+ "type": "tarball",
35+ "url": "https://github.com/NixOS/nixpkgs/archive/63ad5ab28ee380a4c6a974c9f70639f81a15f6ad.tar.gz",
36+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
37+ },
38+ "obelisk": {
39+ "branch": "master",
40+ "description": "Functional reactive web and mobile applications, with batteries included.",
41+ "homepage": "https://reflex-frp.org",
42+ "owner": "obsidiansystems",
43+ "repo": "obelisk",
44+ "rev": "11beb6e8cd2419b2429925b76a98f24035e40985",
45+ "sha256": "0b4m33b7yyzsbkvfz2kwg4v9hlnvbjlmjikbvwd7pg52vy84and0",
46+ "type": "tarball",
47+ "url": "https://github.com/obsidiansystems/obelisk/archive/11beb6e8cd2419b2429925b76a98f24035e40985.tar.gz",
48+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
49+ },
50+ "reflex-platform": {
51+ "branch": "rc/0.6.0.0",
52+ "description": "A curated package set and set of tools that let you build Haskell packages so they can run on a variety of platforms. reflex-platform is built on top of the nix package manager.",
53+ "homepage": "https://reflex-frp.org",
54+ "owner": "reflex-frp",
55+ "repo": "reflex-platform",
56+ "rev": "8d421e9e06b0477cbc065346aaf596c9db6cc387",
57+ "sha256": "06fy5b0mk5k2ps1h78yihf4j76cb855r86y9p4jv5d91nfyl2dck",
58+ "type": "tarball",
59+ "url": "https://github.com/reflex-frp/reflex-platform/archive/8d421e9e06b0477cbc065346aaf596c9db6cc387.tar.gz",
60+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
61+ },
62+ "servant-reflex": {
63+ "branch": "master",
64+ "description": "Generate reflex client functions for querying a servant API",
65+ "homepage": null,
66+ "owner": "imalsogreg",
67+ "repo": "servant-reflex",
68+ "rev": "1109043c8437a375fc00bb48792e8dc377a84494",
69+ "sha256": "0issnp95rnji3v9qifr0brypxsvmjkzanfq5lscj68lynnjv37g0",
70+ "type": "tarball",
71+ "url": "https://github.com/imalsogreg/servant-reflex/archive/1109043c8437a375fc00bb48792e8dc377a84494.tar.gz",
72+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
73+ },
74+ "static-haskell-nix": {
75+ "branch": "master",
76+ "description": "easily build most Haskell programs into fully static Linux executables",
77+ "homepage": "",
78+ "owner": "nh2",
79+ "repo": "static-haskell-nix",
80+ "rev": "382150290ba43b6eb41981c1ab3b32aa31798140",
81+ "sha256": "0zsyplzf1k235rl26irm27y5ljd8ciayw80q575msxa69a9y2nvd",
82+ "type": "tarball",
83+ "url": "https://github.com/nh2/static-haskell-nix/archive/382150290ba43b6eb41981c1ab3b32aa31798140.tar.gz",
84+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
85+ }
86+}
nix/sources.nixnew file+171
@@ -0,0 +1,171 @@
1+# This file has been generated by Niv.
2+
3+let
4+
5+ #
6+ # The fetchers. fetch_<type> fetches specs of type <type>.
7+ #
8+
9+ fetch_file = pkgs: name: spec:
10+ let
11+ name' = sanitizeName name + "-src";
12+ in
13+ if spec.builtin or true then
14+ builtins_fetchurl { inherit (spec) url sha256; name = name'; }
15+ else
16+ pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
17+
18+ fetch_tarball = pkgs: name: spec:
19+ let
20+ name' = sanitizeName name + "-src";
21+ in
22+ if spec.builtin or true then
23+ builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
24+ else
25+ pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
26+
27+ fetch_git = name: spec:
28+ let
29+ ref =
30+ if spec ? ref then spec.ref else
31+ if spec ? branch then "refs/heads/${spec.branch}" else
32+ if spec ? tag then "refs/tags/${spec.tag}" else
33+ abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
34+ in
35+ builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
36+
37+ fetch_local = spec: spec.path;
38+
39+ fetch_builtin-tarball = name: throw
40+ ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
41+ $ niv modify ${name} -a type=tarball -a builtin=true'';
42+
43+ fetch_builtin-url = name: throw
44+ ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
45+ $ niv modify ${name} -a type=file -a builtin=true'';
46+
47+ #
48+ # Various helpers
49+ #
50+
51+ # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
52+ sanitizeName = name:
53+ (
54+ concatMapStrings (s: if builtins.isList s then "-" else s)
55+ (
56+ builtins.split "[^[:alnum:]+._?=-]+"
57+ ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
58+ )
59+ );
60+
61+ # The set of packages used when specs are fetched using non-builtins.
62+ mkPkgs = sources: system:
63+ let
64+ sourcesNixpkgs =
65+ import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
66+ hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
67+ hasThisAsNixpkgsPath = <nixpkgs> == ./.;
68+ in
69+ if builtins.hasAttr "nixpkgs" sources
70+ then sourcesNixpkgs
71+ else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
72+ import <nixpkgs> {}
73+ else
74+ abort
75+ ''
76+ Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
77+ add a package called "nixpkgs" to your sources.json.
78+ '';
79+
80+ # The actual fetching function.
81+ fetch = pkgs: name: spec:
82+
83+ if ! builtins.hasAttr "type" spec then
84+ abort "ERROR: niv spec ${name} does not have a 'type' attribute"
85+ else if spec.type == "file" then fetch_file pkgs name spec
86+ else if spec.type == "tarball" then fetch_tarball pkgs name spec
87+ else if spec.type == "git" then fetch_git name spec
88+ else if spec.type == "local" then fetch_local spec
89+ else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
90+ else if spec.type == "builtin-url" then fetch_builtin-url name
91+ else
92+ abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
93+
94+ # If the environment variable NIV_OVERRIDE_${name} is set, then use
95+ # the path directly as opposed to the fetched source.
96+ replace = name: drv:
97+ let
98+ saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
99+ ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
100+ in
101+ if ersatz == "" then drv else ersatz;
102+
103+ # Ports of functions for older nix versions
104+
105+ # a Nix version of mapAttrs if the built-in doesn't exist
106+ mapAttrs = builtins.mapAttrs or (
107+ f: set: with builtins;
108+ listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
109+ );
110+
111+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
112+ range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
113+
114+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
115+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
116+
117+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
118+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
119+ concatMapStrings = f: list: concatStrings (map f list);
120+ concatStrings = builtins.concatStringsSep "";
121+
122+ # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
123+ optionalAttrs = cond: as: if cond then as else {};
124+
125+ # fetchTarball version that is compatible between all the versions of Nix
126+ builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
127+ let
128+ inherit (builtins) lessThan nixVersion fetchTarball;
129+ in
130+ if lessThan nixVersion "1.12" then
131+ fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
132+ else
133+ fetchTarball attrs;
134+
135+ # fetchurl version that is compatible between all the versions of Nix
136+ builtins_fetchurl = { url, name ? null, sha256 }@attrs:
137+ let
138+ inherit (builtins) lessThan nixVersion fetchurl;
139+ in
140+ if lessThan nixVersion "1.12" then
141+ fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
142+ else
143+ fetchurl attrs;
144+
145+ # Create the final "sources" from the config
146+ mkSources = config:
147+ mapAttrs (
148+ name: spec:
149+ if builtins.hasAttr "outPath" spec
150+ then abort
151+ "The values in sources.json should not have an 'outPath' attribute"
152+ else
153+ spec // { outPath = replace name (fetch config.pkgs name spec); }
154+ ) config.sources;
155+
156+ # The "config" used by the fetchers
157+ mkConfig =
158+ { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
159+ , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
160+ , system ? builtins.currentSystem
161+ , pkgs ? mkPkgs sources system
162+ }: rec {
163+ # The sources, i.e. the attribute set of spec name to spec
164+ inherit sources;
165+
166+ # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
167+ inherit pkgs;
168+ };
169+
170+in
171+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
octo-cli/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2020, Typeable LLC
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Typeable LLC nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
octo-cli/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
octo-cli/octo-cli.cabalnew file+63
@@ -0,0 +1,63 @@
1+cabal-version: 2.4
2+
3+name: octo-cli
4+version: 0.1.0.0
5+description: Please see the README on GitHub at <https://github.com/typeable/octopod#readme>
6+homepage: https://github.com/typeable/octopod
7+bug-reports: https://github.com/typeable/octopod/issues
8+license: BSD-3-Clause
9+license-file: LICENSE
10+author: Typeable
11+maintainer: octopod@typeable.io
12+
13+executable octo
14+ main-is: Main.hs
15+ other-modules:
16+ Octopod.CLI
17+ Octopod.CLI.Args
18+ Octopod.CLI.TLS
19+ Text.Layout.Table.Extras
20+ default-extensions:
21+ BlockArguments
22+ ConstraintKinds
23+ DataKinds
24+ DeriveFunctor
25+ DeriveGeneric
26+ DerivingVia
27+ DuplicateRecordFields
28+ FlexibleContexts
29+ GeneralizedNewtypeDeriving
30+ LambdaCase
31+ OverloadedStrings
32+ ScopedTypeVariables
33+ StandaloneDeriving
34+ TypeApplications
35+ TypeOperators
36+ OverloadedLabels
37+ build-depends:
38+ aeson,
39+ base ^>=4.12.0.0,
40+ bytestring,
41+ chronos,
42+ connection,
43+ data-default-class,
44+ generic-lens,
45+ http-client-tls,
46+ http-client,
47+ http-types,
48+ lens,
49+ octopod-api,
50+ octopod-common,
51+ optparse-applicative,
52+ servant-client-core,
53+ servant-client,
54+ servant,
55+ text,
56+ tls,
57+ transformers,
58+ table-layout ^>= 0.9.0.0,
59+ hs-source-dirs: src
60+ default-language: Haskell2010
61+ ghc-options:
62+ -Wall
63+ -Werror
octo-cli/src/Main.hsnew file+6
@@ -0,0 +1,6 @@
1+module Main where
2+
3+import Octopod.CLI
4+
5+main :: IO ()
6+main = runOcto
octo-cli/src/Octopod/CLI.hsnew file+311
@@ -0,0 +1,311 @@
1+module Octopod.CLI (runOcto) where
2+
3+import Chronos
4+import Control.Lens hiding (List)
5+import Control.Monad
6+import Control.Monad.IO.Class
7+import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
8+import Data.Aeson (decode)
9+import qualified Data.ByteString.Char8 as BSC
10+import qualified Data.ByteString.Lazy.Char8 as LBSC
11+import Data.Coerce
12+import Data.Generics.Labels ()
13+import Data.Generics.Product
14+import Data.Proxy
15+import Data.Text (Text)
16+import qualified Data.Text as T
17+import qualified Data.Text.IO as T
18+import Data.Text.Lens
19+import GHC.IO.Encoding
20+import Network.HTTP.Client (Manager)
21+import Prelude as P
22+import Servant.API
23+import Servant.Client
24+ (BaseUrl(..), ClientEnv, ClientError, ClientM, client, mkClientEnv,
25+ runClientM)
26+import Servant.Client.Core
27+ (ClientError(FailureResponse), ResponseF(..), parseBaseUrl)
28+import System.Environment (lookupEnv)
29+import System.Exit
30+
31+import Common.Types
32+import Common.Utils (dfiName)
33+import Octopod.CLI.Args
34+import Octopod.CLI.TLS (makeClientManager)
35+import Octopod.PowerAPI
36+import Text.Layout.Table
37+import Text.Layout.Table.Extras ()
38+
39+-- | Runs the octo CLI.
40+runOcto :: IO ()
41+runOcto = do
42+ setLocaleEncoding utf8
43+ args <- parseArgs
44+ env <- getBaseUrl
45+ manager <- initClientManager env
46+ let clientEnv = mkClientEnv manager env
47+ flip runReaderT clientEnv $
48+ case args of
49+ Create tName tTag tSetAp tSetDep tSetPAp tSetPDep -> do
50+ setApp <- liftIO $ parseSetApplicationOverrides Public tSetAp
51+ setDep <- liftIO $ parseSetDeploymentOverrides Public tSetDep
52+ setPApp <- liftIO $ parseSetApplicationOverrides Private tSetPAp
53+ setPDep <- liftIO $ parseSetDeploymentOverrides Private tSetPDep
54+ let
55+ appOvs = setApp ++ setPApp
56+ depOvs = setDep ++ setPDep
57+ handleCreate $ Deployment (coerce tName) (coerce tTag) appOvs depOvs
58+ List ->
59+ handleList
60+ Archive tName ->
61+ handleArchive . coerce $ tName
62+ Update tName tTag tSetAp tUnsAp tSetD tUnsD tSetPAp tSetPD -> do
63+ setApp <- liftIO $ parseSetApplicationOverrides Public tSetAp
64+ setDep <- liftIO $ parseSetDeploymentOverrides Public tSetD
65+ unsetApp <- liftIO $ parseUnsetApplicationOverrides Public tUnsAp
66+ unsetDep <- liftIO $ parseUnsetDeploymentOverrides Public tUnsD
67+ setPApp <- liftIO $ parseSetApplicationOverrides Private tSetPAp
68+ setPDep <- liftIO $ parseSetDeploymentOverrides Private tSetPD
69+ let
70+ appOvs = setApp ++ setPApp
71+ depOvs = setDep ++ setPDep
72+ tName' = coerce tName
73+ tTag' = coerce tTag
74+ handleUpdate tName' tTag' appOvs unsetApp depOvs unsetDep
75+ Info tName ->
76+ handleInfo . coerce $ tName
77+ Cleanup tName ->
78+ handleCleanup . coerce $ tName
79+ Restore tName ->
80+ handleRestore . coerce $ tName
81+ CleanArchive ->
82+ handleCleanArchive
83+ GetActionLogs aId l -> handleGetActionInfo aId l
84+
85+-- | Initializes the HTTP connection manager.
86+initClientManager :: BaseUrl -> IO Manager
87+initClientManager baseURL = do
88+ let
89+ cert <- lookupEnv "TLS_CERT_PATH"
90+ >>= maybe (pure $ TLSCertPath "cert.pem") (pure . TLSCertPath . BSC.pack)
91+ key <- lookupEnv "TLS_KEY_PATH"
92+ >>= maybe (pure $ TLSKeyPath "key.pem") (pure . TLSKeyPath . BSC.pack)
93+ makeClientManager (baseUrlHost baseURL) cert key
94+
95+-- | Returns BaseUrl from 'OCTOPOD_URL' environment variable
96+-- or exit with exit_code=1.
97+getBaseUrl :: IO BaseUrl
98+getBaseUrl = do
99+ octopodURL <- lookupEnv "OCTOPOD_URL"
100+ >>= maybe (die "OCTOPOD_URL is not set") pure
101+ case parseBaseUrl octopodURL of
102+ Just url -> pure url
103+ Nothing -> die "could not parse OCTOPOD_URL"
104+
105+-- | Handles the 'create' subcommand.
106+handleCreate :: Deployment -> ReaderT ClientEnv IO ()
107+handleCreate createCmd = do
108+ clientEnv <- ask
109+ liftIO $ do
110+ response <- runClientM (createH createCmd) clientEnv
111+ handleResponse (const $ pure ()) response
112+
113+-- | Handles the 'list' subcommand.
114+handleList :: ReaderT ClientEnv IO ()
115+handleList = do
116+ clientEnv <- ask
117+ liftIO $ do
118+ resp <- runClientM listH clientEnv
119+ let
120+ getName dep = dep ^.dfiName
121+ names = T.unlines . coerce . (getName <$>) <$> resp
122+ handleResponse T.putStr names
123+
124+-- | Handles the 'archive' subcommand.
125+handleArchive :: DeploymentName -> ReaderT ClientEnv IO ()
126+handleArchive dName = do
127+ clientEnv <- ask
128+ liftIO $
129+ handleResponse (const $ pure ()) =<< runClientM (archiveH dName) clientEnv
130+
131+-- | Handles the 'update' subcommand.
132+handleUpdate
133+ :: DeploymentName
134+ -> DeploymentTag
135+ -> ApplicationOverrides
136+ -> ApplicationOverrides
137+ -> DeploymentOverrides
138+ -> DeploymentOverrides
139+ -> ReaderT ClientEnv IO ()
140+handleUpdate dName dTag dNewAppOvs dOldAppOvs dNewDepOvs dOldDepOvs = do
141+ clientEnv <- ask
142+ liftIO $ do
143+ let
144+ dUpdate = DeploymentUpdate
145+ { newTag = dTag
146+ , newAppOverrides = dNewAppOvs
147+ , oldAppOverrides = dOldAppOvs
148+ , newDeploymentOverrides = dNewDepOvs
149+ , oldDeploymentOverrides = dOldDepOvs
150+ }
151+ response <- runClientM (updateH dName dUpdate) clientEnv
152+ handleResponse (const $ pure ()) response
153+
154+-- | Handles the 'info' subcommand.
155+handleInfo :: DeploymentName -> ReaderT ClientEnv IO ()
156+handleInfo dName = do
157+ clientEnv <- ask
158+ liftIO $ do
159+ res <- runClientM (infoH dName) clientEnv
160+ case res of
161+ Right (i : _) -> printInfo i
162+ Right [] -> putStrLn notFoundMsg
163+ Left err -> putStrLn $ "request failed, reason: " ++ show err
164+ where
165+ notFoundMsg = "deployment " ++ T.unpack (coerce dName) ++ " not found"
166+
167+-- | Handles the 'cleanup' subcommand.
168+handleCleanup :: DeploymentName -> ReaderT ClientEnv IO ()
169+handleCleanup dName = do
170+ clientEnv <- ask
171+ liftIO $
172+ handleResponse (const $ pure ()) =<< runClientM (cleanupH dName) clientEnv
173+
174+-- | Handles the 'restore' subcommand.
175+handleRestore :: DeploymentName -> ReaderT ClientEnv IO ()
176+handleRestore dName = do
177+ clientEnv <- ask
178+ liftIO $
179+ handleResponse (const $ pure ()) =<< runClientM (restoreH dName) clientEnv
180+
181+-- | Handles the 'clean-archive' subcommand.
182+handleCleanArchive :: ReaderT ClientEnv IO ()
183+handleCleanArchive = do
184+ clientEnv <- ask
185+ liftIO $
186+ handleResponse (const $ pure ()) =<< runClientM cleanArchiveH clientEnv
187+
188+-- | Handles the 'logs' subcommand.
189+handleGetActionInfo :: ActionId -> LogOutput -> ReaderT ClientEnv IO ()
190+handleGetActionInfo aId l = do
191+ clientEnv <- ask
192+ liftIO $ do
193+ runClientM (getActionInfoH aId) clientEnv >>= \case
194+ Left err -> print err
195+ Right x -> case l of
196+ Out -> T.putStrLn $ x ^. #stdout
197+ Err -> T.putStrLn $ x ^. #stderr
198+ ErrOut -> do
199+ T.putStrLn "\t\tstdout:\n"
200+ T.putStrLn $ x ^. #stdout
201+ T.putStrLn "\t\tstderr:\n"
202+ T.putStrLn $ x ^. #stderr
203+
204+
205+listH :: ClientM [DeploymentFullInfo]
206+
207+createH :: Deployment -> ClientM CommandResponse
208+
209+archiveH :: DeploymentName -> ClientM CommandResponse
210+
211+updateH :: DeploymentName -> DeploymentUpdate -> ClientM CommandResponse
212+
213+infoH :: DeploymentName -> ClientM [DeploymentInfo]
214+
215+_fullInfoH :: DeploymentName -> ClientM DeploymentFullInfo
216+
217+_statusH :: DeploymentName -> ClientM CurrentDeploymentStatus
218+
219+cleanupH :: DeploymentName -> ClientM CommandResponse
220+
221+restoreH :: DeploymentName -> ClientM CommandResponse
222+
223+getActionInfoH :: ActionId -> ClientM ActionInfo
224+
225+cleanArchiveH :: ClientM CommandResponse
226+
227+( listH
228+ :<|> createH
229+ :<|> archiveH
230+ :<|> updateH
231+ :<|> infoH
232+ :<|> _fullInfoH
233+ :<|> _statusH
234+ :<|> cleanupH
235+ :<|> restoreH)
236+ :<|> getActionInfoH
237+ :<|> cleanArchiveH = client (Proxy @PowerAPI)
238+
239+-- | Handles response from Octopod Server.
240+handleResponse :: (a -> IO ()) -> Either ClientError a -> IO ()
241+handleResponse f (Right result) = f result
242+handleResponse _ (Left (FailureResponse _req res)) =
243+ T.putStrLn . decodeError $ responseBody res
244+handleResponse _ (Left err) =
245+ T.putStrLn $ "command failed due to unknown reason: " <> T.pack (show err)
246+
247+decodeError :: LBSC.ByteString -> Text
248+decodeError body =
249+ case decode body of
250+ Just (ValidationError nameErrors tagErrors) ->
251+ T.concat ((<> "\n") <$> nameErrors) <> T.concat ((<> "\n") <$> tagErrors)
252+ Just (AppError errorMsg) -> errorMsg
253+ Just Success -> "ok"
254+ _ -> "error: " <> (T.pack . LBSC.unpack $ body)
255+
256+-- | Pretty-prints the 'info' subcommand result.
257+printInfo :: DeploymentInfo -> IO ()
258+printInfo (DeploymentInfo (Deployment _ dTag dAppOvs dStOvs) dMeta dLogs) = do
259+ T.putStrLn "Current settings:"
260+ T.putStrLn $ "tag: " <> coerce dTag
261+ T.putStrLn $ "application overrides: "
262+ <> (formatOverrides $ coerce <$> dAppOvs)
263+ T.putStrLn $ "deployment overrides: "
264+ <> (formatOverrides $ coerce <$> dStOvs)
265+ T.putStrLn $ "metadata: "
266+ forM_ dMeta $ \m ->
267+ T.putStrLn $
268+ " " <> deploymentMetadataKey m <> ": " <> deploymentMetadataValue m
269+ T.putStrLn ""
270+ T.putStrLn "Last logs:"
271+ ppDeploymentLogs dLogs
272+
273+ppDeploymentLogs :: [DeploymentLog] -> IO ()
274+ppDeploymentLogs ds =
275+ putStrLn
276+ . tableString
277+ [ column expand right noAlign def
278+ , column expand center noAlign def
279+ , column expand center noAlign def
280+ , column expand center noAlign def
281+ , column expand left (charAlign '=') def
282+ , column expand left (charAlign '=') def
283+ , column expand center noAlign def
284+ ]
285+ unicodeBoldHeaderS
286+ (titlesH
287+ [ "Created at"
288+ , "Action id"
289+ , "Action"
290+ , "Tag"
291+ , "App overrides"
292+ , "Deployment overrides"
293+ , "Exit code"
294+ ])
295+ $ ppDeploymentLogRow <$> ds
296+
297+-- | Pretty-prints the deployment log.
298+ppDeploymentLogRow :: DeploymentLog -> RowGroup Text
299+ppDeploymentLogRow dLog = colsAllG top
300+ [ [encode_YmdHMS SubsecondPrecisionAuto w3c
301+ (timeToDatetime . Time . fromIntegral
302+ $ dLog ^. field @"createdAt" * 10 ^ (9 :: Int))]
303+ , [dLog ^. field @"actionId" . to unActionId . re _Show . packed]
304+ , [dLog ^. field @"action" . coerced]
305+ , [dLog ^. field @"deploymentTag" . coerced]
306+ , dLog ^. field @"deploymentAppOverrides"
307+ . to (fmap $ formatOverride . coerce)
308+ , dLog ^. field @"deploymentDepOverrides"
309+ . to (fmap $ formatOverride . coerce)
310+ , [dLog ^. field @"exitCode" . re _Show . packed]
311+ ]
octo-cli/src/Octopod/CLI/Args.hsnew file+200
@@ -0,0 +1,200 @@
1+{-|
2+Module : Octopod.CLI.Args
3+Description : octo CLI arguments parser utils.
4+-}
5+
6+module Octopod.CLI.Args where
7+
8+import Common.Types
9+import Data.Semigroup ((<>))
10+import Data.Text
11+import Options.Applicative
12+
13+-- | octo CLI subcommands and arguments definition.
14+data Args
15+ = Create
16+ { name :: Text
17+ -- ^ deployment name
18+ , tag :: Text
19+ -- ^ deployment tag
20+ , setAppOverrides :: [Text]
21+ -- ^ application-level overrides to set
22+ , setDeploymentOverrides :: [Text]
23+ -- ^ deployment-level overrides to set
24+ , setAppPrivateOverrides :: [Text]
25+ -- ^ application-level private overrides to set
26+ , setDeploymentPrivateOverrides :: [Text]
27+ -- ^ deployment-level private overrides to set
28+ }
29+ | List
30+ | Archive
31+ { name :: Text
32+ -- ^ deployment name
33+ }
34+ | Update
35+ { name :: Text
36+ -- ^ deployment name
37+ , tag :: Text
38+ -- ^ deployment tag
39+ , setAppOverrides :: [Text]
40+ -- ^ application-level overrides to set
41+ , unsetAppOverrides :: [Text]
42+ -- ^ application-level overrides to unset
43+ , setDeploymentOverrides :: [Text]
44+ -- ^ deployment-level overrides to set
45+ , unsetDeploymentOverrides :: [Text]
46+ -- ^ deployment-level overrides to unset
47+ , setAppPrivateOverrides :: [Text]
48+ -- ^ application-level private overrides to set
49+ , setDeploymentPrivateOverrides :: [Text]
50+ -- ^ deployment-level private overrides to set
51+ }
52+ | Info
53+ { name :: Text
54+ -- ^ deployment name
55+ }
56+ | Cleanup
57+ { name :: Text
58+ -- ^ deployment name
59+ }
60+ | Restore
61+ { name :: Text
62+ -- ^ deployment name
63+ }
64+ | CleanArchive
65+ | GetActionLogs ActionId LogOutput
66+ deriving stock (Show)
67+
68+data LogOutput = Err | Out | ErrOut
69+ deriving stock Show
70+
71+readLogOutput :: String -> Maybe LogOutput
72+readLogOutput "stderr" = Just Err
73+readLogOutput "stdout" = Just Out
74+readLogOutput "err" = Just Err
75+readLogOutput "out" = Just Out
76+readLogOutput "all" = Just ErrOut
77+readLogOutput "both" = Just ErrOut
78+readLogOutput _ = Nothing
79+
80+-- | Parses octo CLI subcommands and arguments.
81+parseArgs :: IO Args
82+parseArgs = execParser $ info (commandArgs <**> helper) fullDesc
83+
84+-- | Parses octo CLI subcommands.
85+commandArgs :: Parser Args
86+commandArgs =
87+ hsubparser (
88+ command "create" (info createArgs (progDesc "create a new deployment"))
89+ <> command "list" (info listArgs (progDesc "get names all deployments"))
90+ <> command "archive" (info archiveArgs (progDesc "archive the deployment"))
91+ <> command "update" (info updateArgs (progDesc "update the deployment"))
92+ <> command "info" (info infoArgs (progDesc "get the deployment info"))
93+ <> command "cleanup" (info cleanupArgs (progDesc "cleanup the deployment"))
94+ <> command "restore" (info restoreArgs (progDesc "restore the deployment"))
95+ <> command "clean-archive"
96+ (info cleanupArchiveArgs (progDesc "cleanup all archived deployments"))
97+ <> command "logs"
98+ (info actionLogsArgs (progDesc "get deployment logs of a given action")))
99+
100+-- | Parses arguments of 'create' subcommand.
101+createArgs :: Parser Args
102+createArgs =
103+ Create
104+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
105+ <*> strOption (long "tag" <> short 't' <> help "deployment tag")
106+ <*> many (strOption
107+ (long "set-app-env-override"
108+ <> short 'e'
109+ <> help "set application level override"))
110+ <*> many (strOption
111+ (long "set-deployment-override"
112+ <> short 'o'
113+ <> help "set deployment level override"))
114+ <*> many (strOption
115+ (long "set-app-env-private-override"
116+ <> short 'a'
117+ <> help "set application level private override"
118+ <> internal))
119+ <*> many (strOption
120+ (long "set-deployment-private-override"
121+ <> short 's'
122+ <> help "set deployment level private override"
123+ <> internal))
124+
125+-- | Parses arguments of 'list' subcommand.
126+listArgs :: Parser Args
127+listArgs =
128+ pure List
129+
130+-- | Parses arguments of 'archive' subcommand.
131+archiveArgs :: Parser Args
132+archiveArgs =
133+ Archive
134+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
135+
136+-- | Parses arguments of 'update' subcommand.
137+updateArgs :: Parser Args
138+updateArgs =
139+ Update
140+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
141+ <*> strOption (long "tag" <> short 't' <> help "deployment tag")
142+ <*> many (strOption
143+ (long "set-app-env-override"
144+ <> short 'e'
145+ <> help "set application level override"))
146+ <*> many (strOption
147+ (long "unset-app-env-override"
148+ <> short 'E'
149+ <> help "unset an application level override"))
150+ <*> many (strOption
151+ (long "set-deployment-override"
152+ <> short 'o'
153+ <> help "set deployment level override"))
154+ <*> many (strOption
155+ (long "unset-deployment-override"
156+ <> short 'O'
157+ <> help "unset a deployment level override"))
158+ <*> many (strOption
159+ (long "set-app-env-private-override"
160+ <> short 'a'
161+ <> help "set application level private override"
162+ <> internal))
163+ <*> many (strOption
164+ (long "set-deployment-private-override"
165+ <> short 's'
166+ <> help "set deployment level private override"
167+ <> internal))
168+
169+-- | Parses arguments of 'info' subcommand.
170+infoArgs :: Parser Args
171+infoArgs =
172+ Info
173+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
174+
175+-- | Parses arguments of 'cleanup' subcommand.
176+cleanupArgs :: Parser Args
177+cleanupArgs =
178+ Cleanup
179+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
180+
181+-- | Parses arguments of 'restore' subcommand.
182+restoreArgs :: Parser Args
183+restoreArgs =
184+ Restore
185+ <$> strOption (long "name" <> short 'n' <> help "deployment name")
186+
187+-- | Parses arguments of 'clean-archive' subcommand.
188+cleanupArchiveArgs :: Parser Args
189+cleanupArchiveArgs =
190+ pure CleanArchive
191+
192+actionLogsArgs :: Parser Args
193+actionLogsArgs = GetActionLogs
194+ <$> (ActionId <$> option auto (long "action" <> short 'a' <> help "action id"))
195+ <*> option (maybeReader readLogOutput)
196+ ( long "log-type"
197+ <> short 'l'
198+ <> help "log types to print. Values are: stdout, stderr, all"
199+ <> value ErrOut
200+ <> completeWith ["stdout", "stderr", "all"])
octo-cli/src/Octopod/CLI/TLS.hsnew file+39
@@ -0,0 +1,39 @@
1+module Octopod.CLI.TLS
2+ ( makeClientManager
3+ ) where
4+
5+import Common.Types
6+import qualified Data.ByteString.Char8 as BSC
7+import Data.Coerce
8+import Data.Default.Class (def)
9+import Network.Connection
10+import Network.HTTP.Client
11+ (Manager, managerResponseTimeout, newManager, responseTimeoutMicro)
12+import Network.HTTP.Client.TLS
13+import Network.TLS
14+ (ClientHooks(..), clientHooks, clientSupported, credentialLoadX509,
15+ defaultParamsClient, supportedCiphers)
16+import Network.TLS.Extra.Cipher
17+
18+-- | Initializes the HTTP client using TLS
19+makeClientManager :: String -> TLSCertPath -> TLSKeyPath -> IO Manager
20+makeClientManager octopodHostName cert key = do
21+ creds <- do
22+ let
23+ cert' = BSC.unpack . coerce $ cert
24+ key' = BSC.unpack . coerce $ key
25+ either error Just <$> credentialLoadX509 cert' key'
26+ let
27+ hooks = def
28+ { onCertificateRequest = \_ -> return creds
29+ , onServerCertificate = \_ _ _ _ -> return []
30+ }
31+ clientParams = (defaultParamsClient octopodHostName "")
32+ { clientHooks = hooks
33+ , clientSupported = def { supportedCiphers = ciphersuite_default }
34+ }
35+ tlsSettings = TLSSettings clientParams
36+ timeout = 20 * 60 * 10 ^ (6 :: Int)
37+ tlsManagerSettings' = (mkManagerSettings tlsSettings Nothing)
38+ { managerResponseTimeout = responseTimeoutMicro timeout }
39+ in newManager tlsManagerSettings'
octo-cli/src/Text/Layout/Table/Extras.hsnew file+27
@@ -0,0 +1,27 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+module Text.Layout.Table.Extras
4+ (
5+ ) where
6+
7+import Data.Text
8+import qualified Data.Text as T
9+import Text.Layout.Table.Cell
10+import Text.Layout.Table.Primitives.AlignInfo
11+import Text.Layout.Table.StringBuilder
12+
13+instance Cell Text where
14+ dropLeft = T.drop
15+ dropRight = T.dropEnd
16+ visibleLength = T.length
17+ measureAlignment p xs = case T.break p xs of
18+ (ls, rs) -> AlignInfo (T.length ls) $
19+ if T.null rs
20+ then Nothing
21+ else Just $ T.length rs - 1
22+ buildCell = stringB . T.unpack
23+
24+instance StringBuilder Text where
25+ stringB = T.pack
26+ charB = T.singleton
27+ replicateCharB x c = T.replicate x (T.singleton c)
octopod-api/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2020, Typeable LLC
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Typeable LLC nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
octopod-api/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
octopod-api/octopod-api.cabalnew file+46
@@ -0,0 +1,46 @@
1+cabal-version: 2.4
2+
3+name: octopod-api
4+version: 0.1.0.0
5+description: Please see the README on GitHub at <https://github.com/typeable/octopod#readme>
6+homepage: https://github.com/typeable/octopod
7+bug-reports: https://github.com/typeable/octopod/issues
8+license: BSD-3-Clause
9+license-file: LICENSE
10+author: Typeable
11+maintainer: octopod@typeable.io
12+
13+library
14+ exposed-modules:
15+ Octopod.API
16+ Octopod.API.WebSocket
17+ Octopod.PowerAPI
18+ default-extensions:
19+ BlockArguments
20+ ConstraintKinds
21+ DataKinds
22+ DeriveFunctor
23+ DeriveGeneric
24+ DerivingVia
25+ DuplicateRecordFields
26+ FlexibleContexts
27+ GeneralizedNewtypeDeriving
28+ LambdaCase
29+ OverloadedStrings
30+ ScopedTypeVariables
31+ StandaloneDeriving
32+ TypeApplications
33+ TypeOperators
34+ build-depends:
35+ base ^>=4.12.0.0,
36+ servant,
37+ octopod-common,
38+ if !impl(ghcjs)
39+ build-depends:
40+ servant-websockets,
41+ aeson,
42+ hs-source-dirs: src
43+ default-language: Haskell2010
44+ ghc-options:
45+ -Wall
46+ -Werror
octopod-api/src/Octopod/API.hsnew file+65
@@ -0,0 +1,65 @@
1+{-|
2+Module : Octopod.API
3+Description : Backend endpoints.
4+
5+This module contains backend endpoints.
6+-}
7+
8+
9+module Octopod.API
10+ ( module Octopod.API.WebSocket
11+ , API
12+ ) where
13+
14+import Servant.API
15+
16+import Common.Types
17+import Octopod.API.WebSocket
18+
19+type CaptureName = Capture "name" DeploymentName
20+
21+type ListEndpoint = Get '[JSON] [DeploymentFullInfo]
22+type CreateEndpoint =
23+ ReqBody '[JSON] Deployment :> Post '[JSON] CommandResponse
24+type ArchiveEndpoint c = c :> Delete '[JSON] CommandResponse
25+type UpdateEndpoint c =
26+ c :> ReqBody '[JSON] DeploymentUpdate :> Put '[JSON] CommandResponse
27+type InfoEndpoint c = c :> "info" :> Get '[JSON] [DeploymentInfo]
28+type FullInfoEndpoint c = c :> "full_info" :> Get '[JSON] DeploymentFullInfo
29+type StatusEndpoint c = c :> "status" :> Get '[JSON] CurrentDeploymentStatus
30+type RestoreEndpoint c =
31+ c :> "restore" :> Patch '[JSON] CommandResponse
32+
33+type PingEndpoint =
34+ "ping" :> GetNoContent '[PlainText] NoContent
35+type ProjectNameEndpoint =
36+ "project_name" :> Get '[JSON] ProjectName
37+
38+type DeploymentAPI' c =
39+ "api" :> "v1" :>
40+ ( "deployments" :>
41+ ( ListEndpoint
42+ -- endpoint to get deployment list
43+ :<|> CreateEndpoint
44+ -- endpoint to create a new deployment
45+ :<|> ArchiveEndpoint c
46+ -- endpoint to archive existing deployment
47+ :<|> UpdateEndpoint c
48+ -- endpoint to update exists deployment
49+ :<|> InfoEndpoint c
50+ -- endpoint to get deployment info
51+ :<|> FullInfoEndpoint c
52+ -- endpoint to get deployment full info
53+ :<|> StatusEndpoint c
54+ -- endpoint to get deployment status
55+ :<|> RestoreEndpoint c
56+ -- endpoint to restore deployment
57+ )
58+ :<|> PingEndpoint
59+ -- endpoint to liveness probe
60+ :<|> ProjectNameEndpoint
61+ -- endpoint to get project name
62+ )
63+
64+-- | API for frontend
65+type API = DeploymentAPI' CaptureName
octopod-api/src/Octopod/API/WebSocket.hsnew file+22
@@ -0,0 +1,22 @@
1+{-# LANGUAGE CPP #-}
2+
3+module Octopod.API.WebSocket where
4+
5+#ifndef ghcjs_HOST_OS
6+
7+import Data.Aeson
8+import Servant.API
9+import Servant.API.WebSocketConduit
10+
11+#endif
12+
13+
14+type ApiWSPath = "event"
15+
16+
17+#ifndef ghcjs_HOST_OS
18+
19+-- | WS API
20+type WebSocketAPI = ApiWSPath :> WebSocketSource Value
21+
22+#endif
octopod-api/src/Octopod/PowerAPI.hsnew file+71
@@ -0,0 +1,71 @@
1+{-|
2+Module : Octopod.PowerAPI
3+Description : Backend endpoints.
4+
5+This module contains backend endpoints.
6+-}
7+
8+
9+module Octopod.PowerAPI
10+ ( module Octopod.API.WebSocket
11+ , PowerAPI
12+ ) where
13+
14+import Servant.API
15+
16+import Common.Types
17+import Octopod.API.WebSocket
18+
19+type CaptureName = Capture "name" DeploymentName
20+
21+type ListEndpoint = Get '[JSON] [DeploymentFullInfo]
22+type CreateEndpoint =
23+ ReqBody '[JSON] Deployment :> Post '[JSON] CommandResponse
24+type ArchiveEndpoint c = c :> Delete '[JSON] CommandResponse
25+type UpdateEndpoint c =
26+ c :> ReqBody '[JSON] DeploymentUpdate :> Put '[JSON] CommandResponse
27+type InfoEndpoint c = c :> "info" :> Get '[JSON] [DeploymentInfo]
28+type FullInfoEndpoint c = c :> "full_info" :> Get '[JSON] DeploymentFullInfo
29+type StatusEndpoint c = c :> "status" :> Get '[JSON] CurrentDeploymentStatus
30+type CleanupEndpoint c =
31+ c :> "cleanup" :> Delete '[JSON] CommandResponse
32+type RestoreEndpoint c =
33+ c :> "restore" :> Patch '[JSON] CommandResponse
34+
35+type GetActionInfoEndpoint =
36+ "log" :> Capture "action_id" ActionId :> Get '[JSON] ActionInfo
37+
38+type CleanArchiveEndpoint =
39+ "clean_archive" :> Delete '[JSON] CommandResponse
40+
41+type DeploymentAPI' c =
42+ "api" :> "v1" :>
43+ ( "deployments" :>
44+ ( ListEndpoint
45+ -- endpoint to get deployment list
46+ :<|> CreateEndpoint
47+ -- endpoint to create a new deployment
48+ :<|> ArchiveEndpoint c
49+ -- endpoint to archive an existing deployment
50+ :<|> UpdateEndpoint c
51+ -- endpoint to update an existing deployment
52+ :<|> InfoEndpoint c
53+ -- endpoint to get deployment info
54+ :<|> FullInfoEndpoint c
55+ -- endpoint to get full deployment info
56+ :<|> StatusEndpoint c
57+ -- endpoint to get deployment status
58+ :<|> CleanupEndpoint c
59+ -- endpoint to clean up resources of an archived deployment
60+ :<|> RestoreEndpoint c
61+ -- endpoint to restore an archived deployment
62+ )
63+ :<|> GetActionInfoEndpoint
64+ -- endpoint to get action logs
65+ :<|> CleanArchiveEndpoint
66+ -- endpoint to clean up resources of all archived deployments
67+ -- according to the archive retention policy
68+ )
69+
70+-- | API for the octo CLI
71+type PowerAPI = DeploymentAPI' CaptureName
octopod-backend/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright Author name here (c) 2020
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Author name here nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
octopod-backend/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
octopod-backend/app/Main.hsnew file+6
@@ -0,0 +1,6 @@
1+module Main where
2+
3+import Octopod.Server
4+
5+main :: IO ()
6+main = runOctopodServer
octopod-backend/octopod-backend.cabalnew file+189
@@ -0,0 +1,189 @@
1+cabal-version: 2.4
2+name: octopod-backend
3+version: 0.1.0.0
4+description: Please see the README on GitHub at <https://github.com/typeable/octopod#readme>
5+homepage: https://github.com/typeable/octopod
6+bug-reports: https://github.com/typeable/octopod/issues
7+author: Typeable
8+maintainer: octopod@typeable.io
9+license: BSD-3-Clause
10+license-file: LICENSE
11+build-type: Simple
12+extra-source-files:
13+ README.md
14+ ChangeLog.md
15+
16+source-repository head
17+ type: git
18+ location: https://github.com/typeable/octopod
19+
20+library
21+ ghc-options: -Wall
22+ -Werror
23+ default-extensions: BlockArguments
24+ , ConstraintKinds
25+ , DataKinds
26+ , DeriveFunctor
27+ , DeriveGeneric
28+ , DerivingVia
29+ , DuplicateRecordFields
30+ , FlexibleContexts
31+ , GeneralizedNewtypeDeriving
32+ , LambdaCase
33+ , OverloadedStrings
34+ , ScopedTypeVariables
35+ , StandaloneDeriving
36+ , TypeApplications
37+ , TypeOperators
38+ exposed-modules:
39+ Octopod.Server
40+ Octopod.Server.Args
41+ Octopod.Server.ControlScriptUtils
42+ Octopod.Server.Logger
43+ Octopod.Server.Posix
44+ Orphans
45+ TLS
46+ Types
47+ hs-source-dirs:
48+ src
49+ build-depends:
50+ aeson
51+ , async
52+ , base >=4.7 && <5
53+ , bytestring
54+ , chronos
55+ , conduit
56+ , connection
57+ , data-default-class
58+ , deriving-aeson
59+ , directory
60+ , octopod-common
61+ , fast-logger
62+ , filepath
63+ , generic-lens
64+ , http-api-data
65+ , http-client
66+ , http-client-tls
67+ , lens
68+ , mtl
69+ , network-uri
70+ , octopod-api
71+ , optparse-applicative
72+ , optparse-generic
73+ , postgresql-error-codes
74+ , postgresql-simple
75+ , resource-pool
76+ , servant
77+ , servant-client
78+ , servant-client-core
79+ , servant-server
80+ , servant-websockets
81+ , stm
82+ , temporary
83+ , text
84+ , tls
85+ , transformers
86+ , transformers-base
87+ , typed-process
88+ , unix
89+ , warp
90+ , warp-tls
91+ , x509
92+ , x509-store
93+ , x509-validation
94+ default-language: Haskell2010
95+
96+executable octopod-exe
97+ main-is: Main.hs
98+ ghc-options: -Wall
99+ -Werror
100+ hs-source-dirs: app
101+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
102+ build-depends:
103+ aeson
104+ , async
105+ , base >=4.7 && <5
106+ , bytestring
107+ , chronos
108+ , conduit
109+ , connection
110+ , data-default-class
111+ , directory
112+ , octopod-backend
113+ , fast-logger
114+ , http-client
115+ , http-client-tls
116+ , mtl
117+ , network-uri
118+ , optparse-applicative
119+ , optparse-generic
120+ , postgresql-error-codes
121+ , postgresql-simple
122+ , resource-pool
123+ , servant
124+ , servant-client
125+ , servant-client-core
126+ , servant-server
127+ , servant-websockets
128+ , stm
129+ , temporary
130+ , text
131+ , tls
132+ , transformers
133+ , transformers-base
134+ , typed-process
135+ , unix
136+ , warp
137+ , warp-tls
138+ , x509
139+ , x509-store
140+ , x509-validation
141+ default-language: Haskell2010
142+
143+test-suite octopod-test
144+ type: exitcode-stdio-1.0
145+ main-is: Spec.hs
146+ hs-source-dirs:
147+ test
148+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
149+ build-depends:
150+ aeson
151+ , async
152+ , base >=4.7 && <5
153+ , bytestring
154+ , chronos
155+ , conduit
156+ , connection
157+ , data-default-class
158+ , directory
159+ , octopod-backend
160+ , fast-logger
161+ , filepath
162+ , http-client
163+ , http-client-tls
164+ , mtl
165+ , network-uri
166+ , optparse-applicative
167+ , optparse-generic
168+ , postgresql-error-codes
169+ , postgresql-simple
170+ , resource-pool
171+ , servant
172+ , servant-client
173+ , servant-client-core
174+ , servant-server
175+ , servant-websockets
176+ , stm
177+ , temporary
178+ , text
179+ , tls
180+ , transformers
181+ , transformers-base
182+ , typed-process
183+ , unix
184+ , warp
185+ , warp-tls
186+ , x509
187+ , x509-store
188+ , x509-validation
189+ default-language: Haskell2010
octopod-backend/src/Octopod/Server.hsnew file+1236
@@ -0,0 +1,1236 @@
1+module Octopod.Server (runOctopodServer) where
2+
3+
4+import Chronos (Time, getTime, now)
5+import Control.Applicative
6+import Control.Concurrent (forkFinally, threadDelay)
7+import Control.Concurrent.Async (race_)
8+import Control.Concurrent.MVar
9+import Control.Concurrent.STM
10+import Control.Exception (Exception, throwIO, try)
11+import Control.Monad
12+import Control.Monad.IO.Class
13+import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
14+import Data.Aeson (Value(..), encode, toJSON)
15+import Data.ByteString (ByteString)
16+import qualified Data.ByteString.Lazy as BSL
17+import Data.Coerce
18+import Data.Conduit (ConduitT, yield)
19+import Data.Foldable (foldrM)
20+import Data.Functor ((<&>))
21+import Data.Int (Int64)
22+import Data.IORef
23+import Data.Maybe
24+import Data.Pool
25+import Data.Text (lines, pack, unpack, unwords)
26+import Data.Text.IO (hGetContents)
27+import Data.Traversable
28+import Database.PostgreSQL.Simple
29+import Network.Wai.Handler.Warp
30+import Network.Wai.Handler.WarpTLS
31+import Options.Generic
32+import PostgreSQL.ErrorCodes (unique_violation)
33+import Prelude hiding (lines, log, unlines, unwords)
34+import Servant
35+import System.Environment (lookupEnv)
36+import System.Exit
37+import System.Log.FastLogger
38+import System.Posix.Signals (sigTERM)
39+import System.Process.Typed
40+
41+
42+import Common.Utils
43+import Common.Validation (isNameValid)
44+import Octopod.API
45+import Octopod.PowerAPI
46+import Octopod.Server.Args
47+import Octopod.Server.ControlScriptUtils
48+import Octopod.Server.Logger
49+import Octopod.Server.Posix
50+import Orphans ()
51+import TLS (createTLSOpts)
52+import Types
53+
54+type PgPool = Pool Connection
55+type AppM = ReaderT AppState Handler
56+
57+-- | Octopod Server state definition.
58+data AppState = AppState
59+ { pool :: PgPool
60+ -- ^ postgres pool
61+ , logger :: TimedFastLogger
62+ -- ^ logger
63+ , eventSink :: TChan WSEvent
64+ -- ^ channel for WS events for the frontend
65+ , bgWorkersCounter :: IORef Int
66+ -- ^ background workers counter
67+ , gracefulShutdownActivated :: IORef Bool
68+ -- ^ flag of activating graceful shutdown
69+ , shutdownSem :: MVar ()
70+ -- ^ semaphore for graceful shutdown
71+ , projectName :: ProjectName
72+ -- ^ project name
73+ , baseDomain :: Domain
74+ -- ^ base domain
75+ , namespace :: Namespace
76+ -- ^ namespace
77+ , archiveRetention :: ArchiveRetention
78+ -- ^ archive retention
79+ , statusUpdateTimeout :: Timeout
80+ -- ^ status update timeout
81+ , creationCommand :: Command
82+ -- ^ creation command path
83+ , updateCommand :: Command
84+ -- ^ update command path
85+ , archiveCommand :: Command
86+ -- ^ deletion command path
87+ , checkingCommand :: Command
88+ -- ^ checking command path
89+ , cleanupCommand :: Command
90+ -- ^ cleanup command path
91+ , archiveCheckingCommand :: Command
92+ -- ^ archive checking command path
93+ , tagCheckingCommand :: Command
94+ -- ^ tag checking command path
95+ , infoCommand :: Command
96+ }
97+
98+-- | Deployment exception definition.
99+data DeploymentException
100+ = DeploymentFailed Int
101+ | ThereIsActiveDeployment
102+ deriving (Show)
103+
104+instance Exception DeploymentException
105+
106+-- | Deployment list type definition.
107+data DeploymentListType
108+ = AllDeployments
109+ | ArchivedOnlyDeployments
110+ | ActiveOnlyDeployments
111+ deriving (Show)
112+
113+-- | Full info list type definition.
114+data FullInfoListType
115+ = FullInfoForAll
116+ | FullInfoOnlyForOne DeploymentName
117+ deriving (Show)
118+
119+-- | Deployment search errors definition.
120+data DeploymentNotFound
121+ = DeploymentNotFound
122+ | ArchivedDeploymentNotFound
123+ | ActiveDeploymentNotFound
124+ | DeploymentHasPendingStatus DeploymentStatus
125+
126+-- | Definition of a filter by pending statuses.
127+data FilterByPending
128+ = AllowPending
129+ | DenyPending
130+
131+runOctopodServer :: IO ()
132+runOctopodServer = do
133+ logger' <- newLogger
134+ logInfo logger' "started"
135+ bgWorkersC <- newIORef 0
136+ gracefulShutdownAct <- newIORef False
137+ shutdownS <- newEmptyMVar
138+ void $ do
139+ let
140+ termHandler = terminationHandler bgWorkersC gracefulShutdownAct shutdownS
141+ installShutdownHandler logger' [sigTERM] termHandler
142+ opts <- parseArgs
143+ let
144+ a ?! e = a >>= maybe (die e) pure
145+ getEnvOrDie eName = lookupEnv eName ?! (eName <> " is not set")
146+ projName <- coerce . pack <$> getEnvOrDie "PROJECT_NAME"
147+ domain <- coerce . pack <$> getEnvOrDie "BASE_DOMAIN"
148+ ns <- coerce . pack <$> getEnvOrDie "NAMESPACE"
149+ archRetention <- coerce . read @Int <$> getEnvOrDie "ARCHIVE_RETENTION"
150+ stUpdateTimeout <- coerce . read @Int <$> getEnvOrDie "STATUS_UPDATE_TIMEOUT"
151+ creationCmd <- coerce . pack <$> getEnvOrDie "CREATION_COMMAND"
152+ updateCmd <- coerce . pack <$> getEnvOrDie "UPDATE_COMMAND"
153+ archiveCmd <- coerce . pack <$> getEnvOrDie "ARCHIVE_COMMAND"
154+ checkingCmd <- coerce . pack <$> getEnvOrDie "CHECKING_COMMAND"
155+ cleanupCmd <- coerce . pack <$> getEnvOrDie "CLEANUP_COMMAND"
156+ archiveCheckingCmd <- coerce . pack <$> getEnvOrDie "ARCHIVE_CHECKING_COMMAND"
157+ tagCheckingCmd <- coerce . pack <$> getEnvOrDie "TAG_CHECKING_COMMAND"
158+ infoCmd <- coerce . pack <$> getEnvOrDie "INFO_COMMAND"
159+ pgPool <- initConnectionPool
160+ (unDBConnectionString $ octopodDB opts)
161+ (unDBPoolSize $ octopodDBPoolSize opts)
162+ channel <- liftIO . atomically $ newBroadcastTChan
163+ let
164+ appSt =
165+ AppState
166+ pgPool
167+ logger'
168+ channel
169+ bgWorkersC
170+ gracefulShutdownAct
171+ shutdownS
172+ projName
173+ domain
174+ ns
175+ archRetention
176+ stUpdateTimeout
177+ creationCmd
178+ updateCmd
179+ archiveCmd
180+ checkingCmd
181+ cleanupCmd
182+ archiveCheckingCmd
183+ tagCheckingCmd
184+ infoCmd
185+ app' = app appSt
186+ powerApp' = powerApp appSt
187+ wsApp' = wsApp channel
188+ serverPort = octopodPort opts
189+ uiServerPort = unServerPort $ octopodUIPort opts
190+ wsServerPort = unServerPort $ octopodWSPort opts
191+ tlsOpts =
192+ createTLSOpts (octopodTLSCertPath opts) (octopodTLSKeyPath opts)
193+ (octopodTLSStorePath opts) serverPort
194+ warpOpts = setPort (unServerPort serverPort) defaultSettings
195+ in
196+ (run uiServerPort app')
197+ `race_` (runTLS tlsOpts warpOpts powerApp')
198+ `race_` (run wsServerPort wsApp')
199+ `race_` (runStatusUpdater appSt)
200+ `race_` (runShutdownHandler appSt)
201+
202+-- | Initializes the connection pool.
203+initConnectionPool :: ByteString -> Int -> IO PgPool
204+initConnectionPool dbConnStr =
205+ createPool (connectPostgreSQL dbConnStr) close 1 30
206+
207+-- | Helper to run the server.
208+nt :: AppState -> AppM a -> Handler a
209+nt s x = runReaderT x s
210+
211+-- | Application with the Web UI API.
212+app :: AppState -> Application
213+app s = serve api $ hoistServer api (nt s) server
214+ where
215+ api = Proxy @API
216+
217+-- | Request handlers of the Web UI API application.
218+server :: ServerT API AppM
219+server =
220+ ( listH :<|> createH :<|> archiveH :<|> updateH
221+ :<|> infoH :<|> fullInfoH :<|> statusH :<|> restoreH
222+ ) :<|> pingH :<|> projectNameH
223+
224+-- | Application with the octo CLI API.
225+powerApp :: AppState -> Application
226+powerApp s = serve api $ hoistServer api (nt s) powerServer
227+ where
228+ api = Proxy @PowerAPI
229+
230+-- | Request handlers of application with the octo CLI API.
231+powerServer :: ServerT PowerAPI AppM
232+powerServer =
233+ ( powerListH :<|> createH :<|> archiveH :<|> updateH
234+ :<|> powerInfoH :<|> powerFullInfoH :<|> statusH :<|> cleanupH :<|> restoreH
235+ ) :<|> getActionInfoH :<|> cleanArchiveH
236+
237+-- | Application with the WS API.
238+wsApp :: TChan WSEvent -> Application
239+wsApp channel = serve api $ wsServer channel
240+ where
241+ api = Proxy @WebSocketAPI
242+
243+-- | Request handlers of the application with the WS API.
244+wsServer :: TChan WSEvent -> Server WebSocketAPI
245+wsServer = eventS
246+
247+-- | Handles WS events.
248+eventS :: MonadIO m => TChan WSEvent -> ConduitT () Value m ()
249+eventS channel = do
250+ dupChannel <- liftIO . atomically $ dupTChan channel
251+ forever $ do
252+ event <- liftIO . atomically . readTChan $ dupChannel
253+ yield . toJSON $ event
254+
255+-- | Handles the 'list' request of the Web UI API.
256+listH :: AppM [DeploymentFullInfo]
257+listH = hidePrivateOverridesInFullInfos <$> getFullInfo FullInfoForAll
258+
259+-- | Handles the 'list' request of the octo CLI API.
260+powerListH :: AppM [DeploymentFullInfo]
261+powerListH = getFullInfo FullInfoForAll
262+
263+-- | Handles the 'full_info' request of the Web UI API.
264+fullInfoH :: DeploymentName -> AppM DeploymentFullInfo
265+fullInfoH dName = do
266+ fullInfoList <- hidePrivateOverridesInFullInfos <$>
267+ getFullInfo (FullInfoOnlyForOne dName)
268+ case fullInfoList of
269+ fullInfo : _ -> pure fullInfo
270+ [] -> throwError err404
271+ { errBody = validationError ["Name not found"] [] }
272+
273+-- | Handles the 'full_info' request of the octo CLI API.
274+powerFullInfoH :: DeploymentName -> AppM DeploymentFullInfo
275+powerFullInfoH dName = do
276+ fullInfoList <- getFullInfo $ FullInfoOnlyForOne dName
277+ case fullInfoList of
278+ fullInfo : _ -> pure fullInfo
279+ [] -> throwError err404
280+ { errBody = validationError ["Name not found"] [] }
281+
282+-- | Hides private overrides in 'full_info' response.
283+hidePrivateOverridesInFullInfos :: [DeploymentFullInfo] -> [DeploymentFullInfo]
284+hidePrivateOverridesInFullInfos dFullInfos = do
285+ dFullInfos <&> \(DeploymentFullInfo dep s a ct ut u) ->
286+ let
287+ hidePrivate (Deployment n t appOvs depOvs) =
288+ Deployment n t (hideP appOvs) (hideP depOvs)
289+ hideP o =
290+ coerce o <&> \(Override k v vis) ->
291+ let
292+ v' = case vis of
293+ Private -> "*"
294+ Public -> v
295+ in coerce $ Override k v' vis
296+ in DeploymentFullInfo (hidePrivate dep) s a ct ut u
297+
298+-- | Helper to get full_info from the database.
299+getFullInfo :: FullInfoListType -> AppM [DeploymentFullInfo]
300+getFullInfo listType = do
301+ AppState {pool = p, logger = l} <- ask
302+ deployments <- liftIO $ withResource p $ \conn -> do
303+ rows <- case listType of
304+ FullInfoForAll -> query_ conn qAll
305+ FullInfoOnlyForOne (dName) -> query conn qOne (Only dName)
306+ for rows $ \(n, t, a, ct, ut, st) -> do
307+ (appOvs, depOvs) <- selectOverrides conn n
308+ dMeta <- selectDeploymentMetadata conn n
309+ pure $ do
310+ let dep = (Deployment n t appOvs depOvs)
311+ DeploymentFullInfo dep (read st) a dMeta ct ut
312+ liftIO . logInfo l $ "get deployments: " <> (pack . show $ deployments)
313+ return deployments
314+ where
315+ qAll =
316+ "SELECT name, tag, archived, extract(epoch from created_at)::int, \
317+ \extract(epoch from updated_at)::int, status::text \
318+ \FROM deployments ORDER BY name"
319+ qOne =
320+ "SELECT name, tag, archived, extract(epoch from created_at)::int, \
321+ \extract(epoch from updated_at)::int, status::text \
322+ \FROM deployments \
323+ \WHERE name = ?"
324+
325+-- | Handles the 'create' request.
326+createH :: Deployment -> AppM CommandResponse
327+createH dep = do
328+ failIfGracefulShutdownActivated
329+ unless (isNameValid $ name dep) $ do
330+ let
331+ badNameText =
332+ "Deployment name length should be longer than 2 characters, \
333+ \under 17 characters and begin with a letter."
334+ throwError err400
335+ { errBody = validationError [badNameText] [] }
336+ t1 <- liftIO $ now
337+ st <- ask
338+ let
339+ q =
340+ "INSERT INTO deployments (name, tag, status) \
341+ \VALUES (?, ?, ?) RETURNING id"
342+ pgPool = pool st
343+ createDep :: PgPool -> Deployment -> IO [Only Int]
344+ createDep p Deployment { name = n, tag = t } =
345+ withResource p $ \conn ->
346+ query conn q (n, t, show CreatePending)
347+ failIfImageNotFound (name dep) (tag dep)
348+ failIfGracefulShutdownActivated
349+ res :: Either SqlError [Only Int] <- liftIO . try $ createDep pgPool dep
350+ dId <- case res of
351+ Right ((Only depId) : _) ->
352+ pure . DeploymentId $ depId
353+ Right [] ->
354+ throwError err404
355+ { errBody = validationError ["Name not found"] [] }
356+ Left (SqlError code _ _ _ _) | code == unique_violation ->
357+ throwError err400
358+ { errBody = validationError ["Deployment already exists"] [] }
359+ Left (SqlError _ _ _ _ _) ->
360+ throwError err409 { errBody = appError "Some database error" }
361+ liftIO . withResource pgPool $ \conn ->
362+ upsertNewOverrides conn dId (appOverrides dep) (deploymentOverrides dep)
363+ liftIO . runBgWorker st $ do
364+ sendReloadEvent st
365+ updateDeploymentInfo (name dep) st
366+ (ec, out, err) <- createDeployment dep st
367+ t2 <- now
368+ let
369+ arch = ArchivedFlag False
370+ elTime = elapsedTime t2 t1
371+ createDeploymentLog pgPool dep "create" ec arch elTime out err
372+ sendReloadEvent st
373+ handleExitCode ec
374+ pure Success
375+
376+-- | Updates deployment info.
377+updateDeploymentInfo :: DeploymentName -> AppState -> IO ()
378+updateDeploymentInfo dName st = do
379+ let
380+ log = logWarning (logger st)
381+ args = infoCommandArgs (projectName st) (baseDomain st) (namespace st) dName
382+ cmd = coerce $ infoCommand st
383+ liftIO $ do
384+ (ec, out, err) <- runCommand (unpack cmd) (coerce args)
385+ case ec of
386+ ExitSuccess -> do
387+ dMeta <- parseDeploymentMetadata (lines . unStdout $ out)
388+ forM_ dMeta $ \meta ->
389+ upsertDeploymentMetadata (pool st) dName meta
390+ ExitFailure _ ->
391+ log $
392+ "could not get deployment info, exit code: " <> (pack . show $ ec)
393+ <> ", stdout: " <> coerce out <> "stderr: " <> coerce err
394+
395+-- | Helper to create a new deployment.
396+createDeployment :: Deployment -> AppState -> IO (ExitCode, Stdout, Stderr)
397+createDeployment dep st = do
398+ let
399+ log :: Text -> IO ()
400+ log = logInfo (logger st)
401+ args =
402+ [ "--project-name", coerce $ projectName st
403+ , "--base-domain", coerce $ baseDomain st
404+ , "--namespace", coerce $ namespace st
405+ , "--name", coerce $ name dep
406+ , "--tag", coerce $ tag dep
407+ ] ++ applicationOverridesToArgs (appOverrides dep)
408+ ++ deploymentOverridesToArgs (deploymentOverrides dep)
409+ cmd = coerce $ creationCommand st
410+
411+ liftIO $ do
412+ log $ "call " <> unwords (cmd : args)
413+ (ec, out, err) <- runCommand (unpack cmd) (unpack <$> args)
414+ log $ "deployment created, deployment: " <> (pack . show $ dep)
415+ pure (ec, out, err)
416+
417+-- | Converts an application-level override list to command arguments.
418+applicationOverrideToArg :: ApplicationOverride -> [Text]
419+applicationOverrideToArg o = ["--app-env-override", overrideToArg . coerce $ o]
420+
421+-- | Helper to convert an application-level override to command arguments.
422+applicationOverridesToArgs :: ApplicationOverrides -> [Text]
423+applicationOverridesToArgs ovs = concat [applicationOverrideToArg o | o <- ovs ]
424+
425+-- | Converts a deployment-level override list to command arguments.
426+deploymentOverrideToArg :: DeploymentOverride -> [Text]
427+deploymentOverrideToArg o =
428+ ["--deployment-override", overrideToArg . coerce $ o]
429+
430+-- | Helper to convert a deployment-level override to command arguments.
431+deploymentOverridesToArgs :: DeploymentOverrides -> [Text]
432+deploymentOverridesToArgs ovs = concat [deploymentOverrideToArg o | o <- ovs]
433+
434+-- | Helper to get deployment logs.
435+selectDeploymentLogs
436+ :: PgPool
437+ -> DeploymentId
438+ -> IO [DeploymentLog]
439+selectDeploymentLogs pgPool dId = do
440+ let
441+ q =
442+ "SELECT id, action::text, tag, exit_code, \
443+ \duration, extract(epoch from created_at)::int \
444+ \FROM deployment_logs \
445+ \WHERE deployment_id = ? \
446+ \ORDER BY created_at DESC \
447+ \LIMIT 20"
448+ withResource pgPool $ \conn -> do
449+ rows <- query conn q (Only . unDeploymentId $ dId)
450+ -- FIXME: use FromRow instance instead
451+ for rows $ \(ai, a, t, ec, d, ts) -> do
452+ (appOvs, depOvs) <- selectLogOverrides conn (ActionId ai)
453+ pure $ DeploymentLog (ActionId ai) a t appOvs depOvs ec (Duration d) ts
454+
455+-- | Helper to get a deployment.
456+selectDeployment
457+ :: PgPool
458+ -> DeploymentName
459+ -> DeploymentListType
460+ -> FilterByPending
461+ -> AppM (Either DeploymentNotFound Deployment)
462+selectDeployment pgPool dName lType filterByPending = do
463+ let
464+ baseQuery =
465+ "SELECT name, tag, status::text FROM deployments \
466+ \WHERE name = ?"
467+ q AllDeployments = baseQuery
468+ q ArchivedOnlyDeployments = baseQuery <> " AND archived = 't'"
469+ q ActiveOnlyDeployments = baseQuery <> " AND archived = 'f'"
470+ allowPending =
471+ case filterByPending of
472+ AllowPending -> True
473+ DenyPending -> False
474+ result <- liftIO . withResource pgPool $ \conn -> do
475+ retrieved <- query conn (q lType) (Only dName)
476+ for retrieved $ \(n, t, s) -> do
477+ (appOvs, stOvs) <- selectOverrides conn n
478+ pure $ (Deployment n t appOvs stOvs, read s)
479+ pure $
480+ case result of
481+ [(_, st)] | isPending st && not allowPending ->
482+ Left $ DeploymentHasPendingStatus st
483+ [(dep, _)] ->
484+ Right dep
485+ _ ->
486+ Left $
487+ case lType of
488+ AllDeployments -> DeploymentNotFound
489+ ArchivedOnlyDeployments -> ArchivedDeploymentNotFound
490+ ActiveOnlyDeployments -> ActiveDeploymentNotFound
491+
492+-- | Handles deployment search result.
493+unwrapOrValidateError
494+ :: AppM (Either DeploymentNotFound Deployment)
495+ -> AppM Deployment
496+unwrapOrValidateError deploymentSearchResult = do
497+ deploymentSearchResult' <- deploymentSearchResult
498+ case deploymentSearchResult' of
499+ Right dep -> pure dep
500+ Left DeploymentNotFound ->
501+ throwError err404
502+ { errBody = validationError ["Deployment not found"] [] }
503+ Left ArchivedDeploymentNotFound ->
504+ throwError err404
505+ { errBody = validationError ["Archived deployment not found"] [] }
506+ Left ActiveDeploymentNotFound ->
507+ throwError err404
508+ { errBody = validationError ["Active deployment not found"] [] }
509+ Left (DeploymentHasPendingStatus st) ->
510+ let
511+ err =
512+ "You can not apply this operation \
513+ \on deployment with \"" <> (pack . show $ st) <> "\" status"
514+ in throwError err405 { errBody = validationError [err] [] }
515+
516+-- | Handles the 'archive' request.
517+archiveH :: DeploymentName -> AppM CommandResponse
518+archiveH dName = do
519+ failIfGracefulShutdownActivated
520+ t1 <- liftIO $ now
521+ st <- ask
522+ let
523+ log = logInfo (logger st)
524+ pgPool = pool st
525+ args =
526+ [ "--project-name", coerce $ projectName st
527+ , "--base-domain", coerce $ baseDomain st
528+ , "--namespace", coerce $ namespace st
529+ , "--name", coerce dName
530+ ]
531+ cmd = coerce $ archiveCommand st
532+ arch = ArchivedFlag True
533+ dep <- unwrapOrValidateError
534+ $ selectDeployment pgPool dName ActiveOnlyDeployments DenyPending
535+ liftIO . runBgWorker st $ do
536+ log $ "call " <> unwords (cmd : args)
537+ (ec, out, err) <- runCommand (unpack cmd) (unpack <$> args)
538+ void $ archiveDeployment pgPool dName
539+ sendReloadEvent st
540+ t2 <- now
541+ let elTime = elapsedTime t2 t1
542+ void $ createDeploymentLog pgPool dep "archive" ec arch elTime out err
543+ log $ "deployment archived, name: " <> coerce dName
544+ sendReloadEvent st
545+ handleExitCode ec
546+ pure Success
547+
548+-- | Handles the 'archive' request.
549+archiveDeployment :: PgPool -> DeploymentName -> IO Int64
550+archiveDeployment p dName = withResource p $ \conn -> do
551+ let
552+ q =
553+ "UPDATE deployments \
554+ \SET archived = 't', archived_at = now(), \
555+ \status = ?, status_updated_at = now() \
556+ \WHERE name = ?"
557+ execute conn q (show ArchivePending, dName)
558+
559+-- | Handles the 'update' request.
560+updateH :: DeploymentName -> DeploymentUpdate -> AppM CommandResponse
561+updateH dName dUpdate = do
562+ failIfGracefulShutdownActivated
563+ t1 <- liftIO $ now
564+ st <- ask
565+ let
566+ DeploymentUpdate
567+ { newTag = dTag
568+ , newAppOverrides = newAppOvs
569+ , oldAppOverrides = oldAppOvs
570+ , newDeploymentOverrides = newDepOvs
571+ , oldDeploymentOverrides = oldDepOvs
572+ } = dUpdate
573+ pgPool = pool st
574+ log = logInfo (logger st)
575+
576+ void $ unwrapOrValidateError
577+ $ selectDeployment pgPool dName ActiveOnlyDeployments DenyPending
578+ dId <- selectDeploymentId pgPool dName
579+ failIfImageNotFound dName dTag
580+ failIfGracefulShutdownActivated
581+ liftIO . runBgWorker st $ do
582+ (appOvs, depOvs) <- withResource pgPool $ \conn ->
583+ withTransaction conn $ do
584+ deleteOldOverrides conn dId oldAppOvs oldDepOvs
585+ upsertNewOverrides conn dId newAppOvs newDepOvs
586+ void $ updateDeployment conn dName dTag
587+ selectOverrides conn dName
588+ updateDeploymentInfo dName st
589+ sendReloadEvent st
590+ let
591+ args =
592+ [ "--project-name", coerce $ projectName st
593+ , "--base-domain", coerce $ baseDomain st
594+ , "--namespace", coerce $ namespace st
595+ , "--name", coerce $ dName
596+ , "--tag", coerce $ dTag
597+ ] ++ applicationOverridesToArgs appOvs
598+ ++ deploymentOverridesToArgs depOvs
599+ cmd = coerce $ updateCommand st
600+ log $ "call " <> unwords (cmd : args)
601+ (ec, out, err) <- runCommand (unpack cmd) (unpack <$> args)
602+ log $ "deployment updated, name: "
603+ <> coerce dName <> ", tag: " <> coerce dTag
604+ void $ do
605+ t2 <- now
606+ let
607+ dep = Deployment dName dTag appOvs depOvs
608+ arch = ArchivedFlag False
609+ elTime = elapsedTime t2 t1
610+ createDeploymentLog pgPool dep "update" ec arch elTime out err
611+ sendReloadEvent st
612+ handleExitCode ec
613+ return Success
614+
615+-- | Helper to get overrides from the database.
616+selectOverrides
617+ :: Connection
618+ -> DeploymentName
619+ -> IO (ApplicationOverrides, DeploymentOverrides)
620+selectOverrides conn dName = do
621+ let
622+ q =
623+ "SELECT key, value, scope::text, visibility::text \
624+ \FROM deployment_overrides \
625+ \WHERE deployment_id = ( \
626+ \SELECT id FROM deployments WHERE name = ? \
627+ \)"
628+ parseVis :: Text -> OverrideVisibility
629+ parseVis = read . unpack
630+ parseScope :: Text -> OverrideScope
631+ parseScope = read . unpack
632+ toOverrides (k, v, s, vis) (appOvs, depOvs) =
633+ pure $ case parseScope s of
634+ ApplicationScope ->
635+ (ApplicationOverride (Override k v $ parseVis vis) : appOvs, depOvs)
636+ DeploymentScope ->
637+ (appOvs, DeploymentOverride (Override k v $ parseVis vis) : depOvs)
638+ rows <- query conn q (Only dName)
639+ foldrM toOverrides ([], []) rows
640+
641+-- | Helper to get override logs from the database.
642+selectLogOverrides
643+ :: Connection
644+ -> ActionId
645+ -> IO (ApplicationOverrides, DeploymentOverrides)
646+selectLogOverrides conn aId = do
647+ let
648+ q =
649+ "SELECT key, value, scope::text, visibility::text \
650+ \FROM deployment_log_overrides \
651+ \WHERE deployment_log_id = ?"
652+ parseVis :: Text -> OverrideVisibility
653+ parseVis = read . unpack
654+ parseScope :: Text -> OverrideScope
655+ parseScope = read . unpack
656+ toOverrides (k, v, s, vis) (appOvs, depOvs) =
657+ pure $ case parseScope s of
658+ ApplicationScope ->
659+ (ApplicationOverride (Override k v $ parseVis vis) : appOvs, depOvs)
660+ DeploymentScope ->
661+ (appOvs, DeploymentOverride (Override k v $ parseVis vis) : depOvs)
662+ rows <- query conn q (Only . unActionId $ aId)
663+ foldrM toOverrides ([], []) rows
664+
665+-- | Helper to delete overrides from the database.
666+deleteOldOverrides
667+ :: Connection
668+ -> DeploymentId
669+ -> ApplicationOverrides
670+ -> DeploymentOverrides
671+ -> IO ()
672+deleteOldOverrides conn dId appOvs depOvs = do
673+ let
674+ q =
675+ "DELETE FROM deployment_overrides \
676+ \WHERE deployment_id = ? AND key = ? AND scope = ?"
677+ dId' = unDeploymentId dId
678+ void $ for appOvs $ \o -> do
679+ let
680+ oKey = overrideKey . unApplicationOverride $ o
681+ oScope = show ApplicationScope
682+ execute conn q (dId', oKey, oScope)
683+ void $ for depOvs $ \o -> do
684+ let
685+ oKey = overrideKey . unDeploymentOverride $ o
686+ oScope = show DeploymentScope
687+ execute conn q (dId', oKey, oScope)
688+
689+-- | Helper to get the deployment id from the database.
690+selectDeploymentId :: PgPool -> DeploymentName -> AppM DeploymentId
691+selectDeploymentId pgPool dName = do
692+ dIds :: [(Only Int)] <- liftIO $ withResource pgPool $ \conn ->
693+ query conn "SELECT id FROM deployments WHERE name = ?" (Only dName)
694+ case dIds of
695+ [(Only dId)] -> pure . DeploymentId $ dId
696+ [] -> throwError err404
697+ { errBody = validationError ["Name not found"] [] }
698+ _ -> throwError err406
699+ { errBody = validationError ["More than one name found"] [] }
700+
701+-- | Helper to insert or update overrides.
702+upsertNewOverrides
703+ :: Connection
704+ -> DeploymentId
705+ -> ApplicationOverrides
706+ -> DeploymentOverrides
707+ -> IO ()
708+upsertNewOverrides conn dId appOvs depOvs = do
709+ let
710+ q =
711+ "INSERT INTO deployment_overrides \
712+ \(key, value, deployment_id, scope, visibility) \
713+ \VALUES (?, ?, ?, ?, ?) \
714+ \ON CONFLICT (key, deployment_id, scope) \
715+ \DO \
716+ \UPDATE SET value = ?, visibility = ?, updated_at = now()"
717+ dId' = unDeploymentId dId
718+ void $ for appOvs $ \o -> do
719+ let
720+ oKey = overrideKey . unApplicationOverride $ o
721+ oValue = overrideValue . unApplicationOverride $ o
722+ oScope = show ApplicationScope
723+ oVis = show . overrideVisibility . unApplicationOverride $ o
724+ execute conn q (oKey, oValue, dId', oScope, oVis, oValue, oVis)
725+ void $ for depOvs $ \o -> do
726+ let
727+ oKey = overrideKey . unDeploymentOverride $ o
728+ oValue = overrideValue . unDeploymentOverride $ o
729+ oScope = show DeploymentScope
730+ oVis = show . overrideVisibility . unDeploymentOverride $ o
731+ execute conn q (oKey, oValue, dId', oScope, oVis, oValue, oVis)
732+
733+-- | Helper to update a deployment.
734+updateDeployment
735+ :: Connection
736+ -> DeploymentName
737+ -> DeploymentTag
738+ -> IO Int64
739+updateDeployment conn dName dTag = do
740+ let
741+ q =
742+ "UPDATE deployments \
743+ \SET tag = ?, updated_at = now(), \
744+ \status = ?, status_updated_at = now() \
745+ \WHERE name = ?"
746+ execute conn q (dTag, show UpdatePending, dName)
747+
748+-- | Handles the 'info' request of the Web UI API.
749+infoH :: DeploymentName -> AppM [DeploymentInfo]
750+infoH dName = do
751+ st <- ask
752+ dInfo <- getInfo dName
753+ liftIO . logInfo (logger st) $
754+ "get deployment info: " <> (pack . show $ dInfo)
755+ pure [hidePrivateOverridesInInfo dInfo]
756+
757+-- | Handles the 'info' request of the octo CLI API.
758+powerInfoH :: DeploymentName -> AppM [DeploymentInfo]
759+powerInfoH dName = do
760+ st <- ask
761+ dInfo <- getInfo dName
762+ liftIO . logInfo (logger st) $
763+ "get deployment info: " <> (pack . show $ dInfo)
764+ pure [dInfo]
765+
766+-- | Hides private overrides of 'info' response.
767+hidePrivateOverridesInInfo :: DeploymentInfo -> DeploymentInfo
768+hidePrivateOverridesInInfo (DeploymentInfo dep dMeta dLogs) =
769+ let
770+ dep' =
771+ let (Deployment n t ao so) = dep
772+ in Deployment n t (hideP ao) (hideP so)
773+ dLogs' = dLogs <&> \(DeploymentLog ai a t ao so ec d ct) ->
774+ DeploymentLog ai a t (hideP ao) (hideP so) ec d ct
775+ hideP o = coerce o <&> \(Override k v vis) ->
776+ let
777+ v' = case vis of
778+ Private -> "*"
779+ Public -> v
780+ in coerce $ Override k v' vis
781+ in DeploymentInfo dep' dMeta dLogs'
782+
783+-- | Helper to get deployment info from the database.
784+getInfo :: DeploymentName -> AppM DeploymentInfo
785+getInfo dName = do
786+ st <- ask
787+ let pgPool = pool st
788+ dep <- unwrapOrValidateError
789+ $ selectDeployment pgPool dName AllDeployments AllowPending
790+ dId <- selectDeploymentId pgPool dName
791+ liftIO $ do
792+ depLogs <- selectDeploymentLogs pgPool dId
793+ dMeta <- withResource pgPool $ \conn ->
794+ selectDeploymentMetadata conn dName
795+ let depInfo = DeploymentInfo dep dMeta $ reverse depLogs
796+ pure depInfo
797+
798+-- | Handles the 'ping' request.
799+pingH :: AppM NoContent
800+pingH = do
801+ pgPool <- pool <$> ask
802+ _ :: [Only Int] <- liftIO $ withResource pgPool $ \conn ->
803+ query_ conn "SELECT 1"
804+ pure NoContent
805+
806+-- | Handles the 'project_name' request.
807+projectNameH :: AppM ProjectName
808+projectNameH = projectName <$> ask
809+
810+-- | Handles the 'status' request.
811+statusH :: DeploymentName -> AppM CurrentDeploymentStatus
812+statusH dName = do
813+ st <- ask
814+ let
815+ pgPool = pool st
816+ log = logInfo (logger st)
817+ cmd = checkingCommand st
818+ args =
819+ [ "--namespace", coerce $ namespace st
820+ , "--name", coerce $ dName ]
821+ void $ unwrapOrValidateError
822+ $ selectDeployment pgPool dName AllDeployments AllowPending
823+ liftIO $ log $ "call " <> unwords (coerce cmd : args)
824+ ec <- liftIO $ runCommandWithoutPipes (unpack $ coerce cmd) (unpack <$> args)
825+ pure . CurrentDeploymentStatus $
826+ case ec of
827+ ExitSuccess -> Ok
828+ _ -> Error
829+
830+-- | Handles the 'cleanup' request.
831+cleanupH :: DeploymentName -> AppM CommandResponse
832+cleanupH dName = do
833+ failIfGracefulShutdownActivated
834+ st <- ask
835+ void $ unwrapOrValidateError
836+ $ selectDeployment (pool st) dName ArchivedOnlyDeployments DenyPending
837+ liftIO . runBgWorker st $ cleanupDeployment dName st
838+ pure Success
839+
840+-- | Helper to cleanup deployment.
841+cleanupDeployment :: DeploymentName -> AppState -> IO ()
842+cleanupDeployment dName st = do
843+ let
844+ log = logInfo (logger st)
845+ pgPool = pool st
846+ args =
847+ [ "--project-name", coerce $ projectName st
848+ , "--base-domain", coerce $ baseDomain st
849+ , "--namespace", coerce $ namespace st
850+ , "--name", coerce dName
851+ ]
852+ cmd = coerce $ cleanupCommand st
853+ log $ "call " <> unwords (cmd : args)
854+ (ec, out, err) <- runCommand (unpack cmd) (unpack <$> args)
855+ print out >> print err
856+ void $ deleteDeploymentLogOverrides pgPool dName
857+ void $ deleteDeploymentLogs pgPool dName
858+ void $ deleteDeploymentMetadata pgPool dName
859+ void $ deleteDeploymentOverrides pgPool dName
860+ void $ deleteDeployment pgPool dName
861+ log $ "deployment destroyed, name: " <> coerce dName
862+ sendReloadEvent st
863+ handleExitCode ec
864+
865+-- | Helper to delete deployment log overrides.
866+deleteDeploymentLogOverrides :: PgPool -> DeploymentName -> IO Int64
867+deleteDeploymentLogOverrides p n = withResource p $ \conn -> execute
868+ conn
869+ "DELETE FROM deployment_log_overrides WHERE deployment_log_id in ( \
870+ \SELECT id FROM deployment_logs WHERE deployment_id in ( \
871+ \SELECT id FROM deployments where name = ? \
872+ \) \
873+ \)"
874+ (Only n)
875+
876+-- | Helper to delete deployment logs.
877+deleteDeploymentLogs :: PgPool -> DeploymentName -> IO Int64
878+deleteDeploymentLogs p n = withResource p $ \conn -> execute
879+ conn
880+ "DELETE FROM deployment_logs WHERE deployment_id in\
881+ \ (SELECT id FROM deployments where name = ?)"
882+ (Only n)
883+
884+-- | Helper to delete deployment overrides.
885+deleteDeploymentOverrides :: PgPool -> DeploymentName -> IO Int64
886+deleteDeploymentOverrides p n = withResource p $ \conn -> execute
887+ conn
888+ "DELETE FROM deployment_overrides WHERE deployment_id in\
889+ \ (SELECT id FROM deployments where name = ?)"
890+ (Only n)
891+
892+-- | Helper to delete a deployment.
893+deleteDeployment :: PgPool -> DeploymentName -> IO Int64
894+deleteDeployment p n = withResource p $ \conn ->
895+ execute conn "DELETE FROM deployments WHERE name = ?" (Only n)
896+
897+-- | Handles the 'clean-archive' request.
898+cleanArchiveH :: AppM CommandResponse
899+cleanArchiveH = do
900+ failIfGracefulShutdownActivated
901+ st <- ask
902+ let
903+ pgPool = pool st
904+ archRetention = unArchiveRetention . archiveRetention $ st
905+ q =
906+ "SELECT name FROM deployments \
907+ \WHERE archived = 't' AND archived_at + interval '?' second < now()"
908+ retrieved :: [Only DeploymentName] <- liftIO $
909+ withResource pgPool $ \conn -> query conn q (Only archRetention)
910+ liftIO . runBgWorker st . void $
911+ for retrieved $ \(Only dName) -> cleanupDeployment dName st
912+
913+ pure Success
914+
915+-- | Handles the 'restore' request.
916+restoreH :: DeploymentName -> AppM CommandResponse
917+restoreH dName = do
918+ failIfGracefulShutdownActivated
919+ t1 <- liftIO $ now
920+ st <- ask
921+ let pgPool = pool st
922+ dep <- unwrapOrValidateError
923+ $ selectDeployment pgPool dName ArchivedOnlyDeployments DenyPending
924+ failIfImageNotFound (name dep) (tag dep)
925+ failIfGracefulShutdownActivated
926+ liftIO . runBgWorker st $ do
927+ updateDeploymentInfo dName st
928+ let
929+ q =
930+ "UPDATE deployments \
931+ \SET archived = 'f', archived_at = null, status = 'CreatePending' \
932+ \WHERE name = ?"
933+ void $ withResource pgPool $ \conn -> execute conn q (Only dName)
934+ sendReloadEvent st
935+ (ec, out, err) <- createDeployment dep st
936+ t2 <- now
937+ let
938+ arch = ArchivedFlag False
939+ elTime = elapsedTime t2 t1
940+ createDeploymentLog pgPool dep "restore" ec arch elTime out err
941+ sendReloadEvent st
942+ handleExitCode ec
943+ pure Success
944+
945+-- | Helper to get action info.
946+getActionInfoH :: ActionId -> AppM ActionInfo
947+getActionInfoH aId = do
948+ st <- ask
949+ let
950+ pgPool = pool st
951+ aId' = Only . unActionId $ aId
952+ q = "SELECT stdout, stderr FROM deployment_logs WHERE id = ?"
953+ rows :: [(Text, Text)] <- liftIO $
954+ withResource pgPool $ \conn -> query conn q aId'
955+ case rows of
956+ (out, err) : _ -> pure $ ActionInfo out err
957+ _ ->
958+ throwError err400 { errBody = appError "Action not found" }
959+
960+-- | Helper to run command with pipes.
961+runCommand :: FilePath -> [String] -> IO (ExitCode, Stdout, Stderr)
962+runCommand cmd args = do
963+ let proc' c a = setStdout createPipe . setStderr createPipe $ proc c a
964+ withProcessWait (proc' cmd args) $ \p -> do
965+ out <- hGetContents . getStdout $ p
966+ err <- hGetContents . getStderr $ p
967+ ec <- waitExitCode p
968+ pure (ec, Stdout out, Stderr err)
969+
970+-- | Helper to run command without pipes.
971+runCommandWithoutPipes :: FilePath -> [String] -> IO ExitCode
972+runCommandWithoutPipes cmd args = do
973+ withProcessWait (proc cmd args) $ \p -> do
974+ ec <- waitExitCode p
975+ pure ec
976+
977+-- | Helper to handle exit code.
978+handleExitCode :: ExitCode -> IO ()
979+handleExitCode ExitSuccess = return ()
980+handleExitCode (ExitFailure c) = throwIO $ DeploymentFailed c
981+
982+-- | Helper to log a deployment action.
983+createDeploymentLog
984+ :: PgPool
985+ -> Deployment
986+ -> Action
987+ -> ExitCode
988+ -> ArchivedFlag
989+ -> Duration
990+ -> Stdout
991+ -> Stderr
992+ -> IO ()
993+createDeploymentLog pgPool dep act ec arch dur out err = do
994+ let
995+ (Deployment dName dTag appOvs depOvs) = dep
996+ exitCode' =
997+ case ec of
998+ ExitSuccess -> 0
999+ ExitFailure errCode -> errCode
1000+ arch' = unArchivedFlag arch
1001+ dur' = unDuration dur
1002+ out' = unStdout out
1003+ err' = unStderr err
1004+ qInsertLog =
1005+ "INSERT INTO deployment_logs \
1006+ \(deployment_id, action, tag, exit_code, archived, \
1007+ \duration, stdout, stderr) \
1008+ \(\
1009+ \SELECT id, ?, ?, ?, ?, ?, ?, ? \
1010+ \FROM deployments \
1011+ \WHERE name = ? \
1012+ \) RETURNING id"
1013+ qInsertLogOverride =
1014+ "INSERT INTO deployment_log_overrides \
1015+ \(key, value, deployment_log_id, scope, visibility) \
1016+ \VALUES (?, ?, ?, ?, ?)"
1017+
1018+ void $ withResource pgPool $ \conn ->
1019+ withTransaction conn $ do
1020+ aIds :: [Only Int] <- query conn qInsertLog
1021+ (act, dTag, exitCode', arch', dur', out', err', dName)
1022+ void $ for appOvs $ \o -> do
1023+ let
1024+ [Only aId] = aIds
1025+ oKey = overrideKey . unApplicationOverride $ o
1026+ oValue = overrideValue . unApplicationOverride $ o
1027+ oScope = show ApplicationScope
1028+ oVis = show . overrideVisibility . unApplicationOverride $ o
1029+ execute conn qInsertLogOverride (oKey, oValue, aId, oScope, oVis)
1030+ void $ for depOvs $ \o -> do
1031+ let
1032+ [Only aId] = aIds
1033+ oKey = overrideKey . unDeploymentOverride $ o
1034+ oValue = overrideValue . unDeploymentOverride $ o
1035+ oScope = show DeploymentScope
1036+ oVis = show . overrideVisibility . unDeploymentOverride $ o
1037+ execute conn qInsertLogOverride (oKey, oValue, aId, oScope, oVis)
1038+
1039+-- | Helper to get deployment metadata from the database.
1040+selectDeploymentMetadata
1041+ :: Connection
1042+ -> DeploymentName
1043+ -> IO [DeploymentMetadata]
1044+selectDeploymentMetadata conn dName = do
1045+ let
1046+ q =
1047+ "SELECT key, value FROM deployment_metadata \
1048+ \WHERE deployment_id = (SELECT id FROM deployments WHERE name = ?)"
1049+ rows <- query conn q (Only dName)
1050+ for rows $ \(k, v) -> pure $ DeploymentMetadata k v
1051+
1052+-- | Helper to delete deployment metadata.
1053+deleteDeploymentMetadata
1054+ :: PgPool
1055+ -> DeploymentName
1056+ -> IO ()
1057+deleteDeploymentMetadata pgPool dName = do
1058+ let
1059+ q =
1060+ "DELETE FROM deployment_metadata \
1061+ \WHERE deployment_id = (SELECT id FROM deployments WHERE name = ?)"
1062+ void $ withResource pgPool $ \conn -> execute conn q (Only dName)
1063+
1064+-- | Helper to insert or update deployment metadata.
1065+upsertDeploymentMetadata
1066+ :: PgPool
1067+ -> DeploymentName
1068+ -> DeploymentMetadata
1069+ -> IO ()
1070+upsertDeploymentMetadata pgPool dName dMetadata = do
1071+ let
1072+ q =
1073+ "INSERT INTO deployment_metadata \
1074+ \(deployment_id, key, value, created_at, updated_at) \
1075+ \(SELECT id, ?, ?, now(), now() FROM deployments WHERE name = ?) \
1076+ \ON CONFLICT (deployment_id, key) \
1077+ \DO \
1078+ \UPDATE SET key = ?, value = ?, updated_at = now()"
1079+ key = deploymentMetadataKey dMetadata
1080+ value = deploymentMetadataValue dMetadata
1081+ void $ withResource pgPool $ \conn ->
1082+ execute conn q (key, value, dName, key, value)
1083+
1084+-- | Checks the existence of a deployment tag.
1085+-- Returns 404 'Tag not found' response if the deployment tag doesn't exist.
1086+failIfImageNotFound :: DeploymentName -> DeploymentTag -> AppM ()
1087+failIfImageNotFound dName dTag = do
1088+ st <- ask
1089+ let
1090+ log :: Text -> IO ()
1091+ log = logInfo (logger st)
1092+ args =
1093+ [ "--project-name", coerce $ projectName st
1094+ , "--base-domain", coerce $ baseDomain st
1095+ , "--namespace", coerce $ namespace st
1096+ , "--name", coerce $ dName
1097+ , "--tag", coerce $ dTag
1098+ ]
1099+ cmd = coerce $ tagCheckingCommand st
1100+
1101+ ec <- liftIO $ do
1102+ log $ "call " <> unwords (cmd : args)
1103+ runCommandWithoutPipes (unpack cmd) (unpack <$> args)
1104+ case ec of
1105+ ExitSuccess -> pure ()
1106+ ExitFailure _ ->
1107+ throwError err400 { errBody = validationError [] ["Tag not found"] }
1108+
1109+-- | Helper to create an application-level error.
1110+appError :: Text -> BSL.ByteString
1111+appError = encode . AppError
1112+
1113+-- | Helper to create a validation-level error.
1114+validationError :: [Text] -> [Text] -> BSL.ByteString
1115+validationError nameErrors tagErrors =
1116+ encode $ ValidationError nameErrors tagErrors
1117+
1118+-- | Helper to send an event to the WS event channel.
1119+sendReloadEvent :: AppState -> IO ()
1120+sendReloadEvent state =
1121+ atomically $ writeTChan (eventSink state) FrontendPleaseUpdateEverything
1122+
1123+-- | Returns time delta between 2 timestamps.
1124+elapsedTime :: Time -> Time -> Duration
1125+elapsedTime t1 t2 =
1126+ Duration . fromIntegral . (`div` 1000000) . abs $ getTime t2 - getTime t1
1127+
1128+-- | Runs the status updater.
1129+runStatusUpdater :: AppState -> IO ()
1130+runStatusUpdater state = do
1131+ let
1132+ pgPool = pool state
1133+ interval = 30 :: Int
1134+ selectDeps =
1135+ "SELECT name, status::text, \
1136+ \extract(epoch from now())::int - \
1137+ \extract(epoch from status_updated_at)::int \
1138+ \FROM deployments \
1139+ \WHERE checked_at < now() - interval '?' second AND status != 'Archived'"
1140+ updateStatus =
1141+ "UPDATE deployments \
1142+ \SET status = ?, status_updated_at = now(), checked_at = now() \
1143+ \WHERE name = ? and status = ?"
1144+ updateCheckedAt =
1145+ "UPDATE deployments SET checked_at = now() WHERE name = ? and status = ?"
1146+
1147+ forever $ do
1148+ rows :: [(DeploymentName, Text, Int)] <- liftIO $
1149+ withResource pgPool $ \conn -> query conn selectDeps (Only interval)
1150+ let
1151+ checkList :: [(DeploymentName, DeploymentStatus, Timestamp)] =
1152+ (\(n, s, t) -> (n, read . unpack $ s, coerce t)) <$> rows
1153+ checkResult <- for checkList $ \(dName, dStatus, ts) -> do
1154+ let
1155+ args =
1156+ [ "--project-name", unpack . coerce $ projectName state
1157+ , "--base-domain", unpack . coerce $ baseDomain state
1158+ , "--namespace", unpack . coerce $ namespace state
1159+ , "--name", unpack . coerce $ dName ]
1160+ cmd ArchivePending = unpack . coerce $ archiveCheckingCommand state
1161+ cmd _ = unpack . coerce $ checkingCommand state
1162+ timeout = statusUpdateTimeout state
1163+ ec <- runCommandWithoutPipes (cmd dStatus) args
1164+ pure (dName, newStatus ec dStatus ts timeout, ts)
1165+ void $
1166+ for (zip checkList checkResult) $ \((dName, oldSt, _), (_, newSt, _)) ->
1167+ withResource pgPool $ \conn ->
1168+ if oldSt == newSt
1169+ then execute conn updateCheckedAt (dName, show oldSt)
1170+ else execute conn updateStatus (show newSt, dName, show oldSt)
1171+ if checkList == checkResult
1172+ then pure ()
1173+ else sendReloadEvent state
1174+ threadDelay 5000000
1175+
1176+-- | Returns the new deployment status.
1177+newStatus
1178+ :: ExitCode
1179+ -> DeploymentStatus
1180+ -> Timestamp
1181+ -> Timeout
1182+ -> DeploymentStatus
1183+newStatus ExitSuccess ArchivePending _ _ = Archived
1184+newStatus ExitSuccess _ _ _ = Running
1185+newStatus (ExitFailure _) Running _ _ = Failure
1186+newStatus (ExitFailure _) CreatePending ts timeout | ts > coerce timeout =
1187+ Failure
1188+newStatus (ExitFailure _) UpdatePending ts timeout | ts > coerce timeout =
1189+ Failure
1190+newStatus (ExitFailure _) ArchivePending _ _ = ArchivePending
1191+newStatus (ExitFailure _) oldStatus _ _ = oldStatus
1192+
1193+-- | Checks if graceful shutdown has been activated activated.
1194+-- Returns 405 'Graceful shutdown activated' response
1195+-- if graceful shutdown has been activated.
1196+failIfGracefulShutdownActivated :: AppM ()
1197+failIfGracefulShutdownActivated = do
1198+ gracefulShutdownAct <- gracefulShutdownActivated <$> ask
1199+ gracefulShutdown <- liftIO . readIORef $ gracefulShutdownAct
1200+ if gracefulShutdown
1201+ then throwError err405 { errBody = appError "Graceful shutdown activated" }
1202+ else pure ()
1203+
1204+-- | Handles the graceful shutdown signal.
1205+-- Sends a signal to the 'shutdownSem' semaphore
1206+-- if the background worker counter is 0.
1207+terminationHandler :: IORef Int -> IORef Bool -> MVar () -> IO ()
1208+terminationHandler bgWorkersC gracefulShudownAct shutdownS = do
1209+ atomicWriteIORef gracefulShudownAct True
1210+ c <- readIORef bgWorkersC
1211+ if c == 0
1212+ then putMVar shutdownS ()
1213+ else pure ()
1214+
1215+-- | Waits on the 'shutdownSem' semaphore to determine when the
1216+-- server can shut down.
1217+runShutdownHandler :: AppState -> IO ()
1218+runShutdownHandler = takeMVar . shutdownSem
1219+
1220+-- | Runs background the worker and increases the background worker counter.
1221+-- Decreases background worker counter
1222+-- after the background worker exits.
1223+-- Sends a signal to 'shutdownSem' semaphore
1224+-- if the background worker counter is 0
1225+-- and graceful shutdown is activated.
1226+runBgWorker :: AppState -> IO () -> IO ()
1227+runBgWorker state act = void $ forkFinally act' cleanup
1228+ where
1229+ act' =
1230+ atomicModifyIORef' (bgWorkersCounter state) (\c -> (c + 1, c)) >> act
1231+ cleanup _ = do
1232+ c <- atomicModifyIORef' (bgWorkersCounter state) (\c -> (c - 1, c - 1))
1233+ gracefulShutdown <- readIORef (gracefulShutdownActivated state)
1234+ if gracefulShutdown && c == 0
1235+ then putMVar (shutdownSem state) ()
1236+ else pure ()
octopod-backend/src/Octopod/Server/Args.hsnew file+72
@@ -0,0 +1,72 @@
1+{-|
2+Module : Octopod.Server.Args
3+Description : Octopod Server arguments parser utils.
4+-}
5+
6+module Octopod.Server.Args where
7+
8+
9+import Data.ByteString (ByteString)
10+import Data.Coerce
11+import Options.Generic
12+
13+
14+import Types
15+
16+
17+-- | Octopod Server arguments definition.
18+data Args = Args
19+ { port :: Int
20+ -- ^ port for octo CLI
21+ , uiPort :: Int
22+ -- ^ port for Web UI (HTTP)
23+ , wsPort :: Int
24+ -- ^ port for Web UI (WS)
25+ , db :: ByteString
26+ -- ^ database connection string
27+ , dbPoolSize :: Int
28+ -- ^ database pool size
29+ , tlsCertPath :: ByteString
30+ -- ^ TLS certificate file path
31+ , tlsKeyPath :: ByteString
32+ -- ^ TLS key file path
33+ , tlsStorePath :: ByteString
34+ -- ^ TLS store path
35+ } deriving (Generic, Show)
36+
37+instance ParseRecord Args where
38+ parseRecord = parseRecordWithModifiers lispCaseModifiers
39+
40+-- | Parsed Octopod Server arguments definition.
41+data OctopodOpts = OctopodOpts
42+ { octopodPort :: ServerPort
43+ -- ^ port for Octopod Server
44+ , octopodUIPort :: ServerPort
45+ -- ^ port for UI (HTTP)
46+ , octopodWSPort :: ServerPort
47+ -- ^ port for UI (WS)
48+ , octopodDB :: DBConnectionString
49+ -- ^ database connection string
50+ , octopodDBPoolSize :: DBPoolSize
51+ -- ^ database pool size
52+ , octopodTLSCertPath :: TLSCertPath
53+ -- ^ TLS certificate file path
54+ , octopodTLSKeyPath :: TLSKeyPath
55+ -- ^ TLS key file path
56+ , octopodTLSStorePath :: TLSStorePath
57+ -- ^ TLS store path
58+ } deriving (Show)
59+
60+-- | Parses Octopod Server arguments.
61+parseArgs :: IO OctopodOpts
62+parseArgs = do
63+ args <- getRecord "Octopod.Server"
64+ pure $ OctopodOpts
65+ (coerce $ port args)
66+ (coerce $ uiPort args)
67+ (coerce $ wsPort args)
68+ (coerce $ db args)
69+ (coerce $ dbPoolSize args)
70+ (coerce $ tlsCertPath args)
71+ (coerce $ tlsKeyPath args)
72+ (coerce $ tlsStorePath args)
octopod-backend/src/Octopod/Server/ControlScriptUtils.hsnew file+31
@@ -0,0 +1,31 @@
1+{-|
2+Module : Octopod.Server.ControlScriptUtils
3+Description : Control script utils.
4+
5+This module contains control script utils.
6+-}
7+
8+
9+module Octopod.Server.ControlScriptUtils where
10+
11+
12+import Data.Coerce
13+import Data.Text
14+
15+
16+import Types
17+
18+
19+-- | Creates command arguments for the 'info' deployment control script.
20+infoCommandArgs
21+ :: ProjectName
22+ -> Domain
23+ -> Namespace
24+ -> DeploymentName
25+ -> ControlScriptArgs
26+infoCommandArgs pName domain ns dName =
27+ ControlScriptArgs $
28+ [ "--project-name", unpack . coerce $ pName
29+ , "--base-domain", unpack . coerce $ domain
30+ , "--namespace", unpack . coerce $ ns
31+ , "--name", unpack . coerce $ dName ]
octopod-backend/src/Octopod/Server/Logger.hsnew file+34
@@ -0,0 +1,34 @@
1+{-|
2+Module : Octopod.Server.Logger
3+Description : Octopod Server logger.
4+-}
5+
6+
7+module Octopod.Server.Logger where
8+
9+import Data.ByteString
10+import Data.Text
11+import System.Log.FastLogger
12+
13+-- | Creates new logger.
14+newLogger :: IO TimedFastLogger
15+newLogger = do
16+ timeCache <- newTimeCache "%Y-%m-%d %T%z"
17+ (logger, _) <- newTimedFastLogger timeCache (LogStdout defaultBufSize)
18+ pure logger
19+
20+-- | Logs a message with the 'INFO' severity.
21+logInfo :: TimedFastLogger -> Text -> IO ()
22+logInfo l = logWithSeverity l "INFO"
23+
24+-- | Logs a message with the 'WARN' severity.
25+logWarning :: TimedFastLogger -> Text -> IO ()
26+logWarning l = logWithSeverity l "WARN"
27+
28+-- | Logs a message with the specified severity.
29+logWithSeverity :: ToLogStr msg => TimedFastLogger -> ByteString -> msg -> IO ()
30+logWithSeverity l severity msg = l $ \ft -> metadata ft <> message
31+ where
32+ metadata :: ByteString -> LogStr
33+ metadata ft = foldMap toLogStr ["[", ft, " ", severity, "] "]
34+ message = toLogStr msg <> toLogStr ("\n" :: ByteString)
octopod-backend/src/Octopod/Server/Posix.hsnew file+25
@@ -0,0 +1,25 @@
1+module Octopod.Server.Posix (installShutdownHandler) where
2+
3+
4+import Control.Monad
5+import Control.Monad.Base (liftBase)
6+import Data.Text
7+import System.Log.FastLogger
8+import System.Posix.Signals
9+
10+
11+import Octopod.Server.Logger
12+
13+
14+-- | Installs the given shutdown handler for the specified signals.
15+installShutdownHandler
16+ :: TimedFastLogger
17+ -> [Signal]
18+ -> IO ()
19+ -> IO [Handler]
20+installShutdownHandler logger signals action =
21+ forM signals $ \signal -> installHandler signal (handler signal) Nothing
22+ where
23+ handler signal = Catch $ do
24+ logInfo logger $ "Shutdown initiated by signal " <> (pack . show $ signal)
25+ liftBase action
octopod-backend/src/Orphans.hsnew file+16
@@ -0,0 +1,16 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+module Orphans where
3+
4+import Database.PostgreSQL.Simple.FromField (FromField)
5+import Database.PostgreSQL.Simple.ToField (ToField)
6+
7+import Common.Types
8+
9+deriving instance FromField Action
10+deriving instance ToField Action
11+
12+deriving instance FromField DeploymentTag
13+deriving instance ToField DeploymentTag
14+
15+deriving instance FromField DeploymentName
16+deriving instance ToField DeploymentName
octopod-backend/src/TLS.hsnew file+68
@@ -0,0 +1,68 @@
1+{-|
2+Module : TLS
3+Description : TLS utils.
4+
5+This module contains TLS utils.
6+-}
7+
8+
9+module TLS where
10+
11+
12+import qualified Data.ByteString.Char8 as BSC
13+import Data.Default.Class (def)
14+import Data.X509 (ExtKeyUsagePurpose(..), HashALG(HashSHA256))
15+import Data.X509.CertificateStore (readCertificateStore)
16+import Data.X509.Validation
17+ (ValidationChecks(..), ValidationHooks(..), checkLeafV3, validate)
18+import Network.TLS
19+ (CertificateRejectReason(..), CertificateUsage(..), onClientCertificate)
20+import Network.Wai.Handler.WarpTLS hiding (TLSSettings, tlsSettings)
21+import qualified Network.Wai.Handler.WarpTLS as WTLS
22+
23+
24+import Types
25+
26+
27+-- | Creates TLS options for the HTTP server
28+createTLSOpts
29+ :: TLSCertPath
30+ -> TLSKeyPath
31+ -> TLSStorePath
32+ -> ServerPort
33+ -> WTLS.TLSSettings
34+createTLSOpts tlsCert tlsKey tlsStore serverPort =
35+ initTLSSettings
36+ { tlsWantClientCert = True
37+ , onInsecure = DenyInsecure
38+ "This server only accepts secure HTTPS connections."
39+ , tlsServerHooks = def { onClientCertificate =
40+ fmap certificateUsageFromValidations . validateCertificate }
41+ }
42+ where
43+ tlsCert' = unTLSCertPath tlsCert
44+ tlsKey' = unTLSKeyPath tlsKey
45+ tlsStore' = BSC.unpack . unTLSStorePath $ tlsStore
46+ serverPort' = unServerPort serverPort
47+ initTLSSettings = WTLS.tlsSettings
48+ (BSC.unpack tlsCert') (BSC.unpack tlsKey')
49+ certificateUsageFromValidations = maybe CertificateUsageAccept
50+ (CertificateUsageReject . CertificateRejectOther)
51+ serviceID = ("localhost", BSC.pack $ show serverPort')
52+ validationHooks = def
53+ { hookValidateName = \_ _ -> [] }
54+ validationChecks = def
55+ { checkStrictOrdering = True
56+ , checkLeafKeyPurpose = [KeyUsagePurpose_ClientAuth]
57+ , checkLeafV3 = False
58+ }
59+ validateCertificate cert = do
60+ mstore <- readCertificateStore tlsStore'
61+ maybe
62+ (pure . Just $ "Cannot init a store")
63+ (fmap fromX509FailedReasons . (\store -> validate HashSHA256
64+ validationHooks validationChecks store def serviceID cert))
65+ mstore
66+ fromX509FailedReasons reasons = case reasons of
67+ [] -> Nothing
68+ _ -> Just (show reasons)
octopod-backend/src/Types.hsnew file+83
@@ -0,0 +1,83 @@
1+{-|
2+Module : Types
3+Description : Backend types.
4+
5+This module contains backend types.
6+-}
7+
8+
9+module Types
10+ ( module Common.Types
11+ , parseDeploymentMetadata
12+ , ServerPort(..)
13+ , DBPoolSize(..)
14+ , DBConnectionString(..)
15+ , TLSStorePath(..)
16+ , ProjectName(..)
17+ , Domain(..)
18+ , Namespace(..)
19+ , Command(..)
20+ , ArchiveRetention(..)
21+ , Timeout(..)
22+ , ControlScriptArgs(..)) where
23+
24+import Data.Bifunctor
25+import Data.ByteString (ByteString)
26+import Data.Text as T
27+import Data.Traversable
28+
29+import Common.Types
30+
31+-- | Parses deployment metadata.
32+parseDeploymentMetadata :: [Text] -> IO [DeploymentMetadata]
33+parseDeploymentMetadata texts =
34+ for texts $ \t ->
35+ case T.findIndex (== ',') t of
36+ Just i -> do
37+ let
38+ (key, value) = bimap strip (T.tail . strip) $ T.splitAt i t
39+ pure $ DeploymentMetadata key value
40+ Nothing -> error $
41+ "Malformed metadata key-value pair " <> T.unpack t <>
42+ ", should be similar to foo,bar"
43+
44+-- | Server port.
45+newtype ServerPort = ServerPort { unServerPort :: Int }
46+ deriving (Show)
47+
48+-- | Database pool size.
49+newtype DBPoolSize = DBPoolSize { unDBPoolSize :: Int }
50+ deriving (Show)
51+
52+-- | Database connection string.
53+newtype DBConnectionString = DbConnectionString
54+ { unDBConnectionString :: ByteString }
55+ deriving (Show)
56+
57+-- | TLS store path.
58+newtype TLSStorePath = TLSStore { unTLSStorePath :: ByteString }
59+ deriving (Show)
60+
61+newtype Domain = Domain { unDomain :: Text }
62+ deriving (Show)
63+
64+-- | A Kubernetes namespace.
65+newtype Namespace = Namespace { unNamespace :: Text }
66+ deriving (Show)
67+
68+-- | Archive retention.
69+newtype ArchiveRetention = ArchiveRetention { unArchiveRetention :: Int }
70+ deriving (Show)
71+
72+-- | Timeout.
73+newtype Timeout = Timeout { unTimeout :: Int }
74+ deriving (Show)
75+
76+-- | Path to a deployment control script.
77+newtype Command = Command { unCommand :: Text }
78+ deriving (Show)
79+
80+-- | Control script arguments.
81+newtype ControlScriptArgs = ControlScriptArgs
82+ { unControlScriptArgs :: [String] }
83+ deriving (Show)
octopod-backend/test/Spec.hsnew file+2
@@ -0,0 +1,2 @@
1+main :: IO ()
2+main = putStrLn "Test suite not yet implemented"
octopod-common/CHANGELOG.mdnew file+5
@@ -0,0 +1,5 @@
1+# Revision history for common
2+
3+## 0.1.0.0 -- YYYY-mm-dd
4+
5+* First version. Released on an unsuspecting world.
octopod-common/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2020, Typeable LLC
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Typeable LLC nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
octopod-common/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
octopod-common/octopod-common.cabalnew file+48
@@ -0,0 +1,48 @@
1+-- Initial common.cabal generated by cabal init. For further
2+-- documentation, see http://haskell.org/cabal/users-guide/
3+
4+name: octopod-common
5+version: 0.1.0.0
6+-- synopsis:
7+-- description:
8+license: BSD3
9+license-file: LICENSE
10+author: Typeable
11+maintainer: octopod@typeable.io
12+-- category:
13+build-type: Simple
14+extra-source-files: CHANGELOG.md
15+cabal-version: >=1.10
16+
17+library
18+ exposed-modules: Common.Types
19+ , Common.Utils
20+ , Common.Validation
21+ -- other-modules:
22+ -- other-extensions:
23+ default-extensions: BlockArguments
24+ , ConstraintKinds
25+ , DataKinds
26+ , DeriveFunctor
27+ , DeriveGeneric
28+ , DerivingVia
29+ , DuplicateRecordFields
30+ , FlexibleContexts
31+ , GeneralizedNewtypeDeriving
32+ , LambdaCase
33+ , OverloadedStrings
34+ , ScopedTypeVariables
35+ , TypeApplications
36+ , TypeOperators
37+ build-depends: base >=4.12 && <4.13
38+ , aeson
39+ , bytestring
40+ , deriving-aeson
41+ , generic-lens
42+ , http-api-data
43+ , lens
44+ , regex-tdfa
45+ , servant
46+ , text
47+ hs-source-dirs: src
48+ default-language: Haskell2010
octopod-common/src/Common/Types.hsnew file+272
@@ -0,0 +1,272 @@
1+{-|
2+Module : Common.Types
3+Description : Common types for backend and frontend.
4+
5+This module contains common types between the backend and the frontend.
6+-}
7+
8+
9+module Common.Types where
10+
11+import Data.Bifunctor
12+import Data.ByteString (ByteString)
13+import Data.Coerce
14+import Data.String
15+import Data.Text as T
16+import Data.Traversable
17+import Deriving.Aeson.Stock
18+import Web.HttpApiData
19+
20+-- | Deployment override.
21+data Override = Override
22+ { overrideKey :: Text
23+ , overrideValue :: Text
24+ , overrideVisibility :: OverrideVisibility
25+ }
26+ deriving (Generic, Show, Eq)
27+ deriving (FromJSON, ToJSON) via Snake Override
28+
29+type Overrides = [Override]
30+
31+-- | Deployment override scope.
32+data OverrideScope
33+ = ApplicationScope
34+ | DeploymentScope
35+ deriving (Generic, Show, Read, Eq)
36+ deriving (FromJSON, ToJSON) via Snake OverrideScope
37+
38+-- | Deployment override visibility.
39+data OverrideVisibility
40+ = Private
41+ | Public
42+ deriving (Generic, Show, Read, Eq)
43+ deriving (FromJSON, ToJSON) via Snake OverrideVisibility
44+
45+-- | Deployment application-level override.
46+newtype ApplicationOverride =
47+ ApplicationOverride { unApplicationOverride :: Override }
48+ deriving (Show, Eq, FromJSON, ToJSON)
49+
50+-- | Deployment application-level overrides.
51+type ApplicationOverrides = [ApplicationOverride]
52+
53+-- | Deployment-level override.
54+newtype DeploymentOverride = DeploymentOverride
55+ { unDeploymentOverride :: Override }
56+ deriving (Show, Eq, FromJSON, ToJSON)
57+
58+-- | Deployment-level overrides.
59+type DeploymentOverrides = [DeploymentOverride]
60+
61+newtype DeploymentId = DeploymentId { unDeploymentId :: Int }
62+ deriving (Show)
63+
64+newtype DeploymentName = DeploymentName { unDeploymentName :: Text }
65+ deriving
66+ (Show, Read, FromJSON, ToJSON, ToHttpApiData, FromHttpApiData, Eq, Ord)
67+
68+newtype DeploymentTag = DeploymentTag { unDeploymentTag :: Text }
69+ deriving
70+ (Show, FromJSON, ToJSON, ToHttpApiData, FromHttpApiData, Eq)
71+
72+newtype Action = Action { unAction :: Text }
73+ deriving (Show, FromJSON, ToJSON, IsString)
74+
75+newtype ArchivedFlag = ArchivedFlag { unArchivedFlag :: Bool }
76+ deriving (Show, FromJSON, ToJSON)
77+
78+newtype Duration = Duration { unDuration :: Int }
79+ deriving (Show, FromJSON, ToJSON)
80+
81+newtype Timestamp = Timestamp { unTimestamp :: Int }
82+ deriving (Show, Eq, Ord, FromJSON, ToJSON)
83+
84+newtype ProjectName = ProjectName { uProjectName :: Text }
85+ deriving (Show, FromJSON, ToJSON)
86+
87+data DeploymentStatus
88+ = Running
89+ | Failure
90+ | CreatePending
91+ | UpdatePending
92+ | ArchivePending
93+ | Archived
94+ deriving (Generic, Read, Show, Eq)
95+ deriving (FromJSON, ToJSON) via Snake DeploymentStatus
96+
97+data Deployment = Deployment
98+ { name :: DeploymentName
99+ , tag :: DeploymentTag
100+ , appOverrides :: ApplicationOverrides
101+ , deploymentOverrides :: DeploymentOverrides
102+ }
103+ deriving (Generic, Show, Eq)
104+ deriving (FromJSON, ToJSON) via Snake Deployment
105+
106+data DeploymentLog = DeploymentLog
107+ { actionId :: ActionId
108+ , action :: Action
109+ , deploymentTag :: DeploymentTag
110+ , deploymentAppOverrides :: ApplicationOverrides
111+ , deploymentDepOverrides :: DeploymentOverrides
112+ , exitCode :: Int
113+ , duration :: Duration
114+ , createdAt :: Int
115+ }
116+ deriving (Generic, Show)
117+ deriving (FromJSON, ToJSON) via Snake DeploymentLog
118+
119+data DeploymentMetadata = DeploymentMetadata
120+ { deploymentMetadataKey :: Text
121+ , deploymentMetadataValue :: Text
122+ }
123+ deriving (Generic, Show, Eq)
124+ deriving (FromJSON, ToJSON) via Snake DeploymentMetadata
125+
126+data DeploymentInfo = DeploymentInfo
127+ { deployment :: Deployment
128+ , metadata :: [DeploymentMetadata]
129+ , logs :: [DeploymentLog]
130+ }
131+ deriving (Generic, Show)
132+ deriving (FromJSON, ToJSON) via Snake DeploymentInfo
133+
134+data DeploymentFullInfo = DeploymentFullInfo
135+ { deployment :: Deployment
136+ , status :: DeploymentStatus
137+ , archived :: Bool
138+ , metadata :: [DeploymentMetadata]
139+ , createdAt :: Int
140+ , updatedAt :: Int
141+ }
142+ deriving (Generic, Show, Eq)
143+ deriving (FromJSON, ToJSON) via Snake DeploymentFullInfo
144+
145+data DeploymentUpdate = DeploymentUpdate
146+ { newTag :: DeploymentTag
147+ , newAppOverrides :: ApplicationOverrides
148+ , oldAppOverrides :: ApplicationOverrides
149+ , newDeploymentOverrides :: DeploymentOverrides
150+ , oldDeploymentOverrides :: DeploymentOverrides
151+ }
152+ deriving (Generic, Show)
153+ deriving (FromJSON, ToJSON) via Snake DeploymentUpdate
154+
155+data CurrentStatus
156+ = Ok
157+ | Error
158+ deriving (Generic, Show, Eq)
159+ deriving (FromJSON, ToJSON) via Snake CurrentStatus
160+
161+newtype CurrentDeploymentStatus =
162+ CurrentDeploymentStatus { status :: CurrentStatus }
163+ deriving (Generic, Show, Eq)
164+ deriving (FromJSON, ToJSON) via Snake CurrentDeploymentStatus
165+
166+data CommandResponse
167+ = Success
168+ | ValidationError
169+ { nameField :: [Text]
170+ , tagField :: [Text]
171+ }
172+ | AppError
173+ { errorMessage :: Text }
174+ deriving (Generic, Show, Eq)
175+ deriving (FromJSON, ToJSON) via Snake CommandResponse
176+
177+data WSEvent = FrontendPleaseUpdateEverything
178+ deriving (Generic, Show)
179+ deriving (FromJSON, ToJSON) via Snake WSEvent
180+
181+newtype ActionId = ActionId { unActionId :: Int }
182+ deriving
183+ (Show, Read, FromJSON, ToJSON, ToHttpApiData, FromHttpApiData, Eq)
184+
185+newtype Stdout = Stdout { unStdout :: Text }
186+ deriving (Generic, Show)
187+ deriving (FromJSON, ToJSON) via Snake Stdout
188+
189+newtype Stderr = Stderr { unStderr :: Text }
190+ deriving (Generic, Show)
191+ deriving (FromJSON, ToJSON) via Snake Stderr
192+
193+data ActionInfo = ActionInfo
194+ { stdout :: Text
195+ , stderr :: Text
196+ }
197+ deriving (Generic, Show)
198+ deriving (FromJSON, ToJSON) via Snake ActionInfo
199+
200+-- | TLS certificate path.
201+newtype TLSCertPath = TLSCertPath { unTLSCertPath :: ByteString }
202+ deriving (Show)
203+
204+-- | TLS key path.
205+newtype TLSKeyPath = TLSKeyPath { unTLSKeyPath :: ByteString }
206+ deriving (Show)
207+
208+-- | Parses setting application-level overrides.
209+parseSetApplicationOverrides
210+ :: OverrideVisibility
211+ -> [Text]
212+ -> IO [ApplicationOverride]
213+parseSetApplicationOverrides visibility texts =
214+ coerce <$> parseSetOverrides visibility texts
215+
216+-- | Parses setting deployment-level overrides.
217+parseSetDeploymentOverrides
218+ :: OverrideVisibility
219+ -> [Text]
220+ -> IO [DeploymentOverride]
221+parseSetDeploymentOverrides visibility texts =
222+ coerce <$> parseSetOverrides visibility texts
223+
224+-- | Parses setting overrides.
225+parseSetOverrides :: OverrideVisibility -> [Text] -> IO [Override]
226+parseSetOverrides visibility texts =
227+ for texts $ \t ->
228+ case T.findIndex (== '=') t of
229+ Just i -> do
230+ let
231+ (key, value) = bimap strip (T.tail . strip) $ T.splitAt i t
232+ pure $ Override key value visibility
233+ Nothing -> error $
234+ "Malformed override key-value pair " <> T.unpack t <>
235+ ", should be similar to FOO=bar"
236+
237+-- | Parses unsetting application-level overrides.
238+parseUnsetApplicationOverrides
239+ :: OverrideVisibility
240+ -> [Text]
241+ -> IO [ApplicationOverride]
242+parseUnsetApplicationOverrides visibility texts =
243+ coerce <$> parseUnsetOverrides visibility texts
244+
245+-- | Parses unsetting deployment-level overrides.
246+parseUnsetDeploymentOverrides
247+ :: OverrideVisibility
248+ -> [Text]
249+ -> IO [DeploymentOverride]
250+parseUnsetDeploymentOverrides visibility texts =
251+ coerce <$> parseUnsetOverrides visibility texts
252+
253+-- | Parses unsetting overrides.
254+parseUnsetOverrides :: OverrideVisibility -> [Text] -> IO [Override]
255+parseUnsetOverrides visibility texts =
256+ for texts $ \key ->
257+ pure $ Override key "" visibility
258+
259+-- | Creates pretty-printed text from override.
260+formatOverride :: Override -> Text
261+formatOverride o@(Override _ _ vis) =
262+ overrideToArg o <> case vis of
263+ Private -> " (" <> pack (show vis) <> ")"
264+ Public -> mempty
265+
266+-- | Creates pretty-printed texts from overrides.
267+formatOverrides :: Overrides -> Text
268+formatOverrides = T.unlines . fmap formatOverride
269+
270+-- | Creates a CLI argument from an override.
271+overrideToArg :: Override -> Text
272+overrideToArg (Override k v _) = k <> "=" <> v
octopod-common/src/Common/Utils.hsnew file+41
@@ -0,0 +1,41 @@
1+{-|
2+Module : Common.Utils
3+Description : Common utils for backend and frontend.
4+
5+This module contains common utils between the backend and the frontend.
6+-}
7+
8+
9+module Common.Utils where
10+
11+import Control.Lens
12+import Data.Generics.Product
13+import Data.Monoid
14+
15+import Common.Types
16+
17+-- Lens convenience helpers
18+(<^.>) :: Functor f => f a -> Getting b a b -> f b
19+(<^.>) fa l = fa <&> (^. l)
20+
21+(<^?>) :: Functor f => f a -> Getting (First b) a b -> f (Maybe b)
22+(<^?>) fa l = fa <&> (^? l)
23+
24+(<^..>) :: Functor f => f a -> Getting (Endo [b]) a b -> f [b]
25+(<^..>) fa t = fa <&> (^.. t)
26+
27+infixl 8 <^.>, <^..>, <^?>
28+
29+-- | Gets name from deployment full info.
30+dfiName :: Getter DeploymentFullInfo DeploymentName
31+dfiName = field @"deployment" . field @"name"
32+
33+-- | Checks that deployment status is pending.
34+isPending :: DeploymentStatus -> Bool
35+isPending = \case
36+ Running -> False
37+ Failure -> False
38+ Archived -> False
39+ CreatePending -> True
40+ UpdatePending -> True
41+ ArchivePending -> True
octopod-common/src/Common/Validation.hsnew file+20
@@ -0,0 +1,20 @@
1+{-|
2+Module : Common.Validation
3+Description : Common validations for backend and frontend.
4+
5+This module contains common validations between the backend and the frontend.
6+-}
7+
8+
9+module Common.Validation where
10+
11+import Data.ByteString (ByteString)
12+import Data.Text.Encoding as T (encodeUtf8)
13+import Text.Regex.TDFA
14+
15+import Common.Types
16+
17+-- | Validates a deployment name.
18+isNameValid :: DeploymentName -> Bool
19+isNameValid (DeploymentName name) =
20+ (T.encodeUtf8 name =~ ("^[a-z][a-z0-9\\-]{1,16}$" :: ByteString))
octopod-css/.stylelintrc.jsonnew file+98
@@ -0,0 +1,98 @@
1+{
2+ "rules": {
3+ "at-rule-name-case": "lower",
4+ "at-rule-name-space-after": "always-single-line",
5+ "at-rule-semicolon-newline-after": "always",
6+ "block-closing-brace-empty-line-before": "never",
7+ "block-closing-brace-newline-after": "always",
8+ "block-closing-brace-newline-before": "always-multi-line",
9+ "block-closing-brace-space-before": "always-single-line",
10+ "block-opening-brace-newline-after": "always-multi-line",
11+ "block-opening-brace-space-before": "always",
12+ "block-opening-brace-space-after": "always-single-line",
13+ "color-hex-case": "upper",
14+ "color-hex-length": "long",
15+ "color-no-invalid-hex": true,
16+ "comment-no-empty": true,
17+ "declaration-bang-space-after": "never",
18+ "declaration-bang-space-before": "always",
19+ "declaration-block-no-duplicate-properties": true,
20+ "declaration-block-no-redundant-longhand-properties": true,
21+ "declaration-block-no-shorthand-property-overrides": true,
22+ "declaration-block-semicolon-newline-after": "always-multi-line",
23+ "declaration-block-semicolon-space-after": "always-single-line",
24+ "declaration-block-semicolon-space-before": "never",
25+ "declaration-block-trailing-semicolon": "always",
26+ "declaration-colon-space-before": "never",
27+ "declaration-colon-space-after": "always-single-line",
28+ "declaration-empty-line-before": [ "always", {
29+ "except": [
30+ "after-declaration",
31+ "first-nested"
32+ ],
33+ "ignore": [
34+ "after-comment",
35+ "inside-single-line-block"
36+ ]
37+ } ],
38+ "font-family-name-quotes": "always-unless-keyword",
39+ "function-calc-no-unspaced-operator": true,
40+ "function-comma-newline-after": "always-multi-line",
41+ "function-comma-space-after": "always",
42+ "function-comma-space-before": "never",
43+ "function-linear-gradient-no-nonstandard-direction": true,
44+ "function-max-empty-lines": 0,
45+ "function-name-case": "lower",
46+ "function-parentheses-newline-inside": "always-multi-line",
47+ "function-parentheses-space-inside": "never-single-line",
48+ "function-whitespace-after": "always",
49+ "function-url-quotes": "always",
50+ "keyframe-declaration-no-important": true,
51+ "length-zero-no-unit": true,
52+ "media-feature-colon-space-after": "always",
53+ "media-feature-colon-space-before": "never",
54+ "media-feature-name-case": "lower",
55+ "media-feature-name-no-unknown": true,
56+ "media-feature-parentheses-space-inside": "never",
57+ "media-feature-range-operator-space-after": "always",
58+ "media-feature-range-operator-space-before": "always",
59+ "media-query-list-comma-newline-after": "always-multi-line",
60+ "media-query-list-comma-space-after": "always-single-line",
61+ "media-query-list-comma-space-before": "never",
62+ "no-empty-source": true,
63+ "no-eol-whitespace": true,
64+ "no-extra-semicolons": true,
65+ "no-invalid-double-slash-comments": true,
66+ "number-leading-zero": "always",
67+ "number-no-trailing-zeros": true,
68+ "property-case": "lower",
69+ "property-no-unknown": true,
70+ "selector-attribute-brackets-space-inside": "never",
71+ "selector-attribute-operator-space-after": "never",
72+ "selector-attribute-operator-space-before": "never",
73+ "selector-attribute-quotes": "always",
74+ "selector-combinator-space-after": "always",
75+ "selector-combinator-space-before": "always",
76+ "selector-descendant-combinator-no-non-space": true,
77+ "selector-list-comma-newline-after": "always",
78+ "selector-list-comma-space-before": "never",
79+ "selector-max-empty-lines": 0,
80+ "selector-pseudo-class-case": "lower",
81+ "selector-pseudo-class-no-unknown": true,
82+ "selector-pseudo-class-parentheses-space-inside": "never",
83+ "selector-pseudo-element-case": "lower",
84+ "selector-pseudo-element-colon-notation": "double",
85+ "selector-pseudo-element-no-unknown": true,
86+ "selector-type-case": "lower",
87+ "selector-type-no-unknown": true,
88+ "shorthand-property-no-redundant-values": true,
89+ "string-quotes": "double",
90+ "string-no-newline": true,
91+ "unit-case": "lower",
92+ "unit-no-unknown": true,
93+ "value-list-comma-newline-after": "always-multi-line",
94+ "value-list-comma-space-after": "always-single-line",
95+ "value-list-comma-space-before": "never",
96+ "value-list-max-empty-lines": 0
97+ }
98+}
octopod-css/development/images/dialog-delete.pngnew file

Binary file not shown.

octopod-css/development/images/dialog-delete@2x.pngnew file

Binary file not shown.

octopod-css/development/images/null-data.pngnew file

Binary file not shown.

octopod-css/development/images/null-data@2x.pngnew file

Binary file not shown.

octopod-css/development/images/null-search.pngnew file

Binary file not shown.

octopod-css/development/images/null-search@2x.pngnew file

Binary file not shown.

octopod-css/development/layouts/index.htmlnew file+885
@@ -0,0 +1,885 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>Index</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+ <!--[if IE]>
14+ <div class="browserupgrade">
15+ You are using an <strong>outdated</strong> browser.
16+ Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
17+ </div>
18+ <![endif]-->
19+
20+ <header class="header">
21+ <div class="header__wrap container">
22+ <b class="header__logo">
23+ Deployment Manager
24+ </b>
25+ <div class="header__project">
26+ Project name
27+ </div>
28+ </div>
29+ </header>
30+
31+ <div class="page">
32+ <div class="page__wrap container">
33+ <div class="page__output notification notification--danger">
34+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
35+ <button class="notification__close" type="button"></button>
36+ </div>
37+ <div class="page__head">
38+ <h1 class="page__heading title">
39+ All deployments
40+ </h1>
41+ <div class="page__note">
42+ Updated 5 mins ago
43+ </div>
44+ <div class="page__action page__action--search input input--search input--has-clear-type">
45+ <input class="input__widget" type="text" placeholder="Search for deployments">
46+ <button class="input__clear-type spot spot--cancel">Delete</button>
47+ </div>
48+ <a class="page__action button button--add popup-handler" href="#deployment">
49+ New deployment
50+ </a>
51+ </div>
52+ <div class="page__body">
53+ <div class="data">
54+ <div class="data__primary">
55+ <div class="table table--deployments table--clickable table--double-click">
56+ <table>
57+ <thead>
58+ <tr>
59+ <th>
60+ <button class="sort" type="button">Name</button>
61+ </th>
62+ <th>
63+ Links
64+ </th>
65+ <th>
66+ Tag
67+ </th>
68+ <th>
69+ App overrides
70+ </th>
71+ <th>
72+ <button class="sort" type="button">Deployment overrides</button>
73+ </th>
74+ <th>
75+ <button class="sort sort--active sort--asc" type="button">Created</button>
76+ </th>
77+ <th>
78+ <button class="sort" type="button">Changed</button>
79+ </th>
80+ <th>
81+ <span class="visuallyhidden">Menu</span>
82+ </th>
83+ </tr>
84+ </thead>
85+ <tbody>
86+ <tr class="popup-handler" data-href="#deployment">
87+ <td>
88+ Feature name
89+ <div class="status status--success">
90+ Success
91+ </div>
92+ </td>
93+ <td>
94+ <div class="listing">
95+ <a class="listing__item external bar" href="#">
96+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
97+ </a>
98+ <a class="listing__item external bar" href="#">
99+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
100+ </a>
101+ </div>
102+ </td>
103+ <td>
104+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
105+ </td>
106+ <td>
107+ <div class="listing listing--for-text">
108+ <div class="listing__item">
109+ <b>ENVIRONENT:</b> production
110+ </div>
111+ <div class="listing__item">
112+ <b>app_api_url:</b> http://my-api.my-domain.com
113+ </div>
114+ <div class="listing__item">
115+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
116+ </div>
117+ <button class="listing__more expander" type="button">
118+ Show all (5)
119+ </button>
120+ </div>
121+ </td>
122+ <td>
123+ <div class="listing listing--for-text">
124+ <div class="listing__item">
125+ <b>ENVIRONENT:</b> production
126+ </div>
127+ <div class="listing__item">
128+ <b>app_api_url:</b> http://my-api.my-domain.com
129+ </div>
130+ <div class="listing__item">
131+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
132+ </div>
133+ <button class="listing__more expander" type="button">
134+ Show all (5)
135+ </button>
136+ </div>
137+ </td>
138+ <td>
139+ 15.05.2020
140+ </td>
141+ <td>
142+ 01.06.2020
143+ </td>
144+ <td>
145+ <div class="drop drop--actions">
146+ <button class="drop__handler" type="button">
147+ Actions
148+ </button>
149+ <div class="drop__dropdown">
150+ <button class="action action--edit" type="button">
151+ Edit
152+ </button>
153+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
154+ Move to archive
155+ </button>
156+ </div>
157+ </div>
158+ </td>
159+ </tr>
160+ <tr class="popup-handler" data-href="#deployment">
161+ <td>
162+ Feature name
163+ <div class="loading loading--status-alike">
164+ Loading...
165+ </div>
166+ </td>
167+ <td>
168+ <div class="listing">
169+ <a class="listing__item external bar" href="#">
170+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
171+ </a>
172+ <a class="listing__item external bar" href="#">
173+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
174+ </a>
175+ </div>
176+ </td>
177+ <td>
178+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
179+ </td>
180+ <td>
181+ <div class="listing listing--for-text">
182+ <div class="listing__item">
183+ <b>ENVIRONENT:</b> production
184+ </div>
185+ <div class="listing__item">
186+ <b>app_api_url:</b> http://my-api.my-domain.com
187+ </div>
188+ <div class="listing__item">
189+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
190+ </div>
191+ </div>
192+ </td>
193+ <td>
194+ <div class="listing listing--for-text">
195+ <div class="listing__item">
196+ <b>ENVIRONENT:</b> production
197+ </div>
198+ <div class="listing__item">
199+ <b>app_api_url:</b> http://my-api.my-domain.com
200+ </div>
201+ <div class="listing__item">
202+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
203+ </div>
204+ </div>
205+ </td>
206+ <td>
207+ 15.05.2020
208+ </td>
209+ <td>
210+ 01.06.2020
211+ </td>
212+ <td>
213+ <div class="drop drop--actions">
214+ <button class="drop__handler" type="button">
215+ Actions
216+ </button>
217+ <div class="drop__dropdown">
218+ <button class="action action--edit" type="button">
219+ Edit
220+ </button>
221+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
222+ Move to archive
223+ </button>
224+ </div>
225+ </div>
226+ </td>
227+ </tr>
228+ <tr class="popup-handler" data-href="#deployment">
229+ <td>
230+ Feature name
231+ <div class="status status--pending">
232+ Pending
233+ </div>
234+ </td>
235+ <td>
236+ <div class="listing">
237+ <a class="listing__item external bar" href="#">
238+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
239+ </a>
240+ <a class="listing__item external bar" href="#">
241+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
242+ </a>
243+ </div>
244+ </td>
245+ <td>
246+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
247+ </td>
248+ <td>
249+ <div class="listing listing--for-text">
250+ <div class="listing__item">
251+ <b>ENVIRONENT:</b> production
252+ </div>
253+ <div class="listing__item">
254+ <b>app_api_url:</b> http://my-api.my-domain.com
255+ </div>
256+ <div class="listing__item">
257+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
258+ </div>
259+ </div>
260+ </td>
261+ <td>
262+ <div class="listing listing--for-text">
263+ <div class="listing__item">
264+ <b>ENVIRONENT:</b> production
265+ </div>
266+ <div class="listing__item">
267+ <b>app_api_url:</b> http://my-api.my-domain.com
268+ </div>
269+ <div class="listing__item">
270+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
271+ </div>
272+ <button class="listing__more expander" type="button">
273+ Show all (5)
274+ </button>
275+ </div>
276+ </td>
277+ <td>
278+ 15.05.2020
279+ </td>
280+ <td>
281+ 01.06.2020
282+ </td>
283+ <td>
284+ <div class="drop drop--actions">
285+ <button class="drop__handler" type="button">
286+ Actions
287+ </button>
288+ <div class="drop__dropdown">
289+ <button class="action action--edit" type="button">
290+ Edit
291+ </button>
292+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
293+ Move to archive
294+ </button>
295+ </div>
296+ </div>
297+ </td>
298+ </tr>
299+ <tr class="popup-handler" data-href="#deployment">
300+ <td>
301+ Feature name
302+ <div class="status status--failure">
303+ Failure
304+ </div>
305+ </td>
306+ <td>
307+ <div class="listing">
308+ <a class="listing__item external bar" href="#">
309+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
310+ </a>
311+ <a class="listing__item external bar" href="#">
312+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
313+ </a>
314+ </div>
315+ </td>
316+ <td>
317+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
318+ </td>
319+ <td>
320+ <div class="listing listing--for-text">
321+ <div class="listing__item">
322+ <b>ENVIRONENT:</b> production
323+ </div>
324+ <div class="listing__item">
325+ <b>app_api_url:</b> http://my-api.my-domain.com
326+ </div>
327+ <div class="listing__item">
328+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
329+ </div>
330+ </div>
331+ </td>
332+ <td>
333+ <div class="listing listing--for-text">
334+ <div class="listing__item">
335+ <b>ENVIRONENT:</b> production
336+ </div>
337+ <div class="listing__item">
338+ <b>app_api_url:</b> http://my-api.my-domain.com
339+ </div>
340+ <div class="listing__item">
341+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
342+ </div>
343+ </div>
344+ </td>
345+ <td>
346+ 15.05.2020
347+ </td>
348+ <td>
349+ 01.06.2020
350+ </td>
351+ <td>
352+ <div class="drop drop--actions">
353+ <button class="drop__handler" type="button">
354+ Actions
355+ </button>
356+ <div class="drop__dropdown">
357+ <button class="action action--edit" type="button">
358+ Edit
359+ </button>
360+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
361+ Move to archive
362+ </button>
363+ </div>
364+ </div>
365+ </td>
366+ </tr>
367+ <tr class="no-table">
368+ <td colspan="8">
369+ <div class="null null--search">
370+ <b class="null__heading">No results found</b>
371+ <div class="null__message">It seems we can’t find any results <br>based on your search</div>
372+ </div>
373+ <!-- temp -->
374+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
375+ <!--/temp -->
376+ <div class="null null--data">
377+ <b class="null__heading">Cannot retrieve the data</b>
378+ <div class="null__message">Try to reload page</div>
379+ </div>
380+ <!-- temp -->
381+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
382+ <!--/temp -->
383+ <div class="loading loading--enlarged loading--alternate">
384+ Loading...
385+ </div>
386+ </td>
387+ </tr>
388+ </tbody>
389+ </table>
390+ </div>
391+ </div>
392+ <button class="data__show-archive expander expander--stand-alone" type="button" >
393+ Show Archived deployments (2)
394+ </button>
395+ <div class="data__archive">
396+ <div class="table table--clickable table--deployments">
397+ <table>
398+ <thead>
399+ <tr class="popup-handler" data-href="#deployment">
400+ <th>
401+ Name
402+ </th>
403+ <th>
404+ Links
405+ </th>
406+ <th>
407+ Tag
408+ </th>
409+ <th>
410+ App overrides
411+ </th>
412+ <th>
413+ Deployment overrides
414+ </th>
415+ <th>
416+ Created
417+ </th>
418+ <th>
419+ Changed
420+ </th>
421+ <th>
422+ <span class="visuallyhidden">Menu</span>
423+ </th>
424+ </tr>
425+ </thead>
426+ <tbody>
427+ <tr class="popup-handler" data-href="#deployment">
428+ <td>
429+ Feature name
430+ <div class="status status--archived">
431+ Archived
432+ </div>
433+ </td>
434+ <td>
435+ ...
436+ </td>
437+ <td>
438+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
439+ </td>
440+ <td>
441+ <div class="listing listing--for-text">
442+ <div class="listing__item">
443+ <b>ENVIRONENT:</b> production
444+ </div>
445+ <div class="listing__item">
446+ <b>app_api_url:</b> http://my-api.my-domain.com
447+ </div>
448+ <div class="listing__item">
449+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
450+ </div>
451+ </div>
452+ </td>
453+ <td>
454+ <div class="listing listing--for-text">
455+ <div class="listing__item">
456+ <b>ENVIRONENT:</b> production
457+ </div>
458+ <div class="listing__item">
459+ <b>app_api_url:</b> http://my-api.my-domain.com
460+ </div>
461+ <div class="listing__item">
462+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
463+ </div>
464+ </div>
465+ </td>
466+ <td>
467+ 15.05.2020
468+ </td>
469+ <td>
470+ 01.06.2020
471+ </td>
472+ <td>
473+ <div class="drop drop--actions">
474+ <button class="drop__handler" type="button">
475+ Actions
476+ </button>
477+ <div class="drop__dropdown">
478+ <button class="action action--restore" type="button">
479+ Restore from archive
480+ </button>
481+ </div>
482+ </div>
483+ </td>
484+ </tr>
485+ <tr>
486+ <td>
487+ Feature name
488+ <div class="status status--archived">
489+ Archived
490+ </div>
491+ </td>
492+ <td>
493+ ...
494+ </td>
495+ <td>
496+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
497+ </td>
498+ <td>
499+ <div class="listing listing--for-text">
500+ <div class="listing__item">
501+ <b>ENVIRONENT:</b> production
502+ </div>
503+ <div class="listing__item">
504+ <b>app_api_url:</b> http://my-api.my-domain.com
505+ </div>
506+ <div class="listing__item">
507+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
508+ </div>
509+ </div>
510+ </td>
511+ <td>
512+ <div class="listing listing--for-text">
513+ <div class="listing__item">
514+ <b>ENVIRONENT:</b> production
515+ </div>
516+ <div class="listing__item">
517+ <b>app_api_url:</b> http://my-api.my-domain.com
518+ </div>
519+ <div class="listing__item">
520+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
521+ </div>
522+ </div>
523+ </td>
524+ <td>
525+ 15.05.2020
526+ </td>
527+ <td>
528+ 01.06.2020
529+ </td>
530+ <td>
531+ <div class="drop drop--actions">
532+ <button class="drop__handler" type="button">
533+ Actions
534+ </button>
535+ <div class="drop__dropdown">
536+ <button class="action action--restore" type="button">
537+ Restore from archive
538+ </button>
539+ </div>
540+ </div>
541+ </td>
542+ </tr>
543+ </tbody>
544+ </table>
545+ </div>
546+ </div>
547+ </div>
548+ <!-- или:
549+ <div class="no-data">
550+ <div class="null null--data">
551+ <b class="null__heading">Cannot retrieve the data</b>
552+ <div class="null__message">Try to reload page</div>
553+ </div>
554+ <div class="loading loading--enlarged loading--alternate">
555+ Loading...
556+ </div>
557+ </div>
558+ -->
559+ </div>
560+ </div>
561+ </div>
562+
563+ <!-- или:
564+ <div class="no-page">
565+ <div class="no-page__inner">
566+ <div class="null null--data">
567+ <b class="null__heading">Cannot retrieve the data</b>
568+ <div class="null__message">Try to reload page</div>
569+ </div>
570+ или:
571+ <div class="loading loading--enlarged loading--alternate">
572+ Loading...
573+ </div>
574+ </div>
575+ </div>
576+ -->
577+
578+ <div class="popup" id="deployment">
579+ <div class="popup__overlay" aria-hidden="true">
580+ Close popup
581+ </div>
582+ <div class="popup__body">
583+ <div class="popup__head">
584+ <button class="popup__close" type="button">
585+ Close popup
586+ </button>
587+ <h2 class="popup__project">
588+ Project name
589+ </h2>
590+ <div class="popup__operations">
591+ <button class="popup__action button button--save" type="button">Save</button>
592+ <button class="popup__action button button--secondary" type="button">Cancel</button>
593+ </div>
594+ <div class="popup__menu drop drop--actions">
595+ <button class="drop__handler" type="button">
596+ Actions
597+ </button>
598+ <div class="drop__dropdown">
599+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">Move to archive</button>
600+ <button class="action action--restore classic-popup-handler" type="button" data-href="#dialog">Restore from archive</button>
601+ </div>
602+ </div>
603+ </div>
604+ <div class="popup__content">
605+
606+ <div class="deployment">
607+ <div class="deployment__output notification notification--danger">
608+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
609+ <button class="notification__close" type="button"></button>
610+ </div>
611+ <section class="deployment__section">
612+ <h3 class="deployment__sub-heading">
613+ Name
614+ </h3>
615+ <div class="deployment__widget">
616+ <div class="tag input">
617+ <input class="input__widget" type="text" placeholder="Project name">
618+ </div>
619+ </div>
620+ </section>
621+ <section class="deployment__section">
622+ <h3 class="deployment__sub-heading">
623+ Tag
624+ </h3>
625+ <div class="deployment__widget">
626+ <div class="tag input">
627+ <input class="input__widget" type="text" placeholder="key" value="fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b ">
628+ </div>
629+ </div>
630+ </section>
631+ <section class="deployment__section">
632+ <h3 class="deployment__sub-heading">
633+ App overrides
634+ </h3>
635+ <div class="deployment__widget">
636+ <div class="overrides">
637+ <div class="overrides__item">
638+ <div class="overrides__key input">
639+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
640+ </div>
641+ <div class="overrides__value input">
642+ <input class="input__widget" type="text" placeholder="value" value="production">
643+ </div>
644+ <button class="overrides__delete spot spot--cancel">
645+ Delete
646+ </button>
647+ </div>
648+ <div class="overrides__item">
649+ <div class="overrides__key input">
650+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
651+ </div>
652+ <div class="overrides__value input">
653+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
654+ </div>
655+ <button class="overrides__delete spot spot--cancel">
656+ Delete
657+ </button>
658+ </div>
659+ <div class="overrides__item">
660+ <div class="overrides__key input">
661+ <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
662+ </div>
663+ <div class="overrides__value input">
664+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
665+ </div>
666+ <button class="overrides__delete spot spot--cancel">
667+ Delete
668+ </button>
669+ </div>
670+ <div class="overrides__item">
671+ <div class="overrides__key input">
672+ <input class="input__widget" type="text" placeholder="key">
673+ </div>
674+ <div class="overrides__value input">
675+ <input class="input__widget" type="text" placeholder="value">
676+ </div>
677+ <button class="overrides__delete spot spot--cancel">
678+ Delete
679+ </button>
680+ </div>
681+ <button class="overrides__add dash dash--add" type="button">
682+ Add an override
683+ </button>
684+ </div>
685+ </div>
686+ </section>
687+ <section class="deployment__section">
688+ <h3 class="deployment__sub-heading">
689+ Deployment overrides
690+ </h3>
691+ <div class="deployment__widget">
692+ <div class="overrides">
693+ <div class="overrides__item">
694+ <div class="overrides__key input">
695+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
696+ </div>
697+ <div class="overrides__value input">
698+ <input class="input__widget" type="text" placeholder="value" value="production">
699+ </div>
700+ <button class="overrides__delete spot spot--cancel">
701+ Delete
702+ </button>
703+ </div>
704+ <div class="overrides__item">
705+ <div class="overrides__key input">
706+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
707+ </div>
708+ <div class="overrides__value input">
709+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
710+ </div>
711+ <button class="overrides__delete spot spot--cancel">
712+ Delete
713+ </button>
714+ </div>
715+ <div class="overrides__item">
716+ <div class="overrides__key input">
717+ <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
718+ </div>
719+ <div class="overrides__value input">
720+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
721+ </div>
722+ <button class="overrides__delete spot spot--cancel">
723+ Delete
724+ </button>
725+ </div>
726+ <div class="overrides__item">
727+ <div class="overrides__key input">
728+ <input class="input__widget" type="text" placeholder="key">
729+ </div>
730+ <div class="overrides__value input">
731+ <input class="input__widget" type="text" placeholder="value">
732+ </div>
733+ <button class="overrides__delete spot spot--cancel">
734+ Delete
735+ </button>
736+ </div>
737+ <button class="overrides__add dash dash--add" type="button">
738+ Add an override
739+ </button>
740+ </div>
741+ </div>
742+ </section>
743+ </div>
744+ <!-- temp -->
745+ или:
746+ <!--/temp -->
747+ <div class="no-deployment">
748+ <div class="null null--data">
749+ <div class="null__content">
750+ <b class="null__heading">Cannot retrieve the data</b>
751+ <div class="null__message">Try to reload page</div>
752+ </div>
753+ </div>
754+ <!-- temp -->
755+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
756+ <!--/temp -->
757+ <div class="loading loading--enlarged loading--alternate">
758+ Loading...
759+ </div>
760+ </div>
761+ </div>
762+ </div>
763+ </div>
764+
765+
766+ <div class="popup" id="log">
767+ <div class="popup__overlay" aria-hidden="true">
768+ Close popup
769+ </div>
770+ <div class="popup__body">
771+ <div class="popup__head">
772+ <button class="popup__close" type="button">
773+ Close popup
774+ </button>
775+ <h2 class="popup__project">
776+ Project name, logs
777+ </h2>
778+ </div>
779+ <div class="popup__sub-head">
780+ <div class="nav">
781+ <button class="nav__link nav__link--current" type="button">stdout</button>
782+ <button class="nav__link" type="button">stderr</button>
783+ </div>
784+ </div>
785+ <div class="popup__content">
786+ <div class="log">
787+ DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/lib/bin/ghc-iserv-dyn
788+ <br>"rm" -f inplace/bin/runghc
789+ <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
790+ <br>echo '#!/bin/sh' >> inplace/bin/runghc
791+ <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
792+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
793+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
794+ <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
795+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
796+ <br>"rm" -f inplace/bin/hpc
797+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
798+ <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
799+ <br>echo '#!/bin/sh' >> inplace/bin/hpc
800+ <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
801+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
802+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/bin/runghc
803+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
804+ <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
805+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
806+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
807+ <br>chmod +x inplace/bin/runghc
808+ <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
809+ <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
810+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/bin/hpc
811+ <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
812+ <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
813+ <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
814+ <br>chmod +x inplace/bin/hpc
815+ <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
816+ <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
817+ <br><<ghc: 2787765536 bytes, 171 GCs, 20282449/67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
818+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
819+ <br><<ghc: 766891168 bytes, 91 GCs, 14722329/42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
820+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
821+ <br><<ghc: 3067019736 bytes, 347 GCs, 25353490/71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
822+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
823+ </div>
824+ <!-- temp -->
825+ или:
826+ <!--/temp -->
827+ <div class="no-log">
828+ <div class="null null--data">
829+ <div class="null__content">
830+ <b class="null__heading">Cannot retrieve the data</b>
831+ <div class="null__message">Try to reload page</div>
832+ </div>
833+ </div>
834+ <!-- temp -->
835+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
836+ <!--/temp -->
837+ <div class="loading loading--enlarged loading--alternate">
838+ Loading...
839+ </div>
840+ </div>
841+ </div>
842+ </div>
843+ </div>
844+
845+ <div class="classic-popup" id="dialog">
846+ <div class="classic-popup__container">
847+ <div class="classic-popup__viewport">
848+ <div class="classic-popup__slot">
849+
850+ <div class="dialog dialog--archive">
851+ <div class="dialog__content">
852+ Are you sure you want to archive the
853+ <br>‘Feature name’ deployment?
854+ </div>
855+ <div class="dialog__footer">
856+ <button class="dialog__action button" type="submit">Archive</button>
857+ <button class="dialog__action button--secondary button" type="button">Cancel</button>
858+ </div>
859+ </div>
860+
861+ <button class="classic-popup__close" type="button">
862+ Close
863+ </button>
864+ </div>
865+ </div>
866+ </div>
867+ </div>
868+
869+
870+ <!-- jQuery -->
871+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
872+
873+ <!-- Outline.js -->
874+ <script src="../vendors/outline/outline.js"></script>
875+
876+ <!-- Temp -->
877+ <script src="../scripts/_expander.js"></script>
878+ <script src="../scripts/_drop.js"></script>
879+ <script src="../scripts/_sort.js"></script>
880+ <script src="../scripts/_data.js"></script>
881+ <script src="../scripts/_popup.js"></script>
882+ <script src="../scripts/_classic-popup.js"></script>
883+
884+ </body>
885+</html>
octopod-css/development/layouts/staging.htmlnew file+710
@@ -0,0 +1,710 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>Deployment</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+ <!--[if IE]>
14+ <div class="browserupgrade">
15+ You are using an <strong>outdated</strong> browser.
16+ Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
17+ </div>
18+ <![endif]-->
19+
20+ <header class="header">
21+ <div class="header__wrap container">
22+ <b class="header__logo">
23+ Deployment Manager
24+ </b>
25+ <div class="header__project">
26+ Project name
27+ </div>
28+ </div>
29+ </header>
30+
31+ <div class="page">
32+ <div class="page__wrap container">
33+ <div class="page__output notification notification--success">
34+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
35+ <button class="notification__close" type="button"></button>
36+ </div>
37+ <a class="page__back dash dash--back dash--smaller" href="index.html">
38+ All deployments
39+ </a>
40+ <div class="page__head">
41+ <h1 class="page__heading title">
42+ Project name
43+ </h1>
44+ <a class="page__action button button--edit popup-handler" href="#deployment">
45+ Edit deployment
46+ </a>
47+ <a class="page__action button button--secondary button--archive classic-popup-handler" href="#dialog">
48+ Move to archive
49+ </a>
50+ <!-- temp -->
51+ &nbsp;&nbsp;&nbsp;или для уже архивных:
52+ <!--/temp -->
53+ <a class="page__action button button--secondary button--restore classic-popup-handler" href="#dialog">
54+ Recover from archive
55+ </a>
56+ </div>
57+ <div class="page__body">
58+ <div class="deployment">
59+ <div class="deployment__output notification notification--danger">
60+ <b>Project successfully updated</b>
61+ <button class="notification__close" type="button"></button>
62+ </div>
63+ <div class="deployment__summary">
64+ <div class="deployment__stat">
65+ <b class="deployment__param">
66+ Status
67+ </b>
68+ <div class="deployment__value">
69+ <div class="status status--success">Success</div>
70+ </div>
71+ </div>
72+ <div class="deployment__stat">
73+ <b class="deployment__param">
74+ Created
75+ </b>
76+ <div class="deployment__value">
77+ 15.05.2020
78+ </div>
79+ </div>
80+ <div class="deployment__stat">
81+ <b class="deployment__param">
82+ Changed
83+ </b>
84+ <div class="deployment__value">
85+ 01.06.2020
86+ </div>
87+ </div>
88+ </div>
89+ <section class="deployment__section">
90+ <h3 class="deployment__sub-heading">
91+ Tag
92+ </h3>
93+ <div class="deployment__widget">
94+ H97HF384GKERP39740223
95+ </div>
96+ </section>
97+ <section class="deployment__section">
98+ <h3 class="deployment__sub-heading">
99+ Links
100+ </h3>
101+ <div class="deployment__widget">
102+ <div class="listing">
103+ <a class="listing__item external bar bar--larger" href="#">
104+ app.feature-name.project.name.com
105+ </a>
106+ <a class="listing__item external bar bar--larger" href="#">
107+ admin.feature-name.project.name.com
108+ </a>
109+ </div>
110+ </div>
111+ </section>
112+ <section class="deployment__section">
113+ <h3 class="deployment__sub-heading">
114+ App overrides
115+ </h3>
116+ <div class="deployment__widget">
117+ <div class="listing listing--for-text listing--larger">
118+ <div class="listing__item">
119+ <b>ENVIRONENT:</b> production
120+ </div>
121+ <div class="listing__item">
122+ <b>app_api_url:</b> http: //my-api.my-domain.com
123+ </div>
124+ <div class="listing__item">
125+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij
126+ </div>
127+ </div>
128+ </div>
129+ </section>
130+ <section class="deployment__section">
131+ <h3 class="deployment__sub-heading">
132+ Deployment overrides
133+ </h3>
134+ <div class="deployment__widget">
135+ <div class="listing listing--for-text listing--larger">
136+ <div class="listing__item">
137+ <b>ENVIRONENT:</b> production
138+ </div>
139+ <div class="listing__item">
140+ <b>app_api_url:</b> http: //my-api.my-domain.com
141+ </div>
142+ <div class="listing__item">
143+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij
144+ </div>
145+ </div>
146+ </div>
147+ </section>
148+ <section class="deployment__section">
149+ <h3 class="deployment__sub-heading">
150+ Actions
151+ </h3>
152+ <div class="deployment__widget">
153+ <div class="table table--actions">
154+ <table>
155+ <thead>
156+ <tr>
157+ <th>
158+ Action type
159+ </th>
160+ <th>
161+ Image tag
162+ </th>
163+ <th>
164+ App overrides
165+ </th>
166+ <th>
167+ Deployment overrides
168+ </th>
169+ <th>
170+ Exit code
171+ </th>
172+ <th>
173+ <button class="sort sort--active sort--asc" type="button">Created</button>
174+ </th>
175+ <th>
176+ Deployment duration
177+ </th>
178+ </tr>
179+ </thead>
180+ <tbody>
181+ <tr>
182+ <td>
183+ Create
184+ </td>
185+ <td>
186+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
187+ </td>
188+ <td>
189+ <div class="listing listing--for-text">
190+ <div class="listing__item">
191+ <b>ENVIRONENT:</b> production
192+ </div>
193+ <div class="listing__item">
194+ <b>app_api_url:</b> http://my-api.my-domain.com
195+ </div>
196+ <div class="listing__item">
197+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
198+ </div>
199+ <button class="listing__more expander" type="button">
200+ Show all (5)
201+ </button>
202+ </div>
203+ </td>
204+ <td>
205+ <div class="listing listing--for-text">
206+ <div class="listing__item">
207+ <b>ENVIRONENT:</b> production
208+ </div>
209+ <div class="listing__item">
210+ <b>app_api_url:</b> http://my-api.my-domain.com
211+ </div>
212+ <div class="listing__item">
213+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
214+ </div>
215+ <button class="listing__more expander" type="button">
216+ Show all (5)
217+ </button>
218+ </div>
219+ </td>
220+ <td>
221+ 0
222+ </td>
223+ <td>
224+ 2020-05-13 08:06:00 313811+00
225+ </td>
226+ <td>
227+ 1m 95s
228+ </td>
229+ </tr>
230+ <tr>
231+ <td>
232+ Edit
233+ </td>
234+ <td>
235+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
236+ </td>
237+ <td>
238+ <div class="listing listing--for-text">
239+ <div class="listing__item">
240+ <b>ENVIRONENT:</b> production
241+ </div>
242+ <div class="listing__item">
243+ <b>app_api_url:</b> http://my-api.my-domain.com
244+ </div>
245+ <div class="listing__item">
246+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
247+ </div>
248+ </div>
249+ </td>
250+ <td>
251+ <div class="listing listing--for-text">
252+ <div class="listing__item">
253+ <b>ENVIRONENT:</b> production
254+ </div>
255+ <div class="listing__item">
256+ <b>app_api_url:</b> http://my-api.my-domain.com
257+ </div>
258+ <div class="listing__item">
259+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
260+ </div>
261+ </div>
262+ </td>
263+ <td>
264+ 1
265+ </td>
266+ <td>
267+ 2020-05-13 08:06:00 313811+00
268+ </td>
269+ <td>
270+ 1m 95s
271+ </td>
272+ </tr>
273+ <tr>
274+ <td>
275+ Update
276+ </td>
277+ <td>
278+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
279+ </td>
280+ <td>
281+ <div class="listing listing--for-text">
282+ <div class="listing__item">
283+ <b>ENVIRONENT:</b> production
284+ </div>
285+ <div class="listing__item">
286+ <b>app_api_url:</b> http://my-api.my-domain.com
287+ </div>
288+ <div class="listing__item">
289+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
290+ </div>
291+ </div>
292+ </td>
293+ <td>
294+ <div class="listing listing--for-text">
295+ <div class="listing__item">
296+ <b>ENVIRONENT:</b> production
297+ </div>
298+ <div class="listing__item">
299+ <b>app_api_url:</b> http://my-api.my-domain.com
300+ </div>
301+ <div class="listing__item">
302+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
303+ </div>
304+ </div>
305+ </td>
306+ <td>
307+ 0
308+ </td>
309+ <td>
310+ 2020-05-13 08:06:00 313811+00
311+ </td>
312+ <td>
313+ 1m 95s
314+ </td>
315+ </tr>
316+ <tr>
317+ <td>
318+ Delete
319+ </td>
320+ <td>
321+ fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b
322+ </td>
323+ <td>
324+ <div class="listing listing--for-text">
325+ <div class="listing__item">
326+ <b>ENVIRONENT:</b> production
327+ </div>
328+ <div class="listing__item">
329+ <b>app_api_url:</b> http://my-api.my-domain.com
330+ </div>
331+ <div class="listing__item">
332+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
333+ </div>
334+ </div>
335+ </td>
336+ <td>
337+ <div class="listing listing--for-text">
338+ <div class="listing__item">
339+ <b>ENVIRONENT:</b> production
340+ </div>
341+ <div class="listing__item">
342+ <b>app_api_url:</b> http://my-api.my-domain.com
343+ </div>
344+ <div class="listing__item">
345+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
346+ </div>
347+ </div>
348+ </td>
349+ <td>
350+ 1
351+ </td>
352+ <td>
353+ 2020-05-13 08:06:00 313811+00
354+ </td>
355+ <td>
356+ 1m 95s
357+ </td>
358+ </tr>
359+ <tr class="no-table">
360+ <td colspan="7">
361+ <div class="null null--data">
362+ <b class="null__heading">Cannot retrieve the data</b>
363+ <div class="null__message">Try to reload page</div>
364+ </div>
365+ <!-- temp -->
366+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
367+ <!--/temp -->
368+ <div class="loading loading--enlarged loading--alternate">
369+ Loading...
370+ </div>
371+ </td>
372+ </tr>
373+ </tbody>
374+ </table>
375+ </div>
376+ </div>
377+ </section>
378+ </div>
379+ <!-- temp -->
380+ или:
381+ <!--/temp -->
382+ <div class="no-deployment">
383+ <div class="null null--data">
384+ <div class="null__content">
385+ <b class="null__heading">Cannot retrieve the data</b>
386+ <div class="null__message">Try to reload page</div>
387+ </div>
388+ </div>
389+ или:
390+ <div class="loading loading--enlarged loading--alternate">
391+ Loading...
392+ </div>
393+ </div>
394+ </div>
395+ </div>
396+ </div>
397+
398+ <!-- или:
399+ <div class="no-page">
400+ <div class="no-page__inner">
401+ <div class="null null--data">
402+ <b class="null__heading">Cannot retrieve the data</b>
403+ <div class="null__message">Try to reload page</div>
404+ </div>
405+ или:
406+ <div class="loading loading--enlarged loading--alternate">
407+ Loading...
408+ </div>
409+ </div>
410+ </div>
411+ -->
412+
413+ <div class="popup" id="deployment">
414+ <div class="popup__overlay" aria-hidden="true">
415+ Close popup
416+ </div>
417+ <div class="popup__body">
418+ <div class="popup__head">
419+ <button class="popup__close" type="button">
420+ Close popup
421+ </button>
422+ <h2 class="popup__project">
423+ Project name
424+ </h2>
425+ <div class="popup__operations">
426+ <button class="popup__action button button--save" type="button">Save</button>
427+ <button class="popup__action button button--secondary" type="button">Cancel</button>
428+ </div>
429+ <div class="popup__menu drop drop--actions">
430+ <button class="drop__handler" type="button">
431+ Actions
432+ </button>
433+ <div class="drop__dropdown">
434+ <button class="action action--archive" type="button">Move to archive</button>
435+ <button class="action action--restore" type="button">Restore from archive</button>
436+ </div>
437+ </div>
438+ </div>
439+ <div class="popup__content">
440+
441+ <div class="deployment">
442+ <div class="deployment__output notification notification--danger">
443+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
444+ <button class="notification__close" type="button"></button>
445+ </div>
446+ <section class="deployment__section">
447+ <h3 class="deployment__sub-heading">
448+ Tag
449+ </h3>
450+ <div class="deployment__widget">
451+ <div class="tag input">
452+ <input class="input__widget" type="text" placeholder="key" value="H97HF384GKERP39740223">
453+ </div>
454+ </div>
455+ </section>
456+ <section class="deployment__section">
457+ <h3 class="deployment__sub-heading">
458+ App overrides
459+ </h3>
460+ <div class="deployment__widget">
461+ <div class="overrides">
462+ <div class="overrides__item">
463+ <div class="overrides__key input">
464+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
465+ </div>
466+ <div class="overrides__value input">
467+ <input class="input__widget" type="text" placeholder="value" value="production">
468+ </div>
469+ <button class="overrides__delete spot spot--cancel">
470+ Delete
471+ </button>
472+ </div>
473+ <div class="overrides__item">
474+ <div class="overrides__key input">
475+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
476+ </div>
477+ <div class="overrides__value input">
478+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
479+ </div>
480+ <button class="overrides__delete spot spot--cancel">
481+ Delete
482+ </button>
483+ </div>
484+ <div class="overrides__item">
485+ <div class="overrides__key input">
486+ <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
487+ </div>
488+ <div class="overrides__value input">
489+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
490+ </div>
491+ <button class="overrides__delete spot spot--cancel">
492+ Delete
493+ </button>
494+ </div>
495+ <div class="overrides__item">
496+ <div class="overrides__key input">
497+ <input class="input__widget" type="text" placeholder="key">
498+ </div>
499+ <div class="overrides__value input">
500+ <input class="input__widget" type="text" placeholder="value">
501+ </div>
502+ <button class="overrides__delete spot spot--cancel">
503+ Delete
504+ </button>
505+ </div>
506+ <button class="overrides__add dash dash--add" type="button">
507+ Add an override
508+ </button>
509+ </div>
510+ </div>
511+ </section>
512+ <section class="deployment__section">
513+ <h3 class="deployment__sub-heading">
514+ Deployment overrides
515+ </h3>
516+ <div class="deployment__widget">
517+ <div class="overrides">
518+ <div class="overrides__item">
519+ <div class="overrides__key input">
520+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
521+ </div>
522+ <div class="overrides__value input">
523+ <input class="input__widget" type="text" placeholder="value" value="production">
524+ </div>
525+ <button class="overrides__delete spot spot--cancel">
526+ Delete
527+ </button>
528+ </div>
529+ <div class="overrides__item">
530+ <div class="overrides__key input">
531+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
532+ </div>
533+ <div class="overrides__value input">
534+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
535+ </div>
536+ <button class="overrides__delete spot spot--cancel">
537+ Delete
538+ </button>
539+ </div>
540+ <div class="overrides__item">
541+ <div class="overrides__key input">
542+ <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
543+ </div>
544+ <div class="overrides__value input">
545+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
546+ </div>
547+ <button class="overrides__delete spot spot--cancel">
548+ Delete
549+ </button>
550+ </div>
551+ <div class="overrides__item">
552+ <div class="overrides__key input">
553+ <input class="input__widget" type="text" placeholder="key">
554+ </div>
555+ <div class="overrides__value input">
556+ <input class="input__widget" type="text" placeholder="value">
557+ </div>
558+ <button class="overrides__delete spot spot--cancel">
559+ Delete
560+ </button>
561+ </div>
562+ <button class="overrides__add dash dash--add" type="button">
563+ Add an override
564+ </button>
565+ </div>
566+ </div>
567+ </section>
568+ </div>
569+ <!-- temp -->
570+ или:
571+ <!--/temp -->
572+ <div class="no-deployment">
573+ <div class="null null--data">
574+ <div class="null__content">
575+ <b class="null__heading">Cannot retrieve the data</b>
576+ <div class="null__message">Try to reload page</div>
577+ </div>
578+ </div>
579+ <!-- temp -->
580+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
581+ <!--/temp -->
582+ <div class="loading loading--enlarged loading--alternate">
583+ Loading...
584+ </div>
585+ </div>
586+ </div>
587+ </div>
588+ </div>
589+
590+
591+ <div class="popup" id="log">
592+ <div class="popup__overlay" aria-hidden="true">
593+ Close popup
594+ </div>
595+ <div class="popup__body">
596+ <div class="popup__head">
597+ <button class="popup__close" type="button">
598+ Close popup
599+ </button>
600+ <h2 class="popup__project">
601+ Project name, logs
602+ </h2>
603+ </div>
604+ <div class="popup__sub-head">
605+ <div class="nav">
606+ <button class="nav__link nav__link--current" type="button">stdout</button>
607+ <button class="nav__link" type="button">stderr</button>
608+ </div>
609+ </div>
610+ <div class="popup__content">
611+ <div class="log">
612+ DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/lib/bin/ghc-iserv-dyn
613+ <br>"rm" -f inplace/bin/runghc
614+ <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
615+ <br>echo '#!/bin/sh' >> inplace/bin/runghc
616+ <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
617+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
618+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
619+ <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
620+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
621+ <br>"rm" -f inplace/bin/hpc
622+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
623+ <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
624+ <br>echo '#!/bin/sh' >> inplace/bin/hpc
625+ <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
626+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
627+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/bin/runghc
628+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
629+ <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
630+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
631+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
632+ <br>chmod +x inplace/bin/runghc
633+ <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
634+ <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
635+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/bin/hpc
636+ <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
637+ <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
638+ <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
639+ <br>chmod +x inplace/bin/hpc
640+ <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
641+ <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
642+ <br><<ghc: 2787765536 bytes, 171 GCs, 20282449/67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
643+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
644+ <br><<ghc: 766891168 bytes, 91 GCs, 14722329/42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
645+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
646+ <br><<ghc: 3067019736 bytes, 347 GCs, 25353490/71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
647+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
648+ </div>
649+ <!-- temp -->
650+ или:
651+ <!--/temp -->
652+ <div class="no-log">
653+ <div class="null null--data">
654+ <div class="null__content">
655+ <b class="null__heading">Cannot retrieve the data</b>
656+ <div class="null__message">Try to reload page</div>
657+ </div>
658+ </div>
659+ <!-- temp -->
660+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
661+ <!--/temp -->
662+ <div class="loading loading--enlarged loading--alternate">
663+ Loading...
664+ </div>
665+ </div>
666+ </div>
667+ </div>
668+ </div>
669+
670+ <div class="classic-popup" id="dialog">
671+ <div class="classic-popup__container">
672+ <div class="classic-popup__viewport">
673+ <div class="classic-popup__slot">
674+
675+ <div class="dialog dialog--archive">
676+ <div class="dialog__content">
677+ Are you sure you want to archive the
678+ <br>‘Feature name’ deployment?
679+ </div>
680+ <div class="dialog__footer">
681+ <button class="dialog__action button" type="submit">Archive</button>
682+ <button class="dialog__action button--secondary button" type="button">Cancel</button>
683+ </div>
684+ </div>
685+
686+ <button class="classic-popup__close" type="button">
687+ Close
688+ </button>
689+ </div>
690+ </div>
691+ </div>
692+ </div>
693+
694+
695+ <!-- jQuery -->
696+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
697+
698+ <!-- Outline.js -->
699+ <script src="../vendors/outline/outline.js"></script>
700+
701+ <!-- Temp -->
702+ <script src="../scripts/_expander.js"></script>
703+ <script src="../scripts/_drop.js"></script>
704+ <script src="../scripts/_sort.js"></script>
705+ <script src="../scripts/_data.js"></script>
706+ <script src="../scripts/_popup.js"></script>
707+ <script src="../scripts/_classic-popup.js"></script>
708+
709+ </body>
710+</html>
octopod-css/development/layouts/standalone-loading.htmlnew file+43
@@ -0,0 +1,43 @@
1+<style>
2+ .loading {
3+ position: absolute;
4+ left: 0;
5+ top: 0;
6+ right: 0;
7+ bottom: 0;
8+ margin: auto;
9+ box-sizing: border-box;
10+ width: 62px;
11+ height: 90px;
12+ padding-top: 66px;
13+ padding-bottom: 8px;
14+ /* Здесь не используем Roboto намерено, чтобы не было скачка текста когда шрифты прогрузятся. */
15+ font-family: -apple-system, "BlinkMacSystemFont", "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
16+ font-size: 14px;
17+ line-height: 16px;
18+ color: #757575;
19+ }
20+ @keyframes loading {
21+ to { transform: rotate(360deg); }
22+ }
23+
24+ .loading__indicator {
25+ position: absolute;
26+ left: 0;
27+ top: 0;
28+ right: 0;
29+ bottom: auto;
30+ margin: auto;
31+ display: block;
32+ width: 52px;
33+ height: 52px;
34+ animation: loading 2.2s linear infinite;
35+ }
36+</style>
37+
38+<div class="loading">
39+ <svg class="loading__indicator" width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
40+ <path d="M2 26C2 39.2548 12.7452 50 26 50C39.2548 50 50 39.2548 50 26C50 12.7452 39.2548 2 26 2" stroke="#3671E3" stroke-width="4"/>
41+ </svg>
42+ Loading...
43+</div>
octopod-css/development/markups/_action.htmlnew file+109
@@ -0,0 +1,109 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.action</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.action</h1>
21+
22+
23+ <button class="action" type="button">
24+ Lorem Ipsum
25+ </button>
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <h2>.action--archive</h2>
33+
34+ <button class="action action--archive" type="button">
35+ Move to archive
36+ </button>
37+
38+
39+
40+ <br>
41+ <br>
42+ <br>
43+ <br>
44+ <h2>.action--edit</h2>
45+
46+ <button class="action action--edit" type="button">
47+ Edit
48+ </button>
49+
50+
51+
52+ <br>
53+ <br>
54+ <br>
55+ <br>
56+ <h2>.action--restore</h2>
57+
58+ <button class="action action--restore" type="button">
59+ Restore
60+ </button>
61+
62+
63+
64+
65+ <br>
66+ <br>
67+ <br>
68+ <br>
69+ <h2>.action--disabled</h2>
70+ <p>
71+ Если экшн делается тегом &lt;button&gt; то хорошо бы добавлять атрибут <b>disabled</b>:
72+ </p>
73+
74+ <button class="action action--edit action--disabled" type="button" disabled>
75+ Edit
76+ </button>
77+
78+
79+ <br>
80+ <br>
81+ <br>
82+ <br>
83+ <br>
84+ <br>
85+ <br>
86+ <br>
87+ <br>
88+ <br>
89+ <br>
90+ </div>
91+
92+
93+
94+ <!-- jQuery -->
95+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
96+
97+ <!-- Outline.js -->
98+ <script src="../vendors/outline/outline.js"></script>
99+
100+ <!-- Temp -->
101+ <script src="../scripts/_expander.js"></script>
102+ <script src="../scripts/_drop.js"></script>
103+ <script src="../scripts/_sort.js"></script>
104+ <script src="../scripts/_data.js"></script>
105+ <script src="../scripts/_popup.js"></script>
106+ <script src="../scripts/_classic-popup.js"></script>
107+
108+ </body>
109+</html>
octopod-css/development/markups/_bar.htmlnew file+58
@@ -0,0 +1,58 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.bar</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.bar</h1>
21+
22+
23+ <div class="bar">
24+ <b>ENVIRONENT:</b> production
25+ </div>
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <br>
37+ <br>
38+ <br>
39+ </div>
40+
41+
42+
43+ <!-- jQuery -->
44+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
45+
46+ <!-- Outline.js -->
47+ <script src="../vendors/outline/outline.js"></script>
48+
49+ <!-- Temp -->
50+ <script src="../scripts/_expander.js"></script>
51+ <script src="../scripts/_drop.js"></script>
52+ <script src="../scripts/_sort.js"></script>
53+ <script src="../scripts/_data.js"></script>
54+ <script src="../scripts/_popup.js"></script>
55+ <script src="../scripts/_classic-popup.js"></script>
56+
57+ </body>
58+</html>
octopod-css/development/markups/_break.htmlnew file+82
@@ -0,0 +1,82 @@
1+<!DOCTYPE html>
2+<html lang="ru">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.break</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+
12+ <body>
13+
14+
15+
16+ <div style="width: 600px; margin: 0 auto;">
17+ <br>
18+ <br>
19+ <h1>.break</h1>
20+ <p>
21+ Простой, вспомогательный класс, который переводит режим переносов.
22+ По дефолту текст переносится там где пробелы, табы и любые текстовые разделители.
23+ С этим классом переносится в любом месте. Т.е. слова печатаются до упора
24+ и обрыв на новую строчку может проихойти после любой буквы.
25+ </p>
26+ <br>
27+ <br>
28+ <br>
29+
30+ <div class="break">
31+ DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"' >> inplace/lib/bin/ghc-iserv-dyn
32+ </div>
33+
34+ <br>
35+ <br>
36+ <br>
37+ <p>
38+ Можно завертывать в <b>&lt;div class="break"&gt;</b>, как в примере выше.
39+ Можно просто добавлять класс <b>.break</b> куда угодно. Например к ячейке таблицы,
40+ вот так вот: <b>&lt;td class="break"&gt;</b>.
41+ </p>
42+ <p>
43+ Не работает с &lt;pre&gt;. Там скроллбар. Можно вешать этот класс на обычный &lt;div&gt;,
44+ и давать ему моноширный шрифт. В нашем случае это:
45+ <br><b>font-family: "Roboto Mono", monospace;</b>.
46+ И добавляем &lt;br&gt; перед первой стокой.
47+ Таким образом будет получаться аналог &lt;pre&gt;.
48+ </p>
49+
50+ <br>
51+ <br>
52+ <br>
53+ <br>
54+ <br>
55+ <br>
56+ <br>
57+ <br>
58+ <br>
59+ <br>
60+ <br>
61+ </div>
62+
63+
64+
65+
66+ <!-- jQuery -->
67+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
68+
69+ <!-- Outline.js -->
70+ <script src="../vendors/outline/outline.js"></script>
71+
72+ <!-- Temp -->
73+ <script src="../scripts/_expander.js"></script>
74+ <script src="../scripts/_drop.js"></script>
75+ <script src="../scripts/_sort.js"></script>
76+ <script src="../scripts/_data.js"></script>
77+ <script src="../scripts/_popup.js"></script>
78+ <script src="../scripts/_classic-popup.js"></script>
79+
80+
81+ </body>
82+</html>
octopod-css/development/markups/_button.htmlnew file+134
@@ -0,0 +1,134 @@
1+<!DOCTYPE html>
2+<html lang="ru">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.button</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+
12+ <body>
13+
14+
15+
16+ <div style="width: 600px; margin: 0 auto;">
17+ <br>
18+ <br>
19+ <h1>.button</h1>
20+
21+ <button class="button" type="button">Delete</button>
22+
23+ <br>
24+ <br>
25+ <br>
26+ <br>
27+ <h2>.button--secondary</h2>
28+
29+ <button class="button button--secondary" type="button">Cancel</button>
30+
31+
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <h2>.button--add</h2>
37+
38+ <button class="button button--add" type="button">New deployment</button>
39+
40+
41+
42+ <br>
43+ <br>
44+ <br>
45+ <br>
46+ <h2>.button--save</h2>
47+
48+ <button class="button button--save" type="button">Save</button>
49+
50+
51+ <br>
52+ <br>
53+ <br>
54+ <br>
55+ <h2>.button--disabled</h2>
56+ <p>
57+ Хорошо бы еще добавить атрибут <b>disabled</b>
58+ </p>
59+
60+ <button class="button button--disabled" type="button" disabled>Delete</button>
61+
62+ <button class="button button--add button--disabled" type="button" disabled>New deployment</button>
63+
64+ <br>
65+ <br>
66+
67+ <button class="button button--secondary button--disabled" type="button" disabled>Cancel</button>
68+
69+ <button class="button button--secondary button--archive button--disabled" type="button" disabled>Move to archive</button>
70+
71+
72+
73+
74+ <br>
75+ <br>
76+ <br>
77+ <br>
78+ <h2>Теги</h2>
79+
80+ <div class="button">div.button</div>
81+
82+ <br><br>
83+
84+ <span class="button">span.button</span>
85+
86+ <br><br>
87+
88+ <label class="button" for="#">label.button</label>
89+
90+ <br><br>
91+
92+ <a class="button" href="#">a.button</a>
93+
94+ <br><br>
95+
96+ <button class="button" type="button">button.button</button>
97+
98+ <br><br>
99+
100+ <input class="button" type="button" value="input.button">
101+
102+ <br>
103+ <br>
104+ <br>
105+ <br>
106+ <br>
107+ <br>
108+ <br>
109+ <br>
110+ <br>
111+ <br>
112+ <br>
113+ </div>
114+
115+
116+
117+
118+ <!-- jQuery -->
119+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
120+
121+ <!-- Outline.js -->
122+ <script src="../vendors/outline/outline.js"></script>
123+
124+ <!-- Temp -->
125+ <script src="../scripts/_expander.js"></script>
126+ <script src="../scripts/_drop.js"></script>
127+ <script src="../scripts/_sort.js"></script>
128+ <script src="../scripts/_data.js"></script>
129+ <script src="../scripts/_popup.js"></script>
130+ <script src="../scripts/_classic-popup.js"></script>
131+
132+
133+ </body>
134+</html>
octopod-css/development/markups/_dash.htmlnew file+127
@@ -0,0 +1,127 @@
1+<!DOCTYPE html>
2+<html lang="ru">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.dash</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+
12+ <body>
13+
14+
15+
16+ <div style="width: 600px; margin: 0 auto;">
17+ <br>
18+ <br>
19+ <h1>.dash</h1>
20+
21+ <br>
22+ <br>
23+ <br>
24+ <br>
25+ <h2>.dash--add</h2>
26+
27+ <button class="dash dash--add" type="button">
28+ Add an override
29+ </button>
30+
31+ <br>
32+ <br>
33+ <br>
34+ <br>
35+ <h2>.dash--add</h2>
36+
37+ <button class="dash dash--back" type="button">
38+ All deployments
39+ </button>
40+
41+
42+
43+ <br>
44+ <br>
45+ <br>
46+ <br>
47+ <h2>.dash--smaller</h2>
48+
49+ <button class="dash dash--back dash--smaller" type="button">
50+ All deployments
51+ </button>
52+
53+ <br>
54+ <br>
55+ <br>
56+ <br>
57+ <h2>.dash--disabled</h2>
58+ <p>
59+ Хорошо бы добавлять атрибут <b>disabled</b>
60+ </p>
61+
62+ <button class="dash dash--add dash--disabled" type="button" disabled>
63+ All deployments
64+ </button>
65+
66+
67+ <br>
68+ <br>
69+ <br>
70+ <br>
71+ <h2>Теги</h2>
72+
73+ <div class="dash dash--add">Add an override</div>
74+
75+ <br><br>
76+
77+ <span class="dash dash--add">Add an override</span>
78+
79+ <br><br>
80+
81+ <label class="dash dash--add" for="#">Add an override</label>
82+
83+ <br><br>
84+
85+ <a class="dash dash--add" href="#">Add an override</a>
86+
87+ <br><br>
88+
89+ <button class="dash dash--add" type="button">Add an override</button>
90+
91+ <br><br>
92+
93+ <input class="dash dash--add" type="button" value="Add an override">
94+
95+ <br>
96+ <br>
97+ <br>
98+ <br>
99+ <br>
100+ <br>
101+ <br>
102+ <br>
103+ <br>
104+ <br>
105+ <br>
106+ </div>
107+
108+
109+
110+
111+ <!-- jQuery -->
112+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
113+
114+ <!-- Outline.js -->
115+ <script src="../vendors/outline/outline.js"></script>
116+
117+ <!-- Temp -->
118+ <script src="../scripts/_expander.js"></script>
119+ <script src="../scripts/_drop.js"></script>
120+ <script src="../scripts/_sort.js"></script>
121+ <script src="../scripts/_data.js"></script>
122+ <script src="../scripts/_popup.js"></script>
123+ <script src="../scripts/_classic-popup.js"></script>
124+
125+
126+ </body>
127+</html>
octopod-css/development/markups/_drop.htmlnew file+109
@@ -0,0 +1,109 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.drop</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.drop</h1>
21+
22+
23+ <p>
24+ Общий компонент для выпадаек что по клику.
25+ Они могут отличаться внешне, но жаба-скрипт везде будет один.
26+ </p>
27+ <p>
28+ Смотрим базовую версию:
29+ </p>
30+ <br>
31+ <br>
32+
33+ <div class="drop">
34+ <button class="drop__handler" type="button">
35+ Handler
36+ </button>
37+ <div class="drop__dropdown">
38+ dropdown
39+ </div>
40+ </div>
41+
42+
43+ <br>
44+ <br>
45+ <br>
46+ <br>
47+ <p>
48+ Дальше либо натягиваем скины, либо просто смиксовываем с другими блоками.
49+ </p>
50+
51+
52+ <br>
53+ <br>
54+ <br>
55+ <br>
56+ <h2>.drop--actions</h2>
57+ <br>
58+
59+
60+ <div class="drop drop--actions">
61+ <button class="drop__handler" type="button">
62+ Actions
63+ </button>
64+ <div class="drop__dropdown">
65+ <button class="action action--edit" type="button">
66+ Edit
67+ </button>
68+ <button class="action action--archive" type="button">
69+ Move to archive
70+ </button>
71+ </div>
72+ </div>
73+
74+
75+
76+
77+
78+
79+ <br>
80+ <br>
81+ <br>
82+ <br>
83+ <br>
84+ <br>
85+ <br>
86+ <br>
87+ <br>
88+ <br>
89+ <br>
90+ </div>
91+
92+
93+
94+ <!-- jQuery -->
95+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
96+
97+ <!-- Outline.js -->
98+ <script src="../vendors/outline/outline.js"></script>
99+
100+ <!-- Temp -->
101+ <script src="../scripts/_expander.js"></script>
102+ <script src="../scripts/_drop.js"></script>
103+ <script src="../scripts/_sort.js"></script>
104+ <script src="../scripts/_data.js"></script>
105+ <script src="../scripts/_popup.js"></script>
106+ <script src="../scripts/_classic-popup.js"></script>
107+
108+ </body>
109+</html>
octopod-css/development/markups/_expander.htmlnew file+93
@@ -0,0 +1,93 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.expander</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.expander</h1>
21+
22+
23+ <button class="expander" type="button">
24+ Show all (5)
25+ </button>
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <h2>.expander--stand-alone</h2>
33+ <p>
34+ Название странное, так исторически сложилось
35+ </p>
36+ <br>
37+
38+ <button class="expander expander--stand-alone" type="button">
39+ Show Archived deployments (2)
40+ </button>
41+
42+ <br>
43+ <br>
44+ <br>
45+ <br>
46+ <h2>.expander--open</h2>
47+ <p>
48+ Для расхлопывания добавляем <b>.expander--open</b>:
49+ </p>
50+
51+ <button class="expander expander--open" type="button">
52+ Show all (5)
53+ </button>
54+
55+ <br>
56+ <br>
57+
58+ <button class="expander expander--stand-alone expander--open" type="button">
59+ Show Archived deployments (2)
60+ </button>
61+
62+
63+ <br>
64+ <br>
65+ <br>
66+ <br>
67+ <br>
68+ <br>
69+ <br>
70+ <br>
71+ <br>
72+ <br>
73+ <br>
74+ </div>
75+
76+
77+
78+ <!-- jQuery -->
79+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
80+
81+ <!-- Outline.js -->
82+ <script src="../vendors/outline/outline.js"></script>
83+
84+ <!-- Temp -->
85+ <script src="../scripts/_expander.js"></script>
86+ <script src="../scripts/_drop.js"></script>
87+ <script src="../scripts/_sort.js"></script>
88+ <script src="../scripts/_data.js"></script>
89+ <script src="../scripts/_popup.js"></script>
90+ <script src="../scripts/_classic-popup.js"></script>
91+
92+ </body>
93+</html>
octopod-css/development/markups/_external.htmlnew file+58
@@ -0,0 +1,58 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.external</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.external</h1>
21+
22+
23+ <a class="external bar" href="#">
24+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
25+ </a>
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <br>
37+ <br>
38+ <br>
39+ </div>
40+
41+
42+
43+ <!-- jQuery -->
44+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
45+
46+ <!-- Outline.js -->
47+ <script src="../vendors/outline/outline.js"></script>
48+
49+ <!-- Temp -->
50+ <script src="../scripts/_expander.js"></script>
51+ <script src="../scripts/_drop.js"></script>
52+ <script src="../scripts/_sort.js"></script>
53+ <script src="../scripts/_data.js"></script>
54+ <script src="../scripts/_popup.js"></script>
55+ <script src="../scripts/_classic-popup.js"></script>
56+
57+ </body>
58+</html>
octopod-css/development/markups/_input.htmlnew file+108
@@ -0,0 +1,108 @@
1+<!DOCTYPE html>
2+<html lang="ru">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.input</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+
12+ <body>
13+
14+
15+
16+ <div style="width: 600px; margin: 0 auto;">
17+ <br>
18+ <br>
19+ <h1>.input</h1>
20+
21+ <div class="input">
22+ <input class="input__widget" type="text" placeholder="Key">
23+ </div>
24+
25+
26+ <br>
27+ <br>
28+ <br>
29+ <br>
30+ <h2>Клеартайп</h2>
31+ <p>
32+ Здесь помимо самого клеартайпа еще нужно добавить модификатор
33+ <b>.input--has-clear-type</b> -- он переиграет отступы и другие версточные штуки
34+ (например выключит дефолтный ИЕшный клеартайп)
35+ </p>
36+
37+ <div class="input input--has-clear-type">
38+ <input class="input__widget" type="text" placeholder="Key">
39+ <button class="input__clear-type spot spot--cancel">Delete</button>
40+ </div>
41+
42+ <br>
43+ <br>
44+ <br>
45+ <br>
46+ <h2>.input--search</h2>
47+ <p>
48+ Просто иконка и паддинг
49+ </p>
50+
51+ <div class="input input--search input--has-clear-type">
52+ <input class="input__widget" type="text" placeholder="Key">
53+ <button class="input__clear-type spot spot--cancel">Delete</button>
54+ </div>
55+
56+
57+ <br>
58+ <br>
59+ <br>
60+ <br>
61+ <h2>.input--error и .input__output</h2>
62+ <p>
63+ Первое -- модификатор, что делает сам инпут красным.
64+ <br>Второе -- просто элемент для вывода ошибки.
65+ </p>
66+
67+ <div class="input input--has-clear-type input--error">
68+ <input class="input__widget" type="text" placeholder="Key" value="foobar$gmail.com">
69+ <button class="input__clear-type spot spot--cancel">Delete</button>
70+ <div class="input__output">Error message</div>
71+ <div class="input__output">Another message</div>
72+ </div>
73+
74+
75+
76+ <br>
77+ <br>
78+ <br>
79+ <br>
80+ <br>
81+ <br>
82+ <br>
83+ <br>
84+ <br>
85+ <br>
86+ <br>
87+ </div>
88+
89+
90+
91+
92+ <!-- jQuery -->
93+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
94+
95+ <!-- Outline.js -->
96+ <script src="../vendors/outline/outline.js"></script>
97+
98+ <!-- Temp -->
99+ <script src="../scripts/_expander.js"></script>
100+ <script src="../scripts/_drop.js"></script>
101+ <script src="../scripts/_sort.js"></script>
102+ <script src="../scripts/_data.js"></script>
103+ <script src="../scripts/_popup.js"></script>
104+ <script src="../scripts/_classic-popup.js"></script>
105+
106+
107+ </body>
108+</html>
octopod-css/development/markups/_listing.htmlnew file+97
@@ -0,0 +1,97 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.listing</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.listing</h1>
21+ <p>
22+ Простая обертка выстраивающая элементы типа <a href="_bar.html">.bar</a>
23+ вертикально. Плюс небольшой отступ.
24+ </p>
25+
26+ <div class="listing">
27+ <a class="listing__item external bar" href="#">
28+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
29+ </a>
30+ <a class="listing__item external bar" href="#">
31+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
32+ </a>
33+ </div>
34+
35+
36+
37+ <br>
38+ <br>
39+ <br>
40+ <h2>.listing--for-text</h2>
41+ <p>
42+ В какой-то момент появилась аналогичная версия,
43+ но уже без <a href="_bar.html">.bar</a> а просто с голым текстом внутри.
44+ Пришлось сюда переносить некоторые свойства типа бОльших отступов,
45+ обрезания текста многоточием и т.д. Все это модификатор <b>.listing--for-text</b>:
46+ </p>
47+ <br>
48+ <br>
49+
50+ <div class="listing listing--for-text">
51+ <div class="listing__item">
52+ <b>ENVIRONENT:</b> production
53+ </div>
54+ <div class="listing__item">
55+ <b>app_api_url:</b> http://my-api.my-domain.com
56+ </div>
57+ <div class="listing__item">
58+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
59+ </div>
60+ <button class="listing__more expander" type="button">
61+ Show all (5)
62+ </button>
63+ </div>
64+
65+
66+
67+ <br>
68+ <br>
69+ <br>
70+ <br>
71+ <br>
72+ <br>
73+ <br>
74+ <br>
75+ <br>
76+ <br>
77+ <br>
78+ </div>
79+
80+
81+
82+ <!-- jQuery -->
83+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
84+
85+ <!-- Outline.js -->
86+ <script src="../vendors/outline/outline.js"></script>
87+
88+ <!-- Temp -->
89+ <script src="../scripts/_expander.js"></script>
90+ <script src="../scripts/_drop.js"></script>
91+ <script src="../scripts/_sort.js"></script>
92+ <script src="../scripts/_data.js"></script>
93+ <script src="../scripts/_popup.js"></script>
94+ <script src="../scripts/_classic-popup.js"></script>
95+
96+ </body>
97+</html>
octopod-css/development/markups/_loading.htmlnew file+133
@@ -0,0 +1,133 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.loading</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.loading</h1>
21+ <p>
22+ Дефолтный кейс нигде не используется, но он подводят нас к другим нужным нам кейсам,
23+ и, возможно, когда-нибудь подгоним его под нужный вариант:
24+ </p>
25+
26+ <div class="loading">
27+ Loading...
28+ </div>
29+
30+ <br>
31+ <br>
32+ <br>
33+ <br>
34+ <h2>.loading--status-alike</h2>
35+ <p>
36+ Для использования в кейсах симметричных к <a href="_status.html">.status</a>
37+ </p>
38+
39+ <div class="loading loading--status-alike">
40+ Loading...
41+ </div>
42+
43+ <br>
44+ <br>
45+ <br>
46+ <br>
47+ <h2>.loading--alternate</h2>
48+ <p>
49+ Тоже нигде не используется, но здесь мы учимся делать индикатор синим
50+ </p>
51+
52+ <div class="loading loading--alternate">
53+ Loading...
54+ </div>
55+
56+
57+
58+ <br>
59+ <br>
60+ <br>
61+ <br>
62+ <h2>.loading--enlarged</h2>
63+ <p>
64+ Большой и серый в чистом виде тоже нигде не встречался
65+ </p>
66+
67+ <div class="loading loading--enlarged">
68+ Loading...
69+ </div>
70+
71+ <br>
72+ <br>
73+ <p>
74+ А вот этот уже используется на странице загрузки:
75+ </p>
76+
77+ <div class="loading loading--enlarged loading--alternate">
78+ Loading...
79+ </div>
80+
81+
82+
83+
84+ <br>
85+ <br>
86+ <br>
87+ <br>
88+ <h2>.loading--indicator-only</h2>
89+ <p>
90+ Бонус если вдург понадобится где-то в чистом виде:
91+ </p>
92+
93+ <div class="loading loading--indicator-only">
94+ Loading...
95+ </div>
96+
97+ <br>
98+
99+ <div class="loading loading--enlarged loading--indicator-only">
100+ Loading...
101+ </div>
102+
103+ <br>
104+ <br>
105+ <br>
106+ <br>
107+ <br>
108+ <br>
109+ <br>
110+ <br>
111+ <br>
112+ <br>
113+ <br>
114+ </div>
115+
116+
117+
118+ <!-- jQuery -->
119+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
120+
121+ <!-- Outline.js -->
122+ <script src="../vendors/outline/outline.js"></script>
123+
124+ <!-- Temp -->
125+ <script src="../scripts/_expander.js"></script>
126+ <script src="../scripts/_drop.js"></script>
127+ <script src="../scripts/_sort.js"></script>
128+ <script src="../scripts/_data.js"></script>
129+ <script src="../scripts/_popup.js"></script>
130+ <script src="../scripts/_classic-popup.js"></script>
131+
132+ </body>
133+</html>
octopod-css/development/markups/_notification.htmlnew file+85
@@ -0,0 +1,85 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.notification</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.notification</h1>
21+
22+ <p>
23+ Дефолтного кейса пока не было:
24+ </p>
25+
26+ <div class="notification">
27+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et eros justo. Vivamus scelerisque est sed congue aliquet. Pellentesque vitae sodales lectus. Mauris dignissim id tellus lacinia finibus. Sed egestas sapien urna, at rutrum diam mattis vitae. Duis a tempor enim. Curabitur at facilisis felis. Vestibulum imperdiet arcu a justo cursus mollis eu eget ipsum.
28+ <button class="notification__close" type="button">Close</button>
29+ </div>
30+
31+
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <h2>.notification--success</h2>
37+
38+ <div class="notification notification--success">
39+ <b>Success.</b> The deployment has moved to the archive
40+ <button class="notification__close" type="button">Close</button>
41+ </div>
42+
43+ <br>
44+ <br>
45+ <br>
46+ <br>
47+ <h2>.notification--danger</h2>
48+
49+ <div class="notification notification--danger">
50+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
51+ <button class="notification__close" type="button">Close</button>
52+ </div>
53+
54+
55+ <br>
56+ <br>
57+ <br>
58+ <br>
59+ <br>
60+ <br>
61+ <br>
62+ <br>
63+ <br>
64+ <br>
65+ <br>
66+ </div>
67+
68+
69+
70+ <!-- jQuery -->
71+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
72+
73+ <!-- Outline.js -->
74+ <script src="../vendors/outline/outline.js"></script>
75+
76+ <!-- Temp -->
77+ <script src="../scripts/_expander.js"></script>
78+ <script src="../scripts/_drop.js"></script>
79+ <script src="../scripts/_sort.js"></script>
80+ <script src="../scripts/_data.js"></script>
81+ <script src="../scripts/_popup.js"></script>
82+ <script src="../scripts/_classic-popup.js"></script>
83+
84+ </body>
85+</html>
octopod-css/development/markups/_null.htmlnew file+78
@@ -0,0 +1,78 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.null</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.null</h1>
21+
22+ <br>
23+ <br>
24+ <br>
25+ <br>
26+ <h2>.null--data</h2>
27+
28+ <div class="null null--data">
29+ <b class="null__heading">Cannot retrieve the data</b>
30+ <div class="null__message">Try to reload page</div>
31+ </div>
32+
33+
34+
35+ <br>
36+ <br>
37+ <br>
38+ <br>
39+ <h2>.null--search</h2>
40+
41+ <div class="null null--search">
42+ <b class="null__heading">No results found</b>
43+ <div class="null__message">It seems we can’t find any results <br>based on your search</div>
44+ </div>
45+
46+
47+
48+ <br>
49+ <br>
50+ <br>
51+ <br>
52+ <br>
53+ <br>
54+ <br>
55+ <br>
56+ <br>
57+ <br>
58+ <br>
59+ </div>
60+
61+
62+
63+ <!-- jQuery -->
64+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
65+
66+ <!-- Outline.js -->
67+ <script src="../vendors/outline/outline.js"></script>
68+
69+ <!-- Temp -->
70+ <script src="../scripts/_expander.js"></script>
71+ <script src="../scripts/_drop.js"></script>
72+ <script src="../scripts/_sort.js"></script>
73+ <script src="../scripts/_data.js"></script>
74+ <script src="../scripts/_popup.js"></script>
75+ <script src="../scripts/_classic-popup.js"></script>
76+
77+ </body>
78+</html>
octopod-css/development/markups/_spot.htmlnew file+58
@@ -0,0 +1,58 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.spot</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.spot</h1>
21+
22+ <button class="spot spot--cancel">
23+ Delete
24+ </button>
25+
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <br>
37+ <br>
38+ <br>
39+ </div>
40+
41+
42+
43+ <!-- jQuery -->
44+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
45+
46+ <!-- Outline.js -->
47+ <script src="../vendors/outline/outline.js"></script>
48+
49+ <!-- Temp -->
50+ <script src="../scripts/_expander.js"></script>
51+ <script src="../scripts/_drop.js"></script>
52+ <script src="../scripts/_sort.js"></script>
53+ <script src="../scripts/_data.js"></script>
54+ <script src="../scripts/_popup.js"></script>
55+ <script src="../scripts/_classic-popup.js"></script>
56+
57+ </body>
58+</html>
octopod-css/development/markups/_status.htmlnew file+80
@@ -0,0 +1,80 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.status</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.status</h1>
21+
22+
23+ <div class="status status--success">
24+ Success
25+ </div>
26+
27+ <br>
28+ <br>
29+
30+ <div class="status status--pending">
31+ Pending
32+ </div>
33+
34+ <br>
35+ <br>
36+
37+ <div class="status status--failure">
38+ Failure
39+ </div>
40+
41+ <br>
42+ <br>
43+
44+ <div class="status status--archived">
45+ Archived
46+ </div>
47+
48+
49+
50+ <br>
51+ <br>
52+ <br>
53+ <br>
54+ <br>
55+ <br>
56+ <br>
57+ <br>
58+ <br>
59+ <br>
60+ <br>
61+ </div>
62+
63+
64+
65+ <!-- jQuery -->
66+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
67+
68+ <!-- Outline.js -->
69+ <script src="../vendors/outline/outline.js"></script>
70+
71+ <!-- Temp -->
72+ <script src="../scripts/_expander.js"></script>
73+ <script src="../scripts/_drop.js"></script>
74+ <script src="../scripts/_sort.js"></script>
75+ <script src="../scripts/_data.js"></script>
76+ <script src="../scripts/_popup.js"></script>
77+ <script src="../scripts/_classic-popup.js"></script>
78+
79+ </body>
80+</html>
octopod-css/development/markups/_title.htmlnew file+57
@@ -0,0 +1,57 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.title</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.title</h1>
21+
22+
23+
24+ <h1 class="title">All deployments</h1>
25+
26+
27+ <br>
28+ <br>
29+ <br>
30+ <br>
31+ <br>
32+ <br>
33+ <br>
34+ <br>
35+ <br>
36+ <br>
37+ <br>
38+ </div>
39+
40+
41+
42+ <!-- jQuery -->
43+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
44+
45+ <!-- Outline.js -->
46+ <script src="../vendors/outline/outline.js"></script>
47+
48+ <!-- Temp -->
49+ <script src="../scripts/_expander.js"></script>
50+ <script src="../scripts/_drop.js"></script>
51+ <script src="../scripts/_sort.js"></script>
52+ <script src="../scripts/_data.js"></script>
53+ <script src="../scripts/_popup.js"></script>
54+ <script src="../scripts/_classic-popup.js"></script>
55+
56+ </body>
57+</html>
octopod-css/development/scripts/_classic-popup.jsnew file+92
@@ -0,0 +1,92 @@
1+(function($) {
2+ $(function() {
3+
4+
5+
6+ /*
7+ * Page lock
8+ */
9+
10+ var documentWidthWithScroll = 0;
11+ var documentWidthWithoutScroll = 0;
12+ var scrollWidth = 0;
13+
14+ var $html = $('html');
15+
16+
17+ function lockPage() {
18+ if ( ! $html.hasClass('html-lock') ) {
19+ documentWidthWithScroll = $(window).width();
20+ $html.addClass('html-lock');
21+ documentWidthWithoutScroll = $(window).width();
22+ $html.css( 'padding-right', (documentWidthWithoutScroll - documentWidthWithScroll) + 'px' );
23+ }
24+ }
25+
26+ function unlockPage() {
27+ if ( $html.hasClass('html-lock') ) {
28+ $html.css( 'padding-right', '' );
29+ $html.removeClass('html-lock');
30+ }
31+ }
32+
33+
34+
35+ /*
36+ * Popup
37+ */
38+
39+ function popupShow(popup){
40+ lockPage();
41+ popup.fadeIn(150);
42+ popup.scrollTop(0);
43+ }
44+
45+ function popupHide(popup){
46+ if( ! popup ) { //in case of Esc or something
47+ popup = $('.classic-popup');
48+ }
49+
50+ popup.fadeOut(150,function(){ //hide popup THAN unlock page
51+ unlockPage();
52+ });
53+ }
54+
55+ /* show popup by handler click */
56+
57+ $('.classic-popup-handler').on('click', function(event) {
58+ event.preventDefault();
59+ var popupId = $(this).attr('href') ? $(this).attr('href') : $(this).attr('data-href')
60+ popupShow( $(popupId) );
61+ event.stopPropagation();
62+ });
63+
64+
65+ /* hide popup by window close click */
66+
67+ $('.classic-popup__close').on('click', function(event){
68+ event.preventDefault();
69+ popupHide( $(this).parents('.classic-popup') );
70+ });
71+
72+
73+ /* hide popup by overlay click ( goo.gl/SJG2Hw ) */
74+
75+ $('.classic-popup').on('click', function(event) {
76+ if (!$(event.target).closest('.classic-popup__slot').length) {
77+ popupHide( $('.classic-popup') );
78+ }
79+ });
80+
81+
82+ /* hide popup by Esc press */
83+
84+ $(document).on('keyup', function(event) {
85+ if (event.keyCode == 27) {
86+ popupHide();
87+ }
88+ });
89+
90+ });
91+})(jQuery);
92+
octopod-css/development/scripts/_data.jsnew file+7
@@ -0,0 +1,7 @@
1+(function($) {
2+
3+ $('.data__show-archive').on('click', function () {
4+ $('.data__archive').toggleClass('data__archive--open');
5+ });
6+
7+})(jQuery);
octopod-css/development/scripts/_drop.jsnew file+26
@@ -0,0 +1,26 @@
1+(function($) {
2+
3+ $('.drop__handler').on('click', function (event) {
4+ $(this).parents('.drop').toggleClass('drop--expanded');
5+ event.stopPropagation();
6+ });
7+
8+
9+ /* Закрываем по клику вне ( goo.gl/SJG2Hw ) */
10+
11+ $(document).on('click', function(event) {
12+ if (!$(event.target).closest('.drop').length) {
13+ $('.drop--expanded').removeClass('drop--expanded');
14+ }
15+ });
16+
17+
18+ /* Закрываем по Esc */
19+
20+ $(document).on('keyup', function(event) {
21+ if (event.keyCode === 27) {
22+ $('.drop--expanded').removeClass('drop--expanded');
23+ }
24+ });
25+
26+})(jQuery);
octopod-css/development/scripts/_expander.jsnew file+8
@@ -0,0 +1,8 @@
1+(function($) {
2+
3+ $('.expander').on('click', function (event) {
4+ $(this).toggleClass('expander--open');
5+ event.stopPropagation();
6+ });
7+
8+})(jQuery);
octopod-css/development/scripts/_popup.jsnew file+35
@@ -0,0 +1,35 @@
1+(function($) {
2+
3+ $('.popup-handler').on('click', function (event) {
4+ event.preventDefault();
5+ var popupId = $(this).attr('href') ? $(this).attr('href') : $(this).attr('data-href')
6+ $( popupId ).addClass('popup--visible');
7+ event.stopPropagation();
8+ });
9+
10+
11+ /* Закрываем по крестику */
12+
13+ $('.popup__close').on('click', function(event){
14+ event.preventDefault();
15+ $(this).parents('.popup').removeClass('popup--visible');
16+ });
17+
18+
19+ /* Закрываем по клику вне ( goo.gl/SJG2Hw ) */
20+
21+ $('.popup__overlay').on('click', function(event) {
22+ $('.popup--visible').removeClass('popup--visible');
23+ });
24+
25+
26+ /* Закрываем по Esc */
27+
28+ $(document).on('keyup', function(event) {
29+ if (event.keyCode === 27) {
30+ $('.popup--visible').removeClass('popup--visible');
31+ }
32+ });
33+
34+
35+})(jQuery);
octopod-css/development/scripts/_sort.jsnew file+16
@@ -0,0 +1,16 @@
1+(function($) {
2+
3+ $('.sort').on('click', function () {
4+ if( ! $(this).hasClass('sort--active') ) {
5+ $('.sort--active').removeClass('sort--active').removeClass('sort--desc').removeClass('sort--asc');
6+ $(this).addClass('sort--active').addClass('sort--desc');
7+ } else {
8+ if( $(this).hasClass('sort--asc') ) {
9+ $(this).removeClass('sort--asc').addClass('sort--desc');
10+ } else if( $(this).hasClass('sort--desc') ) {
11+ $(this).removeClass('sort--desc').addClass('sort--asc');
12+ }
13+ }
14+ });
15+
16+})(jQuery);
octopod-css/development/styles/_action.cssnew file+55
@@ -0,0 +1,55 @@
1+.action {
2+ display: inline-block;
3+ vertical-align: top;
4+ width: 100%;
5+ border: none;
6+ padding: 16px 32px 16px 16px;
7+ background-color: transparent;
8+ background-repeat: no-repeat;
9+ background-position: 16px center;
10+ font-family: inherit;
11+ font-size: 14px;
12+ line-height: 16px;
13+ color: #000000;
14+ white-space: nowrap;
15+ text-align: left;
16+ text-decoration: none;
17+ cursor: pointer;
18+}
19+
20+.action:not(.action--disabled):hover {
21+ background-color: var(--grey);
22+}
23+
24+.action:not(.action--disabled):active {
25+ background-color: var(--grey-active);
26+}
27+
28+
29+
30+
31+.action--archive {
32+ padding-left: 50px;
33+ background-image: url("../vectors/common-use-archive.svg");
34+}
35+
36+.action--edit {
37+ padding-left: 50px;
38+ background-image: url("../vectors/common-use-edit.svg");
39+}
40+
41+.action--restore {
42+ padding-left: 50px;
43+ background-image: url("../vectors/common-use-restore.svg");
44+}
45+
46+.action--logs {
47+ padding-left: 50px;
48+ background-image: url("../vectors/common-use-logs.svg");
49+}
50+
51+
52+.action--disabled {
53+ opacity: 0.5;
54+ cursor: default;
55+}
octopod-css/development/styles/_bar.cssnew file+24
@@ -0,0 +1,24 @@
1+.bar {
2+ display: inline-block;
3+ vertical-align: top;
4+ padding: 4px 6px;
5+ background-color: #EEEEEE;
6+ border-radius: 4px;
7+ font-size: 12px;
8+ line-height: 16px;
9+ max-width: 100%;
10+ white-space: nowrap;
11+ overflow: hidden;
12+ text-overflow: ellipsis;
13+}
14+ .bar b {
15+ font-weight: 500;
16+ }
17+
18+
19+
20+.bar--larger {
21+ padding: 4px 8px;
22+ font-size: 16px;
23+ line-height: 24px;
24+}
octopod-css/development/styles/_body.cssnew file+5
@@ -0,0 +1,5 @@
1+body {
2+ height: 100%;
3+ background-color: #FFFFFF;
4+ font-family: "Roboto", sans-serif;
5+}
octopod-css/development/styles/_break.cssnew file+3
@@ -0,0 +1,3 @@
1+.break {
2+ word-break: break-all;
3+}
octopod-css/development/styles/_browserupgrade.cssnew file+5
@@ -0,0 +1,5 @@
1+.browserupgrade {
2+ background: #CCCCCC;
3+ color: #000000;
4+ padding: 4px 10px;
5+}
octopod-css/development/styles/_button.cssnew file+101
@@ -0,0 +1,101 @@
1+.button {
2+ display: inline-block;
3+ vertical-align: top;
4+ height: 40px;
5+ padding: 11px;
6+ border: 1px solid transparent;
7+ background-color: var(--primary);
8+ border-radius: 4px;
9+ font-family: inherit;
10+ font-weight: 500;
11+ font-size: 14px;
12+ line-height: 16px;
13+ letter-spacing: 0.6px;
14+ color: #FFFFFF;
15+ text-align: center;
16+ text-transform: uppercase;
17+ text-decoration: none;
18+ white-space: nowrap;
19+ cursor: pointer;
20+}
21+
22+.button:hover {
23+ background-color: var(--primary-hover);
24+}
25+
26+.button:active {
27+ background-color: var(--primary-active);
28+}
29+
30+.button--disabled,
31+.button--disabled:hover,
32+.button--disabled:active {
33+ background-color: var(--primary-disabled);
34+ cursor: default;
35+}
36+
37+
38+.button--secondary {
39+ background-color: #FFFFFF;
40+ border-color: var(--dark-grey);
41+ color: #000000;
42+}
43+
44+.button--secondary:hover {
45+ background-color: #EEEEEE;
46+}
47+
48+.button--secondary:active {
49+ background-color: #E2E2E2;
50+}
51+
52+
53+.button--secondary.button--disabled,
54+.button--secondary.button--disabled:hover,
55+.button--secondary.button--disabled:active {
56+ background-color: transparent;
57+ opacity: 0.5;
58+ cursor: default;
59+}
60+
61+
62+
63+.button--add {
64+ padding-left: 40px;
65+ background-image: url("../vectors/button-add.svg");
66+ background-size: 24px 24px;
67+ background-position: 11px center;
68+ background-repeat: no-repeat;
69+}
70+
71+.button--edit {
72+ padding-left: 40px;
73+ background-image: url("../vectors/common-use-edit-white.svg");
74+ background-size: 24px 24px;
75+ background-position: 11px center;
76+ background-repeat: no-repeat;
77+}
78+
79+.button--archive {
80+ padding-left: 40px;
81+ background-image: url("../vectors/common-use-archive.svg");
82+ background-size: 24px 24px;
83+ background-position: 11px center;
84+ background-repeat: no-repeat;
85+}
86+
87+.button--restore {
88+ padding-left: 40px;
89+ background-image: url("../vectors/common-use-restore.svg");
90+ background-size: 24px 24px;
91+ background-position: 11px center;
92+ background-repeat: no-repeat;
93+}
94+
95+.button--save {
96+ padding-left: 40px;
97+ background-image: url("../vectors/button-save.svg");
98+ background-size: 24px 24px;
99+ background-position: 11px center;
100+ background-repeat: no-repeat;
101+}
octopod-css/development/styles/_classic-popup.cssnew file+57
@@ -0,0 +1,57 @@
1+.html-lock {
2+ overflow: hidden;
3+}
4+
5+.classic-popup {
6+ position: fixed;
7+ z-index: 800;
8+ left: 0;
9+ top: 0;
10+ width: 100%;
11+ height: 100%;
12+ overflow: auto;
13+ background: rgba(0, 0, 0, 0.5);
14+ display: none;
15+}
16+
17+ .classic-popup__container {
18+ display: table;
19+ table-layout: fixed;
20+ width: 100%;
21+ height: 100%;
22+ }
23+
24+ .classic-popup__viewport {
25+ display: table-cell;
26+ width: 100%;
27+ height: 100%;
28+ text-align: center;
29+ vertical-align: middle;
30+ }
31+
32+ .classic-popup__slot {
33+ position: relative;
34+ display: inline-block;
35+ vertical-align: top;
36+ text-align: left;
37+ margin: 20px;
38+ }
39+
40+ .classic-popup__close {
41+ position: absolute;
42+ right: 16px;
43+ top: 16px;
44+ width: 40px;
45+ height: 40px;
46+ background-color: transparent;
47+ background-image: url("../vectors/classic-popup-close.svg");
48+ background-size: 24px 24px;
49+ background-repeat: no-repeat;
50+ background-position: center center;
51+ padding: 0;
52+ border: none;
53+ text-indent: 200%;
54+ white-space: nowrap;
55+ overflow: hidden;
56+ cursor: pointer;
57+ }
octopod-css/development/styles/_container.cssnew file+4
@@ -0,0 +1,4 @@
1+.container {
2+ padding-left: 24px;
3+ padding-right: 24px;
4+}
octopod-css/development/styles/_dash.cssnew file+57
@@ -0,0 +1,57 @@
1+.dash {
2+ padding: 12px 0 12px 24px;
3+ background-color: transparent;
4+ background-size: 16px 16px;
5+ background-repeat: no-repeat;
6+ background-position: left center;
7+ border: none;
8+ font-family: inherit;
9+ font-weight: 500;
10+ font-size: 14px;
11+ line-height: 16px;
12+ letter-spacing: 0.4px;
13+ text-decoration: none;
14+ text-transform: uppercase;
15+ color: var(--primary);
16+ cursor: pointer;
17+}
18+
19+.dash:hover {
20+ color: var(--primary-hover);
21+}
22+
23+.dash:active {
24+ color: var(--primary-active);
25+}
26+
27+
28+.dash--smaller {
29+ font-size: 12px;
30+ padding-left: 20px;
31+}
32+
33+/* stylelint-disable block-opening-brace-space-before */
34+.dash--add { background-image: url("../vectors/dash-add.svg"); }
35+.dash--add:not(.dash--disabled):hover { background-image: url("../vectors/dash-add-hover.svg"); }
36+.dash--add:not(.dash--disabled):active { background-image: url("../vectors/dash-add-active.svg"); }
37+
38+.dash--back { background-image: url("../vectors/dash-back.svg"); }
39+.dash--back:not(.dash--disabled):hover { background-image: url("../vectors/dash-back-hover.svg"); }
40+.dash--back:not(.dash--disabled):active { background-image: url("../vectors/dash-back-active.svg"); }
41+
42+.dash--next {
43+ padding-left: 0;
44+ padding-right: 20px;
45+ background-position: right center;
46+}
47+
48+.dash--next { background-image: url("../vectors/dash-next.svg"); }
49+.dash--next:not(.dash--disabled):hover { background-image: url("../vectors/dash-next-hover.svg"); }
50+.dash--next:not(.dash--disabled):active { background-image: url("../vectors/dash-next-active.svg"); }
51+/* stylelint-enable */
52+
53+
54+.dash--disabled {
55+ opacity: 0.5;
56+ cursor: default;
57+}
octopod-css/development/styles/_data.cssnew file+19
@@ -0,0 +1,19 @@
1+.data {
2+ flex-shrink: 0;
3+}
4+ .data__primary {
5+
6+ }
7+
8+ .data__show-archive {
9+ margin-top: 18px;
10+ align-self: flex-start;
11+ }
12+
13+ .data__archive {
14+ display: none;
15+ }
16+
17+ .data__archive--open {
18+ display: block;
19+ }
octopod-css/development/styles/_deployment.cssnew file+59
@@ -0,0 +1,59 @@
1+.deployment {
2+ padding: 24px;
3+}
4+
5+.page__body .deployment {
6+ padding: 0;
7+}
8+
9+ .deployment__output {
10+ margin-bottom: 32px;
11+ }
12+
13+ .deployment__summary {
14+ display: flex;
15+ align-self: flex-start;
16+ }
17+ .deployment__stat {
18+ min-width: 120px;
19+ margin-left: 32px;
20+ }
21+
22+ .deployment__stat:first-child {
23+ margin-left: 0;
24+ }
25+ .deployment__param {
26+ display: block;
27+ margin-top: 0;
28+ margin-bottom: 0;
29+ font-weight: 500;
30+ font-size: 14px;
31+ line-height: 16px;
32+ }
33+
34+ .deployment__value {
35+ margin-top: 8px;
36+ font-size: 16px;
37+ line-height: 24px;
38+ }
39+ .deployment__value .status {
40+ border-top: 4px solid transparent;
41+ border-bottom: 4px solid transparent;
42+ }
43+
44+ .deployment__section {
45+ margin-top: 32px;
46+ }
47+ .deployment__sub-heading {
48+ margin-top: 0;
49+ margin-bottom: 0;
50+ font-weight: 500;
51+ font-size: 14px;
52+ line-height: 16px;
53+ }
54+
55+ .deployment__widget {
56+ margin-top: 16px;
57+ font-size: 16px;
58+ line-height: 24px;
59+ }
octopod-css/development/styles/_dialog.cssnew file+47
@@ -0,0 +1,47 @@
1+.dialog {
2+ width: 400px;
3+ padding: 40px 24px;
4+ background-color: #FFFFFF;
5+ border-radius: 16px;
6+}
7+
8+.dialog--archive::before {
9+ content: "";
10+ display: block;
11+ width: 185px;
12+ height: 120px;
13+ margin-left: auto;
14+ margin-right: auto;
15+ background-image: url("../images/dialog-delete.png");
16+ background-size: contain;
17+ background-repeat: no-repeat;
18+ background-position: center center;
19+}
20+
21+@media (min-resolution: 2dppx) {
22+
23+ .dialog--archive::before {
24+ background-image: url("../images/dialog-delete@2x.png");
25+ }
26+}
27+ .dialog__content {
28+ margin-top: 25px;
29+ font-weight: bold;
30+ font-size: 16px;
31+ line-height: 20px;
32+ text-align: center;
33+ }
34+
35+ .dialog__footer {
36+ margin-top: 16px;
37+ display: flex;
38+ align-items: flex-start;
39+ justify-content: center;
40+ }
41+ .dialog__action {
42+ margin-left: 8px;
43+ }
44+
45+ .dialog__action:first-child {
46+ margin-left: 0;
47+ }
octopod-css/development/styles/_drop.cssnew file+70
@@ -0,0 +1,70 @@
1+.drop {
2+ position: relative;
3+ display: inline-block;
4+ vertical-align: top;
5+}
6+ .drop__handler {
7+ border: none;
8+ padding: 0;
9+ background-color: transparent;
10+ cursor: pointer;
11+ }
12+
13+ .drop__dropdown {
14+ position: absolute;
15+ z-index: 500;
16+ right: 0;
17+ top: 100%;
18+ min-width: 100%;
19+ opacity: 0;
20+ visibility: hidden;
21+ transition: opacity var(--transition-basic),
22+ visibility var(--transition-basic);
23+ }
24+
25+
26+
27+.drop--expanded {
28+
29+}
30+ .drop--expanded .drop__dropdown {
31+ opacity: 1;
32+ visibility: visible;
33+ }
34+
35+
36+
37+
38+.drop--actions {
39+
40+}
41+ .drop--actions .drop__handler {
42+ width: 40px;
43+ height: 40px;
44+ background-image: url("../vectors/drop-actions.svg");
45+ background-size: 24px 24px;
46+ background-repeat: no-repeat;
47+ background-position: center center;
48+ text-indent: 200%;
49+ white-space: nowrap;
50+ overflow: hidden;
51+ }
52+
53+ .drop--actions .drop__handler:hover {
54+ background-image: url("../vectors/drop-actions-hover.svg");
55+ }
56+
57+ .drop--actions .drop__handler:active,
58+ .drop--actions.drop--expanded .drop__handler {
59+ background-image: url("../vectors/drop-actions-active.svg");
60+ }
61+
62+ .drop--actions .drop__dropdown {
63+ margin-top: 8px;
64+ margin-right: -17px;
65+ padding-top: 8px;
66+ padding-bottom: 8px;
67+ background-color: #FFFFFF;
68+ box-shadow: var(--shadow_1);
69+ border-radius: 2px;
70+ }
octopod-css/development/styles/_expander.cssnew file+90
@@ -0,0 +1,90 @@
1+.expander { /* дефолтная версия расчитана на использование с .bar */
2+ position: relative;
3+ border: none;
4+ background-color: transparent;
5+ padding: 0 17px 0 0;
6+ font-family: inherit;
7+ font-weight: 500;
8+ font-size: 12px;
9+ line-height: 16px;
10+ color: var(--primary);
11+ text-decoration: none;
12+ cursor: pointer;
13+}
14+ /* Увеличиваем область кликабельности */
15+ .expander::before {
16+ content: "";
17+ position: absolute;
18+ left: -4px;
19+ right: -4px;
20+ top: -10px;
21+ bottom: -10px;
22+ display: block;
23+ }
24+
25+ /* Стрелочка */
26+ .expander::after {
27+ position: absolute;
28+ right: 0;
29+ top: 0;
30+ content: "";
31+ display: block;
32+ width: 16px;
33+ height: 16px;
34+ background-image: url("../vectors/expander.svg");
35+ background-repeat: no-repeat;
36+ background-position: center center;
37+ }
38+
39+
40+.expander:hover {
41+ color: var(--primary-hover);
42+}
43+
44+.expander:active {
45+ color: var(--primary-active);
46+}
47+
48+
49+.expander--open {
50+
51+}
52+ .expander--open::after {
53+ transform: scaleY(-1);
54+ }
55+
56+
57+.expander--stand-alone {
58+ padding: 9px 24px 9px 0;
59+ background-color: transparent;
60+ font-family: inherit;
61+ font-size: 14px;
62+ line-height: 16px;
63+ color: var(--primary);
64+ text-transform: uppercase;
65+ letter-spacing: 0.6px;
66+}
67+ .expander--stand-alone::after {
68+ right: 0;
69+ top: 7px;
70+ width: 20px;
71+ height: 20px;
72+ background-image: url("../vectors/expander-larger.svg");
73+ background-size: contain;
74+ }
75+
76+.expander--stand-alone:hover {
77+ background-color: transparent;
78+ color: var(--primary-hover);
79+}
80+ .expander--stand-alone:hover::after {
81+ background-image: url("../vectors/expander-larger-hover.svg");
82+ }
83+
84+.expander--stand-alone:active {
85+ background-color: transparent;
86+ color: var(--primary-active);
87+}
88+ .expander--stand-alone:active::after {
89+ background-image: url("../vectors/expander-larger-active.svg");
90+ }
octopod-css/development/styles/_external.cssnew file+26
@@ -0,0 +1,26 @@
1+.external {
2+ padding-right: 24px;
3+ background-image: url("../vectors/external.svg");
4+ background-size: 12px 12px;
5+ background-repeat: no-repeat;
6+ background-position: right 6px center;
7+ color: inherit;
8+ text-decoration: none;
9+ cursor: pointer;
10+}
11+
12+.external:hover {
13+ color: var(--primary);
14+ background-color: #E2E2E2;
15+ background-image: url("../vectors/external-hover-active.svg");
16+}
17+
18+.external:active {
19+ color: var(--primary);
20+ background-color: #D6D6D6;
21+ background-image: url("../vectors/external-hover-active.svg");
22+}
23+
24+.external.bar--larger {
25+ background-position: right 8px center;
26+}
octopod-css/development/styles/_header.cssnew file+31
@@ -0,0 +1,31 @@
1+.header {
2+ padding-top: 24px;
3+ padding-bottom: 24px;
4+ background-color: #3671E3;
5+ box-shadow: var(--shadow_1);
6+ color: #FFFFFF;
7+}
8+ .header__wrap {
9+ display: flex;
10+ align-items: center;
11+ }
12+ .header__logo {
13+ margin-top: 0;
14+ margin-bottom: 0;
15+ border-right: 1px solid #709AEE;
16+ padding-right: 15px;
17+ font-weight: bold;
18+ font-size: 20px;
19+ line-height: 24px;
20+ letter-spacing: 0.85px;
21+ text-transform: uppercase;
22+ }
23+
24+ .header__project {
25+ margin-left: -1px;
26+ border-left: 1px solid #709AEE;
27+ padding-left: 16px;
28+ font-weight: 500;
29+ font-size: 16px;
30+ line-height: 24px;
31+ }
octopod-css/development/styles/_html.cssnew file+4
@@ -0,0 +1,4 @@
1+html {
2+ height: 100%;
3+ min-width: 1000px;
4+}
octopod-css/development/styles/_input.cssnew file+91
@@ -0,0 +1,91 @@
1+.input {
2+ position: relative;
3+}
4+ .input__widget {
5+ width: 100%;
6+ height: 40px;
7+ vertical-align: top;
8+ padding: 8px 0;
9+ background-color: transparent;
10+ border: none;
11+ outline: none;
12+ box-shadow: inset 0 -1px #B0B0B0;
13+ background-clip: padding-box; /* reset iOS default shadow */
14+ border-radius: 0; /* reset iOS default border-radius */
15+ font-family: inherit;
16+ font-size: 16px;
17+ line-height: 24px;
18+ color: rgba(0, 0, 0, 0.87);
19+ }
20+
21+ .input__widget:hover {
22+ box-shadow: inset 0 -1px #326EE6;
23+ }
24+
25+ .input__widget:focus {
26+ box-shadow: inset 0 -2px #326EE6;
27+ }
28+
29+ /* stylelint-disable block-opening-brace-space-before */
30+ .input__widget::-webkit-input-placeholder { font-style: normal; color: var(--dark-grey); }
31+ .input__widget::-moz-placeholder { font-style: normal; color: var(--dark-grey); opacity: 1; }
32+ .input__widget:-ms-input-placeholder { font-style: normal; color: var(--dark-grey); }
33+
34+ .input__widget:hover::-webkit-input-placeholder { color: #B1B1B1; }
35+ .input__widget:hover::-moz-placeholder { color: #B1B1B1; }
36+ .input__widget:hover:-ms-input-placeholder { color: #B1B1B1; }
37+
38+ .input__widget:focus::-webkit-input-placeholder { color: #B1B1B1; }
39+ .input__widget:focus::-moz-placeholder { color: #B1B1B1; }
40+ .input__widget:focus:-ms-input-placeholder { color: #B1B1B1; }
41+ /* stylelint-enable */
42+
43+
44+
45+.input--has-clear-type {
46+
47+}
48+ .input--has-clear-type .input__widget {
49+ padding-right: 30px;
50+ }
51+
52+ .input--has-clear-type .input__widget::-ms-clear {
53+ display: none;
54+ }
55+
56+ .input__clear-type {
57+ position: absolute;
58+ right: -8px;
59+ top: 0;
60+ }
61+
62+
63+.input--search {
64+
65+}
66+ .input--search .input__widget {
67+ padding-left: 32px;
68+ background-image: url("../vectors/input-search.svg");
69+ background-size: 24px 24px;
70+ background-repeat: no-repeat;
71+ background-position: left center;
72+ }
73+
74+
75+.input--error {
76+
77+}
78+ .input--error .input__widget {
79+ box-shadow: inset 0 -2px var(--red);
80+ }
81+
82+ .input__output {
83+ margin-top: 7px;
84+ font-size: 12px;
85+ line-height: 16px;
86+ color: var(--red);
87+ }
88+
89+ .input__output + .input__output {
90+ margin-top: 3px;
91+ }
octopod-css/development/styles/_listing.cssnew file+56
@@ -0,0 +1,56 @@
1+.listing {
2+ display: flex;
3+ flex-direction: column;
4+ align-items: flex-start;
5+}
6+ .listing__item.bar {
7+ margin-top: 2px;
8+ }
9+
10+ .listing__item.bar--larger {
11+ margin-top: 4px;
12+ }
13+
14+ .listing__item.bar:first-child {
15+ margin-top: 0;
16+ }
17+
18+ .listing__more {
19+ margin-top: 2px;
20+ }
21+
22+
23+/* пока что не встречалось: */
24+.listing--larger {
25+
26+}
27+
28+
29+.listing--for-text {
30+ display: block;
31+ font-size: 12px; /* поскольку тут нет .bar теперь размеры задаем вручную */
32+ line-height: 16px;
33+}
34+ .listing--for-text .listing__item {
35+ margin-top: 8px;
36+ white-space: nowrap; /* поскольку тут нет .bar теперь обрезание текста задаем вручную */
37+ overflow: hidden;
38+ text-overflow: ellipsis;
39+ }
40+
41+ .listing--for-text .listing__item:first-child {
42+ margin-top: 0;
43+ }
44+ .listing--for-text b {
45+ font-weight: 500;
46+ }
47+
48+ .listing--for-text .listing__more {
49+ margin-top: 8px;
50+ }
51+
52+
53+.listing--for-text.listing--larger {
54+ font-size: 16px;
55+ line-height: 24px;
56+}
octopod-css/development/styles/_loading.cssnew file+82

This diff is too big to show here.

octopod-css/development/styles/_log.cssnew file+10

This diff is too big to show here.

octopod-css/development/styles/_nav.cssnew file+39

This diff is too big to show here.

octopod-css/development/styles/_no-data.cssnew file+8

This diff is too big to show here.

octopod-css/development/styles/_no-deployment.cssnew file+7

This diff is too big to show here.

octopod-css/development/styles/_no-log.cssnew file+7

This diff is too big to show here.

octopod-css/development/styles/_no-page.cssnew file+15

This diff is too big to show here.

octopod-css/development/styles/_no-table.cssnew file+16

This diff is too big to show here.

octopod-css/development/styles/_notification.cssnew file+54

This diff is too big to show here.

octopod-css/development/styles/_null.cssnew file+66

This diff is too big to show here.

octopod-css/development/styles/_overrides.cssnew file+28

This diff is too big to show here.

octopod-css/development/styles/_page.cssnew file+71

This diff is too big to show here.

octopod-css/development/styles/_popup.cssnew file+139

This diff is too big to show here.

octopod-css/development/styles/_root.cssnew file+25

This diff is too big to show here.

octopod-css/development/styles/_sort.cssnew file+73

This diff is too big to show here.

octopod-css/development/styles/_spot.cssnew file+25

This diff is too big to show here.

octopod-css/development/styles/_status.cssnew file+31

This diff is too big to show here.

octopod-css/development/styles/_table.cssnew file+224

This diff is too big to show here.

octopod-css/development/styles/_tag.cssnew file+3

This diff is too big to show here.

octopod-css/development/styles/_title.cssnew file+5

This diff is too big to show here.

octopod-css/development/styles/_universal.cssnew file+11

This diff is too big to show here.

octopod-css/development/styles/_visuallyhidden.cssnew file+41

This diff is too big to show here.

octopod-css/development/styles/style.cssnew file+42

This diff is too big to show here.

octopod-css/development/vectors/button-add.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/button-save.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/classic-popup-close.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/common-use-archive.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/common-use-edit-white.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/common-use-edit.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/common-use-logs.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/common-use-restore.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-add-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-add-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-add.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-back-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-back-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-back.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-next-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-next-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/dash-next.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/drop-actions-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/drop-actions-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/drop-actions.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander-larger-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander-larger-hover.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander-larger.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/expander.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/external-hover-active.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/external.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/input-search.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/loading-alternate.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/loading-enlarged-alternate.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/loading-enlarged.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/loading.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/notification-close-danger-active.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/notification-close-danger-hover.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/notification-close-danger.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/notification-close-success-active.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/notification-close-success-hover.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/notification-close-success.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/popup-close-active.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/popup-close-hover.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/popup-close.svgnew file+3

This diff is too big to show here.

octopod-css/development/vectors/spot.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/status-archived.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/status-failure.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/status-pending.svgnew file+1

This diff is too big to show here.

octopod-css/development/vectors/status-success.svgnew file+1

This diff is too big to show here.

octopod-css/development/vendors/jquery/jquery-3.2.1.min.jsnew file+4

This diff is too big to show here.

octopod-css/development/vendors/normalize/normalize.cssnew file+349

This diff is too big to show here.

octopod-css/development/vendors/outline/index.htmlnew file+67

This diff is too big to show here.

octopod-css/development/vendors/outline/outline.jsnew file+37

This diff is too big to show here.

octopod-css/gulpfile.jsnew file+308

This diff is too big to show here.

octopod-css/package-lock.jsonnew file+6159

This diff is too big to show here.

octopod-css/package.jsonnew file+19

This diff is too big to show here.

octopod-css/production/images/dialog-delete.pngnew file

Binary file not shown.

octopod-css/production/images/dialog-delete@2x.pngnew file

Binary file not shown.

octopod-css/production/images/null-data.pngnew file

Binary file not shown.

octopod-css/production/images/null-data@2x.pngnew file

Binary file not shown.

octopod-css/production/images/null-search.pngnew file

Binary file not shown.

octopod-css/production/images/null-search@2x.pngnew file

Binary file not shown.

octopod-css/production/layouts/index.htmlnew file+888

This diff is too big to show here.

octopod-css/production/layouts/staging.htmlnew file+713

This diff is too big to show here.

octopod-css/production/layouts/standalone-loading.htmlnew file+46

This diff is too big to show here.

octopod-css/production/markups/_action.htmlnew file+112

This diff is too big to show here.

octopod-css/production/markups/_bar.htmlnew file+61

This diff is too big to show here.

octopod-css/production/markups/_break.htmlnew file+85

This diff is too big to show here.

octopod-css/production/markups/_button.htmlnew file+137

This diff is too big to show here.

octopod-css/production/markups/_dash.htmlnew file+130

This diff is too big to show here.

octopod-css/production/markups/_drop.htmlnew file+112

This diff is too big to show here.

octopod-css/production/markups/_expander.htmlnew file+96

This diff is too big to show here.

octopod-css/production/markups/_external.htmlnew file+61

This diff is too big to show here.

octopod-css/production/markups/_input.htmlnew file+111

This diff is too big to show here.

octopod-css/production/markups/_listing.htmlnew file+100

This diff is too big to show here.

octopod-css/production/markups/_loading.htmlnew file+136

This diff is too big to show here.

octopod-css/production/markups/_notification.htmlnew file+88

This diff is too big to show here.

octopod-css/production/markups/_null.htmlnew file+81

This diff is too big to show here.

octopod-css/production/markups/_spot.htmlnew file+61

This diff is too big to show here.

octopod-css/production/markups/_status.htmlnew file+83

This diff is too big to show here.

octopod-css/production/markups/_title.htmlnew file+60

This diff is too big to show here.

octopod-css/production/scripts/_classic-popup.jsnew file+92

This diff is too big to show here.

octopod-css/production/scripts/_data.jsnew file+7

This diff is too big to show here.

octopod-css/production/scripts/_drop.jsnew file+26

This diff is too big to show here.

octopod-css/production/scripts/_expander.jsnew file+8

This diff is too big to show here.

octopod-css/production/scripts/_popup.jsnew file+35

This diff is too big to show here.

octopod-css/production/scripts/_sort.jsnew file+16

This diff is too big to show here.

octopod-css/production/styles/style.cssnew file+1

This diff is too big to show here.

octopod-css/production/vendors/jquery/jquery-3.2.1.min.jsnew file+4

This diff is too big to show here.

octopod-css/production/vendors/outline/index.htmlnew file+67

This diff is too big to show here.

octopod-css/production/vendors/outline/outline.jsnew file+37

This diff is too big to show here.

octopod-frontend/CHANGELOG.mdnew file+5

This diff is too big to show here.

octopod-frontend/LICENSEnew file+30

This diff is too big to show here.

octopod-frontend/Setup.hsnew file+2

This diff is too big to show here.

octopod-frontend/octopod-frontend.cabalnew file+82

This diff is too big to show here.

octopod-frontend/src/Frontend/API.hsnew file+143

This diff is too big to show here.

octopod-frontend/src/Frontend/GHCJS.hsnew file+89

This diff is too big to show here.

octopod-frontend/src/Frontend/Route.hsnew file+48

This diff is too big to show here.

octopod-frontend/src/Frontend/Utils.hsnew file+470

This diff is too big to show here.

octopod-frontend/src/Main.hsnew file+117

This diff is too big to show here.

octopod-frontend/src/Page/ClassicPopup.hsnew file+66

This diff is too big to show here.

octopod-frontend/src/Page/Deployment.hsnew file+365

This diff is too big to show here.

octopod-frontend/src/Page/Deployments.hsnew file+496

This diff is too big to show here.

octopod-frontend/src/Page/Popup/EditDeployment.hsnew file+172

This diff is too big to show here.

octopod-frontend/src/Page/Popup/NewDeployment.hsnew file+156

This diff is too big to show here.

release.shnew file+20

This diff is too big to show here.

stack.yamlnew file+14

This diff is too big to show here.