lib/codebuild-notifier/build_history.rb in codebuild-notifier-0.3.2 vs lib/codebuild-notifier/build_history.rb in codebuild-notifier-1.0.0
- old
+ new
@@ -13,101 +13,116 @@
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with codebuild-notifier. If not, see <http://www.gnu.org/licenses/>.
-require 'active_support'
-require 'active_support/core_ext'
-require 'aws-sdk-dynamodb'
-require 'hashie'
-
module CodeBuildNotifier
- class BuildHistory
- attr_reader :config, :current_build
-
- delegate :dynamo_table, to: :config
+ class BuildHistory < DynamoBase
delegate :branch_name, :launched_by_retry?, to: :current_build
- def initialize(config, current_build)
- @config = config
- @current_build = current_build
- end
-
def last_entry
+ return @last_entry if defined?(@last_entry)
# If this build was launched using the Retry command from the console
# or api we don't have a Pull Request or branch name to use in the
# primary key, so we query by commit hash and project instead.
- item = launched_by_retry? ? find_by_commit_and_project : find_by_id
+ item = launched_by_retry? ? find_by_commit : find_by_id
# Provide .dot access to hash values from Dynamo item.
- item && Hashie::Mash.new(item)
+ @last_entry = item && Hashie::Mash.new(item)
end
def write_entry(source_id)
updates = hash_to_dynamo_update(new_entry).merge(
- key: { source_id: source_id }
+ key: { source_id: source_id, version_key: version_key }
)
yield updates if block_given?
- dynamo_client.update_item(
- updates.merge(table_name: dynamo_table)
- )
+ update_item(updates)
+
+ ProjectSummary.new(config, current_build).update
+ BranchEntry.new(config, current_build).update
end
# The commit hash and project code are used to find which Pull Request
# or branch the current build belongs to, and the previous build status
# for that Pull Request or branch.
- private def find_by_commit_and_project
- dynamo_client.query(
- expression_attribute_values: {
- ':commit_hash' => current_build.commit_hash,
- ':project_code' => current_build.project_code
- },
- filter_expression: 'project_code = :project_code',
+ private def find_by_commit
+ find_latest_version(
+ expression_attribute_values: commit_values,
+ filter_expression: commit_filter,
index_name: 'commit_hash_index',
- key_condition_expression: 'commit_hash = :commit_hash',
- table_name: dynamo_table
- ).items.first
+ key_condition_expression: 'commit_hash = :commit_hash'
+ )
end
+ # When searching by commit hash, if the current build source version
+ # is for a PR, only return commits with that PR as the source ref.
+ # If source version is not for a pr, only return source refs beginning
+ # with branch/. This helps protect against the edge case where the same
+ # commit appears in two different PRs, or in a PR and whitelisted branch
+ # besides than the PR head.
+ private def commit_values
+ source_ref_val = if current_build.for_pr?
+ current_build.source_version
+ else
+ 'branch/'
+ end
+ {
+ ':commit_hash' => current_build.commit_hash,
+ ':project_code' => current_build.project_code,
+ ':source_ref' => source_ref_val
+ }
+ end
+
+ private def commit_filter
+ source_ref_condition = if current_build.for_pr?
+ 'source_ref = :source_ref'
+ else
+ 'begins_with(source_ref, :source_ref)'
+ end
+ "project_code = :project_code AND #{source_ref_condition}"
+ end
+
private def find_by_id
- dynamo_client.get_item(
- key: { 'source_id' => current_build.source_id },
- table_name: dynamo_table
- ).item
+ find_latest_version(
+ expression_attribute_values: {
+ ':source_id' => current_build.source_id
+ },
+ key_condition_expression: 'source_id = :source_id'
+ )
end
+ private def find_latest_version(args)
+ dynamo_client.query(
+ args.merge(
+ scan_index_forward: false, # Reverse sort by range key
+ table_name: dynamo_table
+ )
+ ).items.first
+ end
+
private def new_entry
- {
- commit_hash: current_build.commit_hash,
- project_code: current_build.project_code,
- status: current_build.status
- }.tap do |memo|
+ current_build.history_fields.tap do |memo|
# If launched via manual re-try instead of via a webhook, we don't
# want to overwrite the current source_ref value that tells us which
# branch or pull request originally created the dynamo record.
unless launched_by_retry?
memo[:source_ref] = current_build.trigger
memo[:branch_name] = branch_name unless branch_name.empty?
end
end
end
- private def hash_to_dynamo_update(hash)
- update = hash.each_with_object(
- expression_attribute_names: {},
- expression_attribute_values: {},
- update_expression: []
- ) do |(key, value), memo|
- memo[:expression_attribute_names]["##{key}"] = key.to_s
- memo[:expression_attribute_values][":#{key}"] = value
- memo[:update_expression] << "##{key} = :#{key}"
+ # The first component of the version_key is the timestamp for the
+ # first build of the current commit hash. The second component
+ # is the timestamp for the current build. This allows easily finding
+ # either the latest commit, or the latest re-build of a commit.
+ private def version_key
+ if launched_by_retry?
+ "#{last_entry.start_time}_#{current_build.start_time}"
+ else
+ "#{current_build.start_time}_#{current_build.start_time}"
end
- update.merge(update_expression: "SET #{update[:update_expression].join(', ')}")
- end
-
- private def dynamo_client
- @dynamo_client || Aws::DynamoDB::Client.new(region: config.region)
end
end
end