contur 0.0.3

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.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'contur/constants'
4
+
5
+ module Contur
6
+ class Config
7
+ # Use section
8
+ class Use < Hash
9
+ DEFAULTS ||= {
10
+ 'php' => Contur::DEFAULT_PHP_VERSION,
11
+ 'mysql' => Contur::DEFAULT_MYSQL_VERSION
12
+ }.freeze
13
+
14
+ ALLOWED_VALUES ||= DEFAULTS.keys
15
+
16
+ def initialize(using = {})
17
+ super
18
+ replace(DEFAULTS)
19
+
20
+ validate!(using)
21
+
22
+ merge!(using) unless using.nil?
23
+ end
24
+
25
+ def validate!(using)
26
+ @errors = []
27
+ return unless using.respond_to? :keys
28
+ extra_keys = using.keys - ALLOWED_VALUES
29
+ extra_keys.each do |key|
30
+ @errors << "Unknown key: #{key}"
31
+ end
32
+ end
33
+
34
+ attr_reader :errors
35
+
36
+ def name
37
+ "Section 'use'"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module Contur
3
+ class Config
4
+ # Version section
5
+ class Version
6
+ ALLOWED_VALUES ||= [1.0].freeze
7
+
8
+ # version is required so no default value
9
+ def initialize(version)
10
+ validate!(version)
11
+ @yaml_version = version
12
+ end
13
+
14
+ def inspect
15
+ @yaml_version.to_s
16
+ end
17
+
18
+ def validate!(version)
19
+ @errors = []
20
+ if version.nil?
21
+ @errors << 'Required'
22
+ return
23
+ end
24
+ @errors << "Unknown value: '#{version}'" unless ALLOWED_VALUES.include? version
25
+ end
26
+
27
+ attr_reader :errors
28
+
29
+ def name
30
+ "Section 'version'"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ require 'contur/config/before'
3
+ require 'contur/config/env'
4
+ require 'contur/config/errors'
5
+ require 'contur/config/use'
6
+ require 'contur/config/version'
7
+
8
+ require 'yaml'
9
+
10
+ module Contur
11
+ # Build configuration object
12
+ class Config
13
+ attr_reader :path, :raw, :version, :use, :env, :before
14
+
15
+ def initialize(path_to_config)
16
+ begin
17
+ @path = path_to_config
18
+ build_config = YAML.load_file(path_to_config)
19
+ rescue Errno::ENOENT
20
+ raise Contur::Config::NotFoundError, "'#{path_to_config}': file not found"
21
+ end
22
+
23
+ @errors = []
24
+ unless build_config
25
+ @errors << 'Build file is empty'
26
+ return
27
+ end
28
+
29
+ @raw = build_config
30
+ @version = Version.new(build_config['version'])
31
+ @use = Use.new(build_config['use'])
32
+ @env = Env.new(build_config['env'])
33
+ @before = Before.new(build_config['before'])
34
+ end
35
+
36
+ def init_script
37
+ unless @init_script
38
+ generation_time = "echo \"Generated at: [#{Time.now}]\"\n\n"
39
+ env_script = @env.to_bash_script
40
+ before_script = @before.to_bash_script
41
+ @init_script = generation_time + env_script + before_script
42
+ end
43
+ @init_script
44
+ end
45
+
46
+ def errors
47
+ all_errors = Array.new(@errors)
48
+
49
+ instance_variables.each do |var|
50
+ section = instance_variable_get(var)
51
+ next unless section.respond_to?(:name) && section.respond_to?(:errors)
52
+ section_name = section.send(:name)
53
+ section_errors = section.send(:errors)
54
+ section_errors.each do |err|
55
+ all_errors << "#{section_name}: #{err}"
56
+ end
57
+ end
58
+
59
+ all_errors
60
+ end
61
+
62
+ def inspect
63
+ data = []
64
+ instance_variables.each do |var|
65
+ section = instance_variable_get(var)
66
+ if section.respond_to?(:name)
67
+ section_name = section.send(:name)
68
+ data << "#{section_name}: #{section.inspect}"
69
+ end
70
+ end
71
+ data.join("\n")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ # Contur main module
6
+ module Contur
7
+ GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '../../'))
8
+ TEMPLATE_DIR = "#{Contur::GEM_ROOT}/templates"
9
+ DEFAULT_DOMAIN = 'cheppers.com'
10
+
11
+ IMAGE_NAME ||= 'contur'
12
+ DOCKER_DIR ||= 'docker'
13
+ DEFAULT_PHP_VERSION = '5.6'
14
+ DEFAULT_MYSQL_VERSION = 'latest'
15
+
16
+ DEFAULT_OPTS ||= {
17
+ refreshed_at_date: ::Date.today.strftime('%Y-%m-%d'),
18
+ php_timezone: 'Europe/Budapest',
19
+ php_memory_limit: '256M',
20
+ max_upload: '50M',
21
+ php_max_file_upload: 200,
22
+ php_max_post: '100M',
23
+ extra_files: Contur::TEMPLATE_DIR
24
+ }.freeze
25
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+ require 'docker-api'
3
+ require 'erb'
4
+
5
+ require 'contur/bindable_hash'
6
+ require 'contur/config'
7
+ require 'contur/utils'
8
+
9
+ # rubocop:disable Metrics/ClassLength, Lint/AssignmentInCondition
10
+
11
+ # Contur main module
12
+ module Contur
13
+ # Contur::Controller
14
+ class Controller
15
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
16
+ def self.config(path_to_config = nil)
17
+ # rubocop:disable Style/ClassVars
18
+ @@config ||= nil
19
+
20
+ unless @@config
21
+ @@config = if path_to_config.nil?
22
+ config_file_name = Dir['.contur.y*ml'].first
23
+ Contur::Config.new(File.join(Dir.pwd, config_file_name))
24
+ else
25
+ Contur::Config.new(path_to_config)
26
+ end
27
+ end
28
+ @@config
29
+ end
30
+
31
+ def self.image_exist?
32
+ Docker::Image.exist? Contur::IMAGE_NAME
33
+ end
34
+
35
+ def self.container?
36
+ Docker::Container.all(
37
+ 'filters' => { 'ancestor' => [Contur::IMAGE_NAME] }.to_json, 'all' => true
38
+ ).first
39
+ rescue
40
+ nil
41
+ end
42
+
43
+ def self.promote
44
+ puts '!!! WIP !!!'
45
+ puts "FQDN: #{Contur::Utils.fqdn}"
46
+ end
47
+
48
+ def self.build
49
+ template = load_docker_template(
50
+ 'base-docker-container',
51
+ php_memory_limit: '128M'
52
+ )
53
+
54
+ docker_context = generate_docker_archive(template)
55
+
56
+ Docker::Image.build_from_tar(docker_context, t: Contur::IMAGE_NAME) do |r|
57
+ r.each_line do |log|
58
+ if (message = JSON.parse(log)) && message.key?('stream')
59
+ yield message['stream'] if block_given?
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ # rubocop:disable Metrics/PerceivedComplexity, Lint/UnusedMethodArgument, Metrics/MethodLength
66
+ def self.run(config_path: nil, webroot: nil, initscripts: nil)
67
+ unless image_exist?
68
+ yield "Image doesn't exist" if block_given?
69
+ return false
70
+ end
71
+
72
+ bind_volumes = []
73
+
74
+ if !webroot.nil? && Dir.exist?(File.expand_path(webroot))
75
+ bind_volumes << "#{webroot}:/www"
76
+ end
77
+ if !initscripts.nil? && Dir.exist?(File.expand_path(initscripts))
78
+ bind_volumes << "#{initscripts}:/initscripts"
79
+ end
80
+
81
+ if c = container?
82
+ yield 'Removing existing contur container...' if block_given?
83
+ c.remove(force: true)
84
+ end
85
+
86
+ mysql_container_version = "mysql:#{config.use['mysql']}"
87
+ unless Docker::Image.exist? mysql_container_version
88
+ yield "Downloading #{mysql_container_version}..." if block_given?
89
+ Docker::Image.create('fromImage' => mysql_container_version)
90
+ end
91
+
92
+ mysql_container_name = mysql_container_version.tr(':.', '_')
93
+
94
+ stop_mysql_containers(except: mysql_container_version)
95
+ begin
96
+ mysql_container = Docker::Container.get(mysql_container_name)
97
+ rescue Docker::Error::NotFoundError
98
+ yield 'Creating MySQL container...' if block_given?
99
+ mysql_container = Docker::Container.create(
100
+ 'name' => mysql_container_name,
101
+ 'Image' => mysql_container_version,
102
+ 'Env' => ['MYSQL_ROOT_PASSWORD=admin']
103
+ )
104
+ ensure
105
+ mysql_container_info = Docker::Container.get(mysql_container.id).info
106
+ unless mysql_container_info['State']['Running']
107
+ yield 'Starting MySQL container...' if block_given?
108
+ mysql_container.start!
109
+ sleep 10
110
+ end
111
+ end
112
+ mysql_container_info = Docker::Container.get(mysql_container.id).info
113
+ unless mysql_container_info['State']['Running']
114
+ yield "Couldn't start MySQL container" if block_given?
115
+ return false
116
+ end
117
+ yield "MySQL container: #{mysql_container.id[0, 10]}" if block_given?
118
+
119
+ yield 'Creating Contur container...' if block_given?
120
+ container = Docker::Container.create(
121
+ 'name' => Contur::Utils.generate_conatiner_name,
122
+ 'Image' => Contur::IMAGE_NAME,
123
+ 'Cmd' => ['/init.sh'],
124
+ 'Volumes' => {
125
+ "#{Dir.pwd}/webroot" => {},
126
+ "#{Dir.pwd}/initscripts" => {}
127
+ },
128
+ 'ExposedPorts' => {
129
+ '80/tcp' => {}
130
+ },
131
+ 'HostConfig' => {
132
+ 'Links' => ["#{mysql_container_name}:mysql"],
133
+ 'PortBindings' => {
134
+ '80/tcp' => [{ 'HostPort' => '8088' }]
135
+ },
136
+ 'Binds' => bind_volumes
137
+ }
138
+ )
139
+
140
+ container.store_file('/init.sh', content: load_init_script, permissions: 0o777)
141
+
142
+ container.start!
143
+ end
144
+
145
+ def self.delete_container
146
+ if c = container?
147
+ c.delete(force: true)
148
+ true
149
+ else
150
+ false
151
+ end
152
+ end
153
+
154
+ def self.container_id
155
+ return nil unless c = container?
156
+ c.id[0, 10]
157
+ end
158
+
159
+ def self.container_logs
160
+ return nil unless c = container?
161
+ c.logs(stdout: true)
162
+ end
163
+
164
+ def self.delete_image
165
+ return nil unless image_exist?
166
+ image = Docker::Image.get Contur::IMAGE_NAME
167
+ image.remove(force: true)
168
+ end
169
+
170
+ def self.mysql_containers
171
+ Docker::Container.all.select { |c| c.info['Image'].start_with?('mysql') }
172
+ end
173
+
174
+ def self.delete_mysql_containers
175
+ if mysql_containers.empty?
176
+ false
177
+ else
178
+ mysql_containers.each { |c| c.delete(force: true) }
179
+ true
180
+ end
181
+ end
182
+
183
+ def self.stop_mysql_containers(except: "\n")
184
+ mysql_containers.select { |c| !c.info['Image'].end_with?(except) }.each(&:stop)
185
+ end
186
+
187
+ private_class_method
188
+
189
+ def self.load_docker_template(template_name, opts = {})
190
+ opts = Contur::DEFAULT_OPTS.merge(opts)
191
+
192
+ context = BindableHash.new opts
193
+ ::ERB.new(
194
+ File.read("#{Contur::TEMPLATE_DIR}/#{template_name}.erb")
195
+ ).result(context.get_binding)
196
+ end
197
+
198
+ def self.load_init_script
199
+ context = BindableHash.new(before_script: config.init_script)
200
+ ::ERB.new(
201
+ File.read("#{Contur::TEMPLATE_DIR}/init.sh.erb")
202
+ ).result(context.get_binding)
203
+ end
204
+
205
+ def self.generate_docker_archive(dockerfile_content)
206
+ tar = StringIO.new
207
+
208
+ Gem::Package::TarWriter.new(tar) do |writer|
209
+ writer.add_file('Dockerfile', 0o644) { |f| f.write(dockerfile_content) }
210
+ end
211
+
212
+ compress_archive(tar)
213
+ end
214
+
215
+ def self.compress_archive(tar)
216
+ tar.seek(0)
217
+ gz = StringIO.new(String.new, 'r+b').set_encoding(Encoding::BINARY)
218
+ gz_writer = Zlib::GzipWriter.new(gz)
219
+ gz_writer.write(tar.read)
220
+ tar.close
221
+ gz_writer.finish
222
+ gz.rewind
223
+
224
+ gz
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module Contur
3
+ # Utils class
4
+ class Utils
5
+ def self.generate_conatiner_name(prefix: nil, job_name: nil, build_id: nil)
6
+ prefix ||= ENV['CONTAINER_PREFIX']
7
+ prefix = "#{prefix}-" unless prefix.nil? || prefix.empty?
8
+
9
+ job_name ||= ENV['JOB_NAME'] || random_string
10
+ build_id ||= ENV['BUILD_ID'] || "r#{Random.rand(1000)}"
11
+
12
+ "#{prefix}#{job_name}-#{build_id}"
13
+ end
14
+
15
+ def self.virtual_hostname(prefix: nil, job_name: nil, branch_name: nil)
16
+ prefix ||= ENV['HOST_PREFIX']
17
+ prefix = "#{prefix}." unless prefix.nil? || prefix.empty?
18
+
19
+ job_name ||= ENV['JOB_NAME'] || random_string.downcase
20
+ branch_name ||= (ENV['BRANCH_NAME'] || 'master').gsub(%r{.*/}, '')
21
+
22
+ "#{prefix}#{branch_name}.#{job_name}"
23
+ end
24
+
25
+ def self.fqdn(prefix: nil, job_name: nil, branch_name: nil)
26
+ vhost = virtual_hostname(
27
+ prefix: prefix,
28
+ job_name: job_name,
29
+ branch_name: branch_name
30
+ )
31
+
32
+ main_domain = ENV['DOMAIN'] || Contur::DEFAULT_DOMAIN
33
+
34
+ "#{vhost}.#{main_domain}"
35
+ end
36
+
37
+ def self.random_string(length: 8)
38
+ o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
39
+ (0...length).map { o[rand(o.length)] }.join
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Contur
3
+ VERSION = '0.0.3'
4
+ end
data/lib/contur.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'contur/version'
3
+
4
+ # require application files
5
+ require_relative 'contur/bindable_hash'
6
+ require_relative 'contur/constants'
7
+ require_relative 'contur/controller'
8
+ require_relative 'contur/config'
@@ -0,0 +1,104 @@
1
+ FROM alpine:3.3
2
+
3
+ MAINTAINER 'Cheppers Ltd. <info@cheppers.com> (github:Cheppers)'
4
+
5
+ ENV REFRESHED_AT <%= refreshed_at_date %>
6
+
7
+ # PHP Configuration
8
+ ENV TIMEZONE <%= php_timezone %>
9
+ ENV PHP_MEMORY_LIMIT <%= php_memory_limit %>
10
+ ENV MAX_UPLOAD <%= max_upload %>
11
+ ENV PHP_MAX_FILE_UPLOAD <%= php_max_file_upload %>
12
+ ENV PHP_MAX_POST <%= php_max_post %>
13
+ ENV TERM dumb # for mysql-client
14
+
15
+ # Install packages
16
+ RUN apk add --no-cache \
17
+ bash \
18
+ apache2-proxy apache2-utils \
19
+ mysql-client \
20
+ tzdata \
21
+ php-mcrypt \
22
+ php-soap \
23
+ php-openssl \
24
+ php-gmp \
25
+ php-pdo_odbc \
26
+ php-json \
27
+ php-dom \
28
+ php-pdo \
29
+ php-zip \
30
+ php-mysql \
31
+ php-sqlite3 \
32
+ php-apcu \
33
+ php-pdo_pgsql \
34
+ php-bcmath \
35
+ php-gd \
36
+ php-xcache \
37
+ php-odbc \
38
+ php-pdo_mysql \
39
+ php-pdo_sqlite \
40
+ php-gettext \
41
+ php-xmlreader \
42
+ php-xmlrpc \
43
+ php-bz2 \
44
+ php-memcache \
45
+ php-mssql \
46
+ php-iconv \
47
+ php-pdo_dblib \
48
+ php-curl \
49
+ php-ctype \
50
+ php-phar \
51
+ php-fpm
52
+
53
+ # Configure apache proxy (apache because of `.ht` files)
54
+ RUN echo \
55
+ 'ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/www/$1' >> /etc/apache2/httpd.conf && \
56
+ echo 'DirectoryIndex /index.php' >> /etc/apache2/httpd.conf && \
57
+ sed -ie 's/#ServerName .*/ServerName localhost/i' /etc/apache2/httpd.conf && \
58
+ sed -ie 's/#LoadModule slotmem_shm_module/LoadModule slotmem_shm_module/' \
59
+ /etc/apache2/httpd.conf && \
60
+ sed -ie 's#/var/www/localhost/htdocs#/www#g' /etc/apache2/httpd.conf && \
61
+ sed -ie 's#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so##g' /etc/apache2/conf.d/proxy.conf && \
62
+ mkdir /run/apache2/
63
+
64
+ # Configure Timezone, important for php
65
+ RUN cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \
66
+ echo "${TIMEZONE}" > /etc/timezone
67
+
68
+ # Configure PHP-FPM
69
+ RUN sed -i "s|;*daemonize\s*=\s*yes|daemonize = no|g" /etc/php/php-fpm.conf && \
70
+ sed -ie 's#^listen\s*=\s*.*#listen = 9000#g' \
71
+ /etc/php/php-fpm.conf && \
72
+ sed -i "s|;*date.timezone =.*|date.timezone = ${TIMEZONE}|i" \
73
+ /etc/php/php.ini && \
74
+ sed -i "s|;*memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|i" \
75
+ /etc/php/php.ini && \
76
+ sed -i "s|;*upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|i" \
77
+ /etc/php/php.ini && \
78
+ sed -i "s|;*max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|i" \
79
+ /etc/php/php.ini && \
80
+ sed -i "s|;*post_max_size =.*|post_max_size = ${PHP_MAX_POST}|i" \
81
+ /etc/php/php.ini && \
82
+ sed -i "s|;*cgi.fix_pathinfo=.*|cgi.fix_pathinfo= 0|i" /etc/php/php.ini
83
+
84
+ # Install Composer
85
+ RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
86
+ php -r "if (hash_file('SHA384', 'composer-setup.php') === 'e115a8dc7871f15d853148a7fbac7da27d6c0030b848d9b3dc09e2a0388afed865e6a3d6b3c0fad45c48e2b5fc1196ae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
87
+ php composer-setup.php --install-dir /usr/bin --filename=composer composer && \
88
+ php -r "unlink('composer-setup.php');" && \
89
+ chmod +x /usr/bin/composer
90
+
91
+ # Install Drush
92
+ RUN php -r "readfile('http://files.drush.org/drush.phar');" > /usr/local/bin/drush && \
93
+ php /usr/local/bin/drush core-status && \
94
+ chmod +x /usr/local/bin/drush
95
+
96
+ # Codebase
97
+ RUN mkdir /www
98
+ VOLUME ["/www"]
99
+
100
+ RUN mkdir /initscripts
101
+
102
+ WORKDIR /www
103
+
104
+ EXPOSE 80
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+
3
+ # ========== GENERATED SCRIPT ==========
4
+ <%= before_script %>
5
+ # ========== GENERATED SCRIPT ==========
6
+
7
+ echo "Run InitScripts"
8
+ find /initscripts -type f -print -exec {} \;
9
+
10
+ echo "Start Apache2"
11
+ /usr/sbin/httpd -d . -f /etc/apache2/httpd.conf
12
+
13
+ if [ ! -f /www/index.php ]; then
14
+ echo "Populate '/www' because it's empty"
15
+ echo '<?php phpinfo(); ?>' > /www/index.php
16
+ fi
17
+
18
+ echo "Start PHP-FPM"
19
+ /usr/bin/php-fpm