drunker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,124 @@
1
+ module Drunker
2
+ class Executor
3
+ class Builder
4
+ IN_PROGRESS = "IN_PROGRESS"
5
+ SUCCEEDED = "SUCCEEDED"
6
+ FAILED = "FAILED"
7
+ TIMED_OUT = "TIMED_OUT"
8
+ STOPPED = "STOPPED"
9
+
10
+ RETRY_LIMIT = 3
11
+ PHASE_ACCESS_DENIED = "ACCESS_DENIED"
12
+
13
+ attr_reader :build_id
14
+
15
+ def initialize(project_name:, targets:, artifact:, config:, logger:)
16
+ @project_name = project_name
17
+ @targets = targets
18
+ @artifact = artifact
19
+ @config = config
20
+ @client = Aws::CodeBuild::Client.new(config.aws_client_options)
21
+ @retry_count = 0
22
+ @logger = logger
23
+ end
24
+
25
+ def run
26
+ @build_id = client.start_build(project_name: project_name, buildspec_override: buildspec).build.id
27
+ refresh
28
+ logger.info("Started build: #{build_id}")
29
+ logger.debug("buildspec: #{buildspec}")
30
+ build_id
31
+ end
32
+
33
+ def retriable?
34
+ retry_count < RETRY_LIMIT
35
+ end
36
+
37
+ def retry
38
+ logger.info("Retrying build: #{build_id}")
39
+ @retry_count += 1
40
+ run
41
+ end
42
+
43
+ # Sometimes `* is not authorized to perform` or `Not authorized to perform` error occurs...
44
+ # It is judged that this is not a problem by user setting.
45
+ def access_denied?
46
+ return false unless failed?
47
+ result.builds[0].phases.any? do |phase|
48
+ phase.contexts&.any? do |context|
49
+ context.status_code == PHASE_ACCESS_DENIED && access_denied_message_included?(context.message)
50
+ end
51
+ end
52
+ end
53
+
54
+ def running?
55
+ status == IN_PROGRESS
56
+ end
57
+
58
+ def failed?
59
+ status == FAILED
60
+ end
61
+
62
+ def success?
63
+ status == SUCCEEDED
64
+ end
65
+
66
+ def refresh
67
+ @result = nil
68
+ end
69
+
70
+ def errors
71
+ return unless failed?
72
+ result.builds[0].phases.each_with_object([]) do |phase, results|
73
+ phase.contexts&.each do |context|
74
+ results << {
75
+ phase_type: phase.phase_type,
76
+ phase_status: phase.phase_status,
77
+ status: context.status_code,
78
+ message: context.message
79
+ }
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ attr_reader :project_name
87
+ attr_reader :targets
88
+ attr_reader :artifact
89
+ attr_reader :config
90
+ attr_reader :client
91
+ attr_reader :retry_count
92
+ attr_reader :logger
93
+
94
+ def result
95
+ @result ||= client.batch_get_builds(ids: [build_id])
96
+ end
97
+
98
+ def status
99
+ result.builds[0].build_status
100
+ end
101
+
102
+ def buildspec
103
+ commands = interpolate_commands
104
+ stdout = artifact.stdout
105
+ stderr = artifact.stderr
106
+ exit_status = artifact.exit_status
107
+
108
+ ERB.new(config.buildspec).result(binding)
109
+ end
110
+
111
+ def interpolate_commands
112
+ variables = %w(FILES)
113
+
114
+ config.commands.map do |command|
115
+ variables.include?(command) ? targets : command
116
+ end.flatten
117
+ end
118
+
119
+ def access_denied_message_included?(message)
120
+ message.include?("is not authorized to perform") || message.include?("Not authorized to perform")
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,11 @@
1
+ ---
2
+ version: 0.1
3
+ phases:
4
+ build:
5
+ commands:
6
+ - <%= commands.join(" ") %> 1> <%= stdout %> 2> <%= stderr %>; echo $? > <%= exit_status %>
7
+ artifacts:
8
+ files:
9
+ - <%= stdout %>
10
+ - <%= stderr %>
11
+ - <%= exit_status %>
@@ -0,0 +1,92 @@
1
+ module Drunker
2
+ class Executor
3
+ class IAM
4
+ attr_reader :role
5
+
6
+ def initialize(source:, artifact:, config:, logger:)
7
+ timestamp = Time.now.to_i.to_s
8
+ client = Aws::IAM::Client.new(config.aws_client_options)
9
+ iam = Aws::IAM::Resource.new(client: client)
10
+
11
+ @role = iam.create_role(
12
+ role_name: "drunker-codebuild-servie-role-#{timestamp}",
13
+ assume_role_policy_document: role_json,
14
+ )
15
+ logger.info("Created IAM role: #{role.name}")
16
+ @policy = iam.create_policy(
17
+ policy_name: "drunker-codebuild-service-policy-#{timestamp}",
18
+ policy_document: policy_json(source: source, artifact: artifact)
19
+ )
20
+ logger.info("Created IAM policy: #{policy.policy_name}")
21
+ role.attach_policy(policy_arn: policy.arn)
22
+ logger.debug("Attached #{policy.policy_name} to #{role.name}")
23
+ @logger = logger
24
+ end
25
+
26
+ def delete
27
+ role.detach_policy(policy_arn: policy.arn)
28
+ logger.debug("Detached #{policy.policy_name} from #{role.name}")
29
+ policy.delete
30
+ logger.info("Deleted IAM policy: #{policy.policy_name}")
31
+ role.delete
32
+ logger.info("Deleted IAM role: #{role.name}")
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :policy
38
+ attr_reader :logger
39
+
40
+ def role_json
41
+ {
42
+ Version: "2012-10-17",
43
+ Statement: [
44
+ {
45
+ Effect: "Allow",
46
+ Principal: {
47
+ Service: "codebuild.amazonaws.com",
48
+ },
49
+ Action: "sts:AssumeRole",
50
+ }
51
+ ],
52
+ }.to_json
53
+ end
54
+
55
+ def policy_json(source:, artifact:)
56
+ {
57
+ Version: "2012-10-17",
58
+ Statement: [
59
+ {
60
+ Effect: "Allow",
61
+ Resource: "*",
62
+ Action: [
63
+ "logs:CreateLogGroup",
64
+ "logs:CreateLogStream",
65
+ "logs:PutLogEvents",
66
+ ]
67
+ },
68
+ {
69
+ Effect: "Allow",
70
+ Resource: [
71
+ "arn:aws:s3:::#{source.location}"
72
+ ],
73
+ Action: [
74
+ "s3:GetObject",
75
+ "s3:GetObjectVersion",
76
+ ]
77
+ },
78
+ {
79
+ Effect: "Allow",
80
+ Resource: [
81
+ "arn:aws:s3:::#{artifact.bucket.name}/*"
82
+ ],
83
+ Action: [
84
+ "s3:PutObject"
85
+ ]
86
+ }
87
+ ]
88
+ }.to_json
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,72 @@
1
+ module Drunker
2
+ class Source
3
+ attr_reader :target_files
4
+
5
+ def initialize(target_dir, config:, logger:)
6
+ timestamp = Time.now.to_i.to_s
7
+ s3 = Aws::S3::Resource.new(client: Aws::S3::Client.new(config.aws_client_options))
8
+
9
+ @bucket = s3.create_bucket(bucket: "drunker-source-store-#{timestamp}")
10
+ logger.info("Created source bucket: #{bucket.name}")
11
+ @name = "drunker_source_#{timestamp}.zip"
12
+ @target_files = []
13
+ @config = config
14
+ @logger = logger
15
+
16
+ set_target_files(target_dir)
17
+ archive(target_dir) do |path|
18
+ bucket.object(name).upload_file(path.to_s)
19
+ logger.info("Uploaded source archive: #{location}")
20
+ end
21
+ @logger = logger
22
+ end
23
+
24
+ def location
25
+ "#{bucket.name}/#{name}"
26
+ end
27
+
28
+ def to_h
29
+ {
30
+ type: "S3",
31
+ location: location
32
+ }
33
+ end
34
+
35
+ def delete
36
+ bucket.delete!
37
+ logger.info("Deleted bucket: #{bucket.name}")
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :bucket
43
+ attr_reader :name
44
+ attr_reader :config
45
+ attr_reader :logger
46
+
47
+ def archive(target_dir)
48
+ archive_path = Pathname.new("#{target_dir.to_s}/#{name}")
49
+
50
+ Zip::File.open(archive_path.to_s, Zip::File::CREATE) do |zip|
51
+ Pathname.glob(target_dir.to_s + "/**/*", File::Constants::FNM_DOTMATCH).select(&:file?).each do |real_path|
52
+ archive_file = real_path.relative_path_from(target_dir)
53
+ zip.add(archive_file, real_path.to_s)
54
+ logger.debug("Archived: #{archive_file.to_s}")
55
+ end
56
+ end
57
+ logger.debug("Archived source: #{archive_path.to_s}")
58
+ yield archive_path
59
+ archive_path.unlink
60
+ logger.debug("Deleted archive")
61
+ end
62
+
63
+ def set_target_files(target_dir)
64
+ logger.debug("Use file pattern: #{config.file_pattern}")
65
+ Pathname.glob(target_dir.to_s + "/" + config.file_pattern).select(&:file?).each do |real_path|
66
+ file = real_path.relative_path_from(target_dir).to_s
67
+ @target_files << file
68
+ logger.debug("Set target: #{file}")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module Drunker
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: drunker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kazuma Watanabe
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.19'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.19'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubyzip
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: aws-sdk
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: drunker-aggregator-pretty
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.1'
125
+ description: Distributed CLI runner on AWS CodeBuild
126
+ email:
127
+ - watassbass@gmail.com
128
+ executables:
129
+ - drunker
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".gitignore"
134
+ - ".rspec"
135
+ - ".travis.yml"
136
+ - CODE_OF_CONDUCT.md
137
+ - Gemfile
138
+ - LICENSE.txt
139
+ - README.md
140
+ - Rakefile
141
+ - bin/console
142
+ - bin/setup
143
+ - drunker.gemspec
144
+ - exe/drunker
145
+ - lib/drunker.rb
146
+ - lib/drunker/aggregator.rb
147
+ - lib/drunker/aggregator/base.rb
148
+ - lib/drunker/artifact.rb
149
+ - lib/drunker/artifact/layer.rb
150
+ - lib/drunker/cli.rb
151
+ - lib/drunker/config.rb
152
+ - lib/drunker/executor.rb
153
+ - lib/drunker/executor/builder.rb
154
+ - lib/drunker/executor/buildspec.yml.erb
155
+ - lib/drunker/executor/iam.rb
156
+ - lib/drunker/source.rb
157
+ - lib/drunker/version.rb
158
+ homepage: https://github.com/wata727/drunker
159
+ licenses:
160
+ - MIT
161
+ metadata: {}
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubyforge_project:
178
+ rubygems_version: 2.6.11
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Distributed CLI runner on AWS CodeBuild
182
+ test_files: []