├── presets
├── nest+next
│ ├── gitignore
│ ├── env.dist
│ ├── nest+next-kool.yml
│ ├── nest+next-docker-compose.yml
│ └── config.yml
├── nginx
│ ├── index.html
│ ├── nginx.yml
│ └── config.yml
├── nodejs
│ ├── node-scripts.yml
│ ├── app.js
│ ├── docker-compose.yml
│ └── config.yml
├── wordpress
│ ├── environment
│ └── config.yml
├── nestjs
│ ├── env.dist
│ ├── kool.nestjs.yml
│ ├── docker-compose.nestjs.yml
│ └── config.yml
├── expressjs
│ ├── app.js
│ ├── package.json
│ ├── docker-compose.yml
│ └── config.yml
├── symfony
│ ├── symfony-scripts.yml
│ └── config.yml
├── laravel
│ ├── vite.config.js
│ └── config.yml
├── laravel+octane
│ └── vite.config.js
├── codeigniter
│ ├── codeigniter-scripts.yml
│ └── config.yml
├── golang-cli
│ ├── config.yml
│ └── kool.yml
├── hugo
│ ├── kool.yml
│ ├── docker-compose.yml
│ └── config.yml
├── nuxtjs
│ ├── docker-compose.yml
│ └── config.yml
├── nextjs
│ ├── docker-compose.yml
│ └── config.yml
├── php
│ └── config.yml
└── adonis
│ └── config.yml
├── core
├── environment
│ ├── .env.testing
│ ├── asuser.go
│ ├── uid.go
│ ├── uid_test.go
│ ├── asuser_test.go
│ ├── env_storage.go
│ ├── env.go
│ ├── fake_env_storage.go
│ └── env_test.go
├── parser
│ ├── testing_files
│ │ └── kool.yml
│ ├── errors_test.go
│ ├── fake_parser.go
│ └── errors.go
├── presets
│ ├── fixtures
│ │ └── presets
│ │ │ └── foo
│ │ │ └── config.yml
│ ├── configs_test.go
│ ├── config.go
│ ├── parser_test.go
│ ├── fake_parser_test.go
│ └── fake_parser.go
├── shell
│ ├── errors.go
│ ├── fake_terminal_test.go
│ ├── fake_terminal.go
│ ├── terminal_size.go
│ ├── lookupCache.go
│ ├── errors_test.go
│ ├── lookupCache_test.go
│ ├── terminal.go
│ ├── prompt_input.go
│ ├── terminal_test.go
│ ├── fake_prompt_select.go
│ ├── fake_prompt_select_test.go
│ ├── table_writer.go
│ ├── fake_table_writer_test.go
│ ├── prompt_select.go
│ ├── prompt_select_test.go
│ ├── fake_table_writer.go
│ └── table_writer_test.go
├── network
│ ├── fake_network.go
│ ├── fake_network_test.go
│ ├── network.go
│ └── network_test.go
├── utils
│ └── loading.go
├── automate
│ ├── recipes.go
│ └── actions.go
└── builder
│ ├── fake_command_test.go
│ └── fake_command.go
├── templates
├── scripts
│ ├── wordpress.yml
│ ├── mongodb.yml
│ ├── mysql.yml
│ ├── maria.yml
│ ├── yarn-nextjs.yml
│ ├── yarn-nuxtjs.yml
│ ├── postgresql.yml
│ ├── php.yml
│ ├── npm-nextjs.yml
│ ├── npm-nuxtjs.yml
│ ├── yarn-adonis.yml
│ ├── yarn-expressjs.yml
│ ├── yarn.yml
│ ├── npm-adonis.yml
│ ├── npm-expressjs.yml
│ ├── yarn-laravel.yml
│ ├── npm.yml
│ ├── npm-laravel.yml
│ ├── laravel.yml
│ ├── laravel+octane+roadrunner.yml
│ └── laravel+octane+swoole.yml
├── misc
│ ├── pdf.yml
│ ├── yarn-vitejs.yml
│ └── npm-vitejs.yml
├── cache
│ ├── memcached16.yml
│ └── redis7.yml
├── docker-compose.yml
├── kool.yml
├── app
│ ├── php8.yml
│ ├── php74.yml
│ ├── php81.yml
│ ├── php82.yml
│ ├── php83.yml
│ ├── wordpress74.yml
│ ├── wordpress80.yml
│ ├── node-adonis.yml
│ ├── php82-swoole.yml
│ └── php82-roadrunner.yml
└── database
│ ├── mongodb.yml
│ ├── mariadb105.yml
│ ├── postgresql13.yml
│ ├── mysql57.yml
│ ├── mysql8.yml
│ ├── postgresql13-adonis.yml
│ ├── mysql57-adonis.yml
│ └── mysql8-adonis.yml
├── recipes
├── yarn.yml
├── php-8.yml
├── php-7.4.yml
├── php-8.1.yml
├── php-8.2.yml
├── php-8.3.yml
├── redis-7.yml
├── npm.yml
├── pdf.yml
├── memcached-1.6.yml
├── mongodb.yml
├── mysql-8.yml
├── mysql-5.7.yml
├── maria-10.5.yml
├── npm-laravel.yml
├── postgresql-13.yml
├── yarn-laravel.yml
├── php-8.2-swoole.yml
├── php-8.2-roadrunner.yml
├── pick-node-pkg-mgr.yml
├── pick-laravel-node.yml
├── pick-cache.yml
├── create-nestjs.yml
├── pick-db.yml
├── pick-php.yml
├── create-symfony.yml
├── create-laravel.yml
└── create-codeigniter.yml
├── services
├── updater
│ ├── writeable.go
│ ├── writeable_linux.go
│ └── fake_updater.go
├── user
│ └── elevated.go
├── checker
│ ├── fake_checker.go
│ ├── fake_checker_test.go
│ ├── errors.go
│ └── checker.go
├── cloud
│ ├── api
│ │ ├── api.go
│ │ ├── deploy_error.go
│ │ ├── deploy_destroy.go
│ │ ├── deploy_ping.go
│ │ ├── deploy_start.go
│ │ ├── deploy_exec.go
│ │ ├── deploy_status.go
│ │ ├── errors_test.go
│ │ ├── deploy_create.go
│ │ └── errors.go
│ ├── setup
│ │ └── cloud_setup_parser.go
│ └── deployer.go
├── yamler
│ ├── output.go
│ ├── output_test.go
│ └── merger_test.go
└── compose
│ ├── fake_parser_test.go
│ └── fake_parser.go
├── .editorconfig
├── .env.dist
├── commands
├── kool_service_test.go
├── fake_kool_service_test.go
├── preset_test.go
├── fake_kool_service.go
├── kool_service.go
├── kool_upgrade_available_checker.go
├── restart.go
├── info_test.go
├── cloud_deploy_destroy.go
└── cloud_deploy_destroy_test.go
├── embed.go
├── docs
├── 02-Kool-Cloud
│ ├── 08-Access-Running-Containers.md
│ ├── 17-Custom-Resources.md
│ ├── 15-Deploy-Lifecycle-Hooks.md
│ ├── 10-Persistent-Storage.md
│ └── 05-Environment-Variables.md
├── 15-Snippets
│ └── Generate-PDFs.md
├── 20-Troubleshooting
│ ├── intro.md
│ ├── kool-share.md
│ ├── mysql-perm-error.md
│ └── hmr-eer-empty-response.md
├── 05-Commands-Reference
│ ├── kool-status.md
│ ├── kool-create.md
│ ├── kool-info.md
│ ├── kool-self-update.md
│ ├── kool-restart.md
│ ├── kool-cloud-setup.md
│ ├── kool-recipe.md
│ ├── kool-cloud-destroy.md
│ ├── kool-run.md
│ ├── kool-stop.md
│ ├── kool-exec.md
│ ├── kool-preset.md
│ ├── kool-cloud-help.md
│ ├── kool-share.md
│ ├── kool-start.md
│ ├── kool-logs.md
│ ├── kool-cloud-deploy.md
│ ├── kool-cloud-exec.md
│ ├── kool-docker.md
│ ├── kool-cloud-logs.md
│ ├── kool-cloud.md
│ ├── kool-completion.md
│ └── 0-kool.md
├── 10-Docker-Images
│ └── 1-Introduction.md
└── 03-Presets
│ ├── 1-Creating-a-Preset.md
│ └── 0-About-Presets.md
├── Dockerfile
├── .gitignore
├── .github
├── workflows
│ ├── scan.yml
│ ├── release-drafter.yml
│ ├── docker-description.yml
│ ├── golangci-lint.yml
│ ├── test.yml
│ └── docker.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── stale.yml
├── PULL_REQUEST_TEMPLATE.md
└── release-drafter.yml
├── main.go
├── make_release.sh
├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── SECURITY.md
├── RELEASE.md
├── LICENSE.md
├── kool.yml
└── CONTRIBUTING.md
/presets/nest+next/gitignore:
--------------------------------------------------------------------------------
1 | *.env
2 |
--------------------------------------------------------------------------------
/core/environment/.env.testing:
--------------------------------------------------------------------------------
1 | VAR_TESTING_FILE=1
2 |
--------------------------------------------------------------------------------
/presets/nginx/index.html:
--------------------------------------------------------------------------------
1 | Hello World with Kool
2 |
--------------------------------------------------------------------------------
/core/parser/testing_files/kool.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | testing: "echo testing"
3 |
--------------------------------------------------------------------------------
/presets/nodejs/node-scripts.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | node: kool exec app node
3 |
--------------------------------------------------------------------------------
/presets/wordpress/environment:
--------------------------------------------------------------------------------
1 | DB_USERNAME=user
2 | DB_PASSWORD=pass
3 | DB_DATABASE=wordpress
4 |
--------------------------------------------------------------------------------
/templates/scripts/wordpress.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | php: kool exec app php
3 | wp: kool exec app wp
4 |
--------------------------------------------------------------------------------
/recipes/yarn.yml:
--------------------------------------------------------------------------------
1 | title: "Yarn"
2 |
3 | actions:
4 | - merge: scripts/yarn.yml
5 | dst: kool.yml
6 |
--------------------------------------------------------------------------------
/recipes/php-8.yml:
--------------------------------------------------------------------------------
1 | title: "PHP 8.0"
2 |
3 | actions:
4 | - merge: app/php8.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/php-7.4.yml:
--------------------------------------------------------------------------------
1 | title: "PHP 7.4"
2 |
3 | actions:
4 | - merge: app/php74.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/php-8.1.yml:
--------------------------------------------------------------------------------
1 | title: "PHP 8.1"
2 |
3 | actions:
4 | - merge: app/php81.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/php-8.2.yml:
--------------------------------------------------------------------------------
1 | title: "PHP 8.2"
2 |
3 | actions:
4 | - merge: app/php82.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/php-8.3.yml:
--------------------------------------------------------------------------------
1 | title: "PHP 8.3"
2 |
3 | actions:
4 | - merge: app/php83.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/redis-7.yml:
--------------------------------------------------------------------------------
1 | title: "Redis 7"
2 |
3 | actions:
4 | - merge: cache/redis7.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/npm.yml:
--------------------------------------------------------------------------------
1 | title: "npm - Node Package Manager"
2 |
3 | actions:
4 | - merge: scripts/npm.yml
5 | dst: kool.yml
6 |
--------------------------------------------------------------------------------
/recipes/pdf.yml:
--------------------------------------------------------------------------------
1 | title: "Microservice: PDF generator"
2 |
3 | actions:
4 | - merge: misc/pdf.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/recipes/memcached-1.6.yml:
--------------------------------------------------------------------------------
1 | title: "Memcached 1.6"
2 |
3 | actions:
4 | - merge: cache/memcached16.yml
5 | dst: docker-compose.yml
6 |
--------------------------------------------------------------------------------
/presets/nestjs/env.dist:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # Kool variables - mostly used to tweak docker-compose.yml containers.
4 | #
5 | # KOOL_APP_PORT=
6 | # KOOL_GLOBAL_NETWORK=
7 |
--------------------------------------------------------------------------------
/templates/scripts/mongodb.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # CLI access to MongoDB
3 | mongodb: kool exec database mongosh --port=27017 --username=root --password=root
4 |
--------------------------------------------------------------------------------
/templates/scripts/mysql.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # CLI access to MySQL
3 | mysql: kool exec -e MYSQL_PWD=$DB_PASSWORD database mysql -u $DB_USERNAME $DB_DATABASE
4 |
--------------------------------------------------------------------------------
/services/updater/writeable.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 | // +build !linux
3 |
4 | package updater
5 |
6 | func isWriteable(_ string) bool {
7 | return false
8 | }
9 |
--------------------------------------------------------------------------------
/templates/scripts/maria.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # CLI access to MariaDB
3 | maria: kool exec -e MYSQL_PWD=$DB_PASSWORD database mariadb -u $DB_USERNAME $DB_DATABASE
4 |
--------------------------------------------------------------------------------
/templates/scripts/yarn-nextjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | yarn: kool exec app yarn
3 |
4 | setup:
5 | - kool docker kooldev/node:20 yarn install
6 | - kool start
7 |
--------------------------------------------------------------------------------
/templates/scripts/yarn-nuxtjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | yarn: kool exec app yarn
3 |
4 | setup:
5 | - kool docker kooldev/node:20 yarn install
6 | - kool start
7 |
--------------------------------------------------------------------------------
/templates/scripts/postgresql.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # CLI access to PostgreSQL
3 | psql: kool exec -e PGPASSWORD=$DB_PASSWORD database psql -U $DB_USERNAME $DB_DATABASE
4 |
--------------------------------------------------------------------------------
/recipes/mongodb.yml:
--------------------------------------------------------------------------------
1 | title: "Mongodb"
2 |
3 | actions:
4 | - merge: database/mongodb.yml
5 | dst: docker-compose.yml
6 | - merge: scripts/mongodb.yml
7 | dst: kool.yml
8 |
--------------------------------------------------------------------------------
/recipes/mysql-8.yml:
--------------------------------------------------------------------------------
1 | title: "MySQL 8"
2 |
3 | actions:
4 | - merge: database/mysql8.yml
5 | dst: docker-compose.yml
6 | - merge: scripts/mysql.yml
7 | dst: kool.yml
8 |
--------------------------------------------------------------------------------
/templates/scripts/php.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | php: kool exec app php
3 | composer: kool exec app composer
4 |
5 | setup:
6 | - kool start
7 | # - add more setup commands
8 |
--------------------------------------------------------------------------------
/recipes/mysql-5.7.yml:
--------------------------------------------------------------------------------
1 | title: "MySQL 5.7"
2 |
3 | actions:
4 | - merge: database/mysql57.yml
5 | dst: docker-compose.yml
6 | - merge: scripts/mysql.yml
7 | dst: kool.yml
8 |
--------------------------------------------------------------------------------
/recipes/maria-10.5.yml:
--------------------------------------------------------------------------------
1 | title: "MariaDB 10.5"
2 |
3 | actions:
4 | - merge: database/mariadb105.yml
5 | dst: docker-compose.yml
6 | - merge: scripts/maria.yml
7 | dst: kool.yml
8 |
--------------------------------------------------------------------------------
/templates/scripts/npm-nextjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | npm: kool exec app npm
3 | npx: kool exec app npx
4 |
5 | setup:
6 | - kool docker kooldev/node:20 npm install
7 | - kool start
8 |
--------------------------------------------------------------------------------
/templates/scripts/npm-nuxtjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | npm: kool exec app npm
3 | npx: kool exec app npx
4 |
5 | setup:
6 | - kool docker kooldev/node:20 npm install
7 | - kool start
8 |
--------------------------------------------------------------------------------
/recipes/npm-laravel.yml:
--------------------------------------------------------------------------------
1 | title: "npm for Laravel"
2 |
3 | actions:
4 | - merge: scripts/npm-laravel.yml
5 | dst: kool.yml
6 | - merge: misc/npm-vitejs.yml
7 | dst: docker-compose.yml
8 |
--------------------------------------------------------------------------------
/core/environment/asuser.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | func initAsuser(envStorage EnvStorage) {
4 | if envStorage.Get("KOOL_ASUSER") == "" {
5 | envStorage.Set("KOOL_ASUSER", uid())
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/recipes/postgresql-13.yml:
--------------------------------------------------------------------------------
1 | title: "PostgreSQL 13"
2 |
3 | actions:
4 | - merge: database/postgresql13.yml
5 | dst: docker-compose.yml
6 | - merge: scripts/postgresql.yml
7 | dst: kool.yml
8 |
--------------------------------------------------------------------------------
/recipes/yarn-laravel.yml:
--------------------------------------------------------------------------------
1 | title: "Yarn (Laravel)"
2 |
3 | actions:
4 | - merge: scripts/yarn-laravel.yml
5 | dst: kool.yml
6 | - merge: misc/yarn-vitejs.yml
7 | dst: docker-compose.yml
8 |
--------------------------------------------------------------------------------
/templates/misc/pdf.yml:
--------------------------------------------------------------------------------
1 | services:
2 | # see: https://github.com/kool-dev/pdf
3 | pdf:
4 | image: "kooldev/pdf:latest"
5 | expose:
6 | - 3000
7 | networks:
8 | - kool_local
9 |
--------------------------------------------------------------------------------
/templates/scripts/yarn-adonis.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | adonis: kool exec app adonis
3 | yarn: kool exec app yarn
4 |
5 | setup:
6 | - kool docker kooldev/node:20 yarn install
7 | - kool start
8 |
--------------------------------------------------------------------------------
/templates/scripts/yarn-expressjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | node: kool exec app node
3 | yarn: kool exec app yarn
4 |
5 | setup:
6 | - kool docker kooldev/node:20 yarn install
7 | - kool start
8 |
--------------------------------------------------------------------------------
/templates/scripts/yarn.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # yarn - helpers for JS handling
3 | yarn: kool docker kooldev/node:20 yarn
4 | setup:
5 | - kool run yarn install
6 | reset:
7 | - kool run yarn install
8 |
--------------------------------------------------------------------------------
/presets/nest+next/env.dist:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # Kool variables - mostly used to tweak docker-compose.yml containers.
4 | #
5 | # KOOL_FRONTEND_PORT=
6 | # KOOL_BACKEND_PORT=
7 | # KOOL_DATABASE_PORT=
8 | # KOOL_GLOBAL_NETWORK=
9 |
--------------------------------------------------------------------------------
/templates/cache/memcached16.yml:
--------------------------------------------------------------------------------
1 | services:
2 | cache:
3 | image: memcached:1.6-alpine
4 | volumes:
5 | - cache:/data:delegated
6 | networks:
7 | - kool_local
8 |
9 | volumes:
10 | cache:
11 |
--------------------------------------------------------------------------------
/templates/scripts/npm-adonis.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | adonis: kool exec app adonis
3 | npm: kool exec app npm
4 | npx: kool exec app npx
5 |
6 | setup:
7 | - kool docker kooldev/node:20 npm install
8 | - kool start
9 |
--------------------------------------------------------------------------------
/templates/scripts/npm-expressjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | node: kool exec app node
3 | npm: kool exec app npm
4 | npx: kool exec app npx
5 |
6 | setup:
7 | - kool docker kooldev/node:20 npm install
8 | - kool start
9 |
--------------------------------------------------------------------------------
/templates/scripts/yarn-laravel.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # yarn - helpers for JS handling
3 | yarn: kool docker kooldev/node:20 yarn
4 | before-start:
5 | - kool run yarn install
6 | reset:
7 | - kool run yarn install
8 |
--------------------------------------------------------------------------------
/services/updater/writeable_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 | // +build linux
3 |
4 | package updater
5 |
6 | import "golang.org/x/sys/unix"
7 |
8 | func isWriteable(path string) bool {
9 | return unix.Access(path, unix.W_OK) == nil
10 | }
11 |
--------------------------------------------------------------------------------
/templates/scripts/npm.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # npm - helpers for JS handling
3 | npm: kool docker kooldev/node:20 npm
4 | npx: kool docker kooldev/node:20 npx
5 | setup:
6 | - kool run npm install
7 | reset:
8 | - kool run npm install
9 |
--------------------------------------------------------------------------------
/presets/nginx/nginx.yml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | image: kooldev/nginx:static
4 | ports:
5 | - "${KOOL_APP_PORT:-80}:80"
6 | volumes:
7 | - .:/app:delegated
8 | networks:
9 | - kool_local
10 | - kool_global
11 |
--------------------------------------------------------------------------------
/templates/scripts/npm-laravel.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # npm - helpers for JS handling
3 | npm: kool docker kooldev/node:20 npm
4 | npx: kool docker kooldev/node:20 npx
5 | before-start:
6 | - kool run npm install
7 | reset:
8 | - kool run npm install
9 |
--------------------------------------------------------------------------------
/core/presets/fixtures/presets/foo/config.yml:
--------------------------------------------------------------------------------
1 | tags: [ 'foo' ]
2 |
3 | create:
4 | - name: creating foo
5 | actions:
6 | - scripts:
7 | - echo "foo"
8 |
9 | preset:
10 | - name: 'preset step'
11 | actions:
12 | - copy: bar
13 |
--------------------------------------------------------------------------------
/templates/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 | #
3 | # Services definitions
4 | #
5 | services:
6 | #
7 | # Networks definitions
8 | #
9 | networks:
10 | kool_local:
11 | kool_global:
12 | external: true
13 | name: "${KOOL_GLOBAL_NETWORK:-kool_global}"
14 |
--------------------------------------------------------------------------------
/templates/misc/yarn-vitejs.yml:
--------------------------------------------------------------------------------
1 | services:
2 | node:
3 | image: kooldev/node:20
4 | command: ["yarn", "dev"]
5 | ports:
6 | - "3001:3001"
7 | volumes:
8 | - .:/app:delegated
9 | networks:
10 | - kool_local
11 | - kool_global
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = tab
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [{*.yaml,*.yml,*.sh}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | # GOOS variable for your local development; since the Go docker images are
2 | # Linux based, in case you are developing on MacOS you must uncomment the line below.
3 | #GOOS=darwin
4 |
5 | # GOARCH the architecture to be used for kool run compile script.
6 | #GOARCH=amd64
7 |
--------------------------------------------------------------------------------
/services/user/elevated.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import "os"
4 |
5 | // CurrentUserIsElevated returns true if the current user
6 | // executing the program has admin privileges (sudo/administrator)
7 | func CurrentUserIsElevated() bool {
8 | return os.Getuid() == 0
9 | }
10 |
--------------------------------------------------------------------------------
/templates/cache/redis7.yml:
--------------------------------------------------------------------------------
1 | services:
2 | cache:
3 | image: redis:7-alpine
4 | volumes:
5 | - cache:/data:delegated
6 | networks:
7 | - kool_local
8 | healthcheck:
9 | test: ["CMD", "redis-cli", "ping"]
10 |
11 | volumes:
12 | cache:
13 |
--------------------------------------------------------------------------------
/templates/misc/npm-vitejs.yml:
--------------------------------------------------------------------------------
1 | services:
2 | node:
3 | image: kooldev/node:20
4 | command: ["npm", "run", "dev"]
5 | ports:
6 | - "3001:3001"
7 | volumes:
8 | - .:/app:delegated
9 | networks:
10 | - kool_local
11 | - kool_global
12 |
--------------------------------------------------------------------------------
/presets/expressjs/app.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const app = express();
3 | const port = 3000;
4 |
5 | app.get("/", (req, res) => {
6 | res.send("Hello World");
7 | })
8 |
9 | app.listen(port, () => {
10 | console.log("Server running at http://localhost:"+port+"/");
11 | })
12 |
--------------------------------------------------------------------------------
/presets/nestjs/kool.nestjs.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | setup:
3 | # copy .env file
4 | - cp .env.dist .env
5 | # install backend deps
6 | - kool docker kooldev/node:20 npm install
7 |
8 | # helpers
9 | npm: kool exec app npm
10 | npx: kool exec app npx
11 | nest: kool run npx @nestjs/cli
12 |
--------------------------------------------------------------------------------
/core/environment/uid.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | )
7 |
8 | func initUid(envStorage EnvStorage) {
9 | if envStorage.Get("UID") == "" {
10 | envStorage.Set("UID", uid())
11 | }
12 | }
13 |
14 | func uid() string {
15 | return fmt.Sprintf("%d", os.Getuid())
16 | }
17 |
--------------------------------------------------------------------------------
/templates/kool.yml:
--------------------------------------------------------------------------------
1 | # Here you can define shortcuts and aliases to common tasks (commands)
2 | # you will run in your local environment or CI or deploy.
3 | # Use the scripts defined below with:
4 | # $ kool run
21 | ```
22 |
23 | Not This:
24 |
25 | ```html
26 |
27 | ```
28 |
29 | Using it with asset might cause problems while using `kool share`.
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:stable
2 |
3 | ARG USERNAME=kool
4 | ARG USER_UID=1000
5 | ARG USER_GID=$USER_UID
6 |
7 | RUN groupadd --gid $USER_GID $USERNAME \
8 | && useradd --uid $USER_UID --gid $USER_GID -s /bin/bash -m $USERNAME \
9 | && apt-get update \
10 | && apt-get install -y sudo \
11 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
12 | && chmod 0440 /etc/sudoers.d/$USERNAME
13 |
14 | # install kool
15 | RUN apt update && apt install curl git -y && curl -fsSL https://kool.dev/install | bash
16 |
17 | WORKDIR /home/kool
18 |
19 | USER $USERNAME
20 |
21 | RUN bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)"
22 |
--------------------------------------------------------------------------------
/templates/database/mysql8.yml:
--------------------------------------------------------------------------------
1 | services:
2 | database:
3 | image: mysql/mysql-server:8.0
4 | command: --default-authentication-plugin=mysql_native_password
5 | ports:
6 | - "${KOOL_DATABASE_PORT:-3306}:3306"
7 | environment:
8 | MYSQL_ROOT_HOST: "%"
9 | MYSQL_ROOT_PASSWORD: "${DB_PASSWORD-rootpass}"
10 | MYSQL_DATABASE: "${DB_DATABASE-database}"
11 | MYSQL_USER: "${DB_USERNAME-user}"
12 | MYSQL_PASSWORD: "${DB_PASSWORD-pass}"
13 | MYSQL_ALLOW_EMPTY_PASSWORD: 1
14 | volumes:
15 | - database:/var/lib/mysql:delegated
16 | networks:
17 | - kool_local
18 | healthcheck:
19 | test: ["CMD", "mysqladmin", "ping"]
20 |
21 | volumes:
22 | database:
23 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-setup.md:
--------------------------------------------------------------------------------
1 | ## kool cloud setup
2 |
3 | Set up local configuration files for deployment
4 |
5 | ```
6 | kool cloud setup
7 | ```
8 |
9 | ### Options
10 |
11 | ```
12 | -h, --help help for setup
13 | ```
14 |
15 | ### Options inherited from parent commands
16 |
17 | ```
18 | --domain string Environment domain name to deploy to
19 | --token string Token to authenticate with Kool.dev Cloud API
20 | --verbose Increases output verbosity
21 | -w, --working_dir string Changes the working directory for the command
22 | ```
23 |
24 | ### SEE ALSO
25 |
26 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
27 |
28 |
--------------------------------------------------------------------------------
/docs/20-Troubleshooting/mysql-perm-error.md:
--------------------------------------------------------------------------------
1 | ### `kool run mysql`, `kool run maria` or `kool run setup`
2 |
3 | **Problem:**
4 |
5 | > Access denied for root on mysql and mariaDB databases
6 |
7 | **Answer:**
8 |
9 | > Stop your containers with the purge option in order to delete all volumes
10 |
11 | ```shell
12 | kool stop --purge
13 | ```
14 |
15 | > Make sure your DB username is other than root and define a password on the .env file
16 |
17 | ```diff
18 | -DB_USERNAME=root
19 | +DB_USERNAME=
20 |
21 | -DB_PASSWORD=
22 | +DB_PASSWORD=
23 | ```
24 |
25 | > Start your container
26 |
27 | ```shell
28 | kool start
29 | ```
30 |
31 | > Run your setup
32 |
33 | ```shell
34 | kool run setup
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-recipe.md:
--------------------------------------------------------------------------------
1 | ## kool recipe
2 |
3 | Adds configuration for some recipe in the current work directory.
4 |
5 | ### Synopsis
6 |
7 | Run the defines steps for a recipe which can add/edit files the current project directory in order to add some new service or configuration.
8 |
9 | ```
10 | kool recipe [RECIPE]
11 | ```
12 |
13 | ### Options
14 |
15 | ```
16 | -h, --help help for recipe
17 | ```
18 |
19 | ### Options inherited from parent commands
20 |
21 | ```
22 | --verbose Increases output verbosity
23 | -w, --working_dir string Changes the working directory for the command
24 | ```
25 |
26 | ### SEE ALSO
27 |
28 | * [kool](kool) - Cloud native environments made easy
29 |
30 |
--------------------------------------------------------------------------------
/templates/database/postgresql13-adonis.yml:
--------------------------------------------------------------------------------
1 | services:
2 | database:
3 | image: postgres:13-alpine
4 | ports:
5 | - "${KOOL_DATABASE_PORT:-5432}:5432"
6 | environment:
7 | POSTGRES_DB: "${DB_DATABASE-database}"
8 | POSTGRES_USER: "${DB_USER-user}"
9 | POSTGRES_PASSWORD: "${DB_PASSWORD-pass}"
10 | POSTGRES_HOST_AUTH_METHOD: "trust"
11 | volumes:
12 | - database:/var/lib/postgresql/data:delegated
13 | networks:
14 | - kool_local
15 | healthcheck:
16 | test: ["CMD", "pg_isready", "-q", "-d", "$DB_DATABASE", "-U", "$DB_USER"]
17 |
18 | volumes:
19 | database:
20 |
21 | scripts:
22 | psql: kool exec -e PGPASSWORD=$DB_PASSWORD database psql -U $DB_USER $DB_DATABASE
23 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-destroy.md:
--------------------------------------------------------------------------------
1 | ## kool cloud destroy
2 |
3 | Destroy an environment deployed to Kool.dev Cloud
4 |
5 | ```
6 | kool cloud destroy
7 | ```
8 |
9 | ### Options
10 |
11 | ```
12 | -h, --help help for destroy
13 | ```
14 |
15 | ### Options inherited from parent commands
16 |
17 | ```
18 | --domain string Environment domain name to deploy to
19 | --token string Token to authenticate with Kool.dev Cloud API
20 | --verbose Increases output verbosity
21 | -w, --working_dir string Changes the working directory for the command
22 | ```
23 |
24 | ### SEE ALSO
25 |
26 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
27 |
28 |
--------------------------------------------------------------------------------
/.github/workflows/docker-description.yml:
--------------------------------------------------------------------------------
1 | name: Sync Docker Hub Description
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - README.md
9 | - .github/workflows/docker-description.yml
10 |
11 | jobs:
12 | docker-description:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Sync Docker Hub Description
18 | if: github.ref == 'refs/heads/main' && github.repository == 'kool-dev/kool'
19 | uses: peter-evans/dockerhub-description@v2
20 | env:
21 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
22 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
23 | DOCKERHUB_REPOSITORY: kooldev/kool
24 |
--------------------------------------------------------------------------------
/core/shell/errors_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "testing"
7 | )
8 |
9 | func TestIsUserCancelledError(t *testing.T) {
10 | err := fmt.Errorf("error")
11 |
12 | if IsUserCancelledError(err) {
13 | t.Error("method IsUserCancelledError should return false for non user cancelled errors")
14 | }
15 |
16 | err = ErrUserCancelled
17 |
18 | if !IsUserCancelledError(err) {
19 | t.Error("method IsUserCancelledError should return true for user cancelled errors")
20 | }
21 | }
22 |
23 | func TestExitable(t *testing.T) {
24 | err := errors.New("some error")
25 |
26 | exitable := ErrExitable{Err: err}
27 |
28 | if exitable.Error() != err.Error() {
29 | t.Error("error should be the same")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-run.md:
--------------------------------------------------------------------------------
1 | ## kool run
2 |
3 | Execute a script defined in kool.yml
4 |
5 | ### Synopsis
6 |
7 | Execute the specified SCRIPT, as defined in the kool.yml file.
8 | A single-line SCRIPT can be run with optional arguments.
9 |
10 | ```
11 | kool run SCRIPT [--] [ARG...]
12 | ```
13 |
14 | ### Options
15 |
16 | ```
17 | -e, --env stringArray Environment variables.
18 | -h, --help help for run
19 | ```
20 |
21 | ### Options inherited from parent commands
22 |
23 | ```
24 | --verbose Increases output verbosity
25 | -w, --working_dir string Changes the working directory for the command
26 | ```
27 |
28 | ### SEE ALSO
29 |
30 | * [kool](kool) - Cloud native environments made easy
31 |
32 |
--------------------------------------------------------------------------------
/services/cloud/setup/cloud_setup_parser.go:
--------------------------------------------------------------------------------
1 | package setup
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | const KoolDeployFile string = "kool.cloud.yml"
9 |
10 | type CloudSetupParser interface {
11 | HasDeployConfig() bool
12 | ConfigFilePath() string
13 | }
14 |
15 | type DefaultCloudSetupParser struct {
16 | pwd string
17 | }
18 |
19 | func NewDefaultCloudSetupParser(pwd string) *DefaultCloudSetupParser {
20 | return &DefaultCloudSetupParser{pwd}
21 | }
22 |
23 | func (s *DefaultCloudSetupParser) ConfigFilePath() string {
24 | return filepath.Join(s.pwd, KoolDeployFile)
25 | }
26 |
27 | func (s *DefaultCloudSetupParser) HasDeployConfig() (has bool) {
28 | _, err := os.Stat(s.ConfigFilePath())
29 |
30 | return err == nil
31 | }
32 |
--------------------------------------------------------------------------------
/templates/database/mysql57-adonis.yml:
--------------------------------------------------------------------------------
1 | services:
2 | database:
3 | image: mysql/mysql-server:5.7
4 | ports:
5 | - "${KOOL_DATABASE_PORT:-3306}:3306"
6 | environment:
7 | MYSQL_ROOT_HOST: "%"
8 | MYSQL_ROOT_PASSWORD: "${DB_PASSWORD-rootpass}"
9 | MYSQL_DATABASE: "${DB_DATABASE-database}"
10 | MYSQL_USER: "${DB_USER-user}"
11 | MYSQL_PASSWORD: "${DB_PASSWORD-pass}"
12 | MYSQL_ALLOW_EMPTY_PASSWORD: 1
13 | volumes:
14 | - database:/var/lib/mysql:delegated
15 | networks:
16 | - kool_local
17 | healthcheck:
18 | test: ["CMD", "mysqladmin", "ping"]
19 |
20 | volumes:
21 | database:
22 |
23 | scripts:
24 | mysql: kool exec -e MYSQL_PWD=$DB_PASSWORD database mysql -u $DB_USER $DB_DATABASE
25 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Our current version v3 is an LTS version - first version to bring the cloud services portion to the general public.
6 |
7 | | Version | Supported | Policy |
8 | | :----: | :----: | -- |
9 | | 3.x | :white_check_mark: | Rolling updates as new releases with semantic versioning. |
10 | | 2.x | :x: | You must upgrade to v3 for continued security updates. |
11 | | 1.x | :x: | You must upgrade to v3 for continued security updates. |
12 |
13 |
14 | ## Reporting a Vulnerability
15 |
16 | If you find any security issue or vulnerability please send reach out by email to [contact@kool.dev](mailto:contact@kool.dev) before making it publicly available for previous assessment and counter mesures.
17 |
--------------------------------------------------------------------------------
/presets/nginx/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Static' ]
3 |
4 | name: 'Nginx'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new NGINX Application
9 | actions:
10 | - scripts:
11 | - mkdir -p $CREATE_DIRECTORY/public
12 |
13 | # Preset defines the workflow for installing this preset in the current working directory
14 | preset:
15 | - name: 'Copy basic config files'
16 | actions:
17 | - copy: index.html
18 | dst: public/index.html
19 | - copy: docker-compose.yml
20 | - merge: nginx.yml
21 | dst: docker-compose.yml
22 | - copy: kool.yml
23 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 15
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - R&D
8 | - security
9 | - enhancement
10 | # Label to use when marking an issue as stale
11 | staleLabel: wontfix
12 | # Comment to post when marking an issue as stale. Set to `false` to disable
13 | markComment: >
14 | This issue has been automatically marked as stale because it has not had
15 | recent activity. It will be closed if no further activity occurs. Thank you
16 | for your contributions.
17 | # Comment to post when closing a stale issue. Set to `false` to disable
18 | closeComment: false
19 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_error.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "fmt"
4 |
5 | // DeployError consumes the API endpoint to inform a build error for a deployment
6 | type DeployError struct {
7 | Endpoint
8 | }
9 |
10 | // NewDeployError creates a new DeployError instance
11 | func NewDeployError(created *DeployCreateResponse, err error) (c *DeployError) {
12 | c = &DeployError{
13 | Endpoint: NewDefaultEndpoint("POST"),
14 | }
15 |
16 | c.SetPath("deploy/error")
17 | c.Body().Set("id", fmt.Sprintf("%d", created.Deploy.ID))
18 | c.Body().Set("err", err.Error())
19 |
20 | return
21 | }
22 |
23 | // Run calls deploy/error in the Kool Dev API
24 | func (c *DeployError) Run() (err error) {
25 | var resp interface{}
26 | c.SetResponseReceiver(&resp)
27 | err = c.DoCall()
28 | return
29 | }
30 |
--------------------------------------------------------------------------------
/core/shell/lookupCache_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestLookupCache(t *testing.T) {
9 | cache := newLookupCache()
10 |
11 | if cache.mtx == nil {
12 | t.Error("missing mtx on lookupCache")
13 | }
14 |
15 | if cache.cache != nil {
16 | t.Errorf("unexpected default non-nil value for cache: %v", cache.cache)
17 | }
18 |
19 | if exists, err := cache.fetch("unknown"); err != nil || exists {
20 | t.Errorf("bad return for fetching unknown key from cache")
21 | }
22 |
23 | var err = errors.New("error")
24 | cache.set("key", err)
25 |
26 | if exists, cached := cache.fetch("key"); !exists || !errors.Is(cached, err) {
27 | t.Errorf("failed to fetch error from cache - exists: %v got: %v expected: %v", exists, cached, err)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | permissions:
8 | contents: read
9 | jobs:
10 | golangci:
11 | name: lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | # ref: https://github.com/golangci/golangci-lint-action/issues/442#issuecomment-1203786890
15 | - uses: actions/checkout@v3
16 | - uses: actions/setup-go@v3
17 | with:
18 | go-version-file: go.mod
19 | cache: true
20 | cache-dependency-path: go.sum
21 | - name: Install golangci-lint
22 | run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.1
23 | - name: Run golangci-lint
24 | run: golangci-lint run --version --verbose --out-format=github-actions
25 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-stop.md:
--------------------------------------------------------------------------------
1 | ## kool stop
2 |
3 | Stop and destroy running service containers
4 |
5 | ### Synopsis
6 |
7 | Stop and destroy the specified [SERVICE] containers, which were started
8 | using 'kool start'. If no [SERVICE] is provided, all running containers are stopped.
9 |
10 | ```
11 | kool stop [SERVICE...]
12 | ```
13 |
14 | ### Options
15 |
16 | ```
17 | -h, --help help for stop
18 | --purge Remove all persistent data from volume mounts on containers
19 | ```
20 |
21 | ### Options inherited from parent commands
22 |
23 | ```
24 | --verbose Increases output verbosity
25 | -w, --working_dir string Changes the working directory for the command
26 | ```
27 |
28 | ### SEE ALSO
29 |
30 | * [kool](kool) - Cloud native environments made easy
31 |
32 |
--------------------------------------------------------------------------------
/presets/nuxtjs/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Javascript' ]
3 |
4 | name: 'NuxtJS'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Nuxt Application
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/node:20
12 | - kool docker kooldev/node:20 yarn create nuxt-app $CREATE_DIRECTORY
13 |
14 | # Preset defines the workflow for installing this preset in the current working directory
15 | preset:
16 | - name: 'Copy basic config files'
17 | actions:
18 | - copy: docker-compose.yml
19 | - copy: kool.yml
20 |
21 | - name: 'Customize your setup'
22 | actions:
23 | - recipe: pick-node-pkg-mgr
24 |
--------------------------------------------------------------------------------
/presets/nextjs/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Javascript' ]
3 |
4 | name: 'NextJS'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new NextJS Application
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/node:20
12 | - kool docker kooldev/node:20 yarn create next-app $CREATE_DIRECTORY
13 |
14 | # Preset defines the workflow for installing this preset in the current working directory
15 | preset:
16 | - name: 'Copy basic config files'
17 | actions:
18 | - copy: docker-compose.yml
19 | - copy: kool.yml
20 |
21 | - name: 'Customize your setup'
22 | actions:
23 | - recipe: pick-node-pkg-mgr
24 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-exec.md:
--------------------------------------------------------------------------------
1 | ## kool exec
2 |
3 | Execute a command inside a running service container
4 |
5 | ### Synopsis
6 |
7 | Execute a COMMAND inside the specified SERVICE container (similar to an SSH session).
8 |
9 | ```
10 | kool exec [OPTIONS] SERVICE COMMAND [--] [ARG...]
11 | ```
12 |
13 | ### Options
14 |
15 | ```
16 | -d, --detach Detached mode: Run command in the background.
17 | -e, --env stringArray Environment variables.
18 | -h, --help help for exec
19 | ```
20 |
21 | ### Options inherited from parent commands
22 |
23 | ```
24 | --verbose Increases output verbosity
25 | -w, --working_dir string Changes the working directory for the command
26 | ```
27 |
28 | ### SEE ALSO
29 |
30 | * [kool](kool) - Cloud native environments made easy
31 |
32 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-preset.md:
--------------------------------------------------------------------------------
1 | ## kool preset
2 |
3 | Install configuration files customized for Kool in the current directory
4 |
5 | ### Synopsis
6 |
7 | Initialize a project using the specified [PRESET] by installing configuration
8 | files customized for Kool in the current working directory. If no [PRESET] is provided,
9 | an interactive wizard will present the available options.
10 |
11 | ```
12 | kool preset [PRESET]
13 | ```
14 |
15 | ### Options
16 |
17 | ```
18 | -h, --help help for preset
19 | ```
20 |
21 | ### Options inherited from parent commands
22 |
23 | ```
24 | --verbose Increases output verbosity
25 | -w, --working_dir string Changes the working directory for the command
26 | ```
27 |
28 | ### SEE ALSO
29 |
30 | * [kool](kool) - Cloud native environments made easy
31 |
32 |
--------------------------------------------------------------------------------
/templates/database/mysql8-adonis.yml:
--------------------------------------------------------------------------------
1 | services:
2 | database:
3 | image: mysql/mysql-server:8.0
4 | command: --default-authentication-plugin=mysql_native_password
5 | ports:
6 | - "${KOOL_DATABASE_PORT:-3306}:3306"
7 | environment:
8 | MYSQL_ROOT_HOST: "%"
9 | MYSQL_ROOT_PASSWORD: "${DB_PASSWORD-rootpass}"
10 | MYSQL_DATABASE: "${DB_DATABASE-database}"
11 | MYSQL_USER: "${DB_USER-user}"
12 | MYSQL_PASSWORD: "${DB_PASSWORD-pass}"
13 | MYSQL_ALLOW_EMPTY_PASSWORD: 1
14 | volumes:
15 | - database:/var/lib/mysql:delegated
16 | networks:
17 | - kool_local
18 | healthcheck:
19 | test: ["CMD", "mysqladmin", "ping"]
20 |
21 | volumes:
22 | database:
23 |
24 | scripts:
25 | mysql: kool exec -e MYSQL_PWD=$DB_PASSWORD database mysql -u $DB_USER $DB_DATABASE
26 |
--------------------------------------------------------------------------------
/core/network/fake_network_test.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestFakeHandler(t *testing.T) {
9 | f := &FakeHandler{}
10 |
11 | _ = f.HandleGlobalNetwork("testing_network")
12 |
13 | if !f.CalledHandleGlobalNetwork || f.NetworkNameArg != "testing_network" {
14 | t.Error("failed to use mocked HandleGlobalNetwork function on FakeHandler")
15 | }
16 | }
17 |
18 | func TestFailedFakeChecker(t *testing.T) {
19 | f := &FakeHandler{MockError: errors.New("fake error")}
20 |
21 | err := f.HandleGlobalNetwork("testing_network")
22 |
23 | if err == nil {
24 | t.Error("failed to use mocked failed HandleGlobalNetwork function on FakeHandler")
25 | } else if err.Error() != "fake error" {
26 | t.Error("failed to use mocked failed HandleGlobalNetwork function on FakeHandler")
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/shell/terminal.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import "github.com/moby/term"
4 |
5 | const standardTermWidth int = 80
6 |
7 | // TerminalChecker holds logic to check if environment is a terminal
8 | type TerminalChecker interface {
9 | IsTerminal(...interface{}) bool
10 | }
11 |
12 | // DefaultTerminalChecker holds logic to check if file descriptors are a TTY
13 | type DefaultTerminalChecker struct{}
14 |
15 | // IsTerminal checks if input is a terminal
16 | func (t *DefaultTerminalChecker) IsTerminal(fds ...interface{}) bool {
17 | for i := range fds {
18 | if _, isTerminal := term.GetFdInfo(fds[i]); !isTerminal {
19 | return false
20 | }
21 | }
22 |
23 | return true
24 | }
25 |
26 | // NewTerminalChecker creates a new terminal checker
27 | func NewTerminalChecker() TerminalChecker {
28 | return &DefaultTerminalChecker{}
29 | }
30 |
--------------------------------------------------------------------------------
/presets/symfony/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'PHP' ]
3 |
4 | name: 'Symfony'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Symfony Application
9 | actions:
10 | - recipe: create-symfony
11 |
12 | # Preset defines the workflow for installing this preset in the current working directory
13 | preset:
14 | - name: 'Copy basic config files'
15 | actions:
16 | - copy: docker-compose.yml
17 | - copy: kool.yml
18 | - merge: symfony-scripts.yml
19 | dst: kool.yml
20 |
21 | - name: 'Customize your setup'
22 | actions:
23 | - recipe: pick-php
24 | - recipe: pick-db
25 | - recipe: pick-cache
26 | - recipe: pick-node-pkg-mgr
27 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-help.md:
--------------------------------------------------------------------------------
1 | ## kool cloud help
2 |
3 | Help about any command
4 |
5 | ### Synopsis
6 |
7 | Help provides help for any command in the application.
8 | Simply type cloud help [path to command] for full details.
9 |
10 | ```
11 | kool cloud help [command] [flags]
12 | ```
13 |
14 | ### Options
15 |
16 | ```
17 | -h, --help help for help
18 | ```
19 |
20 | ### Options inherited from parent commands
21 |
22 | ```
23 | --domain string Environment domain name to deploy to
24 | --token string Token to authenticate with Kool.dev Cloud API
25 | --verbose Increases output verbosity
26 | -w, --working_dir string Changes the working directory for the command
27 | ```
28 |
29 | ### SEE ALSO
30 |
31 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
32 |
33 |
--------------------------------------------------------------------------------
/presets/codeigniter/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'PHP' ]
3 |
4 | name: 'CodeIgniter'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new CodeIgniter Application
9 | actions:
10 | - recipe: create-codeigniter
11 |
12 | # Preset defines the workflow for installing this preset in the current working directory
13 | preset:
14 | - name: 'Copy basic config files'
15 | actions:
16 | - copy: docker-compose.yml
17 | - copy: kool.yml
18 | - merge: codeigniter-scripts.yml
19 | dst: kool.yml
20 |
21 | - name: 'Customize your setup'
22 | actions:
23 | - recipe: pick-php
24 | - recipe: pick-db
25 | - recipe: pick-cache
26 | - recipe: pick-node-pkg-mgr
27 |
--------------------------------------------------------------------------------
/presets/laravel/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'PHP' ]
3 |
4 | name: 'Laravel'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Laravel Application
9 | actions:
10 | - recipe: create-laravel
11 |
12 | # Preset defines the workflow for installing this preset in the current working directory
13 | preset:
14 | - name: 'Copy basic config files'
15 | actions:
16 | - copy: docker-compose.yml
17 | - copy: kool.yml
18 | - copy: vite.config.js
19 | - merge: scripts/laravel.yml
20 | dst: kool.yml
21 |
22 | - name: 'Customize your setup'
23 | actions:
24 | - recipe: pick-php
25 | - recipe: pick-db
26 | - recipe: pick-cache
27 | - recipe: pick-laravel-node
28 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_destroy.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // DeployDestroy holds data and logic for consuming the "destroy" endpoint
4 | type DeployDestroy struct {
5 | Endpoint
6 | }
7 |
8 | // DeployDestroyResponse holds data from the "destroy" endpoint
9 | type DeployDestroyResponse struct {
10 | Environment struct {
11 | ID int `json:"id"`
12 | } `json:"environment"`
13 | }
14 |
15 | // NewDeployDestroy creates a new caller for Deploy API exec endpoint
16 | func NewDeployDestroy() (d *DeployDestroy) {
17 | d = &DeployDestroy{
18 | Endpoint: NewDefaultEndpoint("DELETE"),
19 | }
20 |
21 | d.SetPath("deploy/destroy")
22 |
23 | return
24 | }
25 |
26 | // Call performs the request to the endpoint
27 | func (s *DeployDestroy) Call() (resp *DeployDestroyResponse, err error) {
28 | resp = &DeployDestroyResponse{}
29 | s.SetResponseReceiver(resp)
30 | err = s.DoCall()
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-share.md:
--------------------------------------------------------------------------------
1 | ## kool share
2 |
3 | Live share your local environment on the Internet using an HTTP tunnel
4 |
5 | ```
6 | kool share
7 | ```
8 |
9 | ### Options
10 |
11 | ```
12 | -h, --help help for share
13 | --port uint The port from the target service that should be shared. If not provided, it will default to port 80.
14 | --service string The name of the local service container you want to share. (default "app")
15 | --subdomain string The subdomain used to generate your public https://subdomain.kool.live URL.
16 | ```
17 |
18 | ### Options inherited from parent commands
19 |
20 | ```
21 | --verbose Increases output verbosity
22 | -w, --working_dir string Changes the working directory for the command
23 | ```
24 |
25 | ### SEE ALSO
26 |
27 | * [kool](kool) - Cloud native environments made easy
28 |
29 |
--------------------------------------------------------------------------------
/presets/nodejs/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Javascript' ]
3 |
4 | name: 'Vanilla Node.js'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Node.js application
9 | actions:
10 | - scripts:
11 | - mkdir $CREATE_DIRECTORY
12 |
13 | # Preset defines the workflow for installing this preset in the current working directory
14 | preset:
15 | - name: 'Copy basic config files'
16 | actions:
17 | - copy: docker-compose.yml
18 | - copy: kool.yml
19 | - copy: app.js
20 | - merge: node-scripts.yml
21 | dst: kool.yml
22 |
23 | - name: 'Customize your setup'
24 | actions:
25 | - scripts:
26 | - kool docker kooldev/node:20 npm init -f
27 | - recipe: pick-node-pkg-mgr
28 |
--------------------------------------------------------------------------------
/presets/php/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'PHP' ]
3 |
4 | # Name of the preset
5 | name: 'Vanilla PHP (Nginx+PHP-FPM)'
6 |
7 | # Create defines the workflow for creating a new Project where this preset can then be installed
8 | create:
9 | - name: Creating new PHP Application
10 | actions:
11 | - scripts:
12 | - mkdir -p $CREATE_DIRECTORY/public
13 | - echo " $CREATE_DIRECTORY/public/index.php
14 |
15 | # Preset defines the workflow for installing this preset in the current working directory
16 | preset:
17 | - name: 'Copy basic config files'
18 | actions:
19 | - copy: docker-compose.yml
20 | - copy: kool.yml
21 | - merge: scripts/php.yml
22 | dst: kool.yml
23 |
24 | - name: 'Customize your setup'
25 | actions:
26 | - recipe: pick-php
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug, help wanted
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 |
12 | A clear and concise description of what the bug is. Please make sure you didn't find an existing issue with the same subject.
13 |
14 | **Kool version and environment**
15 |
16 | Please provide the version you are running (`kool -v`), which environment (OS, shell, docker, DinD), and also what environment variables you are using (`kool info`).
17 |
18 | **To Reproduce**
19 |
20 | Steps to reproduce the behavior:
21 |
22 | **Expected behavior**
23 |
24 | A clear and concise description of what you expected to happen.
25 |
26 | **Screenshots**
27 |
28 | If applicable, add screenshots to help explain your problem.
29 |
30 | **Additional context**
31 |
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release process
2 |
3 | For generating a new release we follow the steps:
4 |
5 | - Get latest `main` branch.
6 | - Make sure docs are updated - `kool run make-docs`
7 | - Make sure formatting is correct - `kool run fmt`
8 | - Make sure there are no syntax/stylistic errors - `kool run lint`
9 | - Make sure tests are passing - `kool run test`
10 | - Pick the version name you wanna build - `export BUILD_VERSION=0.0.0` (taking into consideration [Semantic Versioning rules for Major, Minor and Patch versions](https://semver.org/#summary))
11 | - Build all artifacts - `bash build_artifacts.sh`
12 | - Review if documentation is updated accordingly (docs/)
13 | - Upload to the existing draft release all the artifacts built at dist/ folder.
14 | - Publish the new version (which will create the tag as well)
15 | - In case of version bumping, check if we need to update `SECURITY.md` to show what version we currently support.
16 |
--------------------------------------------------------------------------------
/core/presets/parser_test.go:
--------------------------------------------------------------------------------
1 | package presets
2 |
3 | import (
4 | "embed"
5 | "testing"
6 |
7 | "github.com/leaanthony/debme"
8 | )
9 |
10 | //go:embed fixtures/*
11 | var fixtures embed.FS
12 |
13 | func TestPresetParser(t *testing.T) {
14 | root, _ := debme.FS(fixtures, "fixtures")
15 |
16 | SetSource(root)
17 |
18 | m := make(map[string]bool)
19 | p := NewParser()
20 |
21 | for _, t := range p.GetTags() {
22 | m[t] = true
23 | }
24 |
25 | if !m["foo"] {
26 | t.Errorf("missing tag 'foo'; %+v", p.GetTags())
27 | }
28 |
29 | if !p.Exists("foo") {
30 | t.Error("preset 'foo' should exist")
31 | } else if p.Exists("bar") {
32 | t.Error("preset 'bar' should not exist")
33 | }
34 |
35 | if len(p.GetPresets("foo")) != 1 {
36 | t.Error("should have found 1 preset with tag foo")
37 | }
38 |
39 | if len(p.GetPresets("bar")) != 0 {
40 | t.Error("should NOT have found any preset with tag bar")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_ping.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "fmt"
4 |
5 | // DeployPingResponse holds data returned from the deploy endpoint
6 | type DeployPingResponse struct {
7 | ID int `json:"id"`
8 | Status string `json:"status"`
9 | }
10 |
11 | // DeployPing consumes the API endpoint to create a new deployment
12 | type DeployPing struct {
13 | Endpoint
14 | }
15 |
16 | // NewDeployPing creates a new DeployStart instance
17 | func NewDeployPing(created *DeployCreateResponse) (c *DeployPing) {
18 | c = &DeployPing{
19 | Endpoint: NewDefaultEndpoint("POST"),
20 | }
21 |
22 | c.SetPath("deploy/ping")
23 | c.Body().Set("id", fmt.Sprintf("%d", created.Deploy.ID))
24 |
25 | return
26 | }
27 |
28 | // Run calls deploy/ping in the Kool Dev API
29 | func (c *DeployPing) Run() (resp *DeployPingResponse, err error) {
30 | resp = &DeployPingResponse{}
31 | c.SetResponseReceiver(resp)
32 | err = c.DoCall()
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | | Issue | # |
10 | | -----: | :-----: |
11 | | :beetle: Bug Fix | No/Yes |
12 | | :toolbox: Improvement | No/Yes |
13 | | :trophy: Feature | No/Yes |
14 | | :pencil: Refactor | No/Yes |
15 | | :x: Removed | No/Yes |
16 | | :open_book: Documentation | No/Yes |
17 | | :warning: Break Change | No/Yes |
18 |
19 | **Description**
20 |
21 |
22 | ---
23 |
24 | **Notes**
25 |
26 |
27 |
--------------------------------------------------------------------------------
/core/utils/loading.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io"
5 | "os"
6 | "time"
7 |
8 | "github.com/briandowns/spinner"
9 | )
10 |
11 | func MakeFastLoading(loadingMsg, loadedMsg string, w io.Writer) *spinner.Spinner {
12 | return makeLoading(loadingMsg, loadedMsg, 14, w)
13 | }
14 |
15 | func MakeSlowLoading(loadingMsg, loadedMsg string, w io.Writer) *spinner.Spinner {
16 | return makeLoading(loadingMsg, loadedMsg, 21, w)
17 | }
18 |
19 | func makeLoading(loadingMsg, loadedMsg string, charset int, w io.Writer) (s *spinner.Spinner) {
20 | s = spinner.New(
21 | spinner.CharSets[charset],
22 | 100*time.Millisecond,
23 | // spinner.WithColor("green"),
24 | spinner.WithFinalMSG(loadedMsg+"\n"),
25 | spinner.WithSuffix(" "+loadingMsg),
26 | )
27 | s.Prefix = " "
28 | s.HideCursor = true
29 | if fh, isFh := w.(*os.File); isFh {
30 | s.WriterFile = fh
31 | } else {
32 | s.Writer = w
33 | }
34 | s.Start()
35 | return
36 | }
37 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-start.md:
--------------------------------------------------------------------------------
1 | ## kool start
2 |
3 | Start service containers defined in docker-compose.yml
4 |
5 | ### Synopsis
6 |
7 | Start one or more specified [SERVICE] containers. If no [SERVICE] is provided,
8 | all containers are started. If the containers are already running, they are recreated.
9 |
10 | ```
11 | kool start [SERVICE...]
12 | ```
13 |
14 | ### Options
15 |
16 | ```
17 | -f, --foreground Start containers in foreground mode
18 | -h, --help help for start
19 | --profile string Specify a profile to enable
20 | -b, --rebuild Updates and builds service's images
21 | ```
22 |
23 | ### Options inherited from parent commands
24 |
25 | ```
26 | --verbose Increases output verbosity
27 | -w, --working_dir string Changes the working directory for the command
28 | ```
29 |
30 | ### SEE ALSO
31 |
32 | * [kool](kool) - Cloud native environments made easy
33 |
34 |
--------------------------------------------------------------------------------
/core/shell/prompt_input.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "github.com/AlecAivazis/survey/v2"
5 | "github.com/AlecAivazis/survey/v2/terminal"
6 | )
7 |
8 | // PromptInput contract that holds logic for prompt an input string
9 | type PromptInput interface {
10 | Input(string, string) (string, error)
11 | }
12 |
13 | // DefaultPromptInput holds data for prompting a select question
14 | type DefaultPromptInput struct{}
15 |
16 | // NewPromptInput creates a new prompt select
17 | func NewPromptInput() PromptInput {
18 | return &DefaultPromptInput{}
19 | }
20 |
21 | // Ask prompt to the user a select question
22 | func (p *DefaultPromptInput) Input(question string, defaultInput string) (input string, err error) {
23 | prompt := &survey.Input{
24 | Message: question,
25 | Default: defaultInput,
26 | }
27 | if err = survey.AskOne(prompt, &input); err != nil && err == terminal.InterruptErr {
28 | err = ErrUserCancelled
29 | }
30 | return
31 | }
32 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_start.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "fmt"
4 |
5 | // DeployStartResponse holds data returned from the deploy endpoint
6 | type DeployStartResponse struct {
7 | ID int `json:"id"`
8 | Status string `json:"status"`
9 | }
10 |
11 | // DeployStart consumes the API endpoint to create a new deployment
12 | type DeployStart struct {
13 | Endpoint
14 | }
15 |
16 | // NewDeployStart creates a new DeployStart instance
17 | func NewDeployStart(created *DeployCreateResponse) (c *DeployStart) {
18 | c = &DeployStart{
19 | Endpoint: NewDefaultEndpoint("POST"),
20 | }
21 |
22 | c.SetPath("deploy/start")
23 | c.Body().Set("id", fmt.Sprintf("%d", created.Deploy.ID))
24 |
25 | return
26 | }
27 |
28 | // Run calls deploy/create in the Kool Dev API
29 | func (c *DeployStart) Run() (resp *DeployStartResponse, err error) {
30 | resp = &DeployStartResponse{}
31 | c.SetResponseReceiver(resp)
32 | err = c.DoCall()
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_exec.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // DeployExec holds data and logic for consuming the "exec" endpoint
4 | type DeployExec struct {
5 | Endpoint
6 | }
7 |
8 | // DeployExecResponse holds data from the "exec" endpoint
9 | type DeployExecResponse struct {
10 | Server string `json:"server"`
11 | Namespace string `json:"namespace"`
12 | Path string `json:"path"`
13 | Token string `json:"token"`
14 | CA string `json:"ca.crt"`
15 | }
16 |
17 | // NewDeployExec creates a new caller for Deploy API exec endpoint
18 | func NewDeployExec() (e *DeployExec) {
19 | e = &DeployExec{
20 | Endpoint: NewDefaultEndpoint("POST"),
21 | }
22 |
23 | e.SetPath("deploy/exec")
24 |
25 | return e
26 | }
27 |
28 | // Call performs the request to the endpoint
29 | func (s *DeployExec) Call() (resp *DeployExecResponse, err error) {
30 | resp = &DeployExecResponse{}
31 | s.SetResponseReceiver(resp)
32 | err = s.DoCall()
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_status.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "fmt"
4 |
5 | // DeployCreate consumes the API endpoint to create a new deployment
6 | type DeployStatus struct {
7 | Endpoint
8 | }
9 |
10 | // DeployCreateResponse holds data returned from the deploy endpoint
11 | type DeployStatusResponse struct {
12 | ID int `json:"id"`
13 | Status string `json:"status"`
14 | }
15 |
16 | // NewDeployCreate creates a new DeployCreate instance
17 | func NewDeployStatus(created *DeployCreateResponse) (c *DeployStatus) {
18 | c = &DeployStatus{
19 | Endpoint: NewDefaultEndpoint("GET"),
20 | }
21 |
22 | c.SetPath(fmt.Sprintf("deploy/%d/status", created.Deploy.ID))
23 |
24 | return
25 | }
26 |
27 | // Run calls deploy/?/status in the Kool Dev API
28 | func (c *DeployStatus) Run() (resp *DeployStatusResponse, err error) {
29 | resp = &DeployStatusResponse{}
30 |
31 | c.SetResponseReceiver(resp)
32 |
33 | err = c.DoCall()
34 |
35 | return
36 | }
37 |
--------------------------------------------------------------------------------
/core/automate/recipes.go:
--------------------------------------------------------------------------------
1 | package automate
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "io/fs"
7 | "strings"
8 |
9 | "gopkg.in/yaml.v2"
10 | )
11 |
12 | type RecipeMetadata struct {
13 | Title string `yaml:"title"`
14 | Slug string
15 | }
16 |
17 | var recipesSource embed.FS
18 |
19 | func SetRecipesSource(src embed.FS) {
20 | recipesSource = src
21 | }
22 |
23 | func GetRecipes() (recipes []*RecipeMetadata, err error) {
24 | var (
25 | entries []fs.DirEntry
26 | raw []byte
27 | )
28 |
29 | if entries, err = recipesSource.ReadDir("recipes"); err != nil {
30 | return
31 | }
32 |
33 | for _, e := range entries {
34 | m := &RecipeMetadata{Slug: strings.ReplaceAll(e.Name(), ".yml", "")}
35 | if raw, err = recipesSource.ReadFile(fmt.Sprintf("recipes/%s", e.Name())); err != nil {
36 | return
37 | }
38 | if err = yaml.Unmarshal(raw, m); err != nil {
39 | recipes = nil
40 | return
41 | }
42 | recipes = append(recipes, m)
43 | }
44 | return
45 | }
46 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-logs.md:
--------------------------------------------------------------------------------
1 | ## kool logs
2 |
3 | Display log output from running service containers
4 |
5 | ### Synopsis
6 |
7 | Display log output from all running service containers,
8 | or one or more specified [SERVICE...] containers. Add a '-f' option to the
9 | the command to follow the log output (i.e. 'kool logs -f [SERVICE...]').
10 |
11 | ```
12 | kool logs [OPTIONS] [SERVICE...]
13 | ```
14 |
15 | ### Options
16 |
17 | ```
18 | -f, --follow Follow log output.
19 | -h, --help help for logs
20 | -t, --tail int Number of lines to show from the end of the logs for each container. A value equal to 0 will show all lines. (default 25)
21 | ```
22 |
23 | ### Options inherited from parent commands
24 |
25 | ```
26 | --verbose Increases output verbosity
27 | -w, --working_dir string Changes the working directory for the command
28 | ```
29 |
30 | ### SEE ALSO
31 |
32 | * [kool](kool) - Cloud native environments made easy
33 |
34 |
--------------------------------------------------------------------------------
/presets/nest+next/nest+next-docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | frontend:
3 | image: kooldev/node:20
4 | command: ["npm", "run", "dev", "--", "-p", "${KOOL_FRONTEND_PORT:-80}"]
5 | ports:
6 | - "${KOOL_FRONTEND_PORT:-80}:${KOOL_FRONTEND_PORT:-80}"
7 | environment:
8 | ASUSER: "${KOOL_ASUSER:-0}"
9 | UID: "${UID:-0}"
10 | KOOL_FRONTEND_PORT: "${KOOL_FRONTEND_PORT:-80}"
11 | volumes:
12 | - .:/app:delegated
13 | working_dir: /app/frontend
14 | networks:
15 | - kool_local
16 | - kool_global
17 | backend:
18 | image: kooldev/node:20
19 | command: ["npm", "run", "start:dev"]
20 | ports:
21 | - "${KOOL_BACKEND_PORT:-81}:${KOOL_BACKEND_PORT:-81}"
22 | environment:
23 | ASUSER: "${KOOL_ASUSER:-0}"
24 | UID: "${UID:-0}"
25 | KOOL_BACKEND_PORT: "${KOOL_BACKEND_PORT:-81}"
26 | volumes:
27 | - ./backend:/app:delegated
28 | networks:
29 | - kool_local
30 | - kool_global
31 |
--------------------------------------------------------------------------------
/core/shell/terminal_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "io"
5 | "os/exec"
6 | "testing"
7 |
8 | "github.com/creack/pty"
9 | )
10 |
11 | func TestPipeIsTerminal(t *testing.T) {
12 | c1 := exec.Command("echo", "testing")
13 | c2 := exec.Command("cat")
14 |
15 | r, w := io.Pipe()
16 |
17 | c1.Stdout = w
18 | c2.Stdin = r
19 |
20 | _ = c1.Start()
21 | _ = c2.Start()
22 | go func() {
23 | defer w.Close()
24 |
25 | _ = c1.Wait()
26 | }()
27 | _ = c2.Wait()
28 |
29 | terminalChecker := NewTerminalChecker()
30 |
31 | if terminalChecker.IsTerminal(c1.Stdin, c1.Stdout) {
32 | t.Error("unexpected tty terminal on piped command")
33 | }
34 | }
35 |
36 | func TestPtyIsTerminal(t *testing.T) {
37 | c := exec.Command("echo", "testing")
38 | f, err := pty.Start(c)
39 |
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 |
44 | terminalChecker := NewTerminalChecker()
45 |
46 | if !terminalChecker.IsTerminal(f) {
47 | t.Error("expecting tty terminal on command")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/presets/nestjs/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Typescript', 'Javascript' ]
3 |
4 | name: 'NestJS'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new NestJS Application
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/node:20
12 | - recipe: create-nestjs
13 |
14 | # Preset defines the workflow for installing this preset in the current working directory
15 | preset:
16 | - name: 'Copy basic config files'
17 | actions:
18 | - copy: docker-compose.yml
19 | - copy: kool.yml
20 | - merge: docker-compose.nestjs.yml
21 | dst: docker-compose.yml
22 | - merge: kool.nestjs.yml
23 | dst: kool.yml
24 | - copy: env.dist
25 | dst: .env.dist
26 |
27 | - name: 'Customize your setup'
28 | actions:
29 | - recipe: pick-db
30 | - recipe: pick-cache
31 |
--------------------------------------------------------------------------------
/commands/fake_kool_service.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "kool-dev/kool/core/shell"
5 | )
6 |
7 | // FakeKoolService is a mock to be used on testing/replacement for KoolService interface
8 | type FakeKoolService struct {
9 | shell *shell.FakeShell
10 |
11 | ArgsExecute []string
12 | CalledExecute bool
13 | MockExecuteErr error
14 | }
15 |
16 | func newFakeKoolService() *FakeKoolService {
17 | return &FakeKoolService{
18 | &shell.FakeShell{MockIsTerminal: true}, nil, false, nil,
19 | }
20 | }
21 |
22 | // Shell returns the stored (possibly fake) Shell implementation
23 | func (f *FakeKoolService) Shell() shell.Shell {
24 | if f.shell == nil {
25 | // workaround interface nil-nil requirement
26 | // for type/value
27 | return nil
28 | }
29 |
30 | return f.shell
31 | }
32 |
33 | // Execute mocks the function for testing
34 | func (f *FakeKoolService) Execute(args []string) (err error) {
35 | f.ArgsExecute = args
36 | f.CalledExecute = true
37 | err = f.MockExecuteErr
38 | return
39 | }
40 |
--------------------------------------------------------------------------------
/presets/wordpress/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'PHP' ]
3 |
4 | name: 'Wordpress'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Wordpress Application
9 | actions:
10 | - scripts:
11 | - mkdir $CREATE_DIRECTORY
12 |
13 | # Preset defines the workflow for installing this preset in the current working directory
14 | preset:
15 | - name: 'Copy basic config files'
16 | actions:
17 | - copy: docker-compose.yml
18 | - copy: kool.yml
19 | - copy: environment
20 | dst: .env
21 | # default PHP 8 only
22 | - merge: app/wordpress80.yml
23 | dst: docker-compose.yml
24 | - merge: scripts/wordpress.yml
25 | dst: kool.yml
26 |
27 | - name: 'Customize your setup'
28 | actions:
29 | - recipe: pick-db
30 | - recipe: pick-cache
31 | - recipe: pick-node-pkg-mgr
32 |
--------------------------------------------------------------------------------
/services/cloud/api/errors_test.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestDefinedErrors(t *testing.T) {
8 | errs := map[string]error{
9 | "ErrBadAPIServer": ErrBadAPIServer,
10 | "ErrDeployFailed": ErrDeployFailed,
11 | "ErrUnauthorized": ErrUnauthorized,
12 | "ErrPayloadValidation": ErrPayloadValidation,
13 | "ErrBadResponseStatus": ErrBadResponseStatus,
14 | "ErrUnexpectedResponse": ErrUnexpectedResponse,
15 | "ErrMissingToken": ErrMissingToken,
16 | }
17 |
18 | for e, v := range errs {
19 | if v == nil {
20 | t.Errorf("default error not defined: %s", e)
21 | }
22 | }
23 | }
24 |
25 | func TestApiErr(t *testing.T) {
26 | err := &ErrAPI{100, "message", nil}
27 |
28 | if err.Error() != "\n100 - message\n" {
29 | t.Errorf("unexpected error message: %s", err.Error())
30 | }
31 |
32 | err.Errors = map[string]interface{}{
33 | "foo": []interface{}{"bar"},
34 | }
35 |
36 | if err.Error() != "\n100 - message\n\n\tfoo > bar\n" {
37 | t.Errorf("unexpected error message: %s", err.Error())
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/shell/fake_prompt_select.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | // FakePromptSelect holds data for fake prompt select behavior
4 | type FakePromptSelect struct {
5 | CalledAsk bool
6 | MockAnswer map[string]string
7 | MockError map[string]error
8 |
9 | CalledConfirm []*struct {
10 | question string
11 | args []any
12 | }
13 | MockConfirm map[string]bool
14 | MockConfirmError map[string]error
15 | }
16 |
17 | // Ask mocked behavior for testing prompting a select question
18 | func (f *FakePromptSelect) Ask(question string, options []string) (answer string, err error) {
19 | f.CalledAsk = true
20 | answer = f.MockAnswer[question]
21 | err = f.MockError[question]
22 | return
23 | }
24 |
25 | // Confirm mocked behavior for testing prompting a confirm question
26 | func (f *FakePromptSelect) Confirm(question string, args ...any) (confirmed bool, err error) {
27 | f.CalledConfirm = append(f.CalledConfirm, &struct {
28 | question string
29 | args []any
30 | }{question, args})
31 | confirmed = f.MockConfirm[question]
32 | err = f.MockConfirmError[question]
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-deploy.md:
--------------------------------------------------------------------------------
1 | ## kool cloud deploy
2 |
3 | Deploy a local application to a Kool.dev Cloud environment
4 |
5 | ```
6 | kool cloud deploy
7 | ```
8 |
9 | ### Options
10 |
11 | ```
12 | --domain-extra stringArray List of extra domain aliases
13 | -h, --help help for deploy
14 | --platform string Platform for docker build (default: linux/amd64) (default "linux/amd64")
15 | --timeout uint Timeout in minutes for waiting the deployment to finish
16 | --www-redirect Redirect www to non-www domain
17 | ```
18 |
19 | ### Options inherited from parent commands
20 |
21 | ```
22 | --domain string Environment domain name to deploy to
23 | --token string Token to authenticate with Kool.dev Cloud API
24 | --verbose Increases output verbosity
25 | -w, --working_dir string Changes the working directory for the command
26 | ```
27 |
28 | ### SEE ALSO
29 |
30 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
31 |
32 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-exec.md:
--------------------------------------------------------------------------------
1 | ## kool cloud exec
2 |
3 | Execute a command inside a running service container deployed to Kool.dev Cloud
4 |
5 | ### Synopsis
6 |
7 | After deploying an application to Kool.dev Cloud using 'kool deploy',
8 | execute a COMMAND inside the specified SERVICE container (similar to an SSH session).
9 | Must use a KOOL_API_TOKEN environment variable for authentication.
10 |
11 | ```
12 | kool cloud exec SERVICE [COMMAND] [--] [ARG...]
13 | ```
14 |
15 | ### Options
16 |
17 | ```
18 | -c, --container string Container target. (default "default")
19 | -h, --help help for exec
20 | ```
21 |
22 | ### Options inherited from parent commands
23 |
24 | ```
25 | --domain string Environment domain name to deploy to
26 | --token string Token to authenticate with Kool.dev Cloud API
27 | --verbose Increases output verbosity
28 | -w, --working_dir string Changes the working directory for the command
29 | ```
30 |
31 | ### SEE ALSO
32 |
33 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
34 |
35 |
--------------------------------------------------------------------------------
/commands/kool_service.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "kool-dev/kool/core/shell"
5 | )
6 |
7 | // KoolService interface holds the contract for a
8 | // general service which implements some bigger chunk
9 | // of logic usually linked to a command.
10 | type KoolService interface {
11 | Execute([]string) error
12 |
13 | Shell() shell.Shell
14 | }
15 |
16 | // DefaultKoolService holds handlers and functions shared by all
17 | // services, meant to be used on commands when executing the services.
18 | type DefaultKoolService struct {
19 | shell shell.Shell
20 | }
21 |
22 | func newDefaultKoolService() *DefaultKoolService {
23 | return &DefaultKoolService{
24 | shell.NewShell(),
25 | }
26 | }
27 |
28 | // Shell exposes the attached shell implementation
29 | func (k *DefaultKoolService) Shell() shell.Shell {
30 | return k.shell
31 | }
32 |
33 | // Fake changes the internal dependencies (most notably shell)
34 | // to be the fake conterpart of the real implementation.
35 | // Meant for tests only.
36 | func (k *DefaultKoolService) Fake() *DefaultKoolService {
37 | k.shell = &shell.FakeShell{MockIsTerminal: true}
38 | return k
39 | }
40 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-docker.md:
--------------------------------------------------------------------------------
1 | ## kool docker
2 |
3 | Create a new container (a powered up 'docker run')
4 |
5 | ### Synopsis
6 |
7 | A helper for 'docker run'. Any [OPTIONS] added before the
8 | IMAGE name will be used by 'docker run' itself (i.e. --env='VAR=VALUE').
9 | Add an optional [COMMAND] to execute on the IMAGE, and use [--] after
10 | the [COMMAND] to provide optional arguments required by the COMMAND.
11 |
12 | ```
13 | kool docker [OPTIONS] IMAGE [COMMAND] [--] [ARG...]
14 | ```
15 |
16 | ### Options
17 |
18 | ```
19 | -e, --env stringArray Environment variables.
20 | -h, --help help for docker
21 | -n, --network stringArray Connect a container to a network.
22 | -p, --publish stringArray Publish a container's port(s) to the host.
23 | -v, --volume stringArray Bind mount a volume.
24 | ```
25 |
26 | ### Options inherited from parent commands
27 |
28 | ```
29 | --verbose Increases output verbosity
30 | -w, --working_dir string Changes the working directory for the command
31 | ```
32 |
33 | ### SEE ALSO
34 |
35 | * [kool](kool) - Cloud native environments made easy
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Kool Dev Sistemas de Informacao LTDA
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/presets/expressjs/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Javascript' ]
3 |
4 | name: 'ExpressJS'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Express Application
9 | actions:
10 | - scripts:
11 | - mkdir $CREATE_DIRECTORY
12 |
13 | # Preset defines the workflow for installing this preset in the current working directory
14 | preset:
15 | - name: 'Copy basic config files'
16 | actions:
17 | - copy: docker-compose.yml
18 | - copy: kool.yml
19 | - copy: app.js
20 | - copy: package.json
21 |
22 | - name: 'Customize your setup'
23 | actions:
24 | # define package manager
25 | - prompt: Which javascript package manager do you want to use?
26 | default: 'npm'
27 | options:
28 | - name: 'npm'
29 | actions:
30 | - merge: scripts/npm-expressjs.yml
31 | dst: kool.yml
32 | - name: 'yarn'
33 | actions:
34 | - merge: scripts/yarn-expressjs.yml
35 | dst: kool.yml
36 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 | name: go test
7 | jobs:
8 | test:
9 | strategy:
10 | matrix:
11 | go-version: [1.21.x]
12 | os: [ubuntu-latest, macos-latest]
13 | runs-on: ${{ matrix.os }}
14 | steps:
15 | - name: Install Go
16 | uses: actions/setup-go@v2
17 | with:
18 | go-version: ${{ matrix.go-version }}
19 | - name: Checkout code
20 | uses: actions/checkout@v4
21 | - name: Caching
22 | uses: actions/cache@v4
23 | with:
24 | path: ~/go/pkg
25 | key: ${{ runner.os }}-kool-${{ hashFiles('**/go.sum') }}
26 | restore-keys: |
27 | ${{ runner.os }}-kool-
28 | - name: Download dependencies
29 | run: |
30 | go mod download
31 | go install
32 | - name: Test
33 | if: matrix.os == 'macos-latest'
34 | run: go test ./...
35 | - name: Test with code coverage
36 | if: matrix.os == 'ubuntu-latest'
37 | run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
38 | - name: Code Coverage
39 | if: matrix.os == 'ubuntu-latest'
40 | run: bash <(curl -s https://codecov.io/bash)
41 |
--------------------------------------------------------------------------------
/core/network/network.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "fmt"
5 | "kool-dev/kool/core/builder"
6 | "kool-dev/kool/core/shell"
7 | )
8 |
9 | // Handler defines network handler
10 | type Handler interface {
11 | HandleGlobalNetwork(string) error
12 | }
13 |
14 | // DefaultHandler holds docker network command
15 | type DefaultHandler struct {
16 | CheckNetworkCmd builder.Command
17 | CreateNetworkCmd builder.Command
18 | shell shell.Shell
19 | }
20 |
21 | // NewHandler initializes handler
22 | func NewHandler(s shell.Shell) *DefaultHandler {
23 | var checkNetCmd, createNetCmd *builder.DefaultCommand
24 |
25 | checkNetCmd = builder.NewCommand("docker", "network", "ls", "-q", "-f")
26 | createNetCmd = builder.NewCommand("docker", "network", "create", "--attachable")
27 |
28 | return &DefaultHandler{checkNetCmd, createNetCmd, s}
29 | }
30 |
31 | // HandleGlobalNetwork handles global network
32 | func (h *DefaultHandler) HandleGlobalNetwork(networkName string) error {
33 | if networkID, err := h.shell.Exec(h.CheckNetworkCmd, fmt.Sprintf("NAME=^%s$", networkName)); err != nil || networkID != "" {
34 | return err
35 | }
36 |
37 | return h.shell.Interactive(h.CreateNetworkCmd, networkName)
38 | }
39 |
--------------------------------------------------------------------------------
/docs/02-Kool-Cloud/17-Custom-Resources.md:
--------------------------------------------------------------------------------
1 | # Custom Resources
2 |
3 | Most real-world web applications will go beyond just computing containers with your code — there's a vast myriad of other resources you will eventually need, like dedicated database services (i.e., RDS), replication, object storage (i.e., S3), CDN (i.e., CloudFront), just to name a few.
4 |
5 | The Kool.dev Cloud team is ready to set it up for you when needed — providing you the Infrastructure-as-Code to live close to your application. While this is not built into our panel for self-service usage yet, you can just [contact us via email with your needs](mailto:contact@kool.dev), and we will work through your request.
6 |
7 | ## Bring Your Own Cloud
8 |
9 | Important to notice - current cloud scenario is flexible enough where you can use Kool.dev Shared cloud, or hire a dedicated stack fully managed by Kool.dev, or even better - if you want total control and access we can set up the Kool.dev Cloud cluster and resources on your own AWS account. [Contact us](mailto:contact@kool.dev) to learn more about this possibility.
10 |
11 | ## Consulting
12 |
13 | If you lack the DevOps expertise to determine your needs, we also have consulting services available to best serve you.
14 |
--------------------------------------------------------------------------------
/services/yamler/output.go:
--------------------------------------------------------------------------------
1 | package yamler
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "gopkg.in/yaml.v3"
10 | )
11 |
12 | type OutputWritter interface {
13 | WriteYAML(string, *yaml.Node) error
14 | }
15 |
16 | type DefaultOutputWritter struct{}
17 |
18 | func (o *DefaultOutputWritter) WriteYAML(filePath string, document *yaml.Node) (err error) {
19 | var (
20 | buff = new(bytes.Buffer)
21 | encoder *yaml.Encoder
22 | file *os.File
23 | )
24 |
25 | if document.Kind != yaml.DocumentNode {
26 | err = fmt.Errorf("unexpected yaml.Node; expected document (1), but got %d", document.Kind)
27 | return
28 | }
29 |
30 | encoder = yaml.NewEncoder(buff)
31 | encoder.SetIndent(2)
32 |
33 | if err = encoder.Encode(document); err != nil {
34 | return
35 | }
36 |
37 | if err = encoder.Close(); err != nil {
38 | return
39 | }
40 |
41 | if file, err = os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm); err != nil {
42 | return
43 | }
44 |
45 | if _, err = io.Copy(file, buff); err != nil {
46 | return
47 | }
48 |
49 | buff.Reset()
50 | buff = nil
51 |
52 | if err = file.Sync(); err != nil {
53 | return
54 | }
55 |
56 | err = file.Close()
57 | return
58 | }
59 |
--------------------------------------------------------------------------------
/core/shell/fake_prompt_select_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestFakePromptSelect(t *testing.T) {
9 | f := &FakePromptSelect{}
10 | f.MockAnswer = make(map[string]string)
11 | f.MockAnswer["question"] = "answer"
12 |
13 | answer, err := f.Ask("question", []string{"option"})
14 |
15 | if err != nil {
16 | t.Errorf("unexpected error on Ask: %v", err)
17 | }
18 |
19 | if answer != "answer" {
20 | t.Errorf("expecting answer 'answer', got %s", answer)
21 | }
22 |
23 | f.MockError = make(map[string]error)
24 | f.MockError["question"] = errors.New("error")
25 |
26 | _, err = f.Ask("question", []string{"option"})
27 |
28 | if err == nil {
29 | t.Errorf("should throw an error on Ask")
30 | }
31 |
32 | f.MockConfirm = make(map[string]bool)
33 | f.MockConfirm["question"] = true
34 | f.MockConfirmError = make(map[string]error)
35 | f.MockConfirmError["question"] = errors.New("error")
36 |
37 | if confirmed, err := f.Confirm("question"); err == nil || err.Error() != "error" || !confirmed {
38 | t.Errorf("bad return from mocked Confirm")
39 | } else if len(f.CalledConfirm) == 0 || f.CalledConfirm[0].question != "question" {
40 | t.Errorf("bad control of calls to mocked Confirm")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/core/parser/errors_test.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestSimilars(t *testing.T) {
9 | err := &ErrPossibleTypo{[]string{"cmd1", "cmd2"}}
10 |
11 | similars := err.Similars()
12 |
13 | if len(similars) != 2 {
14 | t.Error("failed to get the typo error similars")
15 | }
16 |
17 | if similars[0] != "cmd1" || similars[1] != "cmd2" {
18 | t.Error("failed to get the typo error similars")
19 | }
20 | }
21 |
22 | func TestSetSimilars(t *testing.T) {
23 | err := &ErrPossibleTypo{[]string{}}
24 |
25 | err.SetSimilars([]string{"cmd1", "cmd2"})
26 |
27 | similars := err.Similars()
28 |
29 | if len(similars) != 2 {
30 | t.Error("failed to get the typo error similars")
31 | }
32 |
33 | if similars[0] != "cmd1" || similars[1] != "cmd2" {
34 | t.Error("failed to get the typo error similars")
35 | }
36 | }
37 |
38 | func TestIsPossibleTypoError(t *testing.T) {
39 | err := &ErrPossibleTypo{[]string{}}
40 |
41 | isTypoError := IsPossibleTypoError(err)
42 |
43 | if !isTypoError {
44 | t.Error("failed to assert that error was a typo one")
45 | }
46 |
47 | isTypoError = IsPossibleTypoError(errors.New("error"))
48 |
49 | if isTypoError {
50 | t.Error("failed to assert that error was not a typo one")
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud-logs.md:
--------------------------------------------------------------------------------
1 | ## kool cloud logs
2 |
3 | See the logs of running service container deployed to Kool.dev Cloud
4 |
5 | ### Synopsis
6 |
7 | After deploying an application to Kool.dev Cloud using 'kool deploy',
8 | you can see the logs from the specified SERVICE container.
9 | Must use a KOOL_API_TOKEN environment variable for authentication.
10 |
11 | ```
12 | kool cloud logs [OPTIONS] SERVICE
13 | ```
14 |
15 | ### Options
16 |
17 | ```
18 | -c, --container string Container target. (default "default")
19 | -f, --follow Follow log output.
20 | -h, --help help for logs
21 | -t, --tail int Number of lines to show from the end of the logs for each container. A value equal to 0 will show all lines. (default 25)
22 | ```
23 |
24 | ### Options inherited from parent commands
25 |
26 | ```
27 | --domain string Environment domain name to deploy to
28 | --token string Token to authenticate with Kool.dev Cloud API
29 | --verbose Increases output verbosity
30 | -w, --working_dir string Changes the working directory for the command
31 | ```
32 |
33 | ### SEE ALSO
34 |
35 | * [kool cloud](kool_cloud) - Interact with Kool.dev Cloud and manage your deployments.
36 |
37 |
--------------------------------------------------------------------------------
/services/checker/errors.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import "errors"
4 |
5 | // ErrDockerNotFound happens when docker is not installed
6 | var ErrDockerNotFound = errors.New("docker doesn't seem to be installed, install it first and retry")
7 |
8 | // IsDockerNotFoundError tells whether the given error is checker.ErrDockerNotFound
9 | func IsDockerNotFoundError(err error) bool {
10 | return err.Error() == ErrDockerNotFound.Error()
11 | }
12 |
13 | // ErrDockerComposeNotFound happens when docker compose V2 is not installed
14 | var ErrDockerComposeNotFound = errors.New("docker compose V2 doesn't seem to be installed, upgrade your docker installation and retry")
15 |
16 | // IsDockerComposeNotFoundError tells whether the given error is checker.ErrDockerComposeNotFound
17 | func IsDockerComposeNotFoundError(err error) bool {
18 | return err.Error() == ErrDockerComposeNotFound.Error()
19 | }
20 |
21 | // ErrDockerNotRunning happens when docker daemon is not running
22 | var ErrDockerNotRunning = errors.New("docker daemon doesn't seem to be running, run it first and retry")
23 |
24 | // IsDockerNotRunningError tells whether the given error is checker.ErrDockerNotRunning
25 | func IsDockerNotRunningError(err error) bool {
26 | return err.Error() == ErrDockerNotRunning.Error()
27 | }
28 |
--------------------------------------------------------------------------------
/services/compose/fake_parser_test.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestFakeParser(t *testing.T) {
9 | var err error
10 |
11 | f := &FakeParser{}
12 |
13 | f.MockParseError = errors.New("parse error")
14 | err = f.Parse("compose")
15 |
16 | if err == nil {
17 | t.Error("expecting error on Parse, got none")
18 | } else if err.Error() != "parse error" {
19 | t.Errorf("expecting error 'parse error' on Parse, got %v", err)
20 | }
21 |
22 | if val, ok := f.CalledParse["compose"]; !ok || !val {
23 | t.Error("failed calling Parse")
24 | }
25 |
26 | f.SetService("service", "content")
27 |
28 | if val, ok := f.CalledSetService["service"]; !ok || !val {
29 | t.Error("failed calling SetService")
30 | }
31 |
32 | f.SetVolume("volume")
33 |
34 | if val, ok := f.CalledSetVolume["volume"]; !ok || !val {
35 | t.Error("failed calling SetVolume")
36 | }
37 |
38 | f.MockStringError = errors.New("string error")
39 |
40 | _, err = f.String()
41 |
42 | if err == nil {
43 | t.Error("expecting error on String, got none")
44 | } else if err.Error() != "string error" {
45 | t.Errorf("expecting error 'string error' on String, got %v", err)
46 | }
47 |
48 | if !f.CalledString {
49 | t.Error("failed calling String")
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/services/checker/checker.go:
--------------------------------------------------------------------------------
1 | package checker
2 |
3 | import (
4 | "kool-dev/kool/core/builder"
5 | "kool-dev/kool/core/shell"
6 | "strings"
7 | )
8 |
9 | // Checker defines the check kool dependencies method
10 | type Checker interface {
11 | Check() error
12 | }
13 |
14 | // DefaultChecker holds commands to be checked.
15 | type DefaultChecker struct {
16 | dockerCmd builder.Command
17 | dockerComposeCmd builder.Command
18 | shell shell.Shell
19 | }
20 |
21 | // NewChecker initializes checker
22 | func NewChecker(s shell.Shell) *DefaultChecker {
23 | return &DefaultChecker{
24 | builder.NewCommand("docker", "info"),
25 | builder.NewCommand("docker", "compose", "ps"),
26 | s,
27 | }
28 | }
29 |
30 | // Check checks kool dependencies
31 | func (c *DefaultChecker) Check() error {
32 | if err := c.shell.LookPath(c.dockerCmd); err != nil {
33 | return ErrDockerNotFound
34 | }
35 |
36 | if _, err := c.shell.Exec(c.dockerComposeCmd); err != nil {
37 | if strings.Contains(strings.ToLower(err.Error()), "is not a docker command") {
38 | return ErrDockerComposeNotFound
39 | }
40 | // anything else, raise the original error
41 | return err
42 | }
43 |
44 | if _, err := c.shell.Exec(c.dockerCmd); err != nil {
45 | return ErrDockerNotRunning
46 | }
47 |
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/presets/nest+next/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Typescript' ]
3 |
4 | name: 'NestJS + NextJS (monorepo)'
5 |
6 | # Create defines the workflow for creating a new Project
7 | # where this preset can then be installed
8 | create:
9 | - name: Creating new NestJS Application
10 | actions:
11 | - scripts:
12 | - docker pull -q kooldev/node:20
13 | - mkdir $CREATE_DIRECTORY
14 | - kool docker kooldev/node:20 npx -y @nestjs/cli new -l Typescript -p npm --skip-git $CREATE_DIRECTORY/backend
15 | - kool docker kooldev/node:20 npx -y create-next-app@latest --ts --use-npm $CREATE_DIRECTORY/frontend
16 |
17 | preset:
18 | - name: 'Copy basic config files'
19 | actions:
20 | - copy: docker-compose.yml
21 | - copy: kool.yml
22 | - merge: nest+next-docker-compose.yml
23 | dst: docker-compose.yml
24 | - merge: nest+next-kool.yml
25 | dst: kool.yml
26 | - recipe: pick-db
27 | - recipe: pick-cache
28 | - copy: env.dist
29 | dst: .env.dist
30 | - copy: gitignore
31 | dst: .gitignore
32 | - scripts:
33 | - kool docker kooldev/node:20 sed -i 's/app.listen(3000)/app.listen(process.env.KOOL_BACKEND_PORT)/' backend/src/main.ts
34 |
--------------------------------------------------------------------------------
/commands/kool_upgrade_available_checker.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "kool-dev/kool/services/updater"
5 |
6 | "time"
7 | )
8 |
9 | // UpdateAwareService holds functions to implement the checker to see if theres a new version available
10 | type UpdateAwareService struct {
11 | KoolService
12 |
13 | updater updater.Updater
14 | skip bool
15 | }
16 |
17 | // CheckNewVersion wraps the service with checker logic
18 | func CheckNewVersion(service KoolService, updater updater.Updater, skip bool) *UpdateAwareService {
19 | return &UpdateAwareService{
20 | service,
21 | updater,
22 | skip,
23 | }
24 | }
25 |
26 | // Execute runs the check logic and proxies to original service
27 | func (u *UpdateAwareService) Execute(args []string) (err error) {
28 | if u.skip || !u.KoolService.Shell().IsTerminal() {
29 | err = u.KoolService.Execute(args)
30 | return
31 | }
32 |
33 | ch := make(chan bool)
34 |
35 | go u.updater.CheckForUpdates(u.updater.GetCurrentVersion(), ch)
36 |
37 | if err = u.KoolService.Execute(args); err != nil {
38 | return err
39 | }
40 |
41 | select {
42 | case update := <-ch:
43 | if update {
44 | defer u.KoolService.Shell().Warning("There's a new version available! Run kool self-update to upgrade!")
45 | }
46 | case <-time.After(time.Second):
47 | break
48 | }
49 |
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/core/builder/fake_command_test.go:
--------------------------------------------------------------------------------
1 | package builder
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestFakeCommand(t *testing.T) {
9 | f := &FakeCommand{}
10 |
11 | f.AppendArgs("arg1", "arg2")
12 |
13 | if !f.CalledAppendArgs || f.ArgsAppend == nil || f.ArgsAppend[0] != "arg1" || f.ArgsAppend[1] != "arg2" {
14 | t.Errorf("failed to use mocked AppendArgs function on FakeCommand")
15 | }
16 |
17 | _ = f.String()
18 |
19 | if !f.CalledString {
20 | t.Errorf("failed to use mocked String function on FakeCommand")
21 | }
22 |
23 | f.MockCmd = "cmd"
24 |
25 | if cmd := f.Cmd(); !f.CalledCmd || cmd != "cmd" {
26 | t.Errorf("failed to use mocked Cmd function on FakeCommand")
27 | }
28 |
29 | if args := f.Args(); !f.CalledArgs || len(args) != 2 || args[0] != "arg1" || args[1] != "arg2" {
30 | t.Errorf("failed to use mocked Args function on FakeCommand")
31 | }
32 |
33 | f.MockError = errors.New("parse error")
34 |
35 | err := f.Parse("echo x1")
36 |
37 | if !f.CalledParseCommand || err == nil || err.Error() != "parse error" {
38 | t.Errorf("failed to use mocked Parse function on FakeCommand")
39 | }
40 | }
41 |
42 | func TestFakeCopy(t *testing.T) {
43 | f := &FakeCommand{}
44 |
45 | cp := f.Copy()
46 |
47 | if ptr, ok := cp.(*FakeCommand); !ok || ptr != f {
48 | t.Error("unexpected fake copy")
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/shell/table_writer.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/jedib0t/go-pretty/v6/table"
7 | )
8 |
9 | // DefaultTableWriter holds table output writer
10 | type DefaultTableWriter struct {
11 | w table.Writer
12 | }
13 |
14 | // TableWriter holds table output writer logic
15 | type TableWriter interface {
16 | SetWriter(io.Writer)
17 | AppendHeader(...interface{})
18 | AppendRow(...interface{})
19 | Render()
20 | SortBy(int)
21 | }
22 |
23 | // NewTableWriter creates a new table writer
24 | func NewTableWriter() TableWriter {
25 | return &DefaultTableWriter{table.NewWriter()}
26 | }
27 |
28 | // SetWriter set table output writer
29 | func (t *DefaultTableWriter) SetWriter(w io.Writer) {
30 | t.w.SetOutputMirror(w)
31 | }
32 |
33 | // AppendHeader append header columns to table
34 | func (t *DefaultTableWriter) AppendHeader(columns ...interface{}) {
35 | t.w.AppendHeader(columns)
36 | }
37 |
38 | // AppendRow append row columns to table
39 | func (t *DefaultTableWriter) AppendRow(columns ...interface{}) {
40 | t.w.AppendRow(columns)
41 | }
42 |
43 | // Render render the table
44 | func (t *DefaultTableWriter) Render() {
45 | t.w.Render()
46 | }
47 |
48 | // SortBy sort table by column
49 | func (t *DefaultTableWriter) SortBy(column int) {
50 | t.w.SortBy([]table.SortBy{{Number: column, Mode: table.Asc}})
51 | }
52 |
--------------------------------------------------------------------------------
/services/compose/fake_parser.go:
--------------------------------------------------------------------------------
1 | package compose
2 |
3 | // FakeParser implements all fake behaviors for using parser in tests.
4 | type FakeParser struct {
5 | CalledParse map[string]bool
6 | CalledSetService map[string]bool
7 | CalledSetVolume map[string]bool
8 | CalledString bool
9 | MockParseError error
10 | MockStringError error
11 | }
12 |
13 | // Parse implements fake Parse behavior
14 | func (f *FakeParser) Parse(content string) (err error) {
15 | if f.CalledParse == nil {
16 | f.CalledParse = make(map[string]bool)
17 | }
18 |
19 | f.CalledParse[content] = true
20 | err = f.MockParseError
21 | return
22 | }
23 |
24 | // SetService implements fake SetService behavior
25 | func (f *FakeParser) SetService(service string, content interface{}) {
26 | if f.CalledSetService == nil {
27 | f.CalledSetService = make(map[string]bool)
28 | }
29 |
30 | f.CalledSetService[service] = true
31 | }
32 |
33 | // SetVolume implements fake SetVolume behavior
34 | func (f *FakeParser) SetVolume(volume string) {
35 | if f.CalledSetVolume == nil {
36 | f.CalledSetVolume = make(map[string]bool)
37 | }
38 |
39 | f.CalledSetVolume[volume] = true
40 | }
41 |
42 | // String implements fake String behavior
43 | func (f *FakeParser) String() (content string, err error) {
44 | f.CalledString = true
45 | err = f.MockStringError
46 | return
47 | }
48 |
--------------------------------------------------------------------------------
/core/shell/fake_table_writer_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "io"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestFakeTableWriter(t *testing.T) {
10 | f := &FakeTableWriter{}
11 |
12 | f.SetWriter(io.Discard)
13 |
14 | if !f.CalledSetWriter {
15 | t.Errorf("failed to mock method SetWriter on FakeTableWriter")
16 | }
17 |
18 | f.AppendHeader("header")
19 |
20 | if !f.CalledAppendHeader || len(f.Headers) != 1 || len(f.Headers[0]) != 1 || f.Headers[0][0] != "header" {
21 | t.Errorf("failed to mock method AppendHeader on FakeTableWriter")
22 | }
23 |
24 | f.AppendRow("row")
25 |
26 | if !f.CalledAppendRow || len(f.Rows) != 1 || len(f.Rows[0]) != 1 || f.Rows[0][0] != "row" {
27 | t.Errorf("failed to mock method AppendRow on FakeTableWriter")
28 | }
29 |
30 | f.Render()
31 |
32 | expected := `header
33 | row`
34 |
35 | if !f.CalledRender || strings.TrimSpace(expected) != strings.TrimSpace(f.TableOut) {
36 | t.Errorf("failed to mock method Render on FakeTableWriter")
37 | }
38 | }
39 |
40 | func TestSortByFakeTableWriter(t *testing.T) {
41 | f := &FakeTableWriter{}
42 |
43 | f.SetWriter(io.Discard)
44 |
45 | f.AppendRow("zRow")
46 | f.AppendRow("aRow")
47 |
48 | f.SortBy(1)
49 |
50 | if f.Rows[0][0] != "aRow" || f.Rows[1][0] != "zRow" {
51 | t.Errorf("failed to mock method SortBy on FakeTableWriter")
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/presets/adonis/config.yml:
--------------------------------------------------------------------------------
1 | # Which tags are related to this preset; used for branching the choices on preset wizard
2 | tags: [ 'Typescript' ]
3 |
4 | name: 'AdonisJS'
5 |
6 | # Create defines the workflow for creating a new Project where this preset can then be installed
7 | create:
8 | - name: Creating new Adonis Application
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/node:20
12 | - kool docker kooldev/node:20 npm init -y adonis-ts-app@latest $CREATE_DIRECTORY
13 |
14 | # Preset defines the workflow for installing this preset in the current working directory
15 | preset:
16 | - name: 'Copy basic config files'
17 | actions:
18 | - copy: docker-compose.yml
19 | - copy: kool.yml
20 | - merge: app/node-adonis.yml
21 | dst: docker-compose.yml
22 |
23 | - name: 'Customize your setup'
24 | actions:
25 | - recipe: pick-db
26 | - recipe: pick-cache
27 | # define package manager
28 | - prompt: Which javascript package manager do you want to use?
29 | default: 'npm'
30 | options:
31 | - name: 'npm'
32 | actions:
33 | - merge: scripts/npm-adonis.yml
34 | dst: kool.yml
35 | - name: 'yarn'
36 | actions:
37 | - merge: scripts/yarn-adonis.yml
38 | dst: kool.yml
39 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
3 | {
4 | "name": "Kool.dev Container for Go CLI development",
5 |
6 | "build": {
7 | "context": ".",
8 | "dockerfile": "Dockerfile"
9 | },
10 |
11 | // Features to add to the dev container. More info: https://containers.dev/features.
12 | "features": {
13 | "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
14 | "ghcr.io/devcontainers/features/go:1": {
15 | "version": "1.21"
16 | }
17 | },
18 |
19 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
20 | // "forwardPorts": [
21 | // ],
22 |
23 | // Uncomment the next line to run commands after the container is created.
24 | "postCreateCommand": [
25 | // "bash -c \"$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)\""
26 | ],
27 |
28 | // Configure tool-specific properties.
29 | "customizations": {
30 | "vscode": {
31 | "extensions": [
32 | "ms-vscode.go"
33 | ]
34 | }
35 | },
36 |
37 | // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
38 | "remoteUser": "kool"
39 | }
40 |
--------------------------------------------------------------------------------
/services/yamler/output_test.go:
--------------------------------------------------------------------------------
1 | package yamler
2 |
3 | import (
4 | "io"
5 | "os"
6 | "path/filepath"
7 | "strings"
8 | "testing"
9 |
10 | "gopkg.in/yaml.v3"
11 | )
12 |
13 | func TestWriteYAML(t *testing.T) {
14 | o := &DefaultOutputWritter{}
15 | f := filepath.Join(t.TempDir(), "test.yml")
16 |
17 | y := new(yaml.Node)
18 | yml := "foo: bar"
19 |
20 | _ = yaml.Unmarshal([]byte(yml), y)
21 |
22 | if err := o.WriteYAML(f, y); err != nil {
23 | t.Errorf("unexpected error writing YAML: %v", err)
24 | }
25 |
26 | fh, _ := os.Open(f)
27 | bs, _ := io.ReadAll(fh)
28 | fh.Close()
29 | got := strings.Trim(string(bs), " \t\n")
30 |
31 | if got != yml {
32 | t.Errorf("bad YML; expected '%s' but got '%s'", yml, got)
33 | }
34 | }
35 |
36 | func TestWriteYAMLIndentation(t *testing.T) {
37 | o := &DefaultOutputWritter{}
38 | f := filepath.Join(t.TempDir(), "test_indent.yml")
39 |
40 | y := new(yaml.Node)
41 | yml := "foo:\n bar: xxx"
42 |
43 | _ = yaml.Unmarshal([]byte(yml), y)
44 |
45 | if err := o.WriteYAML(f, y); err != nil {
46 | t.Errorf("unexpected error writing YAML: %v", err)
47 | }
48 |
49 | fh, _ := os.Open(f)
50 | bs, _ := io.ReadAll(fh)
51 | fh.Close()
52 | got := strings.Trim(string(bs), " \t\n")
53 |
54 | expect := "foo:\n bar: xxx"
55 |
56 | if got != expect {
57 | t.Errorf("bad YML; expected '%s' but got '%s'", expect, got)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/core/automate/actions.go:
--------------------------------------------------------------------------------
1 | package automate
2 |
3 | type ActionType uint
4 |
5 | const (
6 | TypeUnknown ActionType = iota
7 | TypeCopy
8 | TypeScripts
9 | TypePrompt
10 | TypeRecipe
11 | TypeMerge
12 | )
13 |
14 | // ActionSet represents a set of single actions or a question
15 | type ActionSet struct {
16 | Name string `yaml:"name"`
17 | Actions []*Action `yaml:"actions"`
18 | }
19 |
20 | // Action is a union kind of type that holds
21 | // one specific action within it; used for parsing
22 | type Action struct {
23 | // ref
24 | Ref string `yaml:"ref"`
25 |
26 | // recipe
27 | Recipe string `yaml:"recipe"`
28 | // merge
29 | Merge string `yaml:"merge"`
30 | // copy
31 | Src string `yaml:"copy"`
32 | Dst string `yaml:"dst"`
33 | // scripts
34 | Scripts []string `yaml:"scripts"`
35 | // prompt
36 | Prompt string `yaml:"prompt"`
37 | Default string `yaml:"default"`
38 | Options []*ActionSet `yaml:"options"`
39 | }
40 |
41 | // Type tells the actual implementation of this action
42 | func (a *Action) Type() ActionType {
43 | if a.Scripts != nil {
44 | return TypeScripts
45 | }
46 |
47 | if a.Recipe != "" {
48 | return TypeRecipe
49 | }
50 |
51 | if a.Src != "" {
52 | return TypeCopy
53 | }
54 |
55 | if a.Prompt != "" {
56 | return TypePrompt
57 | }
58 |
59 | if a.Merge != "" {
60 | return TypeMerge
61 | }
62 |
63 | return TypeUnknown
64 | }
65 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-cloud.md:
--------------------------------------------------------------------------------
1 | ## kool cloud
2 |
3 | Interact with Kool.dev Cloud and manage your deployments.
4 |
5 | ### Synopsis
6 |
7 | The cloud subcommand encapsulates a set of APIs to interact with Kool.dev Cloud and deploy, access and tail logs from your deployments.
8 |
9 | ### Examples
10 |
11 | ```
12 | kool cloud deploy
13 | ```
14 |
15 | ### Options
16 |
17 | ```
18 | --domain string Environment domain name to deploy to
19 | -h, --help help for cloud
20 | --token string Token to authenticate with Kool.dev Cloud API
21 | ```
22 |
23 | ### Options inherited from parent commands
24 |
25 | ```
26 | --verbose Increases output verbosity
27 | -w, --working_dir string Changes the working directory for the command
28 | ```
29 |
30 | ### SEE ALSO
31 |
32 | * [kool](kool) - Cloud native environments made easy
33 | * [kool cloud deploy](kool_cloud_deploy) - Deploy a local application to a Kool.dev Cloud environment
34 | * [kool cloud destroy](kool_cloud_destroy) - Destroy an environment deployed to Kool.dev Cloud
35 | * [kool cloud exec](kool_cloud_exec) - Execute a command inside a running service container deployed to Kool.dev Cloud
36 | * [kool cloud logs](kool_cloud_logs) - See the logs of running service container deployed to Kool.dev Cloud
37 | * [kool cloud setup](kool_cloud_setup) - Set up local configuration files for deployment
38 |
39 |
--------------------------------------------------------------------------------
/kool.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | # Helper for local development
3 | # compiling and installing locally
4 | dev:
5 | - kool run compile
6 | - kool run install
7 | # Runs go CLI with proper version for kool development (targets host OS passing down GOOS)
8 | go: kool docker --volume=kool_gopath:/go --env='GOOS=$GOOS' golang:1.21 go
9 | # Runs go CLI with Linux, independent of host OS
10 | go:linux: kool docker --volume=kool_gopath:/go golang:1.21 go
11 | # Compiling kool itself. In case you are on MacOS make sure to have your .env
12 | # file properly setting GOOS=darwin so you will be able to use the binary.
13 | compile:
14 | - kool run fmt
15 | - kool run go build -buildvcs=false -o kool-cli
16 | install: mv ./kool-cli /usr/local/bin/kool
17 | fmt: kool run go:linux fmt ./...
18 | lint: kool docker --volume=kool_gopath:/go golangci/golangci-lint:v1.54.1 golangci-lint run -v
19 | test: kool run test:path ./...
20 | test:path: kool run go:linux test -race
21 | test-coverage: kool run go:linux test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
22 | # Generate documentation for kool commands
23 | make-docs:
24 | - rm -f docs/05-Commands-Reference/*.md
25 | - kool run go:linux run docs.go
26 | # build docker image locally
27 | docker:build:rc: docker build --build-arg BUILD_VERSION=0.0.0-rc --pull -t kooldev/kool:rc .
28 | docker:push:rc: docker push kooldev/kool:rc
29 |
--------------------------------------------------------------------------------
/commands/restart.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | )
6 |
7 | // KoolRestartFlags holds the flags for the kool restart command
8 | type KoolRestartFlags struct {
9 | Purge bool
10 | Rebuild bool
11 | }
12 |
13 | // NewRestartCommand initializes new kool start command
14 | func NewRestartCommand(stop KoolService, start KoolService) (restartCmd *cobra.Command) {
15 | var flags *KoolRestartFlags = &KoolRestartFlags{false, false}
16 |
17 | restartCmd = &cobra.Command{
18 | Use: "restart",
19 | Short: "Restart running service containers (the same as 'kool stop' followed by 'kool start')",
20 | RunE: func(cmd *cobra.Command, args []string) error {
21 | if _, ok := stop.(*KoolStop); ok && flags.Purge {
22 | stop.(*KoolStop).Flags.Purge = true
23 | }
24 | if _, ok := start.(*KoolStart); ok && flags.Rebuild {
25 | start.(*KoolStart).Flags.Rebuild = true
26 | }
27 |
28 | return DefaultCommandRunFunction(stop, start)(cmd, args)
29 | },
30 |
31 | DisableFlagsInUseLine: true,
32 | }
33 |
34 | restartCmd.Flags().BoolVarP(&flags.Purge, "purge", "", false, "Remove all persistent data from volume mounts on containers")
35 | restartCmd.Flags().BoolVarP(&flags.Rebuild, "rebuild", "", false, "Updates and builds service's images")
36 |
37 | return
38 | }
39 |
40 | func AddKoolRestart(root *cobra.Command) {
41 | root.AddCommand(NewRestartCommand(NewKoolStop(), NewKoolStart()))
42 | }
43 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: docker
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | docker-build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@master
15 |
16 | - uses: olegtarasov/get-tag@v2.1
17 | id: tagName
18 |
19 | - name: Build image
20 | env:
21 | TAGNAME: ${{ steps.tagName.outputs.tag }}
22 | run: docker build --build-arg BUILD_VERSION=$TAGNAME --pull -t kooldev/kool:$TAGNAME -t kooldev/kool:4scan .
23 |
24 | - name: Test image
25 | env:
26 | TAGNAME: ${{ steps.tagName.outputs.tag }}
27 | run: docker run kooldev/kool:$TAGNAME kool --version
28 |
29 | # - name: Scan image
30 | # uses: anchore/scan-action@v2
31 | # with:
32 | # image: "kooldev/kool:4scan"
33 | # fail-build: true
34 | # severity-cutoff: critical
35 |
36 | - name: Push to hub
37 | env:
38 | TAGNAME: ${{ steps.tagName.outputs.tag }}
39 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
40 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
41 | run: |
42 | TAGNAME_MAJOR=${TAGNAME%%.*}
43 | docker tag kooldev/kool:$TAGNAME kooldev/kool:$TAGNAME_MAJOR
44 | echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
45 | docker push kooldev/kool:$TAGNAME
46 | docker push kooldev/kool:$TAGNAME_MAJOR
47 |
--------------------------------------------------------------------------------
/core/environment/env_storage.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/fireworkweb/godotenv"
7 | )
8 |
9 | // DefaultEnvStorage holds data to store environment variables
10 | type DefaultEnvStorage struct{}
11 |
12 | // EnvStorage contract that holds environment variables storage logic
13 | type EnvStorage interface {
14 | Get(string) string
15 | Set(string, string)
16 | Load(string) error
17 | All() []string
18 | IsTrue(string) bool
19 | }
20 |
21 | // NewEnvStorage creates a new Environment Storage instance
22 | func NewEnvStorage() EnvStorage {
23 | return &DefaultEnvStorage{}
24 | }
25 |
26 | // Get get environment variable value
27 | func (es *DefaultEnvStorage) Get(key string) string {
28 | return os.Getenv(key)
29 | }
30 |
31 | // Set set environment variable value
32 | func (es *DefaultEnvStorage) Set(key string, value string) {
33 | os.Setenv(key, value)
34 | }
35 |
36 | // Load load environment file
37 | func (es *DefaultEnvStorage) Load(filename string) error {
38 | return godotenv.Load(filename)
39 | }
40 |
41 | // All get all environment variables
42 | func (es *DefaultEnvStorage) All() []string {
43 | return os.Environ()
44 | }
45 |
46 | // IsTrue checks whether the given environment variable is
47 | // to what would be a boolean value of true (either 1 or "true")
48 | func (es *DefaultEnvStorage) IsTrue(key string) bool {
49 | value := os.Getenv(key)
50 | return value == "1" || value == "true"
51 | }
52 |
--------------------------------------------------------------------------------
/core/shell/prompt_select.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/AlecAivazis/survey/v2"
7 | "github.com/AlecAivazis/survey/v2/terminal"
8 | )
9 |
10 | // PromptSelect contract that holds logic for prompt a select question
11 | type PromptSelect interface {
12 | Ask(string, []string) (string, error)
13 |
14 | Confirm(string, ...any) (bool, error)
15 | }
16 |
17 | // DefaultPromptSelect holds data for prompting a select question
18 | type DefaultPromptSelect struct{}
19 |
20 | // NewPromptSelect creates a new prompt select
21 | func NewPromptSelect() PromptSelect {
22 | return &DefaultPromptSelect{}
23 | }
24 |
25 | // Ask prompt to the user a select question
26 | func (p *DefaultPromptSelect) Ask(question string, options []string) (answer string, err error) {
27 | prompt := &survey.Select{
28 | Message: question,
29 | Options: options,
30 | }
31 | if err = survey.AskOne(prompt, &answer); err != nil && err == terminal.InterruptErr {
32 | err = ErrUserCancelled
33 | }
34 | return
35 | }
36 |
37 | // Confirm prompts to the user a Yes/No confirm question
38 | func (p *DefaultPromptSelect) Confirm(question string, args ...any) (confirmed bool, err error) {
39 | if args != nil {
40 | question = fmt.Sprintf(question, args...)
41 | }
42 |
43 | var answer string
44 |
45 | if answer, err = p.Ask(question, []string{"Yes", "No"}); err != nil {
46 | return
47 | }
48 |
49 | confirmed = answer == "Yes"
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/core/builder/fake_command.go:
--------------------------------------------------------------------------------
1 | package builder
2 |
3 | // FakeCommand implements the Command interface and is used for mocking on testing scenarios
4 | type FakeCommand struct {
5 | ArgsAppend []string
6 | CalledAppendArgs bool
7 | CalledString bool
8 | CalledCmd bool
9 | CalledArgs bool
10 | CalledParseCommand bool
11 |
12 | MockCmd string
13 | MockExecOut string
14 | MockError error
15 | MockLookPathError error
16 | MockExecError error
17 | MockInteractiveError error
18 | }
19 |
20 | // AppendArgs mocked function for testing
21 | func (f *FakeCommand) AppendArgs(args ...string) {
22 | f.ArgsAppend = append(f.ArgsAppend, args...)
23 | f.CalledAppendArgs = true
24 | }
25 |
26 | // String mocked function for testing
27 | func (f *FakeCommand) String() string {
28 | f.CalledString = true
29 | return ""
30 | }
31 |
32 | // Args returns the command arguments
33 | func (f *FakeCommand) Args() []string {
34 | f.CalledArgs = true
35 | return f.ArgsAppend
36 | }
37 |
38 | // Cmd returns the command executable
39 | func (f *FakeCommand) Cmd() string {
40 | f.CalledCmd = true
41 | return f.MockCmd
42 | }
43 |
44 | // Parse call the ParseCommand function
45 | func (f *FakeCommand) Parse(line string) (err error) {
46 | f.CalledParseCommand = true
47 | err = f.MockError
48 | return
49 | }
50 |
51 | // Copy mocks a copy be returning itself
52 | func (f *FakeCommand) Copy() Command {
53 | return f
54 | }
55 |
--------------------------------------------------------------------------------
/docs/10-Docker-Images/1-Introduction.md:
--------------------------------------------------------------------------------
1 | When you start developing in containers, you quickly realize the official Docker images are built for deployment, and not for the special considerations (and nuances) of local development. One of the most common and recurring problems we see are permission issues with mapped volumes, due to host users being different from container users. Kool fixes this problem, and many others, by creating custom Docker images optimized for local development environments.
2 |
3 | A few of the optimizations included in Kool's Docker images:
4 | - UID mapping to host user to solve permission issues
5 | - Alpine base images to remain small and up-to-date
6 | - Configured with sane defaults - for development as well as production
7 | - Environment variables to easily update the most common settings
8 | - Battle-tested - a growing community has been using these images in production for quite a long time now!
9 |
10 | ## Kool-Optimized Docker Images
11 |
12 | - PHP images: https://github.com/kool-dev/docker-php
13 | - Nginx images: https://github.com/kool-dev/docker-nginx
14 | - Node images: https://github.com/kool-dev/docker-node
15 | - Java images: https://github.com/kool-dev/docker-java
16 | - DevOps images: https://github.com/kool-dev/docker-toolkit
17 |
18 | > Disclaimer: Kool Docker images follow our recommended best practices, which are aimed at making your life easier. However, you can use the **Kool CLI** with any Docker images you like - assuming you know what you're doing.
19 |
--------------------------------------------------------------------------------
/recipes/create-symfony.yml:
--------------------------------------------------------------------------------
1 | title: "Creating Symfony Application"
2 |
3 | actions:
4 | - prompt: 'Which PHP version do you want to use?'
5 | ref: 'php-version'
6 | default: 'PHP 8.2'
7 | options:
8 | - name: 'PHP 8.3'
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/php:8.3
12 | - kool docker kooldev/php:8.3 composer create-project --no-install --prefer-dist symfony/website-skeleton $CREATE_DIRECTORY
13 | - name: 'PHP 8.2'
14 | actions:
15 | - scripts:
16 | - docker pull -q kooldev/php:8.2
17 | - kool docker kooldev/php:8.2 composer create-project --no-install --prefer-dist symfony/website-skeleton $CREATE_DIRECTORY
18 | - name: 'PHP 8.1'
19 | actions:
20 | - scripts:
21 | - docker pull -q kooldev/php:8.1
22 | - kool docker kooldev/php:8.1 composer create-project --no-install --prefer-dist symfony/website-skeleton $CREATE_DIRECTORY
23 | - name: 'PHP 8.0'
24 | actions:
25 | - scripts:
26 | - docker pull -q kooldev/php:8
27 | - kool docker kooldev/php:p:8 composer create-project --no-install --prefer-dist symfony/website-skeleton $CREATE_DIRECTORY
28 | - name: 'PHP 7.4'
29 | actions:
30 | - scripts:
31 | - docker pull -q kooldev/php:7.4
32 | - kool docker kooldev/php:7.4 composer create-project --no-install --prefer-dist symfony/website-skeleton $CREATE_DIRECTORY
33 |
--------------------------------------------------------------------------------
/services/updater/fake_updater.go:
--------------------------------------------------------------------------------
1 | package updater
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/blang/semver"
7 | )
8 |
9 | // FakeUpdater implements all fake behaviors for self-update
10 | type FakeUpdater struct {
11 | CalledGetCurrentVersion, CalledUpdate,
12 | CalledCheckForUpdates, CalledCheckPermission bool
13 |
14 | MockCurrentVersion, MockLatestVersion string
15 | MockErrorUpdate, MockErrorPermission error
16 | MockTimeoutDelay bool
17 | }
18 |
19 | // GetCurrentVersion get mocked current version
20 | func (u *FakeUpdater) GetCurrentVersion() semver.Version {
21 | u.CalledGetCurrentVersion = true
22 | return semver.MustParse(u.MockCurrentVersion)
23 | }
24 |
25 | // Update implements fake update
26 | func (u *FakeUpdater) Update(currentVersion semver.Version) (updatedVersion semver.Version, err error) {
27 | updatedVersion = semver.MustParse(u.MockLatestVersion)
28 | err = u.MockErrorUpdate
29 | u.CalledUpdate = true
30 | return
31 | }
32 |
33 | // CheckForUpdates implements fake available update check
34 | func (u *FakeUpdater) CheckForUpdates(currentVersion semver.Version, ch chan bool) {
35 | u.CalledCheckForUpdates = true
36 |
37 | if u.MockTimeoutDelay {
38 | // causes a timeout
39 | time.Sleep(time.Millisecond * 1100)
40 | }
41 |
42 | ch <- true
43 | }
44 |
45 | // CheckPermission implements fake permission check
46 | func (u *FakeUpdater) CheckPermission() (err error) {
47 | u.CalledCheckPermission = true
48 | err = u.MockErrorPermission
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/recipes/create-laravel.yml:
--------------------------------------------------------------------------------
1 | title: "Creating Laravel Application"
2 |
3 | actions:
4 | - prompt: 'Which PHP version do you want to use?'
5 | ref: 'php-version'
6 | default: 'PHP 8.2'
7 | options:
8 | - name: 'PHP 8.3'
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/php:8.3
12 | - kool docker kooldev/php:8.3 composer create-project --no-install --no-scripts --prefer-dist laravel/laravel $CREATE_DIRECTORY
13 | - name: 'PHP 8.2'
14 | actions:
15 | - scripts:
16 | - docker pull -q kooldev/php:8.2
17 | - kool docker kooldev/php:8.2 composer create-project --no-install --no-scripts --prefer-dist laravel/laravel $CREATE_DIRECTORY
18 | - name: 'PHP 8.1'
19 | actions:
20 | - scripts:
21 | - docker pull -q kooldev/php:8.1
22 | - kool docker kooldev/php:8.1 composer create-project --no-install --no-scripts --prefer-dist laravel/laravel $CREATE_DIRECTORY
23 | - name: 'PHP 8.0'
24 | actions:
25 | - scripts:
26 | - docker pull -q kooldev/php:8
27 | - kool docker kooldev/php:8 composer create-project --no-install --no-scripts --prefer-dist laravel/laravel $CREATE_DIRECTORY
28 | - name: 'PHP 7.4'
29 | actions:
30 | - scripts:
31 | - docker pull -q kooldev/php:7.4
32 | - kool docker kooldev/php:7.4 composer create-project --no-install --no-scripts --prefer-dist laravel/laravel $CREATE_DIRECTORY
33 |
--------------------------------------------------------------------------------
/core/presets/fake_parser_test.go:
--------------------------------------------------------------------------------
1 | package presets
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestFakeParser(t *testing.T) {
9 | f := &FakeParser{}
10 |
11 | f.MockExists = true
12 | exists := f.Exists("preset")
13 |
14 | if !f.CalledExists || exists != f.MockExists {
15 | t.Error("failed to use mocked Exists function on FakeParser")
16 | }
17 |
18 | f.MockGetPresets = map[string]string{"preset": "preset"}
19 | presets := f.GetPresets("")
20 |
21 | if !f.CalledGetPresets || len(presets) != 1 || presets["preset"] != "preset" {
22 | t.Error("failed to use mocked GetPresets function on FakeParser")
23 | }
24 |
25 | f.MockGetTags = []string{"php"}
26 | tags := f.GetTags()
27 |
28 | if !f.CalledGetTags || len(tags) != 1 || tags[0] != "php" {
29 | t.Error("failed to use mocked GetTags function on FakeParser")
30 | }
31 |
32 | f.MockInstall = errors.New("Install")
33 | errInstall := f.Install("")
34 |
35 | if !f.CalledInstall || errInstall == nil || errInstall.Error() != "Install" {
36 | t.Error("failed to use mocked Install function on FakeParser")
37 | }
38 |
39 | f.MockCreate = errors.New("Create")
40 | errCreate := f.Create("")
41 |
42 | if !f.CalledCreate || errCreate == nil || errCreate.Error() != "Create" {
43 | t.Error("failed to use mocked Create function on FakeParser")
44 | }
45 |
46 | f.MockAdd = errors.New("Add")
47 | errAdd := f.Add("", nil)
48 |
49 | if !f.CalledAdd || errAdd == nil || errAdd.Error() != "Add" {
50 | t.Error("failed to use mocked Add function on FakeParser")
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/recipes/create-codeigniter.yml:
--------------------------------------------------------------------------------
1 | title: "Creating Laravel Application"
2 |
3 | actions:
4 | - prompt: 'Which PHP version do you want to use?'
5 | ref: 'php-version'
6 | default: 'PHP 8.2'
7 | options:
8 | - name: 'PHP 8.3'
9 | actions:
10 | - scripts:
11 | - docker pull -q kooldev/php:8.3
12 | - kool docker kooldev/php:8.3 composer create-project --no-install --no-scripts --prefer-dist codeigniter4/appstarter $CREATE_DIRECTORY
13 | - name: 'PHP 8.2'
14 | actions:
15 | - scripts:
16 | - docker pull -q kooldev/php:8.2
17 | - kool docker kooldev/php:8.2 composer create-project --no-install --no-scripts --prefer-dist codeigniter4/appstarter $CREATE_DIRECTORY
18 | - name: 'PHP 8.1'
19 | actions:
20 | - scripts:
21 | - docker pull -q kooldev/php:8.1
22 | - kool docker kooldev/php:8.1 composer create-project --no-install --no-scripts --prefer-dist codeigniter4/appstarter $CREATE_DIRECTORY
23 | - name: 'PHP 8.0'
24 | actions:
25 | - scripts:
26 | - docker pull -q kooldev/php:8
27 | - kool docker kooldev/php:p:8 composer create-project --no-install --no-scripts --prefer-dist codeigniter4/appstarter $CREATE_DIRECTORY
28 | - name: 'PHP 7.4'
29 | actions:
30 | - scripts:
31 | - docker pull -q kooldev/php:7.4
32 | - kool docker kooldev/php:7.4 composer create-project --no-install --no-scripts --prefer-dist codeigniter4/appstarter $CREATE_DIRECTORY
33 |
--------------------------------------------------------------------------------
/core/shell/prompt_select_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "io"
5 | "os"
6 | "strings"
7 | "testing"
8 | )
9 |
10 | func TestNewPromptSelect(t *testing.T) {
11 | p := NewPromptSelect()
12 |
13 | if _, ok := p.(*DefaultPromptSelect); !ok {
14 | t.Errorf("unexpected PromptSelect on NewPromptSelect")
15 | }
16 | }
17 |
18 | func TestAskPromptSelect(t *testing.T) {
19 | oldStdout := os.Stdout
20 | r, w, _ := os.Pipe()
21 | os.Stdout = w
22 |
23 | p := NewPromptSelect()
24 |
25 | _, _ = p.Ask("testing_question", []string{"testing_option1", "testing_option2"})
26 |
27 | w.Close()
28 | out, err := io.ReadAll(r)
29 | os.Stdout = oldStdout
30 |
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 |
35 | output := string(out)
36 |
37 | if !strings.Contains(output, "testing_question") || !strings.Contains(output, "testing_option1") || !strings.Contains(output, "testing_option2") {
38 | t.Error("failed to render the question and its options")
39 | }
40 | }
41 |
42 | func TestConfirmPromptSelect(t *testing.T) {
43 | oldStdout := os.Stdout
44 | r, w, _ := os.Pipe()
45 | os.Stdout = w
46 |
47 | p := NewPromptSelect()
48 |
49 | _, _ = p.Confirm("testing_question %s", "arg1")
50 |
51 | w.Close()
52 | out, err := io.ReadAll(r)
53 | os.Stdout = oldStdout
54 |
55 | if err != nil {
56 | t.Fatal(err)
57 | }
58 |
59 | output := string(out)
60 |
61 | if !strings.Contains(output, "testing_question arg1") || !strings.Contains(output, "Yes") || !strings.Contains(output, "No") {
62 | t.Error("failed to render the Confirm prompt and its options")
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/services/cloud/api/deploy_create.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // DeployCreateResponse holds data returned from the deploy endpoint
4 | type DeployCreateResponse struct {
5 | Deploy struct {
6 | ID int `json:"id"`
7 | Project string `json:"project"`
8 | Url string `json:"url"`
9 | Environment struct {
10 | Name string `json:"name"`
11 | Env interface{} `json:"env"`
12 | } `json:"environment"`
13 | Cluster struct {
14 | Region string `json:"region"`
15 | } `json:"cluster"`
16 | } `json:"deploy"`
17 |
18 | Config struct {
19 | ImagePrefix string `json:"image_prefix"`
20 | ImageRepository string `json:"image_repository"`
21 | ImageTag string `json:"image_tag"`
22 | } `json:"stuff"`
23 |
24 | Docker struct {
25 | Login string `json:"login"`
26 | Password string `json:"password"`
27 | } `json:"docker"`
28 |
29 | LogsUrl string `json:"logs_url"`
30 | }
31 |
32 | // DeployCreate consumes the API endpoint to create a new deployment
33 | type DeployCreate struct {
34 | Endpoint
35 | }
36 |
37 | // NewDeployCreate creates a new DeployCreate instance
38 | func NewDeployCreate() (c *DeployCreate) {
39 | c = &DeployCreate{
40 | Endpoint: NewDefaultEndpoint("POST"),
41 | }
42 |
43 | c.SetPath("deploy/create")
44 | _ = c.PostField("is_local", "1")
45 |
46 | return
47 | }
48 |
49 | // Run calls deploy/create in the Kool Dev API
50 | func (c *DeployCreate) Run() (resp *DeployCreateResponse, err error) {
51 | resp = &DeployCreateResponse{}
52 |
53 | c.SetResponseReceiver(resp)
54 |
55 | err = c.DoCall()
56 |
57 | return
58 | }
59 |
--------------------------------------------------------------------------------
/docs/03-Presets/1-Creating-a-Preset.md:
--------------------------------------------------------------------------------
1 | # Creating a new preset
2 |
3 | `kool` has a builtin task-runner feature that allows us to automate steps/repetitive tasks - first of being all necessary setup steps for bootstrapping a new project. We use this feature to enable our presets, accomplishing two objectives:
4 |
5 | - Keeping fast, clean and simple how to setup a new local development environment for popular frameworks and start coding.
6 | - Have this project with all good practices and sane defaults for running in containers - for development and later deployment.
7 |
8 | ## Steps to create a preset
9 |
10 | #### 1. Creating `presets/my-preset/config.yml`
11 |
12 | Create a new folder under `presets` and a `config.yml` file in it: `presets/my-preset/config.yml`.
13 |
14 | The `config.yml` file is where we configure:
15 |
16 | - Steps for creating a new project
17 | - Steps for installing `kool` tailored local Docker environment to existing projects
18 |
19 | Both of the two tasks described above are accomplished via a set of declarative `steps` and `actions` on what we can call Kool Automation language. Some of the actions include:
20 |
21 | - `scripts` - running arbitrary shell script.
22 | - `copy` - copying files from our preset of templates folder right into the local project.
23 | - `merge` - merge YAML files - helpful for building `docker-compose.yml` or `kool.yml` dynamically.
24 | - `recipe`: run a Recipe which is a group of steps/actions ready to reuse
25 |
26 | You can find the full reference on the Kool Automation Langauge here TBD.
27 |
28 | Check out some of our current presets as examples.
29 |
--------------------------------------------------------------------------------
/core/parser/fake_parser.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "kool-dev/kool/core/builder"
5 | "strings"
6 | )
7 |
8 | // FakeParser implements all fake behaviors for using parser in tests.
9 | type FakeParser struct {
10 | CalledAddLookupPath bool
11 | TargetFiles []string
12 | CalledParse bool
13 | CalledParseAvailableScripts bool
14 | MockParsedCommands map[string][]builder.Command
15 | MockParseError map[string]error
16 | MockScripts []string
17 | MockParseAvailableScriptsError error
18 | }
19 |
20 | // AddLookupPath implements fake AddLookupPath behavior
21 | func (f *FakeParser) AddLookupPath(rootPath string) (err error) {
22 | f.CalledAddLookupPath = true
23 | f.TargetFiles = append(f.TargetFiles, "kool.yml")
24 | return
25 | }
26 |
27 | // Parse implements fake Parse behavior
28 | func (f *FakeParser) Parse(script string) (commands []builder.Command, err error) {
29 | f.CalledParse = true
30 | commands = f.MockParsedCommands[script]
31 | err = f.MockParseError[script]
32 | return
33 | }
34 |
35 | // ParseAvailableScripts implements fake ParseAvailableScripts behavior
36 | func (f *FakeParser) ParseAvailableScripts(filter string) (scripts []string, err error) {
37 | f.CalledParseAvailableScripts = true
38 |
39 | if filter == "" {
40 | scripts = f.MockScripts
41 | } else {
42 | for _, script := range f.MockScripts {
43 | if strings.HasPrefix(script, filter) {
44 | scripts = append(scripts, script)
45 | }
46 | }
47 | }
48 |
49 | err = f.MockParseAvailableScriptsError
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/core/presets/fake_parser.go:
--------------------------------------------------------------------------------
1 | package presets
2 |
3 | import "kool-dev/kool/core/shell"
4 |
5 | // FakeParser implements all fake behaviors for using parser in tests.
6 | type FakeParser struct {
7 | CalledExists bool
8 | CalledGetTags bool
9 | CalledGetPresets bool
10 | CalledInstall bool
11 | CalledCreate bool
12 | CalledAdd bool
13 |
14 | MockExists bool
15 | MockGetTags []string
16 | MockGetPresets map[string]string
17 | MockInstall error
18 | MockCreate error
19 | MockAdd error
20 | }
21 |
22 | // Exists check if preset exists
23 | func (f *FakeParser) Exists(preset string) (exists bool) {
24 | f.CalledExists = true
25 | exists = f.MockExists
26 | return
27 | }
28 |
29 | func (f *FakeParser) PrepareExecutor(sh shell.Shell) {
30 | // noop
31 | }
32 |
33 | // GetTags get all presets tags
34 | func (f *FakeParser) GetTags() (languages []string) {
35 | f.CalledGetTags = true
36 | languages = f.MockGetTags
37 | return
38 | }
39 |
40 | // GetPresets get all presets names
41 | func (f *FakeParser) GetPresets(tag string) (presets map[string]string) {
42 | f.CalledGetPresets = true
43 | presets = f.MockGetPresets
44 | return
45 | }
46 |
47 | // Install
48 | func (f *FakeParser) Install(tag string) (err error) {
49 | f.CalledInstall = true
50 | err = f.MockInstall
51 | return
52 | }
53 |
54 | // Create
55 | func (f *FakeParser) Create(tag string) (err error) {
56 | f.CalledCreate = true
57 | err = f.MockCreate
58 | return
59 | }
60 |
61 | // Add
62 | func (f *FakeParser) Add(tag string, sh shell.Shell) (err error) {
63 | f.CalledAdd = true
64 | err = f.MockAdd
65 | return
66 | }
67 |
--------------------------------------------------------------------------------
/docs/20-Troubleshooting/hmr-eer-empty-response.md:
--------------------------------------------------------------------------------
1 | ### `kool run npm run hot` or `kool run npm run dev`
2 |
3 | > Also valid for `kool run yarn run hot` or `kool run yarn run dev`
4 |
5 | **Problem:**
6 |
7 | > Browser cannot request "HMR" (hot module replacement) server successfully in Laravel Mix or Laravel Vite.
8 |
9 | > Console errors: **net::ERR_CONNECTION_REFUSED** or **net::ERR_EMPTY_RESPONSE**.
10 |
11 | **Answer:**
12 |
13 | > Publish the HMR's port to the host and change HMR settings to listen on all IPv4 addresses (`0.0.0.0`).
14 |
15 | For the sake of clarity, let's elect port `8080` to publish.
16 |
17 | In your `kool.yml`, apply the following changes:
18 |
19 | ```diff
20 | -npm: kool docker kooldev/node:20 npm
21 | +npm: kool docker -p 8080:8080 kooldev/node:20 npm
22 | ```
23 |
24 | - Alternatively, if you don't want to publish the port for your general `kool run npm` commands, you may add a new entry.
25 |
26 | **> Laravel Mix**
27 |
28 | In your `webpack.mix.js`, include the following changes to the `mix.options` call.
29 |
30 | ```diff
31 | mix.options({
32 | + hmrOptions: {
33 | + host: '0.0.0.0',
34 | + port: 8080,
35 | + },
36 | });
37 | ```
38 |
39 | **> Laravel Vite**
40 |
41 | In your `vite.config.js` file, include the following changes to the `defineConfig` call.
42 |
43 | ```diff
44 | export default defineConfig({
45 | + server: {
46 | + host: '0.0.0.0',
47 | + port: 8080,
48 | + },
49 | plugins: [
50 | laravel({
51 | input: ['resources/css/app.css', 'resources/js/app.js'],
52 | refresh: true,
53 | }),
54 | ],
55 | });
56 | ```
57 |
--------------------------------------------------------------------------------
/commands/info_test.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "kool-dev/kool/core/builder"
5 | "kool-dev/kool/core/environment"
6 | "kool-dev/kool/core/shell"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | func setupInfoTest(f *KoolInfo) {
14 | f.envStorage.Set("KOOL_FILTER_TESTING", "1")
15 | f.envStorage.Set("KOOL_TESTING", "1")
16 | }
17 |
18 | func fakeKoolInfo() *KoolInfo {
19 | return &KoolInfo{
20 | *(newDefaultKoolService().Fake()),
21 | environment.NewFakeEnvStorage(),
22 | &builder.FakeCommand{},
23 | &builder.FakeCommand{},
24 | }
25 | }
26 |
27 | func TestInfo(t *testing.T) {
28 | f := fakeKoolInfo()
29 |
30 | setupInfoTest(f)
31 |
32 | output, err := execInfoCommand(NewInfoCmd(f), f)
33 |
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | for _, expected := range []string{"KOOL_FILTER_TESTING=1", "KOOL_TESTING=1"} {
39 | if !strings.Contains(output, expected) {
40 | t.Errorf("Expected '%s', got '%s'", expected, output)
41 | }
42 | }
43 | }
44 |
45 | func TestFilteredInfo(t *testing.T) {
46 | f := fakeKoolInfo()
47 |
48 | setupInfoTest(f)
49 |
50 | cmd := NewInfoCmd(f)
51 | cmd.SetArgs([]string{"FILTER"})
52 |
53 | output, err := execInfoCommand(cmd, f)
54 |
55 | if err != nil {
56 | t.Fatal(err)
57 | }
58 |
59 | expected := "KOOL_FILTER_TESTING=1"
60 |
61 | if !strings.Contains(output, expected) {
62 | t.Errorf("Expected '%s', got '%s'", expected, output)
63 | }
64 | }
65 |
66 | func execInfoCommand(cmd *cobra.Command, f *KoolInfo) (output string, err error) {
67 | if err = cmd.Execute(); err != nil {
68 | return
69 | }
70 |
71 | output = strings.Join(f.shell.(*shell.FakeShell).OutLines, "\n")
72 | return
73 | }
74 |
--------------------------------------------------------------------------------
/core/environment/env.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "log"
5 | "os"
6 | "strings"
7 |
8 | homedir "github.com/mitchellh/go-homedir"
9 | )
10 |
11 | var envFiles = []string{".env.local", ".env"}
12 |
13 | // InitEnvironmentVariables handles the reading of .env files and
14 | // setting up important environment variables necessary for kool
15 | // to operate as expected.
16 | func InitEnvironmentVariables(envStorage EnvStorage) {
17 | var (
18 | homeDir, workDir string
19 | err error
20 | )
21 |
22 | homeDir, err = homedir.Dir()
23 | if err != nil {
24 | log.Fatal("Could not evaluate HOME directory - ", err)
25 | }
26 | if envStorage.Get("HOME") == "" {
27 | envStorage.Set("HOME", homeDir)
28 | }
29 |
30 | initUid(envStorage)
31 |
32 | if envStorage.Get("PWD") == "" {
33 | workDir, err = os.Getwd()
34 | if err != nil {
35 | log.Fatal("Could not evaluate working directory - ", err)
36 | }
37 | envStorage.Set("PWD", workDir)
38 | }
39 |
40 | for _, envFile := range envFiles {
41 | if _, err = os.Stat(envFile); os.IsNotExist(err) {
42 | continue
43 | }
44 |
45 | err = envStorage.Load(envFile)
46 | if err != nil {
47 | log.Fatal("Failure loading environment file ", envFile, " error: '", err, "'")
48 | }
49 | }
50 |
51 | // Now that we loaded up the files, we will check for
52 | // missing variables that we need to fix
53 | if envStorage.Get("KOOL_NAME") == "" {
54 | pieces := strings.Split(envStorage.Get("PWD"), string(os.PathSeparator))
55 | envStorage.Set("KOOL_NAME", pieces[len(pieces)-1])
56 | }
57 |
58 | if envStorage.Get("KOOL_GLOBAL_NETWORK") == "" {
59 | envStorage.Set("KOOL_GLOBAL_NETWORK", "kool_global")
60 | }
61 |
62 | initAsuser(envStorage)
63 | }
64 |
--------------------------------------------------------------------------------
/core/environment/fake_env_storage.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/fireworkweb/godotenv"
7 | )
8 |
9 | // FakeEnvStorage holds fake environment variables
10 | type FakeEnvStorage struct {
11 | Envs map[string]string
12 | CalledLoad bool
13 | EnvsHistory map[string][]string
14 | }
15 |
16 | // NewFakeEnvStorage creates a new FakeEnvStorage
17 | func NewFakeEnvStorage() *FakeEnvStorage {
18 | return &FakeEnvStorage{
19 | Envs: make(map[string]string),
20 | EnvsHistory: make(map[string][]string),
21 | }
22 | }
23 |
24 | // Get get environment variable value (fake behavior)
25 | func (f *FakeEnvStorage) Get(key string) string {
26 | return f.Envs[key]
27 | }
28 |
29 | // Set set environment variable value (fake behavior)
30 | func (f *FakeEnvStorage) Set(key string, value string) {
31 | f.Envs[key] = value
32 | f.EnvsHistory[key] = append(f.EnvsHistory[key], value)
33 | }
34 |
35 | // Load load environment file (fake behavior)
36 | func (f *FakeEnvStorage) Load(filename string) error {
37 | f.CalledLoad = true
38 | envs, _ := godotenv.Read(filename)
39 | for k, v := range envs {
40 | if _, exists := f.Envs[k]; !exists {
41 | f.Envs[k] = v
42 | }
43 | }
44 | return nil
45 | }
46 |
47 | // All get all environment variables
48 | func (f *FakeEnvStorage) All() (envs []string) {
49 | for key, value := range f.Envs {
50 | envs = append(envs, fmt.Sprintf("%s=%s", key, value))
51 | }
52 |
53 | return
54 | }
55 |
56 | // IsTrue checks whether the given environment variable is
57 | // to what would be a boolean value of true (fake behavior)
58 | func (f *FakeEnvStorage) IsTrue(key string) bool {
59 | value := f.Envs[key]
60 | return value == "1" || value == "true"
61 | }
62 |
--------------------------------------------------------------------------------
/core/parser/errors.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | // ErrMultipleDefinedScript happens when the script asked for is
10 | // found within more than one kool.yml files targeted.
11 | var ErrMultipleDefinedScript = fmt.Errorf("script was found in more than one kool.yml file")
12 |
13 | // IsMultipleDefinedScriptError tells whether the given error is parser.ErrMultipleDefinedScript
14 | func IsMultipleDefinedScriptError(err error) bool {
15 | return err.Error() == ErrMultipleDefinedScript.Error()
16 | }
17 |
18 | // ErrKoolYmlNotFound means there was no kool.yml file in the targeted folders
19 | var ErrKoolYmlNotFound = errors.New("could not find any kool.yml file")
20 |
21 | // ErrPossibleTypo implements error interface and can be used
22 | // to determine specific situations of not-found scripts but
23 | // where similar names exist, indicating a possible typo
24 | type ErrPossibleTypo struct {
25 | similars []string
26 | }
27 |
28 | // Error formats a default string with the similar names found
29 | func (e *ErrPossibleTypo) Error() string {
30 | if len(e.similars) == 1 {
31 | return fmt.Sprintf("did you mean '%s'?", e.similars[0])
32 | }
33 |
34 | return fmt.Sprintf("did you mean one of ['%s']?", strings.Join(e.similars, "', '"))
35 | }
36 |
37 | // Similars get the possible similar scripts
38 | func (e *ErrPossibleTypo) Similars() []string {
39 | return e.similars
40 | }
41 |
42 | // SetSimilars set the possible similar scripts
43 | func (e *ErrPossibleTypo) SetSimilars(similars []string) {
44 | e.similars = similars
45 | }
46 |
47 | // IsPossibleTypoError tells whether the given error is parser.ErrPossibleTypo
48 | func IsPossibleTypoError(err error) (assert bool) {
49 | _, assert = err.(*ErrPossibleTypo)
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/core/environment/env_test.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 | "testing"
8 |
9 | homedir "github.com/mitchellh/go-homedir"
10 | )
11 |
12 | func TestInitEnvironmentVariables(t *testing.T) {
13 | f := NewFakeEnvStorage()
14 |
15 | originalEnvFiles := envFiles
16 | defer func() { envFiles = originalEnvFiles }()
17 |
18 | testEnvFile := filepath.Join(t.TempDir(), ".env.test")
19 | envFiles = []string{testEnvFile}
20 |
21 | if err := os.WriteFile(testEnvFile, []byte("FOO=bar\n"), os.ModePerm); err != nil {
22 | t.Fatal(err)
23 | }
24 |
25 | InitEnvironmentVariables(f)
26 |
27 | homeDir, _ := homedir.Dir()
28 |
29 | if envHomeDir := f.Envs["HOME"]; envHomeDir != homeDir {
30 | t.Errorf("expecting $HOME value '%s', got '%s'", homeDir, envHomeDir)
31 | }
32 |
33 | UID := uid()
34 |
35 | if envUID := f.Envs["UID"]; envUID != UID {
36 | t.Errorf("expecting $UID value '%s', got '%s'", UID, envUID)
37 | }
38 |
39 | workDir, _ := os.Getwd()
40 |
41 | if envWorkDir := f.Envs["PWD"]; envWorkDir != workDir {
42 | t.Errorf("expecting $PWD value '%s', got '%s'", workDir, envWorkDir)
43 | }
44 |
45 | if !f.CalledLoad {
46 | t.Error("did not call Load on EnvSotrage")
47 | }
48 |
49 | if foo := f.Envs["FOO"]; foo != "bar" {
50 | t.Errorf("expected FOO to be bar: %v", foo)
51 | }
52 |
53 | pieces := strings.Split(workDir, string(os.PathSeparator))
54 | koolName := pieces[len(pieces)-1]
55 |
56 | if envKoolName := f.Envs["KOOL_NAME"]; envKoolName != koolName {
57 | t.Errorf("expecting $KOOL_NAME value '%s', got '%s'", koolName, envKoolName)
58 | }
59 |
60 | if envKoolNet := f.Envs["KOOL_GLOBAL_NETWORK"]; envKoolNet != "kool_global" {
61 | t.Errorf("expecting $KOOL_GLOBAL_NETWORK value 'kool_global', got '%s'", envKoolNet)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/kool-completion.md:
--------------------------------------------------------------------------------
1 | ## kool completion
2 |
3 | Generate shell completion configuration script
4 |
5 | ### Synopsis
6 |
7 | Autocompletion:
8 |
9 | If you want to use kool autocompletion in your Unix shell, follow the appropriate instructions below.
10 |
11 | After running one of the below commands, remember to start a new shell for autocompletion to take effect.
12 |
13 | #### Bash
14 |
15 | Temporarily enable autocompletion for your current session only:
16 |
17 | $ source <(kool completion bash)
18 |
19 | Permanently enable autocompletion for all sessions:
20 |
21 | Linux:
22 |
23 | $ kool completion bash > /etc/bash_completion.d/kool
24 |
25 | macOS:
26 |
27 | $ kool completion bash > /usr/local/etc/bash_completion.d/kool
28 |
29 | #### Zsh
30 |
31 | If Zsh tab completion is not already initialized on your machine, run the following command to turn it on.
32 |
33 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc
34 |
35 | Permanently enable autocompletion for all sessions:
36 |
37 | $ kool completion zsh > "${fpath[1]}/_kool"
38 |
39 | #### Fish
40 |
41 | Temporarily enable autocompletion for your current session only:
42 |
43 | $ kool completion fish | source
44 |
45 | Permanently enable autocompletion for all sessions:
46 |
47 | $ kool completion fish > ~/.config/fish/completions/kool.fish
48 |
49 |
50 | ```
51 | kool completion [bash|zsh|fish|powershell]
52 | ```
53 |
54 | ### Options
55 |
56 | ```
57 | -h, --help help for completion
58 | ```
59 |
60 | ### Options inherited from parent commands
61 |
62 | ```
63 | --verbose Increases output verbosity
64 | -w, --working_dir string Changes the working directory for the command
65 | ```
66 |
67 | ### SEE ALSO
68 |
69 | * [kool](kool) - Cloud native environments made easy
70 |
71 |
--------------------------------------------------------------------------------
/docs/02-Kool-Cloud/15-Deploy-Lifecycle-Hooks.md:
--------------------------------------------------------------------------------
1 | # Hooks
2 |
3 | You have the ability to define hooks to run before or after every deploy you make.
4 |
5 | These hooks will run using the very same image that is being deployed. This is usually needed for common routines, such as running database migrations, sending alerts of updates to some other service, etc.
6 |
7 | # Before and After
8 |
9 | To illustrate the options you have in the `kool.cloud.yml` file:
10 |
11 | ```yaml
12 | services:
13 | app:
14 | # ...
15 |
16 | # The 'before' hook is a special section where we can define commands to be executed
17 | # right before a new deployment happens.
18 | # ATTENTION: current limitation - can only have a 'before' hook after a first deploy has created the environment.
19 | before:
20 | - [ sh, run-database-migrations.sh, arg1, arg2 ]
21 |
22 | # The 'after' hook is a special section where we can define procedures to be executed
23 | # right after a new deployment finishes.
24 | after:
25 | - [ sh, run-cache-version-update.sh, arg1, arg2 ]
26 | ```
27 |
28 | ## Failures on lifecycle hooks
29 |
30 | Please notice that these lifecycle hooks are required for the new deploy to be successful—this means that **if any of them fail**—either `before` or `after` newly deployed container versions are running—**the whole deploy is going to be rolled back**. As you can imagine, this poses a challenge, especially on database migrations since they can be problematic and not backwards compatible with the previously running container version.
31 |
32 | ## Limitations
33 |
34 | The `before` hook can only be used after a first deploy has succeded for your environment - that is a limitation that should be lifted in the future, but currently can halt your first deploy if it includes a `before` hook.
35 |
--------------------------------------------------------------------------------
/docs/02-Kool-Cloud/10-Persistent-Storage.md:
--------------------------------------------------------------------------------
1 | Containers were built to be ephemeral—and that is how we like them and how Kubernetes and all other container orchestrators usually work the best with them as well.
2 |
3 | But at times, we know that traditional web applications may not be ready to switch to network-based object storage instead of local disk storage.
4 |
5 | Kool.dev Cloud does offer you the ability to create persisted paths within your deployed containers.
6 |
7 | ```yaml
8 | services:
9 | app:
10 | # ...
11 |
12 | # Tells your app's root folder so all other paths can be relative (optional).
13 | root: /app
14 |
15 | # Containers are ephemeral, that means their filesystem do not persist across restarts.
16 | # If you want to persist stuff into the disk across deployments, you can do so by defining persistent paths here.
17 | persists:
18 | # Total size of the volume you want to attach to the running container.
19 | # This can be increased later, but it may take a while to apply the change.
20 | size: 10Gi
21 | # Paths to persist - within that single volume, you can have one or more paths
22 | # that are going to be mounted every time your containers are running. Note that
23 | # such mounts will be there for before/after hooks as well as daemon containers.
24 | paths:
25 | # The path within the container. Must be either aboslute or relative to the 'root' config.
26 | - path: /app/some/path/persisted
27 | # Tells the Deploy API to sync the folder from your built image to the persisted storage.
28 | # This is very helpful to start off with a known folder structure.
29 | sync: true
30 | # Tells what user and group should own the persisted folder (only used when sync: true)
31 | chown: user:group
32 | ```
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | The Kool project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted.
4 |
5 | At this point, the workflow is focused on supporting the core team with shipping new features and writing documentation.
6 |
7 | There are issues with a [`good first issue`](https://github.com/kool-dev/kool/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label. Feel free to browse open issues, enter discussions, or get straight to work!
8 |
9 | ## Reporting a Security Issue
10 |
11 | As stated on [SECURITY.md](SECURITY.md), in case of a security issue or concern, please consider reporting it privately at first to [contact@kool.dev](mailto:contact@kool.dev).
12 |
13 | ### Rich Content Issues and PRs
14 |
15 | If applicable, please consider adding screenshots to help explain your issues or solutions. Recordings can also be very helpful to facilitate communication.
16 |
17 | - Check out [ASCII Cinema](https://asciinema.org/) for recording your terminal only.
18 | - For recording your whole screen, check out [ShareX Opensource Screen Capture](https://getsharex.com), or similar tools, and upload your recordings somewhere to share.
19 |
20 | ## Local Development Directions
21 |
22 | ### Lint, Formatting and Tests
23 |
24 | Before submitting a PR, make sure to run `fmt` and linters.
25 |
26 | ```bash
27 | kool run fmt
28 | kool run lint
29 | kool run test
30 | ```
31 |
32 | ### Updating Command Signature Documentation
33 |
34 | The Cobra library offers a simple solution for generating markdown documentation for the usage of its commands. In order to update the generated markdown files, you must do the following:
35 |
36 | ```bash
37 | kool run make-docs
38 | git add .
39 | git commit -m "Updated commands docs (auto generated)"
40 | ```
41 |
--------------------------------------------------------------------------------
/docs/03-Presets/0-About-Presets.md:
--------------------------------------------------------------------------------
1 | # Kool Presets: Streamlining Your Development Experience
2 |
3 | Kool Presets are curated configurations tailored to popular stacks and frameworks, designed to jumpstart your projects with efficiency and ease. Whether you're working with Node.js, PHP, JavaScript, or other technologies, Kool Presets provide battle-tested configurations, saving you from the hassle of reinventing the wheel for each new project.
4 |
5 | These presets encapsulate best practices, allowing you to focus on your code rather than spending time on intricate setup processes. From popular backend frameworks like Laravel and NestJS to frontend powerhouses like Next.js and NuxtJS, Kool Presets cover a spectrum of technologies, offering you a head start in building robust and scalable applications.
6 |
7 | By leveraging Kool Presets, you not only reduce the learning curve associated with configuring different environments but also ensure consistency across your projects. With Kool, enjoy a unified and efficient development experience, whether you're a solo developer seeking a quick setup or a team collaborating on complex projects. Explore the available presets and elevate your development workflow with Kool Presets today.
8 |
9 | # Popular Presets
10 |
11 | ## Node
12 |
13 | - [NestJS](docs/03-Presets/NestJS.md)
14 | - [AdonisJs](docs/03-Presets/AdonisJs.md)
15 | - [Express.js](/docs/03-Presets/ExpressJS.md)
16 |
17 | ## PHP
18 |
19 | - [Laravel](/docs/03-Presets/Laravel.md)
20 | - [Laravel Octane](/docs/03-Presets/Laravel+Octane.md)
21 | - [Symfony](/docs/03-Presets/Symfony.md)
22 | - [CodeIgniter](/docs/03-Presets/CodeIgniter.md)
23 |
24 | ## Javascript
25 |
26 | - [Next.js](/docs/03-Presets/NextJS.md)
27 | - [NuxtJS](/docs/03-Presets/NuxtJS.md)
28 |
29 | ## Others
30 |
31 | - [Hugo](/docs/03-Presets/Hugo.md)
32 | - [WordPress](/docs/03-Presets/WordPress.md)
33 |
--------------------------------------------------------------------------------
/commands/cloud_deploy_destroy.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "kool-dev/kool/core/environment"
6 | "kool-dev/kool/services/cloud/api"
7 |
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // KoolDeployDestroy holds handlers and functions for using Deploy API
12 | type KoolDeployDestroy struct {
13 | DefaultKoolService
14 |
15 | env environment.EnvStorage
16 | apiDestroy api.DeployDestroy
17 | }
18 |
19 | // NewDeployDestroyCommand initializes new kool deploy Cobra command
20 | func NewDeployDestroyCommand(destroy *KoolDeployDestroy) *cobra.Command {
21 | return &cobra.Command{
22 | Use: "destroy",
23 | Short: "Destroy an environment deployed to Kool.dev Cloud",
24 | Args: cobra.NoArgs,
25 | RunE: DefaultCommandRunFunction(destroy),
26 |
27 | DisableFlagsInUseLine: true,
28 | }
29 | }
30 |
31 | // NewKoolDeployDestroy creates a new pointer with default KoolDeployDestroy service dependencies
32 | func NewKoolDeployDestroy() *KoolDeployDestroy {
33 | return &KoolDeployDestroy{
34 | *newDefaultKoolService(),
35 | environment.NewEnvStorage(),
36 | *api.NewDeployDestroy(),
37 | }
38 | }
39 |
40 | // Execute runs the deploy destroy logic - integrating with Deploy API
41 | func (d *KoolDeployDestroy) Execute(args []string) (err error) {
42 | var (
43 | domain string
44 | resp *api.DeployDestroyResponse
45 | )
46 |
47 | if url := d.env.Get("KOOL_API_URL"); url != "" {
48 | api.SetBaseURL(url)
49 | }
50 |
51 | if domain = d.env.Get("KOOL_DEPLOY_DOMAIN"); domain == "" {
52 | err = fmt.Errorf("missing deploy domain (env KOOL_DEPLOY_DOMAIN)")
53 | return
54 | }
55 |
56 | d.apiDestroy.Query().Set("domain", domain)
57 |
58 | if resp, err = d.apiDestroy.Call(); err != nil {
59 | return
60 | }
61 |
62 | d.Shell().Success(fmt.Sprintf("Environment (ID: %d) scheduled for deleting.", resp.Environment.ID))
63 |
64 | return
65 | }
66 |
--------------------------------------------------------------------------------
/core/shell/fake_table_writer.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "sort"
7 | "strings"
8 | )
9 |
10 | // FakeTableWriter mock table writer for testing
11 | type FakeTableWriter struct {
12 | CalledSetWriter, CalledAppendHeader, CalledAppendRow, CalledRender bool
13 | Headers, Rows [][]interface{}
14 | TableOut string
15 | }
16 |
17 | // SetWriter fake SetWriter behavior
18 | func (f *FakeTableWriter) SetWriter(w io.Writer) {
19 | f.CalledSetWriter = true
20 | }
21 |
22 | // AppendHeader fake AppendHeader behavior
23 | func (f *FakeTableWriter) AppendHeader(columns ...interface{}) {
24 | f.CalledAppendHeader = true
25 | f.Headers = append(f.Headers, columns)
26 | }
27 |
28 | // AppendRow fake AppendRow behavior
29 | func (f *FakeTableWriter) AppendRow(columns ...interface{}) {
30 | f.CalledAppendRow = true
31 | f.Rows = append(f.Rows, columns)
32 | }
33 |
34 | // Render fake Render behavior
35 | func (f *FakeTableWriter) Render() {
36 | f.CalledRender = true
37 |
38 | for _, columns := range f.Headers {
39 | columnsStr := make([]string, len(columns))
40 |
41 | for i := range columns {
42 | columnsStr[i] = fmt.Sprintf("%v", columns[i])
43 | }
44 |
45 | f.TableOut = f.TableOut + fmt.Sprintln(strings.Join(columnsStr, " | "))
46 | }
47 |
48 | for _, columns := range f.Rows {
49 | columnsStr := make([]string, len(columns))
50 |
51 | for i := range columns {
52 | columnsStr[i] = fmt.Sprintf("%v", columns[i])
53 | }
54 |
55 | f.TableOut = f.TableOut + fmt.Sprintln(strings.Join(columnsStr, " | "))
56 | }
57 | }
58 |
59 | // SortBy fake SortBy behavior
60 | func (f *FakeTableWriter) SortBy(column int) {
61 | sort.SliceStable(f.Rows, func(i, j int) bool {
62 | return f.Rows[i][column-1].(string) < f.Rows[j][column-1].(string)
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/core/shell/table_writer_test.go:
--------------------------------------------------------------------------------
1 | package shell
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "strings"
7 | "testing"
8 | )
9 |
10 | func TestNewTableWriter(t *testing.T) {
11 | tableWriter := NewTableWriter()
12 |
13 | if _, ok := tableWriter.(*DefaultTableWriter); !ok {
14 | t.Errorf("NewTableWriter() did not return a *DefaultTableWriter")
15 | }
16 | }
17 | func TestTableWriter(t *testing.T) {
18 | tableWriter := NewTableWriter()
19 |
20 | b := bytes.NewBufferString("")
21 | tableWriter.SetWriter(b)
22 |
23 | tableWriter.AppendHeader("header")
24 |
25 | tableWriter.AppendRow("row")
26 |
27 | tableWriter.Render()
28 |
29 | var (
30 | out []byte
31 | err error
32 | )
33 |
34 | if out, err = io.ReadAll(b); err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | output := strings.TrimSpace(string(out))
39 | expected := `
40 | +--------+
41 | | HEADER |
42 | +--------+
43 | | row |
44 | +--------+
45 | `
46 | expected = strings.TrimSpace(expected)
47 |
48 | if expected != output {
49 | t.Errorf("expecting output '%s', got '%s'", expected, output)
50 | }
51 | }
52 |
53 | func TestSortByTableWriter(t *testing.T) {
54 | tableWriter := NewTableWriter()
55 |
56 | b := bytes.NewBufferString("")
57 | tableWriter.SetWriter(b)
58 |
59 | tableWriter.AppendHeader("header")
60 |
61 | tableWriter.AppendRow("zRow")
62 | tableWriter.AppendRow("aRow")
63 |
64 | tableWriter.SortBy(1)
65 |
66 | tableWriter.Render()
67 |
68 | var (
69 | out []byte
70 | err error
71 | )
72 |
73 | if out, err = io.ReadAll(b); err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | output := strings.TrimSpace(string(out))
78 | expected := `
79 | +--------+
80 | | HEADER |
81 | +--------+
82 | | aRow |
83 | | zRow |
84 | +--------+
85 | `
86 | expected = strings.TrimSpace(expected)
87 |
88 | if expected != output {
89 | t.Errorf("expecting output '%s', got '%s'", expected, output)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/services/cloud/deployer.go:
--------------------------------------------------------------------------------
1 | package cloud
2 |
3 | import (
4 | "kool-dev/kool/core/environment"
5 | "kool-dev/kool/core/shell"
6 | "kool-dev/kool/services/cloud/api"
7 | )
8 |
9 | // Deployer service handles the deployment process
10 | type Deployer struct {
11 | env environment.EnvStorage
12 | out shell.Shell
13 | }
14 |
15 | // NewDeployer creates a new handler for using the
16 | // Kool Dev API for deploying your application.
17 | func NewDeployer() *Deployer {
18 | return &Deployer{
19 | env: environment.NewEnvStorage(),
20 | out: shell.NewShell(),
21 | }
22 | }
23 |
24 | // SendFile integrates with the API to send the tarball
25 | func (d *Deployer) CreateDeploy(tarballPath string) (resp *api.DeployCreateResponse, err error) {
26 | var create = api.NewDeployCreate()
27 |
28 | if err = create.PostFile("deploy", tarballPath, "deploy.tgz"); err != nil {
29 | return
30 | }
31 |
32 | _ = create.PostField("cluster", d.env.Get("KOOL_CLOUD_CLUSTER"))
33 | _ = create.PostField("domain", d.env.Get("KOOL_DEPLOY_DOMAIN"))
34 | _ = create.PostField("domain_extras", d.env.Get("KOOL_DEPLOY_DOMAIN_EXTRAS"))
35 | _ = create.PostField("www_redirect", d.env.Get("KOOL_DEPLOY_WWW_REDIRECT"))
36 |
37 | resp, err = create.Run()
38 |
39 | return
40 | }
41 |
42 | func (d *Deployer) StartDeploy(created *api.DeployCreateResponse) (started *api.DeployStartResponse, err error) {
43 | var start = api.NewDeployStart(created)
44 |
45 | started, err = start.Run()
46 | return
47 | }
48 |
49 | func (d *Deployer) PingDeploy(created *api.DeployCreateResponse) (pinged *api.DeployPingResponse, err error) {
50 | var ping = api.NewDeployPing(created)
51 |
52 | pinged, err = ping.Run()
53 | return
54 | }
55 |
56 | func (d *Deployer) BuildError(created *api.DeployCreateResponse, gotErr error) (err error) {
57 | var buildErr = api.NewDeployError(created, gotErr)
58 |
59 | err = buildErr.Run()
60 | return
61 | }
62 |
--------------------------------------------------------------------------------
/services/yamler/merger_test.go:
--------------------------------------------------------------------------------
1 | package yamler
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "gopkg.in/yaml.v3"
8 | )
9 |
10 | func testMerger(t *testing.T, foo, bar, expected string) {
11 | var y1, y2 = new(yaml.Node), new(yaml.Node)
12 |
13 | _ = yaml.Unmarshal([]byte(foo), y1)
14 | _ = yaml.Unmarshal([]byte(bar), y2)
15 |
16 | m := &DefaultMerger{}
17 |
18 | if err := m.Merge(y2, y1); err != nil {
19 | t.Errorf("unexpected error merging yaml: %v", err)
20 | }
21 |
22 | bs, _ := yaml.Marshal(y1)
23 | merged := strings.Trim(string(bs), " \t\n")
24 | if merged != expected {
25 | t.Errorf("unexpected merge result: expected '%s' but got '%s'", expected, merged)
26 | }
27 | }
28 |
29 | func TestMergerMap(t *testing.T) {
30 | foo := "foo: yyy"
31 | bar := "bar: xxx"
32 | expected := foo + "\n" + bar
33 |
34 | testMerger(t, foo, bar, expected)
35 | }
36 |
37 | func TestMergerMapOverride(t *testing.T) {
38 | foo := "foo: yyy"
39 | bar := "bar: xxx\nfoo: zzz"
40 | expected := "foo: zzz\nbar: xxx"
41 |
42 | testMerger(t, foo, bar, expected)
43 | }
44 |
45 | func TestMergerComments(t *testing.T) {
46 | comment := "# comment\nfoo: test"
47 | bar := "bar: xxx"
48 | expected := comment + "\n" + bar
49 |
50 | testMerger(t, comment, bar, expected)
51 | }
52 |
53 | func TestMergerListAppend(t *testing.T) {
54 | foo := "foo: [yyy, zzz]"
55 | bar := "foo: [xxx]"
56 | expected := "foo: [yyy, zzz, xxx]"
57 |
58 | testMerger(t, foo, bar, expected)
59 | }
60 |
61 | func TestMergerMapNested(t *testing.T) {
62 | foo := "foo:"
63 | bar := "foo:\n bar: xxx"
64 | expected := "foo:\n bar: xxx"
65 |
66 | testMerger(t, foo, bar, expected)
67 | }
68 |
69 | func TestMergerMapNestedOverride(t *testing.T) {
70 | foo := "foo:\n bar: yyy\n zuk: ppp"
71 | bar := "foo:\n bar: xxx"
72 | expected := "foo:\n bar: xxx\n zuk: ppp"
73 |
74 | testMerger(t, foo, bar, expected)
75 | }
76 |
--------------------------------------------------------------------------------
/services/cloud/api/errors.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | // ErrBadAPIServer represents some issue in the API side
10 | var ErrBadAPIServer error
11 |
12 | // ErrMissingToken is the lack of the access token
13 | var ErrMissingToken error
14 |
15 | // ErrDeployFailed is returned when checking the status of a failed deploy
16 | var ErrDeployFailed error
17 |
18 | // ErrUnauthorized unauthorized; please check your KOOL_API_TOKEN
19 | var ErrUnauthorized error
20 |
21 | // failed validating deploy payload
22 | var ErrPayloadValidation error
23 |
24 | // ErrBadResponseStatus unexpected return status
25 | var ErrBadResponseStatus error
26 |
27 | // ErrUnexpectedResponse bad API response; please ask for support
28 | var ErrUnexpectedResponse error
29 |
30 | // ErrAPI reprents a default error returned from the API
31 | type ErrAPI struct {
32 | Status int
33 |
34 | Message string `json:"message"`
35 | Errors map[string]interface{} `json:"errors"`
36 | }
37 |
38 | // Error returns the string representation for the error
39 | func (e *ErrAPI) Error() string {
40 | if e.Errors != nil {
41 | s := []string{}
42 | for k, e := range e.Errors {
43 | s = append(s, fmt.Sprintf("\t%s > %v", k, e.([]interface{})[0]))
44 | }
45 | return fmt.Sprintf("\n%d - %s\n\n%s\n", e.Status, e.Message, strings.Join(s, "\n"))
46 | }
47 | return fmt.Sprintf("\n%d - %s\n", e.Status, e.Message)
48 | }
49 |
50 | func init() {
51 | ErrBadAPIServer = errors.New("bad API server response")
52 | ErrDeployFailed = errors.New("deploy process has failed")
53 | ErrUnauthorized = errors.New("unauthorized; please check your KOOL_API_TOKEN")
54 | ErrPayloadValidation = errors.New("failed validating deploy payload")
55 | ErrBadResponseStatus = errors.New("unexpected return status")
56 | ErrUnexpectedResponse = errors.New("bad API response; please ask for support")
57 | ErrMissingToken = errors.New("missing KOOL_API_TOKEN")
58 | }
59 |
--------------------------------------------------------------------------------
/docs/05-Commands-Reference/0-kool.md:
--------------------------------------------------------------------------------
1 | ## kool
2 |
3 | Cloud native environments made easy
4 |
5 | ### Synopsis
6 |
7 | From development to production, a robust and easy-to-use developer tool
8 | that makes Docker container adoption quick and easy for building and deploying cloud native
9 | applications.
10 |
11 | Complete documentation is available at https://kool.dev/docs
12 |
13 | ```
14 | kool
15 | ```
16 |
17 | ### Options
18 |
19 | ```
20 | -h, --help help for kool
21 | --verbose Increases output verbosity
22 | -w, --working_dir string Changes the working directory for the command
23 | ```
24 |
25 | ### SEE ALSO
26 |
27 | * [kool cloud](kool-cloud) - Interact with Kool.dev Cloud and manage your deployments.
28 | * [kool create](kool-create) - Create a new project using a preset
29 | * [kool docker](kool-docker) - Create a new container (a powered up 'docker run')
30 | * [kool exec](kool-exec) - Execute a command inside a running service container
31 | * [kool info](kool-info) - Print out information about the local environment
32 | * [kool logs](kool-logs) - Display log output from running service containers
33 | * [kool preset](kool-preset) - Install configuration files customized for Kool in the current directory
34 | * [kool recipe](kool-recipe) - Adds configuration for some recipe in the current work directory.
35 | * [kool restart](kool-restart) - Restart running service containers (the same as 'kool stop' followed by 'kool start')
36 | * [kool run](kool-run) - Execute a script defined in kool.yml
37 | * [kool self-update](kool-self-update) - Update kool to the latest version
38 | * [kool share](kool-share) - Live share your local environment on the Internet using an HTTP tunnel
39 | * [kool start](kool-start) - Start service containers defined in docker-compose.yml
40 | * [kool status](kool-status) - Show the status of all service containers
41 | * [kool stop](kool-stop) - Stop and destroy running service containers
42 |
43 |
--------------------------------------------------------------------------------
/core/network/network_test.go:
--------------------------------------------------------------------------------
1 | package network
2 |
3 | import (
4 | "kool-dev/kool/core/builder"
5 | "kool-dev/kool/core/shell"
6 | "testing"
7 | )
8 |
9 | func TestDefaultHandler(t *testing.T) {
10 | var c Handler = NewHandler(&shell.FakeShell{})
11 |
12 | if _, assert := c.(*DefaultHandler); !assert {
13 | t.Errorf("NewHandler() did not return a *DefaultHandler")
14 | }
15 | }
16 |
17 | func TestGlobalNetworkExists(t *testing.T) {
18 | var h Handler
19 |
20 | checkNetCmd := &builder.FakeCommand{MockExecOut: "NetworkID"}
21 | checkNetCmd.MockCmd = "check"
22 |
23 | createNetCmd := &builder.FakeCommand{}
24 | createNetCmd.MockCmd = "create"
25 |
26 | s := &shell.FakeShell{}
27 | h = &DefaultHandler{checkNetCmd, createNetCmd, s}
28 |
29 | err := h.HandleGlobalNetwork("global_network")
30 |
31 | if val, ok := h.(*DefaultHandler).shell.(*shell.FakeShell).CalledExec["check"]; !val || !ok {
32 | t.Errorf("HandleGlobalNetwork() did not check if network exists.")
33 | }
34 |
35 | if val, ok := h.(*DefaultHandler).shell.(*shell.FakeShell).CalledInteractive["create"]; val && ok {
36 | t.Errorf("HandleGlobalNetwork() should not try to create the global network if it already exists.")
37 | }
38 |
39 | if err != nil {
40 | t.Errorf("Expected no errors, got %v", err)
41 | }
42 | }
43 |
44 | func TestGlobalNetworkNotExists(t *testing.T) {
45 | var h Handler
46 |
47 | checkNetCmd := &builder.FakeCommand{}
48 | checkNetCmd.MockCmd = "check"
49 |
50 | createNetCmd := &builder.FakeCommand{}
51 | createNetCmd.MockCmd = "create"
52 |
53 | s := &shell.FakeShell{}
54 | h = &DefaultHandler{checkNetCmd, createNetCmd, s}
55 |
56 | err := h.HandleGlobalNetwork("global_network")
57 |
58 | if err != nil {
59 | t.Errorf("Expected no errors, got %v", err)
60 | }
61 |
62 | if val, ok := h.(*DefaultHandler).shell.(*shell.FakeShell).CalledInteractive["create"]; !val || !ok {
63 | t.Errorf("HandleGlobalNetwork() is not trying to create the global network when it not exists.")
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: '$RESOLVED_VERSION'
2 | tag-template: '$RESOLVED_VERSION'
3 | autolabeler:
4 | - label: 'release-drafter:added'
5 | body:
6 | - '/Feature\s*\|\s*Yes/'
7 | - label: 'release-drafter:changed'
8 | body:
9 | - '/Improvement\s*\|\s*Yes/'
10 | - '/Refactor\s*\|\s*Yes/'
11 | - label: 'release-drafter:fixed'
12 | body:
13 | - '/Bug Fix\s*\|\s*Yes/'
14 | - label: 'release-drafter:documentation'
15 | body:
16 | - '/Documentation\s*\|\s*Yes/'
17 | - label: 'release-drafter:removed'
18 | body:
19 | - '/Removed\s*\|\s*Yes/'
20 | - label: 'release-drafter:major'
21 | body:
22 | - '/Removed\s*\|\s*Yes/'
23 | - '/Break Change\s*\|\s*Yes/'
24 | - label: 'release-drafter:minor'
25 | body:
26 | - '/Feature\s*\|\s*Yes/'
27 | - '/Improvement\s*\|\s*Yes/'
28 | - '/Refactor\s*\|\s*Yes/'
29 | - label: 'release-drafter:patch'
30 | body:
31 | - '/Bug Fix\s*\|\s*Yes/'
32 | - '/Documentation\s*\|\s*Yes/'
33 | categories:
34 | - title: 'Added'
35 | labels:
36 | - 'feature'
37 | - 'new'
38 | - 'release-drafter:added'
39 | - title: 'Changed'
40 | labels:
41 | - 'enhancement'
42 | - 'chore'
43 | - 'refactor'
44 | - 'release-drafter:changed'
45 | - title: 'Fixed'
46 | labels:
47 | - 'fix'
48 | - 'bugfix'
49 | - 'bug'
50 | - 'release-drafter:fixed'
51 | - title: 'Documentation'
52 | labels:
53 | - 'docs'
54 | - 'documentation'
55 | - 'release-drafter:documentation'
56 | - title: 'Removed'
57 | labels:
58 | - 'remove'
59 | - 'drop'
60 | - 'release-drafter:removed'
61 |
62 | exclude-labels:
63 | - 'release-drafter:skip'
64 |
65 | version-resolver:
66 | major:
67 | labels:
68 | - 'release-drafter:major'
69 | minor:
70 | labels:
71 | - 'release-drafter:minor'
72 | patch:
73 | labels:
74 | - 'release-drafter:patch'
75 | default: patch
76 |
77 | template: |
78 | $CHANGES
79 |
--------------------------------------------------------------------------------
/commands/cloud_deploy_destroy_test.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "kool-dev/kool/core/environment"
6 | "kool-dev/kool/core/shell"
7 | "kool-dev/kool/services/cloud/api"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | func TestNewDeployDestroyCommand(t *testing.T) {
13 | destroy := NewKoolDeployDestroy()
14 | cmd := NewDeployDestroyCommand(destroy)
15 | if cmd.Use != "destroy" {
16 | t.Errorf("bad command use: %s", cmd.Use)
17 | }
18 |
19 | if _, ok := destroy.env.(*environment.DefaultEnvStorage); !ok {
20 | t.Error("unexpected default env on destroy")
21 | }
22 | }
23 |
24 | func TestDeployDestroyExec(t *testing.T) {
25 | destroy := &KoolDeployDestroy{
26 | *(newDefaultKoolService().Fake()),
27 | environment.NewFakeEnvStorage(),
28 | *api.NewDeployDestroy(),
29 | }
30 |
31 | destroy.env.Set("KOOL_API_TOKEN", "fake token")
32 | destroy.env.Set("KOOL_API_URL", "fake-url")
33 |
34 | args := []string{}
35 |
36 | if err := destroy.Execute(args); !strings.Contains(err.Error(), "missing deploy domain") {
37 | t.Errorf("unexpected error - expected missing deploy domain, got: %v", err)
38 | }
39 |
40 | destroy.env.Set("KOOL_DEPLOY_DOMAIN", "domain.com")
41 |
42 | destroy.apiDestroy.Endpoint.(*api.DefaultEndpoint).Fake()
43 | destroy.apiDestroy.Endpoint.(*api.DefaultEndpoint).MockErr(errors.New("failed call"))
44 |
45 | if err := destroy.Execute(args); !strings.Contains(err.Error(), "failed call") {
46 | t.Errorf("unexpected error - expected failed call, got: %v", err)
47 | }
48 |
49 | destroy.apiDestroy.Endpoint.(*api.DefaultEndpoint).MockErr(nil)
50 | destroy.apiDestroy.Endpoint.(*api.DefaultEndpoint).MockResp(&api.DeployDestroyResponse{
51 | Environment: struct {
52 | ID int `json:"id"`
53 | }{
54 | ID: 100,
55 | },
56 | })
57 |
58 | if err := destroy.Execute(args); err != nil {
59 | t.Errorf("unexpected error, got: %v", err)
60 | }
61 |
62 | if !strings.Contains(destroy.shell.(*shell.FakeShell).SuccessOutput[0].(string), "ID: 100") {
63 | t.Errorf("did not get success message")
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/docs/02-Kool-Cloud/05-Environment-Variables.md:
--------------------------------------------------------------------------------
1 | Most applications and frameworks will rely on environment variables to configure important aspects of their functions, specially for providing credentials and other secrets your app needs to work and access other resources.
2 |
3 | Kool.dev Cloud supports a few different ways you can define your environment variables for a deploying container, so pick the one that best suits you.
4 |
5 | ### Using `kool.deploy.env` file for deploy
6 |
7 | `kool.deploy.env` should be a `.env` formatted file. You can point to it like this:
8 |
9 | ```yaml
10 | services:
11 | app:
12 | # ...
13 | environment: kool.deploy.env
14 | ```
15 |
16 | Upon deployment, all of the variables within that file will be parsed, placeholders replaced—if you have any—and then **each variable will become a real environment variable in the running container**.
17 |
18 | This option is usually best suited for automated CI routines since you work your way to have a different `kool.deploy.env` file for each of your deploying environments (i.e., staging and production).
19 |
20 | ### Using a plain YAML object for environment variables
21 |
22 | ```yaml
23 | services:
24 | app:
25 | # ...
26 | environment:
27 | FOO: bar
28 | ```
29 |
30 | You can simply use a YAML map of values that will become your environment variables in the running deployed container. This is handy sometimes when you have simple and not sensitive variables you want to add to a container for deploy.
31 |
32 | ### Build a `.env` file inside the running container
33 |
34 | If your application does rely on and requires a `.env` file existing in the running container, you may achieve so by using the `env:` entry:
35 |
36 | ```yaml
37 | services:
38 | app:
39 | # ...
40 |
41 | # 'env' is a different option that allows you to build a file inside your running container.
42 | env:
43 | source: kool.deploy.env
44 | target: .env
45 | ```
46 |
47 | This is useful for apps that require the .env file, but you do not wish to have that built into your Docker image itself.
48 |
--------------------------------------------------------------------------------