dh-proteus 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +80 -0
- data/README.md +414 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/proteus +5 -0
- data/bin/proteus_testing +8 -0
- data/bin/setup +8 -0
- data/build.sh +28 -0
- data/dh-proteus.gemspec +50 -0
- data/lib/core_ext/hash.rb +14 -0
- data/lib/proteus.rb +9 -0
- data/lib/proteus/app.rb +86 -0
- data/lib/proteus/backend/backend.rb +41 -0
- data/lib/proteus/commands/apply.rb +53 -0
- data/lib/proteus/commands/clean.rb +22 -0
- data/lib/proteus/commands/destroy.rb +51 -0
- data/lib/proteus/commands/graph.rb +22 -0
- data/lib/proteus/commands/import.rb +75 -0
- data/lib/proteus/commands/move.rb +28 -0
- data/lib/proteus/commands/output.rb +29 -0
- data/lib/proteus/commands/plan.rb +55 -0
- data/lib/proteus/commands/remove.rb +71 -0
- data/lib/proteus/commands/render.rb +36 -0
- data/lib/proteus/commands/taint.rb +35 -0
- data/lib/proteus/common.rb +72 -0
- data/lib/proteus/config/config.rb +47 -0
- data/lib/proteus/context_management/context.rb +31 -0
- data/lib/proteus/context_management/helpers.rb +14 -0
- data/lib/proteus/generate.rb +18 -0
- data/lib/proteus/generators/context.rb +57 -0
- data/lib/proteus/generators/environment.rb +42 -0
- data/lib/proteus/generators/init.rb +40 -0
- data/lib/proteus/generators/module.rb +69 -0
- data/lib/proteus/generators/templates/config/config.yaml.erb +22 -0
- data/lib/proteus/generators/templates/context/main.tf.erb +7 -0
- data/lib/proteus/generators/templates/context/variables.tf.erb +3 -0
- data/lib/proteus/generators/templates/environment/terraform.tfvars.erb +6 -0
- data/lib/proteus/generators/templates/module/io.tf.erb +1 -0
- data/lib/proteus/generators/templates/module/module.tf.erb +1 -0
- data/lib/proteus/generators/templates/module/validator.rb.erb +9 -0
- data/lib/proteus/global_commands/validate.rb +45 -0
- data/lib/proteus/helpers.rb +91 -0
- data/lib/proteus/helpers/path_helpers.rb +90 -0
- data/lib/proteus/helpers/string_helpers.rb +13 -0
- data/lib/proteus/init.rb +16 -0
- data/lib/proteus/modules/manager.rb +53 -0
- data/lib/proteus/modules/terraform_module.rb +184 -0
- data/lib/proteus/templates/partial.rb +123 -0
- data/lib/proteus/templates/template_binding.rb +62 -0
- data/lib/proteus/validators/base_validator.rb +24 -0
- data/lib/proteus/validators/validation_dsl.rb +172 -0
- data/lib/proteus/validators/validation_error.rb +9 -0
- data/lib/proteus/validators/validation_helpers.rb +9 -0
- data/lib/proteus/version.rb +7 -0
- metadata +260 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "proteus"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/proteus
ADDED
data/bin/proteus_testing
ADDED
data/bin/setup
ADDED
data/build.sh
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -eo pipefail
|
4
|
+
|
5
|
+
AWS_PROFILE=logistics-production
|
6
|
+
BUCKET=production-eu-gems
|
7
|
+
NEXUS_REPOSITORY=https://nexus.usehurrier.com/repository/proteus/
|
8
|
+
VERSION=$(ruby lib/proteus/version.rb)
|
9
|
+
|
10
|
+
|
11
|
+
if ! gem list --local | grep nexus; then
|
12
|
+
echo Please install the nexus gem in order to push to the gem repository: \"gem install nexus\"
|
13
|
+
exit 1
|
14
|
+
fi
|
15
|
+
|
16
|
+
echo Building version $VERSION
|
17
|
+
|
18
|
+
mkdir -p ./release/gems
|
19
|
+
|
20
|
+
rake build
|
21
|
+
|
22
|
+
cp ./pkg/*.gem ./release/gems/
|
23
|
+
|
24
|
+
gem generate_index --directory ./release/
|
25
|
+
|
26
|
+
aws --profile $AWS_PROFILE s3 sync ./release s3://$BUCKET
|
27
|
+
|
28
|
+
gem nexus release/gems/dh-proteus-$VERSION.gem --url $NEXUS_REPOSITORY
|
data/dh-proteus.gemspec
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "proteus/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dh-proteus"
|
8
|
+
spec.version = Proteus::VERSION
|
9
|
+
spec.authors = ["Simon Albrecht"]
|
10
|
+
spec.email = ["simon.albrecht@deliveryhero.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Proteus is a Terraform wrapper application.}
|
13
|
+
#spec.description = %q{Write a longer description or delete this line.}
|
14
|
+
spec.homepage = "https://github.com/deliveryhero/proteus"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
|
+
|
21
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
22
|
+
spec.metadata["source_code_uri"] = "https://github.com/deliveryhero/proteus"
|
23
|
+
spec.metadata["changelog_uri"] = "https://github.com/deliveryhero/proteus/CHANGELOG"
|
24
|
+
else
|
25
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
26
|
+
"public gem pushes."
|
27
|
+
end
|
28
|
+
|
29
|
+
# Specify which files should be added to the gem when it is released.
|
30
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
31
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
32
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
33
|
+
end
|
34
|
+
spec.bindir = "bin"
|
35
|
+
spec.executables = ['proteus']
|
36
|
+
spec.require_paths = ["lib"]
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
39
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
41
|
+
spec.add_development_dependency "pry"
|
42
|
+
|
43
|
+
spec.add_runtime_dependency 'activesupport', '~> 5.1.1'
|
44
|
+
spec.add_runtime_dependency 'thor', '~> 0.20.0'
|
45
|
+
spec.add_runtime_dependency 'erubis', '~> 2.7.0'
|
46
|
+
spec.add_runtime_dependency 'hcl-checker', '~> 1.0.5'
|
47
|
+
spec.add_runtime_dependency 'aws-sdk-rds', '~> 1.11.0'
|
48
|
+
spec.add_runtime_dependency 'aws-sdk-route53', '~> 1.7.0'
|
49
|
+
spec.add_runtime_dependency 'aws-sdk-elasticsearchservice', '~> 1.4.0'
|
50
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ::Hash
|
2
|
+
def deep_merge(second)
|
3
|
+
merger = proc do |_, v1, v2|
|
4
|
+
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
5
|
+
v1.merge(v2, &merger)
|
6
|
+
elsif v1.is_a?(Array) && v2.is_a?(Array)
|
7
|
+
v1 | v2
|
8
|
+
else
|
9
|
+
[:undefined, nil, :nil].include?(v2) ? v1 : v2
|
10
|
+
end
|
11
|
+
end
|
12
|
+
merge(second.to_h, &merger)
|
13
|
+
end
|
14
|
+
end
|
data/lib/proteus.rb
ADDED
data/lib/proteus/app.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'proteus/common'
|
2
|
+
require 'proteus/generate'
|
3
|
+
require 'proteus/init'
|
4
|
+
require 'proteus/global_commands/validate'
|
5
|
+
require 'proteus/context_management/context'
|
6
|
+
require 'proteus/context_management/helpers'
|
7
|
+
require 'proteus/templates/template_binding'
|
8
|
+
require 'proteus/templates/partial'
|
9
|
+
|
10
|
+
module Proteus
|
11
|
+
|
12
|
+
class Context < Thor; end
|
13
|
+
|
14
|
+
class App < Thor
|
15
|
+
extend Proteus::Helpers
|
16
|
+
extend Proteus::Helpers::StringHelpers
|
17
|
+
extend Proteus::ContextManagement::Helpers
|
18
|
+
|
19
|
+
class_option :dryrun, type: :boolean, default: false, aliases: '-d'
|
20
|
+
class_option :verbose, type: :boolean, default: false, aliases: '-v'
|
21
|
+
|
22
|
+
contexts.each do |context|
|
23
|
+
|
24
|
+
if context.name != 'default'
|
25
|
+
|
26
|
+
# generate class for subcommand within contexts subcommand
|
27
|
+
context_subcommand_class = Class.new(Thor)
|
28
|
+
context_subcommand_class_name = camel_case(context.name)
|
29
|
+
Object.const_set(context_subcommand_class_name, context_subcommand_class)
|
30
|
+
|
31
|
+
Proteus::Context.class_eval do
|
32
|
+
desc "#{context.name} SUBCOMMAND", "Manage the #{context.name} context."
|
33
|
+
subcommand(context.name, const_get(context_subcommand_class_name))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
mod_name = Object.const_set("Module#{camel_case(context.name)}", Module.new)
|
38
|
+
|
39
|
+
context.environments.each do |environment|
|
40
|
+
class_name = camel_case(environment)
|
41
|
+
|
42
|
+
klass = Class.new(Proteus::Common)
|
43
|
+
|
44
|
+
mod_name.const_set(class_name, klass)
|
45
|
+
|
46
|
+
klass.class_eval do
|
47
|
+
include Config
|
48
|
+
@context = context.name
|
49
|
+
@environment = environment
|
50
|
+
|
51
|
+
def self.context
|
52
|
+
@context
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.environment
|
56
|
+
@environment
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if context.name == 'default'
|
61
|
+
# attach subcommands for the standard environments directly
|
62
|
+
# eg.:
|
63
|
+
# ./proteus production_eu SUBCOMMAND
|
64
|
+
desc "#{environment} SUBCOMMAND", "Manage the #{environment} environment in context default"
|
65
|
+
subcommand(environment, mod_name.const_get(class_name))
|
66
|
+
else
|
67
|
+
const_get(context_subcommand_class_name).class_eval do
|
68
|
+
desc "#{environment} SUBCOMMAND", "Manage the #{environment} environment in context #{context.name}"
|
69
|
+
subcommand(environment, mod_name.const_get(class_name))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'generate SUBCOMMAND', 'Generators for modules and environments'
|
76
|
+
subcommand('generate', Proteus::Generate)
|
77
|
+
|
78
|
+
desc 'context SUBCOMMAND', 'Context subcommands'
|
79
|
+
subcommand('context', Context)
|
80
|
+
|
81
|
+
desc 'init', 'Initializes a new proteus root directory in the current working directory'
|
82
|
+
subcommand('init', Proteus::Init)
|
83
|
+
|
84
|
+
include Proteus::GlobalCommands::Validate
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'proteus/helpers/path_helpers'
|
2
|
+
|
3
|
+
module Proteus
|
4
|
+
module Backend
|
5
|
+
class Backend
|
6
|
+
include Proteus::Helpers::PathHelpers
|
7
|
+
include Thor::Shell
|
8
|
+
|
9
|
+
def initialize(config:, context:, environment:)
|
10
|
+
@config = config
|
11
|
+
@context = context
|
12
|
+
@environment = environment
|
13
|
+
end
|
14
|
+
|
15
|
+
def render
|
16
|
+
File.open(File.join(context_path(@context), 'backend.tf'), 'w') do |file|
|
17
|
+
file.write(Erubis::Eruby.new(template).result(binding))
|
18
|
+
end
|
19
|
+
rescue StandardError => e
|
20
|
+
say 'Error rendering backend config.', :magenta
|
21
|
+
say e.message, :magenta
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def template
|
28
|
+
<<~TEMPLATE
|
29
|
+
terraform {
|
30
|
+
backend "s3" {
|
31
|
+
bucket = "<%= @config[:backend][:bucket][:name] %>"
|
32
|
+
key = "<%= @config[:backend][:key_prefix] %>#{@context}-#{@environment}.tfstate"
|
33
|
+
region = "<%= @config[:backend][:bucket][:region] %>"
|
34
|
+
profile = "<%= @config[:backend][:bucket][:profile]%>"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
TEMPLATE
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Proteus
|
2
|
+
module Commands
|
3
|
+
module Apply
|
4
|
+
def self.included(thor_class)
|
5
|
+
thor_class.class_eval do
|
6
|
+
|
7
|
+
desc 'apply', 'Applies the current terraform code'
|
8
|
+
long_desc <<-LONGDESC
|
9
|
+
Applies the current terraform code
|
10
|
+
|
11
|
+
With --limit option, Terraform will only apply changes for the the specified resources
|
12
|
+
LONGDESC
|
13
|
+
option :limit, type: :array, aliases: "-l", required: false, default: nil
|
14
|
+
def apply
|
15
|
+
init(verbose: parent_options[:verbose])
|
16
|
+
|
17
|
+
confirm question: "Do you really want to run 'terraform apply' on environment '#{environment}' in context '#{context}'?", color: :on_red, exit_code: 0 do
|
18
|
+
|
19
|
+
|
20
|
+
if !options[:dryrun]
|
21
|
+
slack_notification(
|
22
|
+
context: context,
|
23
|
+
environment: environment,
|
24
|
+
message: 'is running terraform apply. Wait for it.'
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
apply_command = <<~APPLY_COMMAND
|
29
|
+
cd #{context_path(context)} && \
|
30
|
+
terraform apply \
|
31
|
+
-input=true \
|
32
|
+
-refresh=true \
|
33
|
+
#{limit(options[:limit])} \
|
34
|
+
#{plan_file(context, environment)}
|
35
|
+
APPLY_COMMAND
|
36
|
+
|
37
|
+
syscall apply_command.squeeze(' '), dryrun: dryrun
|
38
|
+
|
39
|
+
if !options[:dryrun]
|
40
|
+
slack_notification(
|
41
|
+
context: context,
|
42
|
+
environment: environment,
|
43
|
+
message: 'applied a new version of me!'
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'proteus/modules/manager'
|
2
|
+
|
3
|
+
module Proteus
|
4
|
+
module Commands
|
5
|
+
module Clean
|
6
|
+
def self.included(thor_class)
|
7
|
+
thor_class.class_eval do |klass|
|
8
|
+
|
9
|
+
desc "clean", "Deletes rendered module manifests"
|
10
|
+
long_desc <<-LONGDESC
|
11
|
+
Deletes rendered module manifests without running Terraform
|
12
|
+
LONGDESC
|
13
|
+
def clean
|
14
|
+
module_manager = Proteus::Modules::Manager.new(context: context, environment: environment)
|
15
|
+
module_manager.clean
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Proteus
|
2
|
+
module Commands
|
3
|
+
module Destroy
|
4
|
+
def self.included(thor_class)
|
5
|
+
thor_class.class_eval do
|
6
|
+
|
7
|
+
desc "destroy", "Destroys AWS resources."
|
8
|
+
long_desc <<-LONGDESC
|
9
|
+
Destroys AWS resources.
|
10
|
+
|
11
|
+
With --limit option, Terraform will only destroy the specified resources
|
12
|
+
If --limit does not get passed as an argument, terraform will destroy all of the AWS assets in the state.
|
13
|
+
LONGDESC
|
14
|
+
option :limit, type: :array, aliases: "-l", required: false
|
15
|
+
def destroy
|
16
|
+
init(verbose: parent_options[:verbose])
|
17
|
+
|
18
|
+
destroy_command = <<~DESTROY_COMMAND
|
19
|
+
cd #{context_path(context)} && \
|
20
|
+
terraform destroy \
|
21
|
+
-var-file=#{var_file(context, environment)} \
|
22
|
+
#{aws_profile} \
|
23
|
+
##{limit(options[:limit])}
|
24
|
+
DESTROY_COMMAND
|
25
|
+
|
26
|
+
plan_destroy_command = <<~PLAN_DESTROY_COMMAND
|
27
|
+
cd #{context_path(context)} && \
|
28
|
+
terraform plan \
|
29
|
+
-destroy \
|
30
|
+
-out=#{plan_file(context, environment)} \
|
31
|
+
-var-file=#{var_file(context, environment)} \
|
32
|
+
#{aws_profile} \
|
33
|
+
##{limit(options[:limit])}
|
34
|
+
PLAN_DESTROY_COMMAND
|
35
|
+
|
36
|
+
|
37
|
+
if dryrun
|
38
|
+
puts destroy_command.squeeze(' ')
|
39
|
+
else
|
40
|
+
syscall plan_destroy_command.squeeze(' ')
|
41
|
+
confirm question: "Do you really want to run 'terraform destroy' on environment '#{environment}'?", color: :on_red, exit_code: 0 do
|
42
|
+
exec destroy_command.squeeze(' ')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Proteus
|
2
|
+
module Commands
|
3
|
+
module Graph
|
4
|
+
def self.included(thor_class)
|
5
|
+
thor_class.class_eval do
|
6
|
+
|
7
|
+
desc "graph", "Creates a graph showing the resources and their relations."
|
8
|
+
def graph
|
9
|
+
say "Generating graph. This might take a while"
|
10
|
+
graph_command = <<~GRAPH_COMMAND
|
11
|
+
cd #{context_path(context)} \
|
12
|
+
&& terraform graph -draw-cycles -module-depth=1 | dot -Tpng > graph.png \
|
13
|
+
&& open graph.png
|
14
|
+
GRAPH_COMMAND
|
15
|
+
`#{graph_command}`
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Proteus
|
2
|
+
module Commands
|
3
|
+
module Import
|
4
|
+
def self.included(thor_class)
|
5
|
+
thor_class.class_eval do
|
6
|
+
|
7
|
+
desc "import", "Imports an existing resource into the terraform state"
|
8
|
+
long_desc <<-LONGDESC
|
9
|
+
Imports existing resources into the terraform state
|
10
|
+
|
11
|
+
--address Terraform internal address of the resource
|
12
|
+
|
13
|
+
--bulk Enables bulk import mode
|
14
|
+
|
15
|
+
--resource The resource to import into the terraform state
|
16
|
+
|
17
|
+
--resources_file File containing resource addresses and identifiers
|
18
|
+
LONGDESC
|
19
|
+
option :address, type: :string, aliases: "-a", default: nil
|
20
|
+
option :bulk, type: :boolean, aliases: "-b", required: false, default: false
|
21
|
+
option :resource, type: :string, aliases: "-r", default: nil
|
22
|
+
option :resources_file, type: :string, aliases: "-f", required: false, default: nil
|
23
|
+
def import
|
24
|
+
if options[:bulk]
|
25
|
+
if !options[:resources_file]
|
26
|
+
say "Supply a file containing resource identifiers and Terraform addresses for bulk operations", :red
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
else
|
30
|
+
if !(options[:address] || !options[:resource])
|
31
|
+
say "You need to supply a resource address and a resource.", :red
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
init(verbose: parent_options[:verbose])
|
37
|
+
|
38
|
+
confirm question: "Do you really want to run 'terraform import' in context '(#{context}, #{environment})'?", color: :on_red, exit_code: 0 do
|
39
|
+
|
40
|
+
import_command = <<~IMPORT_COMMAND
|
41
|
+
cd #{context_path(context)} && \
|
42
|
+
terraform import \
|
43
|
+
-var-file=#{var_file(context, environment)} \
|
44
|
+
#{aws_profile} \
|
45
|
+
%{address} \
|
46
|
+
%{resource}
|
47
|
+
IMPORT_COMMAND
|
48
|
+
|
49
|
+
if options[:bulk]
|
50
|
+
if File.file?(options[:resources_file])
|
51
|
+
File.open(options[:resources_file], "r") do |file|
|
52
|
+
resource_count = File.foreach(file).inject(0) {|count, line| count + 1}
|
53
|
+
index = 1
|
54
|
+
file.each_line do |line|
|
55
|
+
say "Processing resource #{index}/#{resource_count}", :green
|
56
|
+
resource = line.chomp.split(" = ")
|
57
|
+
syscall (import_command % { address: resource[0], resource: resource[1] }).squeeze(' ')
|
58
|
+
index += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
else
|
62
|
+
say "File #{options[:resources_file]} does not exist.", :red
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
else
|
66
|
+
syscall (import_command % { address: options[:address], resource: options[:resource] })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|