minke 0.16.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +8 -8
  2. data/.codeclimate.yml +7 -0
  3. data/.gitignore +1 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +4 -1
  6. data/Gemfile +5 -0
  7. data/Guardfile +70 -0
  8. data/README.md +12 -9
  9. data/bin/minke +48 -0
  10. data/examples/config_go.yml +44 -25
  11. data/lib/minke/config/config.rb +326 -0
  12. data/lib/minke/config/reader.rb +94 -0
  13. data/lib/minke/docker/docker_compose.rb +53 -0
  14. data/lib/minke/docker/docker_runner.rb +123 -0
  15. data/lib/minke/docker/service_discovery.rb +24 -0
  16. data/lib/minke/docker/system_runner.rb +16 -0
  17. data/lib/minke/generators/config.rb +106 -0
  18. data/lib/minke/generators/config_processor.rb +47 -0
  19. data/lib/minke/generators/config_variables.rb +23 -0
  20. data/lib/minke/generators/processor.rb +131 -0
  21. data/lib/minke/generators/register.rb +19 -0
  22. data/lib/minke/helpers/helper.rb +68 -0
  23. data/lib/minke/rake/app.rake +50 -198
  24. data/lib/minke/rake/config.rake +1 -5
  25. data/lib/minke/rake/docker.rake +7 -5
  26. data/lib/minke/tasks/build.rb +17 -0
  27. data/lib/minke/tasks/build_image.rb +13 -0
  28. data/lib/minke/tasks/cucumber.rb +34 -0
  29. data/lib/minke/tasks/fetch.rb +16 -0
  30. data/lib/minke/tasks/push.rb +22 -0
  31. data/lib/minke/tasks/run.rb +23 -0
  32. data/lib/minke/tasks/task.rb +107 -0
  33. data/lib/minke/tasks/test.rb +17 -0
  34. data/lib/minke/version.rb +1 -1
  35. data/lib/minke.rb +33 -9
  36. data/minke.gemspec +16 -11
  37. metadata +84 -9
  38. data/lib/minke/commands/go.rb +0 -27
  39. data/lib/minke/commands/swift.rb +0 -32
  40. data/lib/minke/docker.rb +0 -122
  41. data/lib/minke/docker_compose.rb +0 -31
  42. data/lib/minke/helpers.rb +0 -56
