require 'yaml' require 'securerandom' module Kontena::Cli::Apps class InitCommand < Clamp::Command include Kontena::Cli::Common option ["-f", "--file"], "FILE", "Specify a docker-compose file", attribute_name: :docker_compose_file, default: 'docker-compose.yml' option ["-i", "--image-name"], "IMAGE_NAME", "Specify a docker image name" option ["-b", "--base-image"], "BASE_IMAGE_NAME", "Specify a docker base image name", default: "kontena/buildstep" option ["-p", "--project-name"], "NAME", "Specify an alternate project name (default: directory name)" attr_reader :service_prefix def execute require 'highline/import' @service_prefix = project_name || File.basename(Dir.getwd) if File.exist?('Dockerfile') puts 'Found Dockerfile' else create_dockerfile if create_dockerfile? end if File.exist?(docker_compose_file) puts "Found #{docker_compose_file}." else if File.exist?('Procfile') procfile = YAML.load(File.read('Procfile')) else procfile = {} end app_env = nil addons = [] if app_json app_env = create_env_file(app_json) addons = app_json['addons'] || [] end create_docker_compose_yml(procfile, addons, app_env) if create_docker_compose_yml? end services = generate_kontena_services(docker_compose_file) if File.exist?('kontena.yml') merge_kontena_yml!(services) puts "#{'kontena.yml'.colorize(:cyan)} updated." else puts "Creating #{'kontena.yml'.colorize(:cyan)}" end create_yml(services, 'kontena.yml') puts "Your app is ready! Deploy with 'kontena app deploy'.".colorize(:green) end protected def app_json if !@app_json && File.exist?('app.json') @app_json = JSON.parse(File.read('app.json')) end @app_json end def create_dockerfile? ['', 'y', 'yes'].include? ask('Dockerfile not found. Do you want to create it? [Yn]: ').downcase end def create_env_file(app_json) if app_json['env'] app_env = File.new('.env', 'w') app_json['env'].each do |key, env| if env['generator'] == 'secret' value = SecureRandom.hex(64) else value = env['value'] end app_env.puts "#{key}=#{value}" end app_env.close return '.env' end nil end def current_user token = require_token client(token).get('user') rescue '' end def create_dockerfile puts "Creating #{'Dockerfile'.colorize(:cyan)}" dockerfile = File.new('Dockerfile', 'w') dockerfile.puts "FROM #{base_image}" dockerfile.puts "MAINTAINER #{current_user['email']}" dockerfile.puts 'CMD ["/start", "web"]' dockerfile.close end def merge_kontena_yml!(services) puts "kontena.yml already exists. Merging changes." kontena_services = YAML.load(File.read('kontena.yml')) services.each do |name, options| if kontena_services[name] services[name].merge!(kontena_services[name]) end end end def create_docker_compose_yml? ['', 'y', 'yes'].include? ask("#{docker_compose_file} not found. Do you want to create it? [Yn]: ").downcase end def create_docker_compose_yml(procfile, addons, env_file) puts "Creating #{docker_compose_file.colorize(:cyan)}" if procfile.keys.size > 0 # generate services found in Procfile docker_compose = {} procfile.keys.each do |service| docker_compose[service] = {'build' => '.' } docker_compose[service]['environment'] = ['PORT=5000'] if app_json && service == 'web' # Heroku generates PORT env variable so should we do too docker_compose[service]['command'] = "/start #{service}" if service != 'web' docker_compose[service]['env_file'] = env_file if env_file # generate addon services addons.each do |addon| addon_service = addon.split(":")[0] addon_service.slice!('heroku-') if valid_addons.has_key?(addon_service) docker_compose[service]['links'] = [] unless docker_compose[service]['links'] docker_compose[service]['links'] << "#{addon_service}:#{addon_service}" docker_compose[service]['environment'] = [] unless docker_compose[service]['environment'] docker_compose[service]['environment'] += valid_addons[addon_service]['environment'] docker_compose[addon_service] = {'image' => valid_addons[addon_service]['image']} end end end else # no Procfile found, create dummy web service docker_compose = {'web' => { 'build' => '.'}} docker_compose['web']['env_file'] = env_file if env_file end # create docker-compose.yml file create_yml(docker_compose, docker_compose_file) end def generate_kontena_services(docker_compose = nil) services = {} if docker_compose && File.exist?(docker_compose) # extend services from docker-compose.yml compose_services = YAML.load(File.read(docker_compose)) compose_services.each do |name, options| services[name] = {'extends' => { 'file' => 'docker-compose.yml', 'service' => name }} if options.has_key?('build') image = image_name || "registry.kontena.local/#{File.basename(Dir.getwd)}:latest" services[name]['image'] = image end # we have to generate Kontena urls to env vars for Heroku addons # redis://openredis:6379 -> redis://project-name-openredis:6379 if options['links'] options['links'].each do |link| service_link = link.split(':').first if valid_addons.has_key?(service_link) services[name]['environment'] ||= [] services[name]['environment'] += valid_addons(service_prefix)[service_link]['environment'] end end end end else # no docker-compose.yml found, just create dummy service with image name services = {'web' => { 'image' => "registry.kontena.local/#{File.basename(Dir.getwd)}:latest" }} end services end def create_yml(services, file='kontena.yml') yml = File.new(file, 'w') yml.puts services.to_yaml yml.close end def valid_addons(prefix=nil) if prefix prefix = "#{prefix}-" end { 'openredis' => { 'image' => 'redis:latest', 'environment' => ["REDIS_URL=redis://#{prefix}openredis:6379"] }, 'redis' => { 'image' => 'redis:latest', 'environment' => ["REDIS_URL=redis://#{prefix}redis:6379"] }, 'rediscloud' => { 'image' => 'redis:latest', 'environment' => ["REDISCLOUD_URL=#{prefix}redis://rediscloud:6379"] }, 'postgresql' => { 'image' => 'postgres:latest', 'environment' => ["DATABASE_URL=postgres://#{prefix}postgres:@postgresql:5432/postgres"] }, 'mongolab' => { 'image' => 'mongo:latest', 'environment' => ["MONGOLAB_URI=#{prefix}mongolab:27017"] }, 'memcachedcloud' => { 'image' => 'memcached:latest', 'enviroment' => ["MEMCACHEDCLOUD_SERVERS=#{prefix}memcachedcloud:11211"] } } end end end