minke 0.16.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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