@@ -0,0 +1,94 @@
1
+ module Minke
2
+ module Config
3
+ ##
4
+ # Reader reads a yaml based configuration and processes it into a Minke::Config::Config instance
5
+ class Reader
6
+ ##
7
+ # read yaml config file and return Minke::Config::Config instance
8
+ def read config_file
9
+ b = binding
10
+
11
+ config = Config.new
12
+ file = ERB.new(File.read(config_file)).result b
13
+ file = YAML.load(file)
14
+
15
+ config.namespace = file['namespace']
16
+ config.application_name = file['application_name']
17
+ config.generator_name = file['generator_name']
18
+
19
+ config.docker_registry = read_docker_registry file['docker_registry'] unless file['docker_registry'] == nil
20
+ config.docker = read_docker_section file['docker'] unless file['docker'] == nil
21
+
22
+ config.fetch = read_task_section file['fetch'], config.docker unless file['fetch'] == nil
23
+ config.build = read_task_section file['build'], config.docker unless file['build'] == nil
24
+ config.run = read_task_section file['run'], config.docker unless file['run'] == nil
25
+ config.cucumber = read_task_section file['cucumber'], config.docker unless file['cucumber'] == nil
26
+
27
+ return config
28
+ end
29
+
30
+ def read_docker_registry section
31
+ DockerRegistrySettings.new.tap do |d|
32
+ d.url = section['url']
33
+ d.user = section['user']
34
+ d.password = section['password']
35
+ d.email = section['email']
36
+ d.namespace = section['namespace']
37
+ end
38
+ end
39
+
40
+ def read_docker_section section
41
+ DockerSettings.new.tap do |d|
42
+ d.build_image = section['build_image'] unless section['build_image'] == nil
43
+ d.build_docker_file = section['build_docker_file'] unless section['build_docker_file'] == nil
44
+ d.application_docker_file = section['application_docker_file'] unless section['application_docker_file'] == nil
45
+ d.application_compose_file = section['application_compose_file'] unless section['application_compose_file'] == nil
46
+ end
47
+ end
48
+
49
+ def read_task_section section, docker_config
50
+ Task.new.tap do |t|
51
+ t.docker = read_docker_section section['docker'] unless section['docker'] == nil
52
+ t.pre = read_pre_post_section section['pre'] unless section['pre'] == nil
53
+ t.post = read_pre_post_section section['post'] unless section['post'] == nil
54
+ end
55
+ end
56
+
57
+ def read_pre_post_section section
58
+ TaskRunSettings.new.tap do |tr|
59
+ tr.tasks = section['tasks'] unless section['tasks'] == nil
60
+ tr.copy = read_copy_section section['copy'] unless section['copy'] == nil
61
+ tr.consul_loader = read_consul_loader_section section['consul_loader'] unless section['consul_loader'] == nil
62
+ tr.health_check = read_url section['health_check'] unless section['health_check'] == nil
63
+ end
64
+ end
65
+
66
+ def read_copy_section section
67
+ section.map do |s|
68
+ Copy.new.tap do |c|
69
+ c.from = s['from']
70
+ c.to = s['to']
71
+ end
72
+ end
73
+ end
74
+
75
+ def read_consul_loader_section section
76
+ ConsulLoader.new.tap do |c|
77
+ c.config_file = section['config_file']
78
+ c.url = read_url section['url']
79
+ end
80
+ end
81
+
82
+ def read_url section
83
+ URL.new.tap do |url|
84
+ url.address = section['address']
85
+ url.port = section['port'] != nil ? section['port'].to_s : '80'
86
+ url.path = section['path'] != nil ? section['path'] : ''
87
+ url.protocol = section['protocol'] != nil ? section['protocol'] : 'http'
88
+ url.type = section['type']
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,53 @@
1
+ module Minke
2
+ module Docker
3
+ class DockerComposeFactory
4
+ def initialize system_runner
5
+ @system_runner = system_runner
6
+ end
7
+
8
+ def create compose_file
9
+ Minke::Docker::DockerCompose.new compose_file, @system_runner
10
+ end
11
+ end
12
+
13
+ class DockerCompose
14
+ @compose_file = nil
15
+
16
+ def initialize compose_file, system_runner
17
+ @compose_file = compose_file
18
+ @system_runner = system_runner
19
+ end
20
+
21
+ ##
22
+ # start the containers in a stack defined by the docker compose file
23
+ def up
24
+ @system_runner.execute "docker-compose -f #{@compose_file} up -d"
25
+ sleep 2
26
+ end
27
+
28
+ ##
29
+ # stop the containers in a stack defined by the docker compose file
30
+ def stop
31
+ @system_runner.execute "docker-compose -f #{@compose_file} stop"
32
+ end
33
+
34
+ ##
35
+ # remove the containers started in a stack defined by the docker compose file
36
+ def rm
37
+ @system_runner.execute "echo y | docker-compose -f #{@compose_file} rm -v" unless ::Docker.info["Driver"] == "btrfs"
38
+ end
39
+
40
+ ##
41
+ # stream the logs for the current running stack
42
+ def logs
43
+ @system_runner.execute "docker-compose -f #{@compose_file} logs -f"
44
+ end
45
+
46
+ ##
47
+ # return the local address and port of a containers private port
48
+ def public_address container, private_port
49
+ @system_runner.execute_and_return "docker-compose -f #{@compose_file} port #{container} #{private_port}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,123 @@
1
+ module Minke
2
+ module Docker
3
+ class DockerRunner
4
+ ##
5
+ # returns the ip address that docker is running on
6
+ def get_docker_ip_address
7
+ if ENV['DOCKER_IP'] == nil
8
+ if ENV['DOCKER_HOST']
9
+ # dockerhost set
10
+ host = ENV['DOCKER_HOST'].dup
11
+ host.gsub!(/tcp:\/\//, '')
12
+ host.gsub!(/:\d+/,'')
13
+
14
+ return host
15
+ else
16
+ return '127.0.0.1'
17
+ end
18
+ end
19
+ end
20
+
21
+ ##
22
+ # find_image finds a docker image in the local registry
23
+ # Returns
24
+ #
25
+ # Docker::Image
26
+ def find_image image_name
27
+ found = nil
28
+ ::Docker::Image.all.each do | image |
29
+ found = image if image.info["RepoTags"].include? image_name
30
+ end
31
+
32
+ return found
33
+ end
34
+
35
+ ##
36
+ # pull_image pulls a new copy of the given image from the registry
37
+ def pull_image image_name
38
+ puts "Pulling Image: #{image_name}"
39
+ puts `docker pull #{image_name}`
40
+ end
41
+
42
+ ##
43
+ # create_and_run_container starts a conatainer of the given image name and executes a command
44
+ #
45
+ # Returns:
46
+ # - Docker::Container
47
+ # - sucess (true if command succeded without error)
48
+ def create_and_run_container image, volumes, environment, working_directory, cmd
49
+ # update the timeout for the Excon Http Client
50
+ # set the chunk size to enable streaming of log files
51
+ ::Docker.options = {:chunk_size => 1, :read_timeout => 3600}
52
+ container = ::Docker::Container.create(
53
+ 'Image' => image,
54
+ 'Cmd' => cmd,
55
+ "Binds" => volumes,
56
+ "Env" => environment,
57
+ 'WorkingDir' => working_directory)
58
+
59
+ success = true
60
+
61
+ thread = Thread.new do
62
+ container.attach(:stream => true, :stdin => nil, :stdout => true, :stderr => true, :logs => false, :tty => false) do
63
+ |stream, chunk|
64
+ stream.to_s == 'stdout' ? color = :green : color = :red
65
+ puts "#{chunk.strip}".colorize(color)
66
+
67
+ if stream.to_s == "stderr"
68
+ success = false
69
+ else
70
+ success = true
71
+ end
72
+ end
73
+ end
74
+
75
+ container.start
76
+ thread.join
77
+
78
+ return container, success
79
+ end
80
+
81
+ ##
82
+ # build_image creates a new image from the given Dockerfile and name
83
+ def build_image dockerfile_dir, name
84
+ ::Docker.options = {:read_timeout => 6200}
85
+ begin
86
+ ::Docker::Image.build_from_dir(dockerfile_dir, {:t => name}) do |v|
87
+ data = /{"stream.*:"(.*)".*/.match(v)
88
+ data = data[1].encode(Encoding.find('UTF-8'), {invalid: :replace, undef: :replace, replace: ''}) unless data == nil || data.length < 1
89
+ $stdout.puts data unless data == nil
90
+ end
91
+ rescue => e
92
+ message = /.*{"message":"(.*?)"}/.match(e.to_s)
93
+ puts "Error: #{message[1]}" unless message == nil || message.length < 1
94
+ end
95
+ end
96
+
97
+ def delete_container container
98
+ if container != nil
99
+ begin
100
+ container.delete()
101
+ rescue => e
102
+ puts "Error: Unable to delete container"
103
+ end
104
+ end
105
+ end
106
+
107
+ def login_registry url, user, password, email
108
+ system("docker login -u #{user} -p #{password} -e #{email} #{url}")
109
+ $?.exitstatus
110
+ end
111
+
112
+ def tag_image image_name, tag
113
+ image = self.find_image "#{image_name}:latest"
114
+ image.tag('repo' => tag, 'force' => true) unless image.info["RepoTags"].include? "#{tag}:latest"
115
+ end
116
+
117
+ def push_image image_name
118
+ system("docker push #{image_name}:latest")
119
+ $?.exitstatus == 0
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,24 @@
1
+ module Minke
2
+ module Docker
3
+ ##
4
+ # ServiceDiscovery allows you to look up the publicly accessible address and port for a server
5
+ class ServiceDiscovery
6
+ def initialize config
7
+ reader = Minke::Config::Reader.new
8
+ @config = reader.read config
9
+ end
10
+
11
+ ##
12
+ # Will attempt to locate the public details for a running container given
13
+ # its name and private port
14
+ # Parameters:
15
+ # - container_name: the name of the running container
16
+ # - private_port: the private port which you wish to retrieve an address for
17
+ # - task: :run, :cucumber search either the run or cucumber section of the config
18
+ def public_address_for container_name, private_port, task
19
+ compose = Minke::Docker::DockerCompose.new @config.compose_file_for(task), Minke::Docker::SystemRunner.new
20
+ compose.public_address container_name, private_port
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Minke
2
+ module Docker
3
+ class SystemRunner
4
+
5
+ def execute command
6
+ system("#{command}")
7
+ end
8
+
9
+ def execute_and_return command
10
+ log = `#{command}`
11
+ return log.strip
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,106 @@
1
+ module Minke
2
+ module Generators
3
+
4
+ ##
5
+ # This class encapsulates the config required by the generator.
6
+ class Config
7
+ ##
8
+ # Name of the generator.
9
+ attr_accessor :name
10
+
11
+ ##
12
+ # Location of the ERB template files
13
+ attr_accessor :template_location
14
+
15
+ ##
16
+ # Instance of Minke::Generators::GenerateSettings
17
+ attr_accessor :generate_settings
18
+
19
+ ##
20
+ # Instance of Minke::Generators::BuildSettings
21
+ attr_accessor :build_settings
22
+ end
23
+
24
+ ##
25
+ # This class encapsulates the settings required to generate a new template.
26
+ class GenerateSettings
27
+ ##
28
+ # [OPTIONAL]
29
+ # A command to execute when generating a new template.
30
+ #
31
+ # Using this attribute it is possible to delegate reponsibility for generation of part of the codebase
32
+ # to an external command.
33
+ # For example you could execute rails new... to generate a new rails project as part of the template
34
+ # generation.
35
+ attr_accessor :command
36
+
37
+ ##
38
+ # The name of a docker image to run the commands inside.
39
+ #
40
+ # All commands are run inside a docker container to remove the dependency on installed software
41
+ # on the build machine.
42
+ attr_accessor :docker_image
43
+
44
+ ##
45
+ # The folder location of a docker file from which an image will be build before running commands inside it.
46
+ #
47
+ # This option can be used as an alternative to providing a docker image, Dockerfiles can be bundled with the
48
+ # template.
49
+ # Minke will attempt to create an image from this Dockerfile before executing the generate commands.
50
+ attr_accessor :docker_file
51
+ end
52
+
53
+ ##
54
+ # BuildSettings contains the commands and settings used to build and test your code, these are not
55
+ # related to template generation but when Minke is used to build source created from a template.
56
+ class BuildSettings
57
+ ##
58
+ # Instance of Minke::Generators::BuildCommands
59
+ attr_accessor :build_commands
60
+
61
+ ##
62
+ # Instance of Minke::Generators::DockerSettings
63
+ attr_accessor :docker_settings
64
+ end
65
+
66
+ ##
67
+ # BuildCommands define the command that will be executed in the docker container for each
68
+ # of the build steps.
69
+ class BuildCommands
70
+
71
+ ##
72
+ # An array of commands to execute for the fetch step.
73
+ attr_accessor :fetch
74
+
75
+ ##
76
+ # An array of commands to execute for the build step.
77
+ attr_accessor :build
78
+
79
+ ##
80
+ # An array of commands to execute for the test step.
81
+ attr_accessor :test
82
+ end
83
+
84
+ ##
85
+ # DockerSettings encapsulates the settings required for the Docker container
86
+ # within which the commands will execute.
87
+ class DockerSettings
88
+
89
+ ##
90
+ # Docker image used to execute commands
91
+ attr_accessor :image
92
+
93
+ ##
94
+ # Docker environment to set on running container
95
+ attr_accessor :env
96
+
97
+ ##
98
+ # Volume mapping information for Docker container, must be fully qualified path
99
+ attr_accessor :binds
100
+
101
+ ##
102
+ # Workging directory for executed commands
103
+ attr_accessor :working_directory
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,47 @@
1
+ module Minke
2
+ module Generators
3
+
4
+ ##
5
+ # ConfigProcessor replaces variable placeholders in the config object
6
+ # with the values specified in the initialize method
7
+ class ConfigProcessor
8
+
9
+ ##
10
+ # initialize takes a single parameter of Minke::Generators::ConfigVariables
11
+ def initialize variables
12
+ @variables = variables
13
+ end
14
+
15
+ ##
16
+ # process a Minke::Generators::Config object and replace given variables
17
+ def process config
18
+ replace_variables config.template_location
19
+
20
+ replace_variables config.generate_settings.command unless config.generate_settings == nil || config.generate_settings.command == nil
21
+ replace_variables config.generate_settings.docker_file unless config.generate_settings == nil || config.generate_settings.docker_file == nil
22
+
23
+ replace_variables config.build_settings.build_commands.fetch unless config.build_settings == nil || config.build_settings.build_commands.fetch == nil
24
+ replace_variables config.build_settings.build_commands.build unless config.build_settings == nil || config.build_settings.build_commands.build == nil
25
+ replace_variables config.build_settings.build_commands.test unless config.build_settings == nil || config.build_settings.build_commands.test == nil
26
+
27
+ replace_variables config.build_settings.docker_settings.image unless config.build_settings == nil || config.build_settings.docker_settings.image == nil
28
+ replace_variables config.build_settings.docker_settings.env unless config.build_settings == nil || config.build_settings.docker_settings.env == nil
29
+ replace_variables config.build_settings.docker_settings.binds unless config.build_settings == nil || config.build_settings.docker_settings.binds == nil
30
+ replace_variables config.build_settings.docker_settings.working_directory unless config.build_settings == nil || config.build_settings.docker_settings.working_directory == nil
31
+
32
+ return config
33
+ end
34
+
35
+ def replace_variables section
36
+ if section.is_a?(Array)
37
+ section.each { |a| replace_variables a }
38
+ else
39
+ section.gsub! '<%= application_name %>', @variables.application_name
40
+ section.gsub! '<%= namespace %>', @variables.namespace
41
+ section.gsub! '<%= src_root %>', @variables.src_root
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ module Minke
2
+ module Generators
3
+
4
+ ##
5
+ # ConfigVariables encapsulates the variables that are evaluated at runtime when
6
+ # a template is loaded.
7
+ class ConfigVariables
8
+
9
+ ##
10
+ # src_root is the absolute path where the src files will be generated
11
+ attr_accessor :src_root
12
+
13
+ ##
14
+ # namespace is the namespace of the application
15
+ attr_accessor :namespace
16
+
17
+ ##
18
+ # application_name is the name of the application
19
+ attr_accessor :application_name
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,131 @@
1
+ module Minke
2
+ module Generators
3
+ ##
4
+ # Process handles the creation of new projects from a generator template.
5
+ class Processor
6
+
7
+ def initialize variables
8
+ @variables = variables
9
+ end
10
+
11
+ def process generator_name, output_folder
12
+ generator = get_generator generator_name
13
+
14
+ # process the files
15
+ puts '# Modifiying templates'
16
+ puts "#{generator.template_location}"
17
+
18
+ process_directory generator.template_location, '**/*', output_folder, @variables.application_name
19
+ process_directory generator.template_location, '**/.*', output_folder, @variables.application_name
20
+
21
+ # run generate command if present
22
+ if generator.generate_settings != nil && generator.generate_settings.command
23
+ build_image unless generator.generate_settings.command.docker_file == nil
24
+ fetch_image unless generator.generate_settings.command.docker_image == nil
25
+
26
+ #run_command_in_container
27
+ end
28
+ end
29
+
30
+ def build_image
31
+ puts "## Building custom docker image"
32
+
33
+ image_name = APPLICATION_NAME + "-buildimage"
34
+ Docker.options = {:read_timeout => 6200}
35
+ image = Docker::Image.build_from_dir generator.generate_command_docker_file, {:t => image}
36
+ end
37
+
38
+ def fetch_image
39
+ Minke::Docker.pull_image generator.generate_command_docker_image unless Minke::Docker.find_image generator.generate_command_docker_image
40
+ image_name = generator.generate_command_docker_image
41
+ end
42
+
43
+ def run_command_in_container
44
+ begin
45
+ config = {
46
+ :build_config => {
47
+ :docker => {
48
+ :image => image_name,
49
+ :binds => ["#{File.expand_path(options[:output])}:/src"],
50
+ :working_directory => "/src"
51
+ }
52
+ }
53
+ }
54
+
55
+ #command = Minke::Helpers.replace_vars_in_section generator.generate_command, '##SERVICE_NAME##', APPLICATION_NAME
56
+ #container, ret = Minke::Docker.create_and_run_container config, command
57
+ ensure
58
+ # Minke::Docker.delete_container container
59
+ end
60
+ end
61
+
62
+ def process_directory template_location, folder, output_folder, service_name
63
+ Dir.glob("#{template_location}/#{folder}").each do |file_name|
64
+ puts "## Processing #{file_name}"
65
+ process_file template_location, file_name, output_folder, service_name
66
+ end
67
+ end
68
+
69
+ def process_file template_location, original, output_folder, service_name
70
+ new_filename = create_new_filename template_location, original, output_folder, service_name
71
+
72
+ dirname = File.dirname(new_filename)
73
+ unless File.directory?(dirname)
74
+ FileUtils.mkdir_p(dirname)
75
+ end
76
+
77
+ if !File.directory?(original)
78
+ if File.extname(original) == ".erb"
79
+ render_erb original, new_filename
80
+ elsif
81
+ FileUtils.cp(original, new_filename)
82
+ end
83
+ end
84
+ end
85
+
86
+ def render_erb original, new_filename
87
+ b = binding
88
+ b.local_variable_set(:application_name, @variables.application_name)
89
+ b.local_variable_set(:namespace, @variables.namespace)
90
+ b.local_variable_set(:src_root, @variables.src_root)
91
+
92
+ renderer = ERB.new(File.read(original))
93
+ File.open(new_filename, 'w') {|f| f.write renderer.result(b) }
94
+ end
95
+
96
+ def create_new_filename template_location, original, output_folder, service_name
97
+ new_filename = original.sub(template_location + '/', '')
98
+ new_filename.sub!('.erb', '')
99
+ new_filename.sub!('<%= application_name %>', service_name)
100
+
101
+ output_folder + '/' + new_filename
102
+ end
103
+
104
+ ##
105
+ #
106
+ def local_gems
107
+ Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.group_by{ |g| g.name }
108
+ end
109
+
110
+ def load_generators
111
+ puts '# Loading installed generators'
112
+ Gem::Specification.find_all.each do |spec|
113
+ if spec.metadata != nil && spec.metadata['entrypoint'] != nil
114
+ require spec.metadata['entrypoint']
115
+ end
116
+ end
117
+ end
118
+
119
+ def get_generator generator
120
+ config = Minke::Generators.get_registrations.select { |c| c.name == generator}.first
121
+ if config == nil
122
+ puts "Generator not installed please select from the above list of installed generators or install the required gem"
123
+ exit 1
124
+ end
125
+ processor = Minke::Generators::ConfigProcessor.new @variables
126
+ return processor.process config
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,19 @@
1
+ module Minke
2
+ module Generators
3
+ @@registrations = []
4
+
5
+ def register config
6
+ puts "registered #{config.name}"
7
+
8
+ @@registrations.push(config)
9
+ #puts "registered #{config.template_location}"
10
+ end
11
+
12
+ def get_registrations
13
+ @@registrations
14
+ end
15
+
16
+ module_function :register
17
+ module_function :get_registrations
18
+ end
19
+ end