├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── bin └── kapten ├── kapten.gemspec └── lib ├── drydock └── helpers.rb ├── kapten.rb └── kapten ├── config.rb ├── docker.rb └── helpers.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'thor' 4 | gem 'colorize' 5 | gem 'docker-api' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | colorize (0.8.1) 5 | docker-api (1.34.2) 6 | excon (>= 0.47.0) 7 | multi_json 8 | excon (0.72.0) 9 | multi_json (1.14.1) 10 | thor (1.0.1) 11 | 12 | PLATFORMS 13 | ruby 14 | 15 | DEPENDENCIES 16 | colorize 17 | docker-api 18 | thor 19 | 20 | BUNDLED WITH 21 | 1.17.2 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fabian Lindfors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kapten 2 | 3 | Create simple and containerized development environments from the command line. Say goodbye to dependency issues, language version management, and complicated setups. 4 | 5 | Supports: 6 | - Ruby 7 | - Python 8 | - Node.js 9 | - Elixir 10 | - PHP 11 | - Go 12 | - Haskell 13 | - Java 14 | - Perl 15 | 16 | 17 | ## Installation 18 | 19 | `$ gem install kapten` 20 | 21 | Kapten is built upon and requires [Docker](https://www.docker.com). 22 | 23 | 24 | ## Usage 25 | 26 | Creating a development environment with Kapten requires only two commands. Start by navigating to your project's root directory and run: 27 | 28 | `$ kapten init ruby|python|node|elixir|php|go|haskell|java|perl` 29 | 30 | After initialization you can start your environment by running: 31 | 32 | `$ kapten start` 33 | 34 | Your isolated environment will now set itself up and once finished boot into a shell with all the necessities for your chosen language. If you need more than one shell simply run `kapten start` again. 35 | 36 | ### Docker and containers 37 | 38 | Kapten environments are really barebones Docker containers into which your projects files are mounted. The containers will continue running in the background after you disconnect. It's recommended to stop an environment with `$ kapten stop` once finished. 39 | 40 | More commands: 41 | ``` 42 | # Get info about the current environment and its status. 43 | $ kapten info 44 | 45 | # Remove environment and container. Use 'kapten start' to start fresh. 46 | $ kapten destroy 47 | 48 | # Fully remove Kapten from your project. 49 | $ kapten remove 50 | ``` 51 | 52 | ## License 53 | Kapten is licensed under [MIT](https://github.com/Fabianlindfors/kapten/blob/master/LICENSE). 54 | -------------------------------------------------------------------------------- /bin/kapten: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "kapten" 4 | 5 | Kapten::CLI.start 6 | -------------------------------------------------------------------------------- /kapten.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'kapten' 3 | s.version = '0.1.2' 4 | s.summary = "Simple containerized development environments directly from the command line" 5 | s.description = "Kapten is a tool for creating simple containerized development environments directly from the command line. Check out the Github repo for usage instructions." 6 | s.authors = ["Fabian Lindfors"] 7 | s.email = 'fabian@flapplabs.se' 8 | s.files = ["lib/kapten.rb"] 9 | s.files += Dir['lib/kapten/*.rb'] 10 | s.files += ['bin/kapten'] 11 | s.executables = ['kapten'] 12 | s.homepage = 'https://github.com/fabianlindfors/kapten' 13 | s.license = 'MIT' 14 | end 15 | -------------------------------------------------------------------------------- /lib/drydock/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'colorize' 2 | 3 | puts "hej" 4 | 5 | module Kapten::Helpers 6 | 7 | TYPES = { 8 | 'ruby' => 'ruby:latest', 9 | 'python' => 'python:latest', 10 | 'node' => 'node:latest', 11 | 'elixir' => 'elixir:latest', 12 | 'php' => 'php:latest', 13 | 'go' => 'golang:latest', 14 | 'haskell' => 'haskell:latest', 15 | 'java' => 'openjdk:latest', 16 | 'perl' => 'perl:latest', 17 | } 18 | 19 | def self.validate_install 20 | 21 | config = Kapten::Config::get 22 | 23 | unless config 24 | puts 'Kapten not initalized'.red 25 | puts 'Run "kapten init" to get started'.red 26 | end 27 | 28 | return config 29 | 30 | end 31 | 32 | 33 | def self.get_image(type) 34 | 35 | return Kapten::Helpers::TYPES[ type ] 36 | 37 | end 38 | 39 | 40 | def self.get_types 41 | 42 | return Kapten::Helpers::TYPES.keys 43 | 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /lib/kapten.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'colorize' 3 | 4 | module Kapten;end 5 | 6 | require 'kapten/helpers' 7 | require 'kapten/docker' 8 | require 'kapten/config' 9 | 10 | class Kapten::CLI < Thor 11 | 12 | desc 'init [TYPE]', 'Initalize Kapten in the current directory' 13 | method_option :name, :aliases => "-n", :desc => "Specify a name. Defaults to the current directory name." 14 | long_desc <<-LONGDESC 15 | Initalize Kapten in the current directory with a specified environment type 16 | 17 | Available types are: #{Kapten::Helpers::get_types.join(', ')} 18 | LONGDESC 19 | def init(type) 20 | 21 | if Kapten::Config.get 22 | puts 'Environment already initialized' 23 | puts 'Use "kapten start" to get developing' 24 | return 25 | end 26 | 27 | types = Kapten::Helpers::get_types 28 | 29 | if not types.include?(type) 30 | puts 'No such environment type'.red 31 | puts 'Available environment types are: ' + types.join(', ') 32 | return 33 | end 34 | 35 | name = options[:name] || File.basename(Dir.getwd) 36 | 37 | config = Kapten::Config::generate(type, name) 38 | 39 | Kapten::Config::save(config) 40 | 41 | puts 'Kapten initialized!'.green 42 | puts 'Use "kapten start" to load your environment' 43 | 44 | end 45 | 46 | 47 | desc 'start', 'Run and attach environemnt' 48 | def start 49 | 50 | config = Kapten::Helpers::validate_install 51 | return unless config 52 | 53 | image = Kapten::Helpers::get_image( config['type'] ) 54 | 55 | Kapten::DockerApi::start( config['name'], image ) 56 | 57 | end 58 | 59 | 60 | desc 'stop', 'Stop environment' 61 | def stop 62 | 63 | config = Kapten::Helpers::validate_install 64 | return unless config 65 | 66 | puts 'Stopping Kapten environment...' 67 | 68 | results = Kapten::DockerApi::stop( config['name'] ) 69 | 70 | puts 'Environment no longer active!'.green 71 | 72 | end 73 | 74 | 75 | desc 'destroy', 'Destroy environment (use "kapten start" to recreate)' 76 | def destroy 77 | 78 | config = Kapten::Helpers::validate_install 79 | return unless config 80 | 81 | unless Kapten::DockerApi::get_container( config['name'] ) 82 | 83 | puts 'No environment to destroy!' 84 | puts 'Run "kapten start" to set it up' 85 | return 86 | 87 | end 88 | 89 | 90 | puts 'Destroying environment...' 91 | 92 | results = Kapten::DockerApi::destroy( config['name'] ) 93 | 94 | puts 'Environment destroyed! Rebuild it by running "kapten start".'.green 95 | 96 | end 97 | 98 | 99 | desc 'info', 'Info about current environment' 100 | def info 101 | 102 | config = Kapten::Helpers::validate_install 103 | return unless config 104 | 105 | container = Kapten::DockerApi::get_container( config["name"] ) 106 | 107 | if not container 108 | status = 'Not created' 109 | else 110 | status = "Not running".yellow 111 | status = "Running".green if container.json["State"]["Running"] 112 | end 113 | 114 | puts '' 115 | puts 'Name: ' + config['name'] 116 | puts 'Type: ' + config['type'] 117 | puts 'Status: ' + status 118 | puts '' 119 | 120 | end 121 | 122 | 123 | desc 'remove', 'Fully remove Kapten from the current project' 124 | def remove 125 | 126 | config = Kapten::Helpers::validate_install 127 | return unless config 128 | 129 | Kapten::Helpers::remove( config['name'] ) 130 | 131 | puts 'All traces of Kapten have been removed'.green 132 | 133 | end 134 | 135 | end 136 | -------------------------------------------------------------------------------- /lib/kapten/config.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module Kapten::Config 4 | 5 | CONFIG_FILE = ".kapten.json" 6 | 7 | 8 | # Get config from current directory (.kapten.json file) 9 | def self.get 10 | 11 | return false unless File.file?( Kapten::Config::CONFIG_FILE ) 12 | 13 | config_contents = File.read( Kapten::Config::CONFIG_FILE ) 14 | 15 | config = JSON.parse(config_contents) 16 | 17 | end 18 | 19 | 20 | # Generate a basic config file 21 | def self.generate(type, name) 22 | 23 | config = { 24 | type: type, 25 | name: name, 26 | } 27 | 28 | return config 29 | 30 | end 31 | 32 | 33 | # Update config file with new data 34 | def self.save(config) 35 | File.write( Kapten::Config::CONFIG_FILE, config.to_json ) 36 | return true 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/kapten/docker.rb: -------------------------------------------------------------------------------- 1 | require 'docker' 2 | require 'colorize' 3 | 4 | module Kapten::DockerApi 5 | 6 | # Check if Docker is installed 7 | def self.has_docker? 8 | 9 | begin 10 | 11 | version = Docker.version 12 | return true if version 13 | 14 | rescue 15 | 16 | return false 17 | 18 | end 19 | 20 | return false 21 | 22 | end 23 | 24 | 25 | # Get Docker container by Kapten name 26 | def self.get_container(name) 27 | 28 | begin 29 | 30 | container = Docker::Container.get('kapten_' + name) 31 | return container 32 | 33 | rescue 34 | 35 | return false 36 | 37 | end 38 | 39 | end 40 | 41 | 42 | # Get Docker image by name 43 | def self.get_image(image) 44 | 45 | begin 46 | 47 | image = Docker::Image.get( image ) 48 | return image 49 | 50 | rescue 51 | 52 | return false 53 | 54 | end 55 | 56 | end 57 | 58 | 59 | # Start Docker container, attaches STDIN and STDOUT 60 | def self.start(name, image) 61 | 62 | container = Kapten::DockerApi::get_container(name) 63 | 64 | if not container 65 | 66 | # Pull image if not installed 67 | docker_image = Kapten::DockerApi::get_image( image ) 68 | 69 | unless docker_image 70 | 71 | puts "First time running, installing environment (this might take a few minutes)...".green 72 | docker_image = Docker::Image.create( 'fromImage' => image ) 73 | 74 | end 75 | 76 | 77 | puts "Creating environment...".green 78 | 79 | container = Docker::Container.create( 80 | 'Image' => image, 81 | 'name' => 'kapten_' + name, 82 | 'Hostname' => name, 83 | 'Cmd' => ['/bin/bash'], 84 | "OpenStdin" => true, 85 | "StdinOnce" => true, 86 | "Tty" => true, 87 | "WorkingDir" => "/usr/src/" + name, 88 | "Hostconfig" => { 89 | "Binds" => [Dir.pwd + ":/usr/src/" + name] 90 | } 91 | ) 92 | 93 | end 94 | 95 | container.start 96 | puts "---------------------------------".green 97 | puts 'Kapten: You\'re now inside the development environment, go wild! (use "exit" to get out of here)'.green 98 | 99 | 100 | # Connect to container shell with both STDIN and STDOUT 101 | require "io/console" 102 | STDIN.raw do |stdin| 103 | container.exec(["bash"], stdin: stdin, tty: true) do |chunk| 104 | print chunk 105 | end 106 | end 107 | 108 | end 109 | 110 | 111 | # Stop running container 112 | def self.stop(name) 113 | 114 | container = Kapten::DockerApi::get_container(name) 115 | 116 | if container 117 | 118 | container.stop 119 | return true 120 | 121 | end 122 | 123 | return false 124 | 125 | end 126 | 127 | 128 | # Fully remove container 129 | def self.destroy(name) 130 | 131 | container = Kapten::DockerApi::get_container(name) 132 | 133 | if container 134 | 135 | container.stop 136 | container.delete(:force => true) 137 | return true 138 | 139 | end 140 | 141 | return false 142 | 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /lib/kapten/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'colorize' 2 | 3 | module Kapten::Helpers 4 | 5 | TYPES = { 6 | 'ruby' => 'ruby:latest', 7 | 'python' => 'python:latest', 8 | 'node' => 'node:latest', 9 | 'elixir' => 'elixir:latest', 10 | 'php' => 'php:latest', 11 | 'go' => 'golang:latest', 12 | 'haskell' => 'haskell:latest', 13 | 'java' => 'openjdk:latest', 14 | 'perl' => 'perl:latest', 15 | } 16 | 17 | 18 | # Validate that Kapten is initalized and Docker is installed 19 | def self.validate_install 20 | 21 | config = Kapten::Config::get 22 | 23 | # Make sure a config exists (Kapten has been initialized) 24 | unless config 25 | puts 'Kapten not initalized'.red 26 | puts 'Run "kapten init" to get started'.red 27 | return false 28 | end 29 | 30 | # Make sure Docker is instsalled 31 | unless Kapten::DockerApi::has_docker? 32 | puts 'Kapten requires Docker'.red 33 | puts 'Install and then try running command again: https://www.docker.com'.red 34 | return false 35 | end 36 | 37 | return config 38 | 39 | end 40 | 41 | 42 | # Get Docker image by environment type 43 | def self.get_image(type) 44 | 45 | return Kapten::Helpers::TYPES[ type ] 46 | 47 | end 48 | 49 | 50 | # Get all available environment types 51 | def self.get_types 52 | 53 | return Kapten::Helpers::TYPES.keys 54 | 55 | end 56 | 57 | 58 | # Remove all traces of Kapten (stop and destory container, delete config file) 59 | def self.remove(name) 60 | 61 | Kapten::DockerApi::destroy( name ) 62 | 63 | File.delete( Kapten::Config::CONFIG_FILE ) 64 | 65 | end 66 | 67 | end 68 | --------------------------------------------------------------------------------