afterlife 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c8dfb530dade840c8e03042de3ba5508eec1002830c0b978ab42aa1f88b83fab
4
+ data.tar.gz: 39452459ace3d25eb5270e0e83295e0919f97b11813128b98a6deb39b407431b
5
+ SHA512:
6
+ metadata.gz: 30fb609ce63cd5038050a2afae03741fd9d8739d4d014cfbd85ca09a0c1f44a9eb3521b85866b64763bfeea287891ea6d00c84491bf39f4bbf6cea152b8ef8d5
7
+ data.tar.gz: e94831df012dfe22f833e277b0fbc5b6c11ae5c9c85d9897068d14b05171c4ab7fd542b2f578d0c95fd17feef9419e0c7abb8aeb50421b29e9e93322bd17fa47
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,46 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 2.7
8
+
9
+ Lint/ConstantDefinitionInBlock:
10
+ Enabled: false
11
+
12
+ Layout/IndentationConsistency:
13
+ Enabled: false
14
+
15
+ Layout/HashAlignment:
16
+ Enabled: false
17
+
18
+ Layout/SpaceAroundEqualsInParameterDefault:
19
+ Enabled: false
20
+
21
+ Style/HashAsLastArrayItem:
22
+ EnforcedStyle: no_braces
23
+
24
+ Style/OptionalBooleanParameter:
25
+ Enabled: false
26
+
27
+ Style/Documentation:
28
+ Enabled: false
29
+
30
+ Layout/EmptyLinesAroundBlockBody:
31
+ Enabled: false
32
+
33
+ Style/TrailingCommaInArguments:
34
+ EnforcedStyleForMultiline: consistent_comma
35
+
36
+ Style/TrailingCommaInArrayLiteral:
37
+ EnforcedStyleForMultiline: consistent_comma
38
+
39
+ Style/TrailingCommaInHashLiteral:
40
+ EnforcedStyleForMultiline: consistent_comma
41
+
42
+ Layout/AccessModifierIndentation:
43
+ EnforcedStyle: outdent
44
+
45
+ Style/SignalException:
46
+ EnforcedStyle: semantic
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ afterlife
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.5
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-01-13
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in afterlife.gemspec
6
+ gemspec
7
+
8
+ gem 'pry'
9
+
10
+ gem 'rake'
11
+
12
+ gem 'rspec'
13
+
14
+ gem 'rubocop'
15
+ gem 'rubocop-rake'
16
+ gem 'rubocop-rspec'
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Afterlife
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/afterlife`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text abovxe, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ 1. Execute `bin/install` in the repository root
10
+ 2. Put a `env.yml` on ~/.afterlife/env.yml based on the AWS credentials found on mifiel's bitwarden vault
11
+ 3. Install AWS CLI following the [official guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
12
+
13
+ ## Usage
14
+ Run `afterlife help` for a list of all available commands
15
+
16
+
17
+ ## About CDN
18
+ Logs can be found on `~/.afterlife/logs/cdn.log`
19
+
20
+ Running `afterlife cdn start` will run the CDN web server in the background. Running in foreground using `afterlife cdn start --foreground` will display errors and content normally found in logs in console instead.
21
+
22
+ ## Development
23
+
24
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
25
+
26
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
27
+
28
+ ## Contributing
29
+
30
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/afterlife.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ desc 'start a console'
13
+ task :console do
14
+ exec 'pry -r afterlife -I ./lib'
15
+ end
16
+
17
+ task default: %i[spec rubocop]
data/afterlife.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/afterlife/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'afterlife'
7
+ spec.version = Afterlife::VERSION
8
+ spec.authors = ['Genaro Madrid']
9
+ spec.email = ['genmadrid@gmail.com']
10
+
11
+ spec.summary = 'Devops tools for Mifiel'
12
+ spec.description = 'Afterlife helps you setup your development environment and deploy code easily'
13
+ spec.required_ruby_version = '>= 2.6'
14
+
15
+ spec.metadata['source_code_uri'] = 'https://bitbucket.org/volabit/afterlife'
16
+ spec.metadata['changelog_uri'] = 'https://bitbucket.org/volabit/afterlife/src/master/CHANGELOG.md'
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
23
+ end
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'activesupport'
30
+ spec.add_dependency 'git'
31
+ spec.add_dependency 'puma'
32
+ spec.add_dependency 'rackup'
33
+ spec.add_dependency 'thor'
34
+ spec.add_dependency 'zeitwerk'
35
+
36
+ spec.metadata['rubygems_mfa_required'] = 'true'
37
+ end
data/config.ru ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'rack/directory'
5
+
6
+ logger_file = File.open(Afterlife::Cdn.log_path, 'a')
7
+ logger_file.sync = true
8
+
9
+ class AssetHeaders
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ response = @app.call(env)
16
+ response[1]['access-control-allow-origin'] = '*'
17
+ response
18
+ end
19
+ end
20
+
21
+ use AssetHeaders
22
+ use Rack::CommonLogger, Logger.new(logger_file)
23
+ run do |env|
24
+ Rack::Directory.new(Afterlife::Cdn.local_path).call(env)
25
+ end
data/exe/afterlife ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_dir = File.expand_path('../lib', __dir__)
5
+ require "#{lib_dir}/afterlife"
6
+
7
+ Afterlife.root = Pathname(File.expand_path('..', __dir__))
8
+ Afterlife::Cli.start
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Afterlife
6
+ module BaseCli
7
+ def log_success(message)
8
+ say set_color(message, :green)
9
+ say "\n"
10
+ end
11
+
12
+ def log_error(message)
13
+ say set_color(message, :red)
14
+ say "\n"
15
+ end
16
+
17
+ def log_info(message)
18
+ say set_color(message, :cyan)
19
+ say "\n"
20
+ end
21
+
22
+ def log_interrupted
23
+ say ''
24
+ fatal! 'Interrupted'
25
+ end
26
+
27
+ def sure?(msg)
28
+ say msg
29
+ return true if yes?('Are you sure? [y/n]')
30
+
31
+ fatal! 'Good bye'
32
+ end
33
+
34
+ def fatal!(msg)
35
+ log_error "ERROR: #{msg}"
36
+ exit 1
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Bump
5
+ module Semver
6
+ module_function
7
+
8
+ DEFAULT_PRERELEASE_NAME = 'rc'
9
+ VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)/.freeze
10
+
11
+ def calculate_next(part, pre = nil) # rubocop:disable Metrics/MethodLength
12
+ x, y, z = release
13
+ case part.to_sym
14
+ when :major
15
+ "#{x + 1}.0.0"
16
+ when :minor
17
+ "#{x}.#{y + 1}.0"
18
+ when :patch
19
+ "#{x}.#{y}.#{z + 1}"
20
+ when :pre
21
+ name, ver = parse_prerelease(pre)
22
+ "#{x}.#{y}.#{z}-#{name}.#{ver + 1}"
23
+ end
24
+ end
25
+
26
+ def parse_prerelease(name)
27
+ curr_name, curr_ver = curr_prerelease
28
+
29
+ if name
30
+ # if the user specifies a name different than the current one, reseting the current version
31
+ curr_ver = -1 if name != curr_name
32
+ curr_name = name
33
+ end
34
+
35
+ [curr_name, curr_ver]
36
+ end
37
+
38
+ def release
39
+ version_chunks[0].split('.').map(&:to_i)
40
+ end
41
+
42
+ def curr_prerelease
43
+ return [DEFAULT_PRERELEASE_NAME, -1] unless version_chunks[1]
44
+
45
+ name, ver = version_chunks[1].split('.')
46
+ [name, ver.to_i]
47
+ end
48
+
49
+ def version_chunks
50
+ @version_chunks ||= version.split('-')
51
+ end
52
+
53
+ def version
54
+ result = %x(git describe --tags `git rev-list --tags --max-count=1`).chomp
55
+ # remove the first 'v' if exists, all mifiel tags always have it
56
+ result[0] == 'v' ? result[1..] : result
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Bump
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Afterlife
6
+ module Cdn
7
+ class Cli < Thor
8
+ include BaseCli
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc 'start', 'Start the CDN server'
14
+ option :port,
15
+ type: :numeric,
16
+ aliases: '-p',
17
+ default: 5000
18
+ option :foreground,
19
+ type: :boolean,
20
+ aliases: '-f'
21
+ option 'no-link', type: :boolean
22
+ def start
23
+ Cdn.link unless options['no-link']
24
+ say_status 'Starting', "http://localhost:#{options[:port]}"
25
+ Cdn::Server.start(options.symbolize_keys.slice(:port, :foreground))
26
+ log_success "The server started at http://localhost:#{options[:port]}"
27
+ rescue Afterlife::Error => e
28
+ log_error(e)
29
+ end
30
+
31
+ desc 'stop', 'stop the CDN server'
32
+ def stop
33
+ say_status 'Stopping', nil
34
+ Server.kill
35
+ log_success 'The server stopped'
36
+ rescue Afterlife::Error => e
37
+ log_error(e)
38
+ end
39
+
40
+ desc 'restart', 'restart the CDN server'
41
+ def restart
42
+ invoke :stop if Server.running?
43
+ invoke :start
44
+ end
45
+
46
+ desc 'logs', 'tail the logs of the CDN server'
47
+ def logs
48
+ exec("tail -f #{Afterlife::Cdn.log_path}")
49
+ end
50
+
51
+ desc 'link', 'create symlinks in .cdn directory. ' \
52
+ 'Usually you dont need to run this script by itself'
53
+ def link
54
+ Cdn.link
55
+ log_success('Repos successfully linked to local CDN path')
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+
5
+ module Afterlife
6
+ module Cdn
7
+ class Server
8
+ attr_reader :instance
9
+
10
+ def self.pid_file
11
+ Afterlife.local_path.join('pids/server.pid')
12
+ end
13
+
14
+ def self.kill
15
+ fail Afterlife::Error, 'The server is not running' unless running?
16
+
17
+ Process.kill('INT', pid_file.read.to_i)
18
+ end
19
+
20
+ def self.start(args)
21
+ fail Afterlife::Error, 'A server is already running' if running?
22
+
23
+ new(args).start
24
+ end
25
+
26
+ def self.running?
27
+ pid_file.exist?
28
+ end
29
+
30
+ def initialize(args)
31
+ @instance = Rackup::Server.new(
32
+ environment: ENV['RACK_ENV'] || 'development',
33
+ quiet: true, # we setup log in config.ru
34
+ Port: args[:port],
35
+ config: Afterlife.root.join('config.ru').to_s,
36
+ pid: Server.pid_file.to_s,
37
+ daemonize: !args[:foreground],
38
+ )
39
+ end
40
+
41
+ def start
42
+ instance.start
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Cdn
5
+ module_function
6
+
7
+ def url(repo = nil)
8
+ return Afterlife.current_stage.cdn_url unless repo
9
+
10
+ "#{url}/#{repo.full_name}"
11
+ end
12
+
13
+ def log_path
14
+ Afterlife.local_path.join('logs/cdn.log')
15
+ end
16
+
17
+ def local_path
18
+ Afterlife.local_path.join('cdn')
19
+ end
20
+
21
+ def link
22
+ FileUtils.rm_rf(Afterlife::Cdn.local_path)
23
+ FileUtils.mkdir_p(Afterlife::Cdn.local_path)
24
+ Repo.all.each do |repo|
25
+ dest = Afterlife::Cdn.local_path.join(repo.full_name)
26
+ FileUtils.mkdir_p(File.dirname(dest))
27
+ FileUtils.ln_sf(repo.dist_path, dest)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Afterlife
6
+ class Cli < Thor
7
+ include BaseCli
8
+ def self.exit_on_failure?
9
+ true
10
+ end
11
+
12
+ package_name 'Afterlife'
13
+ class_option :quiet, type: :boolean, group: :runtime
14
+ class_option :verbose, type: :boolean, group: :runtime
15
+
16
+ desc 'init', 'Init afterlife'
17
+ def init
18
+ FileUtils.mkdir_p(Afterlife.local_path.join('logs'))
19
+ FileUtils.mkdir_p(Afterlife.local_path.join('pids'))
20
+ FileUtils.mkdir_p(Afterlife::Cdn.local_path)
21
+ FileUtils.touch(Afterlife::Config.file)
22
+ log_success 'afterlife is ready!'
23
+ end
24
+
25
+ desc 'config <path>', 'get config options'
26
+ def config(path) # rubocop:disable Metrics/MethodLength
27
+ path = path.split('.').map(&:to_sym)
28
+ result = repo.conf.dig(*path)
29
+ case result
30
+ when Hash
31
+ say JSON.generate(result)
32
+ when Array
33
+ result.each do |item|
34
+ say item
35
+ end
36
+ else
37
+ say result
38
+ end
39
+ end
40
+
41
+ desc 'cdn', 'CDN stuff'
42
+ subcommand 'cdn', Cdn::Cli
43
+
44
+ desc 'deploy <stage>', 'Deploy current directory'
45
+ option 'no-build', type: :boolean
46
+ option 'no-install', type: :boolean
47
+ option 'dry-run', type: :boolean
48
+ option 'skip-after-hooks', type: :boolean
49
+ option :yes, type: :boolean
50
+ # this only has logic for CDN deployments
51
+ # TODO: Add logic for different deployment types
52
+ def deploy(stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
53
+ Afterlife.cli = self
54
+ Afterlife.current_stage = Afterlife.config.stages[stage.to_sym]
55
+ fill_env
56
+ ask_confirmation(repo.confirmation_message) unless options['yes']
57
+ install_dependencies unless options['no-install']
58
+ build unless options['no-build']
59
+
60
+ say_status 'Deploying', repo.dist_path
61
+ output = Deploy.call
62
+ say_status 'Deployed', output
63
+ rescue Deploy::Error => e
64
+ fatal!(e.message)
65
+ rescue Interrupt
66
+ log_interrupted
67
+ end
68
+
69
+ desc 'release', 'Releases management'
70
+ subcommand 'release', Release::Cli
71
+
72
+ map %w[-v --version] => :version
73
+ desc 'version', 'Prints afterlife version'
74
+ def version
75
+ say Afterlife::VERSION
76
+ end
77
+
78
+ private
79
+
80
+ def fill_env
81
+ repo.env.merge!(
82
+ 'DIST_PATH' => repo.dist_path.to_s,
83
+ 'CURRENT_BRANCH' => repo.current_branch,
84
+ 'AFTERLIFE_STAGE' => Afterlife.current_stage.name,
85
+ )
86
+ end
87
+
88
+ def install_dependencies
89
+ return unless repo.install_dependencies_command
90
+
91
+ say_status 'Installing', '*' * 20
92
+ Exec.run(repo.install_dependencies_command)
93
+ end
94
+
95
+ def build
96
+ return unless repo.build_command
97
+
98
+ say_status 'Building', '*' * 20
99
+ Exec.run(repo.build_command)
100
+ end
101
+
102
+ def ask_confirmation(message)
103
+ if message
104
+ say message
105
+ else
106
+ say set_color("You're about to deploy the contents of '#{repo.dist_path}' to")
107
+ say set_color(Cdn.url(repo), :bold)
108
+ say set_color("\nWARNING: Please be aware that this may be a destructive operation", :yellow)
109
+ end
110
+ exit 1 unless yes?('Are you sure? [y/n]')
111
+ end
112
+
113
+ def repo
114
+ Afterlife.current_repo
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Config
5
+ def self.file
6
+ Afterlife.local_path.join('config.yml')
7
+ end
8
+
9
+ def respond_to_missing?(sym, include_all)
10
+ config.key?(sym) || super
11
+ end
12
+
13
+ def method_missing(sym, *args, &block)
14
+ config.key?(sym) ? config[sym] : super
15
+ end
16
+
17
+ def stages
18
+ @stages ||= config.delete(:stages)&.to_h do |name, val|
19
+ [name, Stage.new(val.merge(name: name.to_s))]
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def config
26
+ @config ||= YAML.load_file(Config.file).deep_symbolize_keys
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Deploy
5
+ class CdnDeployment < Deployment
6
+ def run
7
+ Exec.run(commands)
8
+ Cdn.url(repo)
9
+ end
10
+
11
+ def setup
12
+ repo.env.merge!(
13
+ 'AWS_PROFILE' => Afterlife.config.credentials.dig(:aws, :profile),
14
+ 'AWS_BUCKET' => Afterlife.current_stage.bucket,
15
+ 'AWS_REGION' => Afterlife.current_stage.region,
16
+ 'S3_FULL_PATH' => s3_full_path,
17
+ )
18
+ end
19
+
20
+ private
21
+
22
+ def commands
23
+ [
24
+ repo_command || javascripts,
25
+ revision,
26
+ ].flatten
27
+ end
28
+
29
+ def javascripts
30
+ # by default we use no-cache, if something else is needed
31
+ # do it in the deploy.command option
32
+ <<-BASH
33
+ aws s3 sync
34
+ %<DIST_PATH>s
35
+ %<S3_FULL_PATH>s
36
+ --exclude '*.ts'
37
+ --exclude '*.tsx'
38
+ --cache-control 'no-cache'
39
+ BASH
40
+ end
41
+
42
+ def revision
43
+ <<-BASH
44
+ echo "#{repo.current_revision}" | aws s3 cp --content-type text/plain - %<S3_FULL_PATH>s/revision
45
+ BASH
46
+ end
47
+
48
+ def s3_full_path
49
+ "s3://#{Afterlife.current_stage.bucket}/#{repo.full_name}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Deploy
5
+ class Deployment
6
+ def self.run
7
+ deployment = new.tap(&:setup)
8
+ deployment.run
9
+ end
10
+
11
+ def run
12
+ fail NotImplementedError
13
+ end
14
+
15
+ def repo_command
16
+ Array(repo.conf.dig(:deploy, :command))
17
+ end
18
+
19
+ def setup; end
20
+
21
+ protected
22
+
23
+ def repo
24
+ Afterlife.current_repo
25
+ end
26
+ end
27
+ end
28
+ end