diff --git a/lib/datadog/ci/configuration/components.rb b/lib/datadog/ci/configuration/components.rb index 9abe9443..aafad49f 100644 --- a/lib/datadog/ci/configuration/components.rb +++ b/lib/datadog/ci/configuration/components.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../flush" module Datadog diff --git a/lib/datadog/ci/configuration/settings.rb b/lib/datadog/ci/configuration/settings.rb index d75c5a77..fc3c38f9 100644 --- a/lib/datadog/ci/configuration/settings.rb +++ b/lib/datadog/ci/configuration/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../ext/settings" module Datadog diff --git a/lib/datadog/ci/contrib/cucumber/configuration/settings.rb b/lib/datadog/ci/contrib/cucumber/configuration/settings.rb index 9a0e6ccc..713094e6 100644 --- a/lib/datadog/ci/contrib/cucumber/configuration/settings.rb +++ b/lib/datadog/ci/contrib/cucumber/configuration/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../ext" require "datadog/tracing/contrib/configuration/settings" diff --git a/lib/datadog/ci/contrib/cucumber/ext.rb b/lib/datadog/ci/contrib/cucumber/ext.rb index 75fc21b4..4878a445 100644 --- a/lib/datadog/ci/contrib/cucumber/ext.rb +++ b/lib/datadog/ci/contrib/cucumber/ext.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Datadog module CI module Contrib @@ -5,14 +7,14 @@ module Cucumber # Cucumber integration constants # TODO: mark as `@public_api` when GA, to protect from resource and tag name changes. module Ext - APP = "cucumber".freeze - ENV_ENABLED = "DD_TRACE_CUCUMBER_ENABLED".freeze - ENV_OPERATION_NAME = "DD_TRACE_CUCUMBER_OPERATION_NAME".freeze - FRAMEWORK = "cucumber".freeze - OPERATION_NAME = "cucumber.test".freeze - SERVICE_NAME = "cucumber".freeze - STEP_SPAN_TYPE = "step".freeze - TEST_TYPE = "test".freeze + APP = "cucumber" + ENV_ENABLED = "DD_TRACE_CUCUMBER_ENABLED" + ENV_OPERATION_NAME = "DD_TRACE_CUCUMBER_OPERATION_NAME" + FRAMEWORK = "cucumber" + OPERATION_NAME = "cucumber.test" + SERVICE_NAME = "cucumber" + STEP_SPAN_TYPE = "step" + TEST_TYPE = "test" end end end diff --git a/lib/datadog/ci/contrib/cucumber/formatter.rb b/lib/datadog/ci/contrib/cucumber/formatter.rb index 239ae902..150218ac 100644 --- a/lib/datadog/ci/contrib/cucumber/formatter.rb +++ b/lib/datadog/ci/contrib/cucumber/formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../test" require_relative "../../ext/app_types" require_relative "../../ext/environment" diff --git a/lib/datadog/ci/contrib/cucumber/instrumentation.rb b/lib/datadog/ci/contrib/cucumber/instrumentation.rb index 87688ddf..9e98cc58 100644 --- a/lib/datadog/ci/contrib/cucumber/instrumentation.rb +++ b/lib/datadog/ci/contrib/cucumber/instrumentation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "formatter" module Datadog diff --git a/lib/datadog/ci/contrib/cucumber/integration.rb b/lib/datadog/ci/contrib/cucumber/integration.rb index 2e9c4b2d..115ba47a 100644 --- a/lib/datadog/ci/contrib/cucumber/integration.rb +++ b/lib/datadog/ci/contrib/cucumber/integration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "configuration/settings" require_relative "patcher" diff --git a/lib/datadog/ci/contrib/cucumber/patcher.rb b/lib/datadog/ci/contrib/cucumber/patcher.rb index 9e1430da..268404bf 100644 --- a/lib/datadog/ci/contrib/cucumber/patcher.rb +++ b/lib/datadog/ci/contrib/cucumber/patcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "datadog/tracing/contrib/patcher" require_relative "instrumentation" diff --git a/lib/datadog/ci/contrib/minitest/configuration/settings.rb b/lib/datadog/ci/contrib/minitest/configuration/settings.rb index 1ac6cd54..f412978e 100644 --- a/lib/datadog/ci/contrib/minitest/configuration/settings.rb +++ b/lib/datadog/ci/contrib/minitest/configuration/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../ext" require "datadog/tracing/contrib/configuration/settings" diff --git a/lib/datadog/ci/contrib/minitest/ext.rb b/lib/datadog/ci/contrib/minitest/ext.rb index 0ffaf746..b45ab2a3 100644 --- a/lib/datadog/ci/contrib/minitest/ext.rb +++ b/lib/datadog/ci/contrib/minitest/ext.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Datadog module CI module Contrib diff --git a/lib/datadog/ci/contrib/minitest/hooks.rb b/lib/datadog/ci/contrib/minitest/hooks.rb index d8bba439..0bb61195 100644 --- a/lib/datadog/ci/contrib/minitest/hooks.rb +++ b/lib/datadog/ci/contrib/minitest/hooks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Datadog module CI module Contrib diff --git a/lib/datadog/ci/contrib/minitest/integration.rb b/lib/datadog/ci/contrib/minitest/integration.rb index 4be1ae1c..e918e25e 100644 --- a/lib/datadog/ci/contrib/minitest/integration.rb +++ b/lib/datadog/ci/contrib/minitest/integration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "configuration/settings" require_relative "patcher" diff --git a/lib/datadog/ci/contrib/rspec/configuration/settings.rb b/lib/datadog/ci/contrib/rspec/configuration/settings.rb index 2ece8d0c..c573b9e5 100644 --- a/lib/datadog/ci/contrib/rspec/configuration/settings.rb +++ b/lib/datadog/ci/contrib/rspec/configuration/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../ext" require "datadog/tracing/contrib/configuration/settings" diff --git a/lib/datadog/ci/contrib/rspec/example.rb b/lib/datadog/ci/contrib/rspec/example.rb index 7be45b27..e45e26c6 100644 --- a/lib/datadog/ci/contrib/rspec/example.rb +++ b/lib/datadog/ci/contrib/rspec/example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../test" require_relative "../../ext/app_types" diff --git a/lib/datadog/ci/contrib/rspec/ext.rb b/lib/datadog/ci/contrib/rspec/ext.rb index 65351fac..9ce6ae1a 100644 --- a/lib/datadog/ci/contrib/rspec/ext.rb +++ b/lib/datadog/ci/contrib/rspec/ext.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Datadog module CI module Contrib @@ -5,13 +7,13 @@ module RSpec # RSpec integration constants # TODO: mark as `@public_api` when GA, to protect from resource and tag name changes. module Ext - APP = "rspec".freeze - ENV_ENABLED = "DD_TRACE_RSPEC_ENABLED".freeze - ENV_OPERATION_NAME = "DD_TRACE_RSPEC_OPERATION_NAME".freeze - FRAMEWORK = "rspec".freeze - OPERATION_NAME = "rspec.example".freeze - SERVICE_NAME = "rspec".freeze - TEST_TYPE = "test".freeze + APP = "rspec" + ENV_ENABLED = "DD_TRACE_RSPEC_ENABLED" + ENV_OPERATION_NAME = "DD_TRACE_RSPEC_OPERATION_NAME" + FRAMEWORK = "rspec" + OPERATION_NAME = "rspec.example" + SERVICE_NAME = "rspec" + TEST_TYPE = "test" end end end diff --git a/lib/datadog/ci/contrib/rspec/integration.rb b/lib/datadog/ci/contrib/rspec/integration.rb index 5ce19878..2121c50e 100644 --- a/lib/datadog/ci/contrib/rspec/integration.rb +++ b/lib/datadog/ci/contrib/rspec/integration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "datadog/tracing/contrib/integration" require_relative "configuration/settings" diff --git a/lib/datadog/ci/contrib/rspec/patcher.rb b/lib/datadog/ci/contrib/rspec/patcher.rb index 0dc057b6..80365e67 100644 --- a/lib/datadog/ci/contrib/rspec/patcher.rb +++ b/lib/datadog/ci/contrib/rspec/patcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "datadog/tracing/contrib/patcher" require_relative "example" diff --git a/lib/datadog/ci/ext/app_types.rb b/lib/datadog/ci/ext/app_types.rb index 297e78a1..e83ad3c3 100644 --- a/lib/datadog/ci/ext/app_types.rb +++ b/lib/datadog/ci/ext/app_types.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + module Datadog module CI module Ext module AppTypes - TYPE_TEST = "test".freeze + TYPE_TEST = "test" end end end diff --git a/lib/datadog/ci/ext/environment.rb b/lib/datadog/ci/ext/environment.rb index 98ec37e8..5e53253c 100644 --- a/lib/datadog/ci/ext/environment.rb +++ b/lib/datadog/ci/ext/environment.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -require "datadog/core/git/ext" - -require "open3" -require "json" +require_relative "git" +require_relative "environment/extractor" module Datadog module CI @@ -22,559 +20,61 @@ module Environment TAG_NODE_LABELS = "ci.node.labels" TAG_NODE_NAME = "ci.node.name" TAG_CI_ENV_VARS = "_dd.ci.env_vars" - TAG_NODE_LABELS = "ci.node.labels" - TAG_NODE_NAME = "ci.node.name" - PROVIDERS = [ - ["APPVEYOR", :extract_appveyor], - ["TF_BUILD", :extract_azure_pipelines], - ["BITBUCKET_COMMIT", :extract_bitbucket], - ["BUDDY", :extract_buddy], - ["BUILDKITE", :extract_buildkite], - ["CIRCLECI", :extract_circle_ci], - ["GITHUB_SHA", :extract_github_actions], - ["GITLAB_CI", :extract_gitlab], - ["JENKINS_URL", :extract_jenkins], - ["TEAMCITY_VERSION", :extract_teamcity], - ["TRAVIS", :extract_travis], - ["BITRISE_BUILD_SLUG", :extract_bitrise], - ["CF_BUILD_ID", :extract_codefresh] - ].freeze + HEX_NUMBER_REGEXP = /[0-9a-f]{40}/i.freeze module_function def tags(env) # Extract metadata from CI provider environment variables - _, extractor = PROVIDERS.find { |provider_env_var, _| env.key?(provider_env_var) } - tags = extractor ? public_send(extractor, env).reject { |_, v| v.nil? || v.strip.empty? } : {} + tags = Environment::Extractor.new(env).tags # If user defined metadata is defined, overwrite - tags.merge!(extract_user_defined_git(env)) - if !tags[Core::Git::Ext::TAG_BRANCH].nil? && tags[Core::Git::Ext::TAG_BRANCH].include?("tags/") - tags[Core::Git::Ext::TAG_TAG] = tags[Core::Git::Ext::TAG_BRANCH] - tags.delete(Core::Git::Ext::TAG_BRANCH) - end - - # Normalize Git references - tags[Core::Git::Ext::TAG_TAG] = normalize_ref(tags[Core::Git::Ext::TAG_TAG]) - tags[Core::Git::Ext::TAG_BRANCH] = normalize_ref(tags[Core::Git::Ext::TAG_BRANCH]) - tags[Core::Git::Ext::TAG_REPOSITORY_URL] = filter_sensitive_info( - tags[Core::Git::Ext::TAG_REPOSITORY_URL] + tags.merge!( + Environment::Extractor.new(env, provider_klass: Providers::UserDefinedTags).tags ) - # Expand ~ - workspace_path = tags[TAG_WORKSPACE_PATH] - if !workspace_path.nil? && (workspace_path == "~" || workspace_path.start_with?("~/")) - tags[TAG_WORKSPACE_PATH] = File.expand_path(workspace_path) - end - # Fill out tags from local git as fallback - extract_local_git.each do |key, value| + local_git_tags = Environment::Extractor.new(env, provider_klass: Providers::LocalGit).tags + local_git_tags.each do |key, value| tags[key] ||= value end - tags.reject { |_, v| v.nil? } - end - - def normalize_ref(name) - refs = %r{^refs/(heads/)?} - origin = %r{^origin/} - tags = %r{^tags/} - name.gsub(refs, "").gsub(origin, "").gsub(tags, "") unless name.nil? - end - - def filter_sensitive_info(url) - url.gsub(%r{(https?://)[^/]*@}, '\1') unless url.nil? - end - - # CI providers - - def extract_appveyor(env) - url = "https://ci.appveyor.com/project/#{env["APPVEYOR_REPO_NAME"]}/builds/#{env["APPVEYOR_BUILD_ID"]}" - - if env["APPVEYOR_REPO_PROVIDER"] == "github" - repository = "https://github.com/#{env["APPVEYOR_REPO_NAME"]}.git" - commit = env["APPVEYOR_REPO_COMMIT"] - branch = (env["APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"] || env["APPVEYOR_REPO_BRANCH"]) - tag = env["APPVEYOR_REPO_TAG_NAME"] - end - - commit_message = env["APPVEYOR_REPO_COMMIT_MESSAGE"] - if commit_message - extended = env["APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED"] - commit_message = "#{commit_message}\n#{extended}" if extended - end - - { - TAG_PROVIDER_NAME => "appveyor", - Core::Git::Ext::TAG_REPOSITORY_URL => repository, - Core::Git::Ext::TAG_COMMIT_SHA => commit, - TAG_WORKSPACE_PATH => env["APPVEYOR_BUILD_FOLDER"], - TAG_PIPELINE_ID => env["APPVEYOR_BUILD_ID"], - TAG_PIPELINE_NAME => env["APPVEYOR_REPO_NAME"], - TAG_PIPELINE_NUMBER => env["APPVEYOR_BUILD_NUMBER"], - TAG_PIPELINE_URL => url, - TAG_JOB_URL => url, - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_TAG => tag, - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env["APPVEYOR_REPO_COMMIT_AUTHOR"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env["APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => commit_message - } - end - - def extract_azure_pipelines(env) - build_id = env["BUILD_BUILDID"] - - if build_id && - (team_foundation_server_uri = env["SYSTEM_TEAMFOUNDATIONSERVERURI"]) && - (team_project_id = env["SYSTEM_TEAMPROJECTID"]) - pipeline_url = "#{team_foundation_server_uri}#{team_project_id}/_build/results?buildId=#{build_id}" - job_url = "#{pipeline_url}&view=logs&j=#{env["SYSTEM_JOBID"]}&t=#{env["SYSTEM_TASKINSTANCEID"]}" - end - - branch, tag = branch_or_tag( - env["SYSTEM_PULLREQUEST_SOURCEBRANCH"] || env["BUILD_SOURCEBRANCH"] || env["BUILD_SOURCEBRANCHNAME"] - ) - - { - TAG_PROVIDER_NAME => "azurepipelines", - TAG_WORKSPACE_PATH => env["BUILD_SOURCESDIRECTORY"], - TAG_PIPELINE_ID => build_id, - TAG_PIPELINE_NAME => env["BUILD_DEFINITIONNAME"], - TAG_PIPELINE_NUMBER => build_id, - TAG_PIPELINE_URL => pipeline_url, - TAG_JOB_URL => job_url, - TAG_STAGE_NAME => env["SYSTEM_STAGEDISPLAYNAME"], - TAG_JOB_NAME => env["SYSTEM_JOBDISPLAYNAME"], - Core::Git::Ext::TAG_REPOSITORY_URL => - env["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI"] || env["BUILD_REPOSITORY_URI"], - Core::Git::Ext::TAG_COMMIT_SHA => env["SYSTEM_PULLREQUEST_SOURCECOMMITID"] \ - || env["BUILD_SOURCEVERSION"], - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_TAG => tag, - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env["BUILD_REQUESTEDFORID"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env["BUILD_REQUESTEDFOREMAIL"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["BUILD_SOURCEVERSIONMESSAGE"], - TAG_CI_ENV_VARS => { - "SYSTEM_TEAMPROJECTID" => env["SYSTEM_TEAMPROJECTID"], - "BUILD_BUILDID" => env["BUILD_BUILDID"], - "SYSTEM_JOBID" => env["SYSTEM_JOBID"] - }.to_json - } - end - - def extract_bitbucket(env) - pipeline_url = "https://bitbucket.org/#{env["BITBUCKET_REPO_FULL_NAME"]}/addon/pipelines/home#" \ - "!/results/#{env["BITBUCKET_BUILD_NUMBER"]}" - - repository_url = filter_sensitive_info( - env["BITBUCKET_GIT_SSH_ORIGIN"] || env["BITBUCKET_GIT_HTTP_ORIGIN"] - ) - - { - Core::Git::Ext::TAG_BRANCH => env["BITBUCKET_BRANCH"], - Core::Git::Ext::TAG_COMMIT_SHA => env["BITBUCKET_COMMIT"], - Core::Git::Ext::TAG_REPOSITORY_URL => repository_url, - Core::Git::Ext::TAG_TAG => env["BITBUCKET_TAG"], - TAG_JOB_URL => pipeline_url, - TAG_PIPELINE_ID => env["BITBUCKET_PIPELINE_UUID"] ? env["BITBUCKET_PIPELINE_UUID"].tr("{}", "") : nil, - TAG_PIPELINE_NAME => env["BITBUCKET_REPO_FULL_NAME"], - TAG_PIPELINE_NUMBER => env["BITBUCKET_BUILD_NUMBER"], - TAG_PIPELINE_URL => pipeline_url, - TAG_PROVIDER_NAME => "bitbucket", - TAG_WORKSPACE_PATH => env["BITBUCKET_CLONE_DIR"] - } - end - - def extract_buddy(env) - { - TAG_PROVIDER_NAME => "buddy", - TAG_PIPELINE_ID => "#{env["BUDDY_PIPELINE_ID"]}/#{env["BUDDY_EXECUTION_ID"]}", - TAG_PIPELINE_NAME => env["BUDDY_PIPELINE_NAME"], - TAG_PIPELINE_NUMBER => env["BUDDY_EXECUTION_ID"], - TAG_PIPELINE_URL => env["BUDDY_EXECUTION_URL"], - TAG_WORKSPACE_PATH => env["CI_WORKSPACE_PATH"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["BUDDY_SCM_URL"], - Core::Git::Ext::TAG_COMMIT_SHA => env["BUDDY_EXECUTION_REVISION"], - Core::Git::Ext::TAG_BRANCH => env["BUDDY_EXECUTION_BRANCH"], - Core::Git::Ext::TAG_TAG => env["BUDDY_EXECUTION_TAG"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["BUDDY_EXECUTION_REVISION_MESSAGE"], - Core::Git::Ext::TAG_COMMIT_COMMITTER_NAME => env["BUDDY_EXECUTION_REVISION_COMMITTER_NAME"], - Core::Git::Ext::TAG_COMMIT_COMMITTER_EMAIL => env["BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL"] - } - end - - def extract_buildkite(env) - tags = { - Core::Git::Ext::TAG_BRANCH => env["BUILDKITE_BRANCH"], - Core::Git::Ext::TAG_COMMIT_SHA => env["BUILDKITE_COMMIT"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["BUILDKITE_REPO"], - Core::Git::Ext::TAG_TAG => env["BUILDKITE_TAG"], - TAG_PIPELINE_ID => env["BUILDKITE_BUILD_ID"], - TAG_PIPELINE_NAME => env["BUILDKITE_PIPELINE_SLUG"], - TAG_PIPELINE_NUMBER => env["BUILDKITE_BUILD_NUMBER"], - TAG_PIPELINE_URL => env["BUILDKITE_BUILD_URL"], - TAG_JOB_URL => "#{env["BUILDKITE_BUILD_URL"]}##{env["BUILDKITE_JOB_ID"]}", - TAG_PROVIDER_NAME => "buildkite", - TAG_WORKSPACE_PATH => env["BUILDKITE_BUILD_CHECKOUT_PATH"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env["BUILDKITE_BUILD_AUTHOR"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env["BUILDKITE_BUILD_AUTHOR_EMAIL"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["BUILDKITE_MESSAGE"], - TAG_NODE_NAME => env["BUILDKITE_AGENT_ID"], - TAG_CI_ENV_VARS => { - "BUILDKITE_BUILD_ID" => env["BUILDKITE_BUILD_ID"], - "BUILDKITE_JOB_ID" => env["BUILDKITE_JOB_ID"] - }.to_json - } - - extra_tags = env - .select { |key| key.start_with?("BUILDKITE_AGENT_META_DATA_") } - .map { |key, value| "#{key.to_s.sub("BUILDKITE_AGENT_META_DATA_", "").downcase}:#{value}" } - .sort_by(&:length) - - tags[TAG_NODE_LABELS] = extra_tags.to_json unless extra_tags.empty? + ensure_post_conditions(tags) tags end - def extract_circle_ci(env) - { - Core::Git::Ext::TAG_BRANCH => env["CIRCLE_BRANCH"], - Core::Git::Ext::TAG_COMMIT_SHA => env["CIRCLE_SHA1"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["CIRCLE_REPOSITORY_URL"], - Core::Git::Ext::TAG_TAG => env["CIRCLE_TAG"], - TAG_PIPELINE_ID => env["CIRCLE_WORKFLOW_ID"], - TAG_PIPELINE_NAME => env["CIRCLE_PROJECT_REPONAME"], - TAG_PIPELINE_URL => "https://app.circleci.com/pipelines/workflows/#{env["CIRCLE_WORKFLOW_ID"]}", - TAG_JOB_NAME => env["CIRCLE_JOB"], - TAG_JOB_URL => env["CIRCLE_BUILD_URL"], - TAG_PROVIDER_NAME => "circleci", - TAG_WORKSPACE_PATH => env["CIRCLE_WORKING_DIRECTORY"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env["BUILD_REQUESTEDFORID"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env["BUILD_REQUESTEDFOREMAIL"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["BUILD_SOURCEVERSIONMESSAGE"], - TAG_CI_ENV_VARS => { - "CIRCLE_WORKFLOW_ID" => env["CIRCLE_WORKFLOW_ID"], - "CIRCLE_BUILD_NUM" => env["CIRCLE_BUILD_NUM"] - }.to_json - } - end - - def extract_github_actions(env) - ref = env["GITHUB_HEAD_REF"] - ref = env["GITHUB_REF"] if ref.nil? || ref.empty? - branch, tag = branch_or_tag(ref) - - pipeline_url = "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}/actions/runs/#{env["GITHUB_RUN_ID"]}" - pipeline_url = "#{pipeline_url}/attempts/#{env["GITHUB_RUN_ATTEMPT"]}" if env["GITHUB_RUN_ATTEMPT"] - - { - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_COMMIT_SHA => env["GITHUB_SHA"], - Core::Git::Ext::TAG_REPOSITORY_URL => "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}.git", - Core::Git::Ext::TAG_TAG => tag, - TAG_JOB_URL => "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}/commit/#{env["GITHUB_SHA"]}/checks", - TAG_JOB_NAME => env["GITHUB_JOB"], - TAG_PIPELINE_ID => env["GITHUB_RUN_ID"], - TAG_PIPELINE_NAME => env["GITHUB_WORKFLOW"], - TAG_PIPELINE_NUMBER => env["GITHUB_RUN_NUMBER"], - TAG_PIPELINE_URL => pipeline_url, - TAG_PROVIDER_NAME => "github", - TAG_WORKSPACE_PATH => env["GITHUB_WORKSPACE"], - TAG_CI_ENV_VARS => { - "GITHUB_SERVER_URL" => env["GITHUB_SERVER_URL"], - "GITHUB_REPOSITORY" => env["GITHUB_REPOSITORY"], - "GITHUB_RUN_ID" => env["GITHUB_RUN_ID"], - "GITHUB_RUN_ATTEMPT" => env["GITHUB_RUN_ATTEMPT"] - }.reject { |_k, v| v.nil? }.to_json - } - end - - def extract_gitlab(env) - commit_author_name, commit_author_email = extract_name_email(env["CI_COMMIT_AUTHOR"]) - - { - Core::Git::Ext::TAG_BRANCH => env["CI_COMMIT_REF_NAME"], - Core::Git::Ext::TAG_COMMIT_SHA => env["CI_COMMIT_SHA"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["CI_REPOSITORY_URL"], - Core::Git::Ext::TAG_TAG => env["CI_COMMIT_TAG"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => commit_author_name, - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => commit_author_email, - Core::Git::Ext::TAG_COMMIT_AUTHOR_DATE => env["CI_COMMIT_TIMESTAMP"], - TAG_STAGE_NAME => env["CI_JOB_STAGE"], - TAG_JOB_NAME => env["CI_JOB_NAME"], - TAG_JOB_URL => env["CI_JOB_URL"], - TAG_PIPELINE_ID => env["CI_PIPELINE_ID"], - TAG_PIPELINE_NAME => env["CI_PROJECT_PATH"], - TAG_PIPELINE_NUMBER => env["CI_PIPELINE_IID"], - TAG_PIPELINE_URL => env["CI_PIPELINE_URL"], - TAG_PROVIDER_NAME => "gitlab", - TAG_WORKSPACE_PATH => env["CI_PROJECT_DIR"], - TAG_NODE_LABELS => env["CI_RUNNER_TAGS"], - TAG_NODE_NAME => env["CI_RUNNER_ID"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["CI_COMMIT_MESSAGE"], - TAG_CI_ENV_VARS => { - "CI_PROJECT_URL" => env["CI_PROJECT_URL"], - "CI_PIPELINE_ID" => env["CI_PIPELINE_ID"], - "CI_JOB_ID" => env["CI_JOB_ID"] - }.to_json - } - end - - def extract_jenkins(env) - branch, tag = branch_or_tag(env["GIT_BRANCH"]) - - if (name = env["JOB_NAME"]) - name = name.gsub("/#{normalize_ref(branch)}", "") if branch - name = name.split("/").reject { |v| v.nil? || v.include?("=") }.join("/") - end - - node_labels = env["NODE_LABELS"] && env["NODE_LABELS"].split.to_json - - { - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_COMMIT_SHA => env["GIT_COMMIT"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["GIT_URL"] || env["GIT_URL_1"], - Core::Git::Ext::TAG_TAG => tag, - TAG_PIPELINE_ID => env["BUILD_TAG"], - TAG_PIPELINE_NAME => name, - TAG_PIPELINE_NUMBER => env["BUILD_NUMBER"], - TAG_PIPELINE_URL => env["BUILD_URL"], - TAG_PROVIDER_NAME => "jenkins", - TAG_WORKSPACE_PATH => env["WORKSPACE"], - TAG_NODE_LABELS => node_labels, - TAG_NODE_NAME => env["NODE_NAME"], - TAG_CI_ENV_VARS => { - "DD_CUSTOM_TRACE_ID" => env["DD_CUSTOM_TRACE_ID"] - }.to_json - } - end - - def extract_teamcity(env) - { - TAG_PROVIDER_NAME => "teamcity", - TAG_JOB_NAME => env["TEAMCITY_BUILDCONF_NAME"], - TAG_JOB_URL => env["BUILD_URL"] - } - end - - def extract_travis(env) - { - Core::Git::Ext::TAG_BRANCH => (env["TRAVIS_PULL_REQUEST_BRANCH"] || env["TRAVIS_BRANCH"]), - Core::Git::Ext::TAG_COMMIT_SHA => env["TRAVIS_COMMIT"], - Core::Git::Ext::TAG_REPOSITORY_URL => "https://github.com/#{env["TRAVIS_REPO_SLUG"]}.git", - Core::Git::Ext::TAG_TAG => env["TRAVIS_TAG"], - TAG_JOB_URL => env["TRAVIS_JOB_WEB_URL"], - TAG_PIPELINE_ID => env["TRAVIS_BUILD_ID"], - TAG_PIPELINE_NAME => env["TRAVIS_REPO_SLUG"], - TAG_PIPELINE_NUMBER => env["TRAVIS_BUILD_NUMBER"], - TAG_PIPELINE_URL => env["TRAVIS_BUILD_WEB_URL"], - TAG_PROVIDER_NAME => "travisci", - TAG_WORKSPACE_PATH => env["TRAVIS_BUILD_DIR"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["TRAVIS_COMMIT_MESSAGE"] - } - end - - def extract_bitrise(env) - commit = ( - env["BITRISE_GIT_COMMIT"] || env["GIT_CLONE_COMMIT_HASH"] - ) - branch = ( - env["BITRISEIO_GIT_BRANCH_DEST"] || env["BITRISE_GIT_BRANCH"] - ) - commiter_email = ( - env["GIT_CLONE_COMMIT_COMMITER_EMAIL"] || env["GIT_CLONE_COMMIT_COMMITER_NAME"] - ) - - { - TAG_PROVIDER_NAME => "bitrise", - TAG_PIPELINE_ID => env["BITRISE_BUILD_SLUG"], - TAG_PIPELINE_NAME => env["BITRISE_TRIGGERED_WORKFLOW_ID"], - TAG_PIPELINE_NUMBER => env["BITRISE_BUILD_NUMBER"], - TAG_PIPELINE_URL => env["BITRISE_BUILD_URL"], - TAG_WORKSPACE_PATH => env["BITRISE_SOURCE_DIR"], - Core::Git::Ext::TAG_REPOSITORY_URL => env["GIT_REPOSITORY_URL"], - Core::Git::Ext::TAG_COMMIT_SHA => commit, - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_TAG => env["BITRISE_GIT_TAG"], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env["BITRISE_GIT_MESSAGE"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env["GIT_CLONE_COMMIT_AUTHOR_NAME"], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env["GIT_CLONE_COMMIT_AUTHOR_EMAIL"], - Core::Git::Ext::TAG_COMMIT_COMMITTER_NAME => env["GIT_CLONE_COMMIT_COMMITER_NAME"], - Core::Git::Ext::TAG_COMMIT_COMMITTER_EMAIL => commiter_email - } - end - - def extract_codefresh(env) - branch, tag = branch_or_tag(env["CF_BRANCH"]) - - { - TAG_PROVIDER_NAME => "codefresh", - TAG_PIPELINE_ID => env["CF_BUILD_ID"], - TAG_PIPELINE_NAME => env["CF_PIPELINE_NAME"], - TAG_PIPELINE_URL => env["CF_BUILD_URL"], - TAG_JOB_NAME => env["CF_STEP_NAME"], - Core::Git::Ext::TAG_BRANCH => branch, - Core::Git::Ext::TAG_TAG => tag, - TAG_CI_ENV_VARS => { - "CF_BUILD_ID" => env["CF_BUILD_ID"] - }.to_json - } - end - - def extract_user_defined_git(env) - { - Core::Git::Ext::TAG_REPOSITORY_URL => env[Core::Git::Ext::ENV_REPOSITORY_URL], - Core::Git::Ext::TAG_COMMIT_SHA => env[Core::Git::Ext::ENV_COMMIT_SHA], - Core::Git::Ext::TAG_BRANCH => env[Core::Git::Ext::ENV_BRANCH], - Core::Git::Ext::TAG_TAG => env[Core::Git::Ext::ENV_TAG], - Core::Git::Ext::TAG_COMMIT_MESSAGE => env[Core::Git::Ext::ENV_COMMIT_MESSAGE], - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => env[Core::Git::Ext::ENV_COMMIT_AUTHOR_NAME], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => env[Core::Git::Ext::ENV_COMMIT_AUTHOR_EMAIL], - Core::Git::Ext::TAG_COMMIT_AUTHOR_DATE => env[Core::Git::Ext::ENV_COMMIT_AUTHOR_DATE], - Core::Git::Ext::TAG_COMMIT_COMMITTER_NAME => env[Core::Git::Ext::ENV_COMMIT_COMMITTER_NAME], - Core::Git::Ext::TAG_COMMIT_COMMITTER_EMAIL => env[Core::Git::Ext::ENV_COMMIT_COMMITTER_EMAIL], - Core::Git::Ext::TAG_COMMIT_COMMITTER_DATE => env[Core::Git::Ext::ENV_COMMIT_COMMITTER_DATE] - }.reject { |_, v| v.nil? || v.strip.empty? } - end - - def git_commit_users - # Get committer and author information in one command. - output = exec_git_command("git show -s --format='%an\t%ae\t%at\t%cn\t%ce\t%ct'") - return unless output - - fields = output.split("\t").each(&:strip!) - - { - author_name: fields[0], - author_email: fields[1], - # Because we can't get a reliable UTC time from all recent versions of git - # We have to rely on converting the date to UTC ourselves. - author_date: Time.at(fields[2].to_i).utc.to_datetime.iso8601, - committer_name: fields[3], - committer_email: fields[4], - # Because we can't get a reliable UTC time from all recent versions of git - # We have to rely on converting the date to UTC ourselves. - committer_date: Time.at(fields[5].to_i).utc.to_datetime.iso8601 - } - rescue => e - Datadog.logger.debug( - "Unable to read git commit users: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_repository_url - exec_git_command("git ls-remote --get-url") - rescue => e - Datadog.logger.debug( - "Unable to read git repository url: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_commit_message - exec_git_command("git show -s --format=%s") - rescue => e - Datadog.logger.debug( - "Unable to read git commit message: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_branch - exec_git_command("git rev-parse --abbrev-ref HEAD") - rescue => e - Datadog.logger.debug( - "Unable to read git branch: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_commit_sha - exec_git_command("git rev-parse HEAD") - rescue => e - Datadog.logger.debug( - "Unable to read git commit SHA: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_tag - exec_git_command("git tag --points-at HEAD") - rescue => e - Datadog.logger.debug( - "Unable to read git tag: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil - end - - def git_base_directory - exec_git_command("git rev-parse --show-toplevel") - rescue => e - Datadog.logger.debug( - "Unable to read git base directory: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" - ) - nil + def ensure_post_conditions(tags) + validate_repository_url(tags[Git::TAG_REPOSITORY_URL]) + validate_git_sha(tags[Git::TAG_COMMIT_SHA]) end - def exec_git_command(cmd) - out, status = Open3.capture2e(cmd) - - raise "Failed to run git command #{cmd}: #{out}" unless status.success? - - out.strip! # There's always a "\n" at the end of the command output + def validate_repository_url(repo_url) + return if !repo_url.nil? && !repo_url.empty? - return nil if out.empty? - - out + Datadog.logger.error("DD_GIT_REPOSITORY_URL is not set or empty; no repo URL was automatically extracted") end - def extract_local_git - env = { - TAG_WORKSPACE_PATH => git_base_directory, - Core::Git::Ext::TAG_REPOSITORY_URL => git_repository_url, - Core::Git::Ext::TAG_COMMIT_SHA => git_commit_sha, - Core::Git::Ext::TAG_BRANCH => git_branch, - Core::Git::Ext::TAG_TAG => git_tag, - Core::Git::Ext::TAG_COMMIT_MESSAGE => git_commit_message - } + def validate_git_sha(git_sha) + message = "DD_GIT_COMMIT_SHA must be a full-length git SHA." - if (commit_users = git_commit_users) - env.merge!( - Core::Git::Ext::TAG_COMMIT_AUTHOR_NAME => commit_users[:author_name], - Core::Git::Ext::TAG_COMMIT_AUTHOR_EMAIL => commit_users[:author_email], - Core::Git::Ext::TAG_COMMIT_AUTHOR_DATE => commit_users[:author_date], - Core::Git::Ext::TAG_COMMIT_COMMITTER_NAME => commit_users[:committer_name], - Core::Git::Ext::TAG_COMMIT_COMMITTER_EMAIL => commit_users[:committer_email], - Core::Git::Ext::TAG_COMMIT_COMMITTER_DATE => commit_users[:committer_date] - ) + if git_sha.nil? || git_sha.empty? + message += " No value was set and no SHA was automatically extracted." + Datadog.logger.error(message) + return end - env - end - - def branch_or_tag(branch_or_tag) - branch = tag = nil - if branch_or_tag && branch_or_tag.include?("tags/") - tag = branch_or_tag - else - branch = branch_or_tag + if git_sha.length < Git::SHA_LENGTH + message += " Expected SHA length #{Git::SHA_LENGTH}, was #{git_sha.length}." + Datadog.logger.error(message) + return end - [branch, tag] - end - - def extract_name_email(name_and_email) - if name_and_email.include?("<") && (match = /^([^<]*)<([^>]*)>$/.match(name_and_email)) - name = match[1] - name = name.strip if name - email = match[2] - return [name, email] if name && email + unless HEX_NUMBER_REGEXP =~ git_sha + message += " Expected SHA to be a valid HEX number, got #{git_sha}." + Datadog.logger.error(message) end - - [nil, name_and_email] end end end diff --git a/lib/datadog/ci/ext/environment/extractor.rb b/lib/datadog/ci/ext/environment/extractor.rb new file mode 100644 index 00000000..99e64408 --- /dev/null +++ b/lib/datadog/ci/ext/environment/extractor.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require_relative "../environment" +require_relative "../git" +require_relative "../../utils/git" +require_relative "providers" + +module Datadog + module CI + module Ext + module Environment + # Provider is a specific CI provider like Azure Pipelines, Github Actions, Gitlab CI, etc + # Extractor is responsible for detecting where pipeline is being executed based on environment vars + # and return the specific extractor that is able to return environment- and git-specific tags + class Extractor + def initialize(env, provider_klass: nil) + @env = env + @provider = provider_klass ? provider_klass.new(env) : Providers.for_environment(env) + end + + def tags + return @tags if defined?(@tags) + + @tags = { + Environment::TAG_JOB_NAME => @provider.job_name, + Environment::TAG_JOB_URL => @provider.job_url, + Environment::TAG_PIPELINE_ID => @provider.pipeline_id, + Environment::TAG_PIPELINE_NAME => @provider.pipeline_name, + Environment::TAG_PIPELINE_NUMBER => @provider.pipeline_number, + Environment::TAG_PIPELINE_URL => @provider.pipeline_url, + Environment::TAG_PROVIDER_NAME => @provider.provider_name, + Environment::TAG_STAGE_NAME => @provider.stage_name, + Environment::TAG_WORKSPACE_PATH => @provider.workspace_path, + Environment::TAG_NODE_LABELS => @provider.node_labels, + Environment::TAG_NODE_NAME => @provider.node_name, + Environment::TAG_CI_ENV_VARS => @provider.ci_env_vars, + + Git::TAG_BRANCH => @provider.git_branch, + Git::TAG_REPOSITORY_URL => @provider.git_repository_url, + Git::TAG_TAG => @provider.git_tag, + Git::TAG_COMMIT_AUTHOR_DATE => @provider.git_commit_author_date, + Git::TAG_COMMIT_AUTHOR_EMAIL => @provider.git_commit_author_email, + Git::TAG_COMMIT_AUTHOR_NAME => @provider.git_commit_author_name, + Git::TAG_COMMIT_COMMITTER_DATE => @provider.git_commit_committer_date, + Git::TAG_COMMIT_COMMITTER_EMAIL => @provider.git_commit_committer_email, + Git::TAG_COMMIT_COMMITTER_NAME => @provider.git_commit_committer_name, + Git::TAG_COMMIT_MESSAGE => @provider.git_commit_message, + Git::TAG_COMMIT_SHA => @provider.git_commit_sha + } + + # Normalize Git references and filter sensitive data + normalize_git! + # Expand ~ + expand_workspace! + + # remove empty tags + @tags.reject! do |_, v| + # setting type of v here to untyped because steep does not + # understand `v.nil? || something` + + # @type var v: untyped + v.nil? || v.strip.empty? + end + + @tags + end + + private + + def normalize_git! + branch_ref = @tags[Git::TAG_BRANCH] + if Datadog::CI::Utils::Git.is_git_tag?(branch_ref) + @tags[Git::TAG_TAG] = branch_ref + @tags.delete(Git::TAG_BRANCH) + end + + @tags[Git::TAG_TAG] = Datadog::CI::Utils::Git.normalize_ref(@tags[Git::TAG_TAG]) + @tags[Git::TAG_BRANCH] = Datadog::CI::Utils::Git.normalize_ref(@tags[Git::TAG_BRANCH]) + @tags[Git::TAG_REPOSITORY_URL] = filter_sensitive_info( + @tags[Git::TAG_REPOSITORY_URL] + ) + end + + def expand_workspace! + workspace_path = @tags[TAG_WORKSPACE_PATH] + + if !workspace_path.nil? && (workspace_path == "~" || workspace_path.start_with?("~/")) + @tags[TAG_WORKSPACE_PATH] = File.expand_path(workspace_path) + end + end + + def filter_sensitive_info(url) + return nil if url.nil? + + url.gsub(%r{(https?://)[^/]*@}, '\1') + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers.rb b/lib/datadog/ci/ext/environment/providers.rb new file mode 100644 index 00000000..40b4fcd0 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative "providers/base" +require_relative "providers/appveyor" +require_relative "providers/azure" +require_relative "providers/bitbucket" +require_relative "providers/bitrise" +require_relative "providers/buddy" +require_relative "providers/buildkite" +require_relative "providers/circleci" +require_relative "providers/codefresh" +require_relative "providers/github_actions" +require_relative "providers/gitlab" +require_relative "providers/jenkins" +require_relative "providers/teamcity" +require_relative "providers/travis" + +require_relative "providers/local_git" +require_relative "providers/user_defined_tags" + +module Datadog + module CI + module Ext + module Environment + module Providers + PROVIDERS = [ + ["APPVEYOR", Providers::Appveyor], + ["TF_BUILD", Providers::Azure], + ["BITBUCKET_COMMIT", Providers::Bitbucket], + ["BITRISE_BUILD_SLUG", Providers::Bitrise], + ["BUDDY", Providers::Buddy], + ["BUILDKITE", Providers::Buildkite], + ["CIRCLECI", Providers::Circleci], + ["CF_BUILD_ID", Providers::Codefresh], + ["GITHUB_SHA", Providers::GithubActions], + ["GITLAB_CI", Providers::Gitlab], + ["JENKINS_URL", Providers::Jenkins], + ["TEAMCITY_VERSION", Providers::Teamcity], + ["TRAVIS", Providers::Travis] + ] + + def self.for_environment(env) + _, provider_klass = PROVIDERS.find { |provider_env_var, _| env.key?(provider_env_var) } + provider_klass = Providers::Base if provider_klass.nil? + + provider_klass.new(env) + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/appveyor.rb b/lib/datadog/ci/ext/environment/providers/appveyor.rb new file mode 100644 index 00000000..09c7881f --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/appveyor.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Appveyor: https://www.appveyor.com/ + # Environment variables docs: https://www.appveyor.com/docs/environment-variables/ + class Appveyor < Base + def provider_name + "appveyor" + end + + def pipeline_url + url + end + + def job_url + url + end + + def workspace_path + env["APPVEYOR_BUILD_FOLDER"] + end + + def pipeline_id + env["APPVEYOR_BUILD_ID"] + end + + def pipeline_name + env["APPVEYOR_REPO_NAME"] + end + + def pipeline_number + env["APPVEYOR_BUILD_NUMBER"] + end + + def git_repository_url + return nil unless github_repo_provider? + + "https://github.com/#{env["APPVEYOR_REPO_NAME"]}.git" + end + + def git_commit_sha + return nil unless github_repo_provider? + + env["APPVEYOR_REPO_COMMIT"] + end + + def git_branch + return nil unless github_repo_provider? + + env["APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"] || env["APPVEYOR_REPO_BRANCH"] + end + + def git_tag + return nil unless github_repo_provider? + + env["APPVEYOR_REPO_TAG_NAME"] + end + + def git_commit_author_name + env["APPVEYOR_REPO_COMMIT_AUTHOR"] + end + + def git_commit_author_email + env["APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"] + end + + def git_commit_message + commit_message = env["APPVEYOR_REPO_COMMIT_MESSAGE"] + if commit_message + extended = env["APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED"] + commit_message = "#{commit_message}\n#{extended}" if extended + end + commit_message + end + + private + + def github_repo_provider? + return @github_repo_provider if defined?(@github_repo_provider) + + @github_repo_provider = env["APPVEYOR_REPO_PROVIDER"] == "github" + end + + def url + @url ||= "https://ci.appveyor.com/project/#{env["APPVEYOR_REPO_NAME"]}/builds/#{env["APPVEYOR_BUILD_ID"]}" + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/azure.rb b/lib/datadog/ci/ext/environment/providers/azure.rb new file mode 100644 index 00000000..f80ab243 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/azure.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Azure Pipelines: https://azure.microsoft.com/en-us/products/devops/pipelines + # Environment variables docs: https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml + class Azure < Base + def provider_name + "azurepipelines" + end + + def pipeline_url + return unless url_defined? + + @pipeline_url ||= "#{team_foundation_server_uri}#{team_project_id}/_build/results?buildId=#{build_id}" + end + + def job_url + return unless url_defined? + + @job_url ||= "#{pipeline_url}&view=logs&j=#{env["SYSTEM_JOBID"]}&t=#{env["SYSTEM_TASKINSTANCEID"]}" + end + + def workspace_path + env["BUILD_SOURCESDIRECTORY"] + end + + def pipeline_id + build_id + end + + def pipeline_number + build_id + end + + def pipeline_name + env["BUILD_DEFINITIONNAME"] + end + + def stage_name + env["SYSTEM_STAGEDISPLAYNAME"] + end + + def job_name + env["SYSTEM_JOBDISPLAYNAME"] + end + + def git_repository_url + env["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI"] || env["BUILD_REPOSITORY_URI"] + end + + def git_commit_sha + env["SYSTEM_PULLREQUEST_SOURCECOMMITID"] || env["BUILD_SOURCEVERSION"] + end + + def git_branch_or_tag + env["SYSTEM_PULLREQUEST_SOURCEBRANCH"] || env["BUILD_SOURCEBRANCH"] || env["BUILD_SOURCEBRANCHNAME"] + end + + def git_commit_author_name + env["BUILD_REQUESTEDFORID"] + end + + def git_commit_author_email + env["BUILD_REQUESTEDFOREMAIL"] + end + + def git_commit_message + env["BUILD_SOURCEVERSIONMESSAGE"] + end + + def ci_env_vars + { + "SYSTEM_TEAMPROJECTID" => env["SYSTEM_TEAMPROJECTID"], + "BUILD_BUILDID" => env["BUILD_BUILDID"], + "SYSTEM_JOBID" => env["SYSTEM_JOBID"] + }.to_json + end + + private + + def build_id + env["BUILD_BUILDID"] + end + + def team_foundation_server_uri + env["SYSTEM_TEAMFOUNDATIONSERVERURI"] + end + + def team_project_id + env["SYSTEM_TEAMPROJECTID"] + end + + def url_defined? + !(build_id && team_foundation_server_uri && team_project_id).nil? + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/base.rb b/lib/datadog/ci/ext/environment/providers/base.rb new file mode 100644 index 00000000..82d2b514 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/base.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module Datadog + module CI + module Ext + module Environment + module Providers + class Base + attr_reader :env + + def initialize(env) + @env = env + end + + def job_name + end + + def job_url + end + + def pipeline_id + end + + def pipeline_name + end + + def pipeline_number + end + + def pipeline_url + end + + def provider_name + end + + def stage_name + end + + def workspace_path + end + + def node_labels + end + + def node_name + end + + def ci_env_vars + end + + def git_branch + return @branch if defined?(@branch) + + set_branch_and_tag + @branch + end + + def git_repository_url + end + + def git_tag + return @tag if defined?(@tag) + + set_branch_and_tag + @tag + end + + def git_branch_or_tag + end + + def git_commit_author_date + end + + def git_commit_author_email + end + + def git_commit_author_name + end + + def git_commit_committer_date + end + + def git_commit_committer_email + end + + def git_commit_committer_name + end + + def git_commit_message + end + + def git_commit_sha + end + + private + + def set_branch_and_tag + branch_or_tag_string = git_branch_or_tag + @branch = @tag = nil + + # @type var branch_or_tag_string: untyped + if branch_or_tag_string && branch_or_tag_string.include?("tags/") + @tag = branch_or_tag_string + else + @branch = branch_or_tag_string + end + + [@branch, @tag] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/bitbucket.rb b/lib/datadog/ci/ext/environment/providers/bitbucket.rb new file mode 100644 index 00000000..e5a0bed1 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/bitbucket.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Bitbucket Pipelines: https://bitbucket.org/product/features/pipelines + # Environment variables docs: https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ + class Bitbucket < Base + # overridden methods + def provider_name + "bitbucket" + end + + def pipeline_id + env["BITBUCKET_PIPELINE_UUID"] ? env["BITBUCKET_PIPELINE_UUID"].tr("{}", "") : nil + end + + def pipeline_name + env["BITBUCKET_REPO_FULL_NAME"] + end + + def pipeline_number + env["BITBUCKET_BUILD_NUMBER"] + end + + def pipeline_url + url + end + + def job_url + url + end + + def workspace_path + env["BITBUCKET_CLONE_DIR"] + end + + def git_repository_url + env["BITBUCKET_GIT_SSH_ORIGIN"] || env["BITBUCKET_GIT_HTTP_ORIGIN"] + end + + def git_commit_sha + env["BITBUCKET_COMMIT"] + end + + def git_branch + env["BITBUCKET_BRANCH"] + end + + def git_tag + env["BITBUCKET_TAG"] + end + + private + + def url + "https://bitbucket.org/#{env["BITBUCKET_REPO_FULL_NAME"]}/addon/pipelines/home#" \ + "!/results/#{env["BITBUCKET_BUILD_NUMBER"]}" + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/bitrise.rb b/lib/datadog/ci/ext/environment/providers/bitrise.rb new file mode 100644 index 00000000..6e35d382 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/bitrise.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Bitrise: https://bitrise.io/ + # Environment variables docs: https://devcenter.bitrise.io/en/references/available-environment-variables.html + class Bitrise < Base + def provider_name + "bitrise" + end + + def pipeline_id + env["BITRISE_BUILD_SLUG"] + end + + def pipeline_name + env["BITRISE_TRIGGERED_WORKFLOW_ID"] + end + + def pipeline_number + env["BITRISE_BUILD_NUMBER"] + end + + def pipeline_url + env["BITRISE_BUILD_URL"] + end + + def workspace_path + env["BITRISE_SOURCE_DIR"] + end + + def git_repository_url + env["GIT_REPOSITORY_URL"] + end + + def git_commit_sha + env["BITRISE_GIT_COMMIT"] || env["GIT_CLONE_COMMIT_HASH"] + end + + def git_branch + env["BITRISEIO_GIT_BRANCH_DEST"] || env["BITRISE_GIT_BRANCH"] + end + + def git_tag + env["BITRISE_GIT_TAG"] + end + + def git_commit_message + env["BITRISE_GIT_MESSAGE"] + end + + def git_commit_author_name + env["GIT_CLONE_COMMIT_AUTHOR_NAME"] + end + + def git_commit_author_email + env["GIT_CLONE_COMMIT_AUTHOR_EMAIL"] + end + + def git_commit_committer_name + env["GIT_CLONE_COMMIT_COMMITER_NAME"] + end + + def git_commit_committer_email + env["GIT_CLONE_COMMIT_COMMITER_EMAIL"] || env["GIT_CLONE_COMMIT_COMMITER_NAME"] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/buddy.rb b/lib/datadog/ci/ext/environment/providers/buddy.rb new file mode 100644 index 00000000..91d7332d --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/buddy.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Buddy: https://buddy.works/ + # Environment variables docs: https://buddy.works/docs/pipelines/environment-variables + class Buddy < Base + def provider_name + "buddy" + end + + def pipeline_id + "#{env["BUDDY_PIPELINE_ID"]}/#{env["BUDDY_EXECUTION_ID"]}" + end + + def pipeline_name + env["BUDDY_PIPELINE_NAME"] + end + + def pipeline_number + env["BUDDY_EXECUTION_ID"] + end + + def pipeline_url + env["BUDDY_EXECUTION_URL"] + end + + def workspace_path + env["CI_WORKSPACE_PATH"] + end + + def git_repository_url + env["BUDDY_SCM_URL"] + end + + def git_commit_sha + env["BUDDY_EXECUTION_REVISION"] + end + + def git_branch + env["BUDDY_EXECUTION_BRANCH"] + end + + def git_tag + env["BUDDY_EXECUTION_TAG"] + end + + def git_commit_message + env["BUDDY_EXECUTION_REVISION_MESSAGE"] + end + + def git_commit_committer_name + env["BUDDY_EXECUTION_REVISION_COMMITTER_NAME"] + end + + def git_commit_committer_email + env["BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL"] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/buildkite.rb b/lib/datadog/ci/ext/environment/providers/buildkite.rb new file mode 100644 index 00000000..4fa1bbfe --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/buildkite.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Buildkite: https://buildkite.com/ + # Environment variables docs: https://buildkite.com/docs/pipelines/environment-variables + class Buildkite < Base + def provider_name + "buildkite" + end + + def job_url + "#{env["BUILDKITE_BUILD_URL"]}##{env["BUILDKITE_JOB_ID"]}" + end + + def pipeline_id + env["BUILDKITE_BUILD_ID"] + end + + def pipeline_name + env["BUILDKITE_PIPELINE_SLUG"] + end + + def pipeline_number + env["BUILDKITE_BUILD_NUMBER"] + end + + def pipeline_url + env["BUILDKITE_BUILD_URL"] + end + + def node_name + env["BUILDKITE_AGENT_ID"] + end + + def node_labels + labels = env + .select { |key| key.start_with?("BUILDKITE_AGENT_META_DATA_") } + .map { |key, value| "#{key.to_s.sub("BUILDKITE_AGENT_META_DATA_", "").downcase}:#{value}" } + .sort_by(&:length) + + labels.to_json unless labels.empty? + end + + def workspace_path + env["BUILDKITE_BUILD_CHECKOUT_PATH"] + end + + def git_repository_url + env["BUILDKITE_REPO"] + end + + def git_commit_sha + env["BUILDKITE_COMMIT"] + end + + def git_branch + env["BUILDKITE_BRANCH"] + end + + def git_tag + env["BUILDKITE_TAG"] + end + + def git_commit_author_name + env["BUILDKITE_BUILD_AUTHOR"] + end + + def git_commit_author_email + env["BUILDKITE_BUILD_AUTHOR_EMAIL"] + end + + def git_commit_message + env["BUILDKITE_MESSAGE"] + end + + def ci_env_vars + { + "BUILDKITE_BUILD_ID" => env["BUILDKITE_BUILD_ID"], + "BUILDKITE_JOB_ID" => env["BUILDKITE_JOB_ID"] + }.to_json + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/circleci.rb b/lib/datadog/ci/ext/environment/providers/circleci.rb new file mode 100644 index 00000000..fd65e656 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/circleci.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Circle CI: https://circleci.com/ + # Environment variables docs: https://circleci.com/docs/variables/#built-in-environment-variables + class Circleci < Base + def provider_name + "circleci" + end + + def job_url + env["CIRCLE_BUILD_URL"] + end + + def job_name + env["CIRCLE_JOB"] + end + + def pipeline_id + env["CIRCLE_WORKFLOW_ID"] + end + + def pipeline_name + env["CIRCLE_PROJECT_REPONAME"] + end + + def pipeline_url + "https://app.circleci.com/pipelines/workflows/#{env["CIRCLE_WORKFLOW_ID"]}" + end + + def workspace_path + env["CIRCLE_WORKING_DIRECTORY"] + end + + def git_repository_url + env["CIRCLE_REPOSITORY_URL"] + end + + def git_commit_sha + env["CIRCLE_SHA1"] + end + + def git_branch + env["CIRCLE_BRANCH"] + end + + def git_tag + env["CIRCLE_TAG"] + end + + def git_commit_author_name + env["BUILD_REQUESTEDFORID"] + end + + def git_commit_author_email + env["BUILD_REQUESTEDFOREMAIL"] + end + + def git_commit_message + env["BUILD_SOURCEVERSIONMESSAGE"] + end + + def ci_env_vars + { + "CIRCLE_WORKFLOW_ID" => env["CIRCLE_WORKFLOW_ID"], + "CIRCLE_BUILD_NUM" => env["CIRCLE_BUILD_NUM"] + }.to_json + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/codefresh.rb b/lib/datadog/ci/ext/environment/providers/codefresh.rb new file mode 100644 index 00000000..2dd5ffea --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/codefresh.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Codefresh: https://codefresh.io/ + # Environment variables docs: https://codefresh.io/docs/docs/pipelines/variables/#export-variables-to-all-steps-with-cf_export + class Codefresh < Base + def provider_name + "codefresh" + end + + def job_name + env["CF_STEP_NAME"] + end + + def pipeline_id + env["CF_BUILD_ID"] + end + + def pipeline_name + env["CF_PIPELINE_NAME"] + end + + def pipeline_url + env["CF_BUILD_URL"] + end + + def git_branch_or_tag + env["CF_BRANCH"] + end + + def ci_env_vars + { + "CF_BUILD_ID" => env["CF_BUILD_ID"] + }.to_json + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/github_actions.rb b/lib/datadog/ci/ext/environment/providers/github_actions.rb new file mode 100644 index 00000000..f5fe7861 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/github_actions.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Github Actions: https://github.com/features/actions + # Environment variables docs: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + class GithubActions < Base + def provider_name + "github" + end + + def job_name + env["GITHUB_JOB"] + end + + def job_url + "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}/commit/#{env["GITHUB_SHA"]}/checks" + end + + def pipeline_id + env["GITHUB_RUN_ID"] + end + + def pipeline_name + env["GITHUB_WORKFLOW"] + end + + def pipeline_number + env["GITHUB_RUN_NUMBER"] + end + + def pipeline_url + res = "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}/actions/runs/#{env["GITHUB_RUN_ID"]}" + res = "#{res}/attempts/#{env["GITHUB_RUN_ATTEMPT"]}" if env["GITHUB_RUN_ATTEMPT"] + res + end + + def workspace_path + env["GITHUB_WORKSPACE"] + end + + def git_repository_url + "#{env["GITHUB_SERVER_URL"]}/#{env["GITHUB_REPOSITORY"]}.git" + end + + def git_commit_sha + env["GITHUB_SHA"] + end + + def git_branch_or_tag + ref = env["GITHUB_HEAD_REF"] + ref = env["GITHUB_REF"] if ref.nil? || ref.empty? + ref + end + + def ci_env_vars + { + "GITHUB_SERVER_URL" => env["GITHUB_SERVER_URL"], + "GITHUB_REPOSITORY" => env["GITHUB_REPOSITORY"], + "GITHUB_RUN_ID" => env["GITHUB_RUN_ID"], + "GITHUB_RUN_ATTEMPT" => env["GITHUB_RUN_ATTEMPT"] + }.reject { |_, v| v.nil? }.to_json + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/gitlab.rb b/lib/datadog/ci/ext/environment/providers/gitlab.rb new file mode 100644 index 00000000..28ea27ba --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/gitlab.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Gitlab CI: https://docs.gitlab.com/ee/ci/ + # Environment variables docs: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html + class Gitlab < Base + def provider_name + "gitlab" + end + + def job_name + env["CI_JOB_NAME"] + end + + def job_url + env["CI_JOB_URL"] + end + + def pipeline_id + env["CI_PIPELINE_ID"] + end + + def pipeline_name + env["CI_PROJECT_PATH"] + end + + def pipeline_number + env["CI_PIPELINE_IID"] + end + + def pipeline_url + env["CI_PIPELINE_URL"] + end + + def stage_name + env["CI_JOB_STAGE"] + end + + def workspace_path + env["CI_PROJECT_DIR"] + end + + def node_name + env["CI_RUNNER_ID"] + end + + def node_labels + env["CI_RUNNER_TAGS"] + end + + def git_repository_url + env["CI_REPOSITORY_URL"] + end + + def git_commit_sha + env["CI_COMMIT_SHA"] + end + + def git_branch + env["CI_COMMIT_REF_NAME"] + end + + def git_tag + env["CI_COMMIT_TAG"] + end + + def git_commit_author_name + name, _ = extract_name_email + name + end + + def git_commit_author_email + _, email = extract_name_email + email + end + + def git_commit_author_date + env["CI_COMMIT_TIMESTAMP"] + end + + def git_commit_message + env["CI_COMMIT_MESSAGE"] + end + + def ci_env_vars + { + "CI_PROJECT_URL" => env["CI_PROJECT_URL"], + "CI_PIPELINE_ID" => env["CI_PIPELINE_ID"], + "CI_JOB_ID" => env["CI_JOB_ID"] + }.to_json + end + + private + + def extract_name_email + return @name_email_tuple if defined?(@name_email_tuple) + + name_and_email_string = env["CI_COMMIT_AUTHOR"] + if name_and_email_string.include?("<") && (match = /^([^<]*)<([^>]*)>$/.match(name_and_email_string)) + name = match[1] + name = name.strip if name + email = match[2] + return @name_email_tuple = [name, email] if name && email + end + + @name_email_tuple = [nil, name_and_email_string] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/jenkins.rb b/lib/datadog/ci/ext/environment/providers/jenkins.rb new file mode 100644 index 00000000..f60918c3 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/jenkins.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "json" + +require_relative "base" +require_relative "../../../utils/git" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Jenkins: https://www.jenkins.io/ + # Environment variables docs: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables + class Jenkins < Base + def provider_name + "jenkins" + end + + def pipeline_id + env["BUILD_TAG"] + end + + def pipeline_name + if (name = env["JOB_NAME"]) + name = name.gsub("/#{Datadog::CI::Utils::Git.normalize_ref(git_branch)}", "") if git_branch + name = name.split("/").reject { |v| v.nil? || v.include?("=") }.join("/") + end + name + end + + def pipeline_number + env["BUILD_NUMBER"] + end + + def pipeline_url + env["BUILD_URL"] + end + + def workspace_path + env["WORKSPACE"] + end + + def node_name + env["NODE_NAME"] + end + + def node_labels + env["NODE_LABELS"] && env["NODE_LABELS"].split.to_json + end + + def git_repository_url + env["GIT_URL"] || env["GIT_URL_1"] + end + + def git_commit_sha + env["GIT_COMMIT"] + end + + def git_branch_or_tag + env["GIT_BRANCH"] + end + + def ci_env_vars + { + "DD_CUSTOM_TRACE_ID" => env["DD_CUSTOM_TRACE_ID"] + }.to_json + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/local_git.rb b/lib/datadog/ci/ext/environment/providers/local_git.rb new file mode 100644 index 00000000..ed56b590 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/local_git.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require "open3" + +require_relative "base" +require_relative "../../git" + +module Datadog + module CI + module Ext + module Environment + module Providers + # As a fallback we try to fetch git information from the local git repository + class LocalGit < Base + def git_repository_url + exec_git_command("git ls-remote --get-url") + rescue => e + Datadog.logger.debug( + "Unable to read git repository url: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + def git_commit_sha + exec_git_command("git rev-parse HEAD") + rescue => e + Datadog.logger.debug( + "Unable to read git commit SHA: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + def git_branch + exec_git_command("git rev-parse --abbrev-ref HEAD") + rescue => e + Datadog.logger.debug( + "Unable to read git branch: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + def git_tag + exec_git_command("git tag --points-at HEAD") + rescue => e + Datadog.logger.debug( + "Unable to read git tag: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + def git_commit_message + exec_git_command("git show -s --format=%s") + rescue => e + Datadog.logger.debug( + "Unable to read git commit message: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + def git_commit_author_name + author.name + end + + def git_commit_author_email + author.email + end + + def git_commit_author_date + author.date + end + + def git_commit_committer_name + committer.name + end + + def git_commit_committer_email + committer.email + end + + def git_commit_committer_date + committer.date + end + + def workspace_path + exec_git_command("git rev-parse --show-toplevel") + rescue => e + Datadog.logger.debug( + "Unable to read git base directory: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + nil + end + + private + + def exec_git_command(cmd) + out, status = Open3.capture2e(cmd) + + raise "Failed to run git command #{cmd}: #{out}" unless status.success? + + out.strip! # There's always a "\n" at the end of the command output + + return nil if out.empty? + + out + end + + def author + return @author if defined?(@author) + + set_git_commit_users + @author + end + + def committer + return @committer if defined?(@committer) + + set_git_commit_users + @committer + end + + def set_git_commit_users + # Get committer and author information in one command. + output = exec_git_command("git show -s --format='%an\t%ae\t%at\t%cn\t%ce\t%ct'") + unless output + Datadog.logger.debug( + "Unable to read git commit users: git command output is nil" + ) + @author = @committer = NilUser.new + return + end + + author_name, author_email, author_timestamp, + committer_name, committer_email, committer_timestamp = output.split("\t").each(&:strip!) + + @author = GitUser.new(author_name, author_email, author_timestamp) + @committer = GitUser.new(committer_name, committer_email, committer_timestamp) + rescue => e + Datadog.logger.debug( + "Unable to read git commit users: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" + ) + @author = @committer = NilUser.new + end + + class GitUser + attr_reader :name, :email, :timestamp + + def initialize(name, email, timestamp) + @name = name + @email = email + @timestamp = timestamp + end + + def date + return nil if timestamp.nil? + + Time.at(timestamp.to_i).utc.to_datetime.iso8601 + end + end + + class NilUser < GitUser + def initialize + super(nil, nil, nil) + end + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/teamcity.rb b/lib/datadog/ci/ext/environment/providers/teamcity.rb new file mode 100644 index 00000000..f57d17d2 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/teamcity.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Teamcity: https://www.jetbrains.com/teamcity/ + # Environment variables docs: https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html + class Teamcity < Base + def provider_name + "teamcity" + end + + def job_name + env["TEAMCITY_BUILDCONF_NAME"] + end + + def job_url + env["BUILD_URL"] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/travis.rb b/lib/datadog/ci/ext/environment/providers/travis.rb new file mode 100644 index 00000000..0d175f67 --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/travis.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Travis CI: https://www.travis-ci.com/ + # Environment variables docs: https://docs.travis-ci.com/user/environment-variables#default-environment-variables + class Travis < Base + def provider_name + "travisci" + end + + def job_url + env["TRAVIS_JOB_WEB_URL"] + end + + def pipeline_id + env["TRAVIS_BUILD_ID"] + end + + def pipeline_name + env["TRAVIS_REPO_SLUG"] + end + + def pipeline_number + env["TRAVIS_BUILD_NUMBER"] + end + + def pipeline_url + env["TRAVIS_BUILD_WEB_URL"] + end + + def workspace_path + env["TRAVIS_BUILD_DIR"] + end + + def git_repository_url + "https://github.com/#{env["TRAVIS_REPO_SLUG"]}.git" + end + + def git_commit_sha + env["TRAVIS_COMMIT"] + end + + def git_branch + env["TRAVIS_PULL_REQUEST_BRANCH"] || env["TRAVIS_BRANCH"] + end + + def git_tag + env["TRAVIS_TAG"] + end + + def git_commit_message + env["TRAVIS_COMMIT_MESSAGE"] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/environment/providers/user_defined_tags.rb b/lib/datadog/ci/ext/environment/providers/user_defined_tags.rb new file mode 100644 index 00000000..ce099ddc --- /dev/null +++ b/lib/datadog/ci/ext/environment/providers/user_defined_tags.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require_relative "base" +require_relative "../../git" + +module Datadog + module CI + module Ext + module Environment + module Providers + # Parses user defined git data from the environment variables + # User documentation: https://docs.datadoghq.com/continuous_integration/troubleshooting/#data-appears-in-test-runs-but-not-tests + class UserDefinedTags < Base + def git_repository_url + env[Git::ENV_REPOSITORY_URL] + end + + def git_commit_sha + env[Git::ENV_COMMIT_SHA] + end + + def git_branch + env[Git::ENV_BRANCH] + end + + def git_tag + env[Git::ENV_TAG] + end + + def git_commit_message + env[Git::ENV_COMMIT_MESSAGE] + end + + def git_commit_author_name + env[Git::ENV_COMMIT_AUTHOR_NAME] + end + + def git_commit_author_email + env[Git::ENV_COMMIT_AUTHOR_EMAIL] + end + + def git_commit_author_date + env[Git::ENV_COMMIT_AUTHOR_DATE] + end + + def git_commit_committer_name + env[Git::ENV_COMMIT_COMMITTER_NAME] + end + + def git_commit_committer_email + env[Git::ENV_COMMIT_COMMITTER_EMAIL] + end + + def git_commit_committer_date + env[Git::ENV_COMMIT_COMMITTER_DATE] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/git.rb b/lib/datadog/ci/ext/git.rb new file mode 100644 index 00000000..a8f9d6f5 --- /dev/null +++ b/lib/datadog/ci/ext/git.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Datadog + module CI + module Ext + # Defines constants for Git tags + module Git + SHA_LENGTH = 40 + + TAG_BRANCH = "git.branch" + TAG_REPOSITORY_URL = "git.repository_url" + TAG_TAG = "git.tag" + + TAG_COMMIT_AUTHOR_DATE = "git.commit.author.date" + TAG_COMMIT_AUTHOR_EMAIL = "git.commit.author.email" + TAG_COMMIT_AUTHOR_NAME = "git.commit.author.name" + TAG_COMMIT_COMMITTER_DATE = "git.commit.committer.date" + TAG_COMMIT_COMMITTER_EMAIL = "git.commit.committer.email" + TAG_COMMIT_COMMITTER_NAME = "git.commit.committer.name" + TAG_COMMIT_MESSAGE = "git.commit.message" + TAG_COMMIT_SHA = "git.commit.sha" + + ENV_REPOSITORY_URL = "DD_GIT_REPOSITORY_URL" + ENV_COMMIT_SHA = "DD_GIT_COMMIT_SHA" + ENV_BRANCH = "DD_GIT_BRANCH" + ENV_TAG = "DD_GIT_TAG" + ENV_COMMIT_MESSAGE = "DD_GIT_COMMIT_MESSAGE" + ENV_COMMIT_AUTHOR_NAME = "DD_GIT_COMMIT_AUTHOR_NAME" + ENV_COMMIT_AUTHOR_EMAIL = "DD_GIT_COMMIT_AUTHOR_EMAIL" + ENV_COMMIT_AUTHOR_DATE = "DD_GIT_COMMIT_AUTHOR_DATE" + ENV_COMMIT_COMMITTER_NAME = "DD_GIT_COMMIT_COMMITTER_NAME" + ENV_COMMIT_COMMITTER_EMAIL = "DD_GIT_COMMIT_COMMITTER_EMAIL" + ENV_COMMIT_COMMITTER_DATE = "DD_GIT_COMMIT_COMMITTER_DATE" + end + end + end +end diff --git a/lib/datadog/ci/ext/settings.rb b/lib/datadog/ci/ext/settings.rb index c9424739..d74dbbd6 100644 --- a/lib/datadog/ci/ext/settings.rb +++ b/lib/datadog/ci/ext/settings.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + module Datadog module CI module Ext # Defines constants for test tags module Settings - ENV_MODE_ENABLED = "DD_TRACE_CI_ENABLED".freeze + ENV_MODE_ENABLED = "DD_TRACE_CI_ENABLED" end end end diff --git a/lib/datadog/ci/ext/test.rb b/lib/datadog/ci/ext/test.rb index 8d4a0471..af7199fb 100644 --- a/lib/datadog/ci/ext/test.rb +++ b/lib/datadog/ci/ext/test.rb @@ -1,33 +1,35 @@ +# frozen_string_literal: true + module Datadog module CI module Ext # Defines constants for test tags module Test - CONTEXT_ORIGIN = "ciapp-test".freeze + CONTEXT_ORIGIN = "ciapp-test" - TAG_ARGUMENTS = "test.arguments".freeze - TAG_FRAMEWORK = "test.framework".freeze - TAG_FRAMEWORK_VERSION = "test.framework_version".freeze - TAG_NAME = "test.name".freeze - TAG_SKIP_REASON = "test.skip_reason".freeze # DEV: Not populated yet - TAG_STATUS = "test.status".freeze - TAG_SUITE = "test.suite".freeze - TAG_TRAITS = "test.traits".freeze - TAG_TYPE = "test.type".freeze + TAG_ARGUMENTS = "test.arguments" + TAG_FRAMEWORK = "test.framework" + TAG_FRAMEWORK_VERSION = "test.framework_version" + TAG_NAME = "test.name" + TAG_SKIP_REASON = "test.skip_reason" # DEV: Not populated yet + TAG_STATUS = "test.status" + TAG_SUITE = "test.suite" + TAG_TRAITS = "test.traits" + TAG_TYPE = "test.type" # Environment runtime tags - TAG_OS_ARCHITECTURE = "os.architecture".freeze - TAG_OS_PLATFORM = "os.platform".freeze - TAG_RUNTIME_NAME = "runtime.name".freeze - TAG_RUNTIME_VERSION = "runtime.version".freeze + TAG_OS_ARCHITECTURE = "os.architecture" + TAG_OS_PLATFORM = "os.platform" + TAG_RUNTIME_NAME = "runtime.name" + TAG_RUNTIME_VERSION = "runtime.version" # TODO: is there a better place for SPAN_KIND? - TAG_SPAN_KIND = "span.kind".freeze + TAG_SPAN_KIND = "span.kind" module Status - PASS = "pass".freeze - FAIL = "fail".freeze - SKIP = "skip".freeze + PASS = "pass" + FAIL = "fail" + SKIP = "skip" end end end diff --git a/lib/datadog/ci/extensions.rb b/lib/datadog/ci/extensions.rb index e0ec9f28..7c0c1ec2 100644 --- a/lib/datadog/ci/extensions.rb +++ b/lib/datadog/ci/extensions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "datadog/core/configuration/settings" require "datadog/core/configuration/components" diff --git a/lib/datadog/ci/flush.rb b/lib/datadog/ci/flush.rb index c3771404..5fabf41e 100644 --- a/lib/datadog/ci/flush.rb +++ b/lib/datadog/ci/flush.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "datadog/tracing/metadata/ext" require "datadog/tracing/flush" diff --git a/lib/datadog/ci/utils/git.rb b/lib/datadog/ci/utils/git.rb new file mode 100644 index 00000000..4e674966 --- /dev/null +++ b/lib/datadog/ci/utils/git.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Datadog + module CI + module Utils + module Git + def self.normalize_ref(ref) + return nil if ref.nil? + + refs = %r{^refs/(heads/)?} + origin = %r{^origin/} + tags = %r{^tags/} + ref.gsub(refs, "").gsub(origin, "").gsub(tags, "") + end + + def self.is_git_tag?(ref) + !ref.nil? && ref.include?("tags/") + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment.rbs b/sig/datadog/ci/ext/environment.rbs index d6b0dc1f..73f8b132 100644 --- a/sig/datadog/ci/ext/environment.rbs +++ b/sig/datadog/ci/ext/environment.rbs @@ -27,63 +27,17 @@ module Datadog TAG_CI_ENV_VARS: String + HEX_NUMBER_REGEXP: Regexp + PROVIDERS: ::Array[Array[String | Symbol]] def self?.tags: (untyped env) -> Hash[String, String] - def self?.normalize_ref: (untyped name) -> untyped - - def self?.filter_sensitive_info: (untyped url) -> (String | nil) - - def self?.extract_appveyor: (untyped env) -> ::Hash[String, String?] - - def self?.extract_azure_pipelines: (untyped env) -> ::Hash[String, String?] - - def self?.extract_bitbucket: (untyped env) -> ::Hash[String, String?] - - def self?.extract_buddy: (untyped env) -> ::Hash[String, String?] - - def self?.extract_buildkite: (untyped env) -> Hash[String, String?] - - def self?.extract_circle_ci: (untyped env) -> ::Hash[String, String?] - - def self?.extract_github_actions: (untyped env) -> ::Hash[String, String?] - - def self?.extract_gitlab: (untyped env) -> ::Hash[String, String?] - - def self?.extract_jenkins: (untyped env) -> ::Hash[String, String?] - - def self?.extract_teamcity: (untyped env) -> ::Hash[String, String?] - - def self?.extract_travis: (untyped env) -> ::Hash[String, String?] - - def self?.extract_bitrise: (untyped env) -> ::Hash[String, String?] - - def self?.extract_codefresh: (untyped env) -> ::Hash[String, String?] - - def self?.extract_user_defined_git: (untyped env) -> Hash[String, String?] - - def self?.git_commit_users: () -> untyped - - def self?.git_repository_url: () -> untyped - - def self?.git_commit_message: () -> untyped - - def self?.git_branch: () -> untyped - - def self?.git_commit_sha: () -> untyped - - def self?.git_tag: () -> untyped - - def self?.git_base_directory: () -> untyped - - def self?.exec_git_command: (untyped cmd) -> (nil | untyped) - - def self?.extract_local_git: () -> untyped + def self?.ensure_post_conditions: (Hash[String, String] tags) -> void - def self?.branch_or_tag: (untyped branch_or_tag) -> ::Array[untyped] + def self?.validate_repository_url: (String? repo_url) -> void - def self?.extract_name_email: (untyped name_and_email) -> (::Array[untyped] | ::Array[nil | untyped]) + def self?.validate_git_sha: (String? git_sha) -> void end end end diff --git a/sig/datadog/ci/ext/environment/extractor.rbs b/sig/datadog/ci/ext/environment/extractor.rbs new file mode 100644 index 00000000..541f9671 --- /dev/null +++ b/sig/datadog/ci/ext/environment/extractor.rbs @@ -0,0 +1,27 @@ +module Datadog + module CI + module Ext + module Environment + class Extractor + @env: Hash[String, String?] + @provider: Providers::Base + @tags: Hash[String, untyped] + + def initialize: (Hash[String, String?] env, ?provider_klass: singleton(Providers::Base)?) -> void + + def tags: () -> Hash[String, untyped] + + private + + attr_reader env: untyped + + def normalize_git!: () -> void + + def expand_workspace!: () -> void + + def filter_sensitive_info: (String? url) -> String? + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers.rbs b/sig/datadog/ci/ext/environment/providers.rbs new file mode 100644 index 00000000..21a7fb5b --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers.rbs @@ -0,0 +1,13 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + PROVIDERS: ::Array[[String, untyped]] + + def self.for_environment: (Hash[String, String?] env) -> Providers::Base + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/appveyor.rbs b/sig/datadog/ci/ext/environment/providers/appveyor.rbs new file mode 100644 index 00000000..2868d732 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/appveyor.rbs @@ -0,0 +1,48 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Appveyor < Base + @github_repo_provider: bool + @url: String + + def provider_name: () -> "appveyor" + + def pipeline_url: () -> String? + + def job_url: () -> String? + + def workspace_path: () -> String? + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_message: () -> String? + + def github_repo_provider?: () -> bool + + private + + def url: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/azure.rbs b/sig/datadog/ci/ext/environment/providers/azure.rbs new file mode 100644 index 00000000..52daa08c --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/azure.rbs @@ -0,0 +1,56 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Azure < Base + @pipeline_url: String + @job_url: String + + def provider_name: () -> "azurepipelines" + + def pipeline_url: () -> String? + + def job_url: () -> String? + + def workspace_path: () -> String? + + def pipeline_id: () -> String? + + def pipeline_number: () -> String? + + def pipeline_name: () -> String? + + def stage_name: () -> String? + + def job_name: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch_or_tag: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_message: () -> String? + + def ci_env_vars: () -> String? + + private + + def build_id: () -> String? + + def team_foundation_server_uri: () -> String? + + def team_project_id: () -> String? + + def url_defined?: () -> bool + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/base.rbs b/sig/datadog/ci/ext/environment/providers/base.rbs new file mode 100644 index 00000000..2b46f5f9 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/base.rbs @@ -0,0 +1,69 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Base + attr_reader env: Hash[String, String?] + @branch: String? + @tag: String? + + def initialize: (Hash[String, String?] env) -> void + + def job_name: () -> nil + + def job_url: () -> nil + + def pipeline_id: () -> nil + + def pipeline_name: () -> nil + + def pipeline_number: () -> nil + + def pipeline_url: () -> nil + + def provider_name: () -> nil + + def stage_name: () -> nil + + def workspace_path: () -> nil + + def node_labels: () -> nil + + def node_name: () -> nil + + def ci_env_vars: () -> nil + + def git_branch: () -> String? + + def git_repository_url: () -> nil + + def git_tag: () -> String? + + def git_branch_or_tag: () -> nil + + def git_commit_author_date: () -> nil + + def git_commit_author_email: () -> nil + + def git_commit_author_name: () -> nil + + def git_commit_committer_date: () -> nil + + def git_commit_committer_email: () -> nil + + def git_commit_committer_name: () -> nil + + def git_commit_message: () -> nil + + def git_commit_sha: () -> nil + + private + + def set_branch_and_tag: () -> [String?, String?] + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/bitbucket.rbs b/sig/datadog/ci/ext/environment/providers/bitbucket.rbs new file mode 100644 index 00000000..72ce9304 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/bitbucket.rbs @@ -0,0 +1,37 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Bitbucket < Base + def provider_name: () -> "bitbucket" + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def job_url: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + private + + def url: () -> ::String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/bitrise.rbs b/sig/datadog/ci/ext/environment/providers/bitrise.rbs new file mode 100644 index 00000000..51c53a8e --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/bitrise.rbs @@ -0,0 +1,41 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Bitrise < Base + def provider_name: () -> "bitrise" + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_message: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_committer_name: () -> String? + + def git_commit_committer_email: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/buddy.rbs b/sig/datadog/ci/ext/environment/providers/buddy.rbs new file mode 100644 index 00000000..ac34a4bf --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/buddy.rbs @@ -0,0 +1,37 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Buddy < Extractor + def provider_name: () -> "buddy" + + def pipeline_id: () -> ::String + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_message: () -> String? + + def git_commit_committer_name: () -> String? + + def git_commit_committer_email: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/buildkite.rbs b/sig/datadog/ci/ext/environment/providers/buildkite.rbs new file mode 100644 index 00000000..f6a58e49 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/buildkite.rbs @@ -0,0 +1,45 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Buildkite < Extractor + def provider_name: () -> "buildkite" + + def job_url: () -> ::String + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def node_name: () -> String? + + def node_labels: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_message: () -> String? + + def ci_env_vars: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/circleci.rbs b/sig/datadog/ci/ext/environment/providers/circleci.rbs new file mode 100644 index 00000000..6197bca3 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/circleci.rbs @@ -0,0 +1,41 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Circleci < Extractor + def provider_name: () -> "circleci" + + def job_url: () -> String? + + def job_name: () -> String? + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_url: () -> ::String + + def workspace_path: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_message: () -> String? + + def ci_env_vars: () -> ::String + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/codefresh.rbs b/sig/datadog/ci/ext/environment/providers/codefresh.rbs new file mode 100644 index 00000000..e1338753 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/codefresh.rbs @@ -0,0 +1,25 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Codefresh < Extractor + def provider_name: () -> "codefresh" + + def job_name: () -> String? + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_url: () -> String? + + def git_branch_or_tag: () -> String? + + def ci_env_vars: () -> String + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/github_actions.rbs b/sig/datadog/ci/ext/environment/providers/github_actions.rbs new file mode 100644 index 00000000..78fc7eba --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/github_actions.rbs @@ -0,0 +1,37 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class GithubActions < Extractor + @ref: String + + def provider_name: () -> "github" + + def job_name: () -> String? + + def job_url: () -> ::String + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> ::String + + def git_commit_sha: () -> String? + + def git_branch_or_tag: () -> String? + + def ci_env_vars: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/gitlab.rbs b/sig/datadog/ci/ext/environment/providers/gitlab.rbs new file mode 100644 index 00000000..ce24cd03 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/gitlab.rbs @@ -0,0 +1,57 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Gitlab < Extractor + @name_email_tuple: [String?, String?] + + def provider_name: () -> "gitlab" + + def job_name: () -> String? + + def job_url: () -> String? + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def stage_name: () -> String? + + def workspace_path: () -> String? + + def node_name: () -> String? + + def node_labels: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_author_date: () -> String? + + def git_commit_message: () -> String? + + def ci_env_vars: () -> String? + + private + + def extract_name_email: () -> [String?, String?] + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/jenkins.rbs b/sig/datadog/ci/ext/environment/providers/jenkins.rbs new file mode 100644 index 00000000..591e9cbe --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/jenkins.rbs @@ -0,0 +1,35 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Jenkins < Base + def provider_name: () -> "jenkins" + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def workspace_path: () -> String? + + def node_name: () -> String? + + def node_labels: () -> String? + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch_or_tag: () -> String? + + def ci_env_vars: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/local_git.rbs b/sig/datadog/ci/ext/environment/providers/local_git.rbs new file mode 100644 index 00000000..7fe6e1eb --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/local_git.rbs @@ -0,0 +1,66 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class LocalGit < Base + class GitUser + attr_reader name: String? + attr_reader email: String? + attr_reader timestamp: String? + + @name: String? + @email: String? + @timestamp: String? + + def initialize: (String? name, String? email, String? timestamp) -> void + + def date: () -> String? + end + + class NilUser < GitUser + def initialize: () -> void + end + + private + + @author: GitUser + @committer: GitUser + + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_message: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_author_date: () -> String? + + def git_commit_committer_name: () -> String? + + def git_commit_committer_email: () -> String? + + def git_commit_committer_date: () -> String? + + def workspace_path: () -> String? + + def exec_git_command: (String cmd) -> String? + + def author: () -> GitUser + + def committer: () -> GitUser + + def set_git_commit_users: () -> void + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/teamcity.rbs b/sig/datadog/ci/ext/environment/providers/teamcity.rbs new file mode 100644 index 00000000..eca4b33a --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/teamcity.rbs @@ -0,0 +1,17 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Teamcity < Extractor + def provider_name: () -> "teamcity" + + def job_name: () -> String? + + def job_url: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/travis.rbs b/sig/datadog/ci/ext/environment/providers/travis.rbs new file mode 100644 index 00000000..03f75504 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/travis.rbs @@ -0,0 +1,35 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class Travis < Extractor + def provider_name: () -> "travisci" + + def job_url: () -> String? + + def pipeline_id: () -> String? + + def pipeline_name: () -> String? + + def pipeline_number: () -> String? + + def pipeline_url: () -> String? + + def workspace_path: () -> String? + + def git_repository_url: () -> ::String + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_message: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/environment/providers/user_defined_tags.rbs b/sig/datadog/ci/ext/environment/providers/user_defined_tags.rbs new file mode 100644 index 00000000..5e4745a4 --- /dev/null +++ b/sig/datadog/ci/ext/environment/providers/user_defined_tags.rbs @@ -0,0 +1,33 @@ +module Datadog + module CI + module Ext + module Environment + module Providers + class UserDefinedTags < Base + def git_repository_url: () -> String? + + def git_commit_sha: () -> String? + + def git_branch: () -> String? + + def git_tag: () -> String? + + def git_commit_message: () -> String? + + def git_commit_author_name: () -> String? + + def git_commit_author_email: () -> String? + + def git_commit_author_date: () -> String? + + def git_commit_committer_name: () -> String? + + def git_commit_committer_email: () -> String? + + def git_commit_committer_date: () -> String? + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/git.rbs b/sig/datadog/ci/ext/git.rbs new file mode 100644 index 00000000..ce436a7b --- /dev/null +++ b/sig/datadog/ci/ext/git.rbs @@ -0,0 +1,53 @@ +module Datadog + module CI + module Ext + module Git + SHA_LENGTH: 40 + + TAG_BRANCH: "git.branch" + + TAG_REPOSITORY_URL: "git.repository_url" + + TAG_TAG: "git.tag" + + TAG_COMMIT_AUTHOR_DATE: "git.commit.author.date" + + TAG_COMMIT_AUTHOR_EMAIL: "git.commit.author.email" + + TAG_COMMIT_AUTHOR_NAME: "git.commit.author.name" + + TAG_COMMIT_COMMITTER_DATE: "git.commit.committer.date" + + TAG_COMMIT_COMMITTER_EMAIL: "git.commit.committer.email" + + TAG_COMMIT_COMMITTER_NAME: "git.commit.committer.name" + + TAG_COMMIT_MESSAGE: "git.commit.message" + + TAG_COMMIT_SHA: "git.commit.sha" + + ENV_REPOSITORY_URL: "DD_GIT_REPOSITORY_URL" + + ENV_COMMIT_SHA: "DD_GIT_COMMIT_SHA" + + ENV_BRANCH: "DD_GIT_BRANCH" + + ENV_TAG: "DD_GIT_TAG" + + ENV_COMMIT_MESSAGE: "DD_GIT_COMMIT_MESSAGE" + + ENV_COMMIT_AUTHOR_NAME: "DD_GIT_COMMIT_AUTHOR_NAME" + + ENV_COMMIT_AUTHOR_EMAIL: "DD_GIT_COMMIT_AUTHOR_EMAIL" + + ENV_COMMIT_AUTHOR_DATE: "DD_GIT_COMMIT_AUTHOR_DATE" + + ENV_COMMIT_COMMITTER_NAME: "DD_GIT_COMMIT_COMMITTER_NAME" + + ENV_COMMIT_COMMITTER_EMAIL: "DD_GIT_COMMIT_COMMITTER_EMAIL" + + ENV_COMMIT_COMMITTER_DATE: "DD_GIT_COMMIT_COMMITTER_DATE" + end + end + end +end diff --git a/sig/datadog/ci/utils/git.rbs b/sig/datadog/ci/utils/git.rbs new file mode 100644 index 00000000..d32f93f5 --- /dev/null +++ b/sig/datadog/ci/utils/git.rbs @@ -0,0 +1,11 @@ +module Datadog + module CI + module Utils + module Git + def self?.normalize_ref: (untyped name) -> (nil | untyped) + + def self?.is_git_tag?: (untyped ref) -> untyped + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/appveyor_spec.rb b/spec/datadog/ci/ext/environment/providers/appveyor_spec.rb new file mode 100644 index 00000000..6c0fa412 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/appveyor_spec.rb @@ -0,0 +1,78 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Appveyor do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "APPVEYOR" => "true", + "APPVEYOR_BUILD_FOLDER" => "/foo/bar", + "APPVEYOR_BUILD_ID" => "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER" => "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH" => "master", + "APPVEYOR_REPO_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR" => "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL" => "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE" => "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED" => "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME" => "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER" => "github" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.job.url" => "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id" => "appveyor-build-id", + "ci.pipeline.name" => "appveyor-repo-name", + "ci.pipeline.number" => "appveyor-pipeline-number", + "ci.pipeline.url" => "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name" => "appveyor", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.author.email" => "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name" => "appveyor-commit-author-name", + "git.commit.message" => "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://github.com/appveyor-repo-name.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + + context "when commit message is not provided" do + let(:env) do + hash = super() + hash.delete("APPVEYOR_REPO_COMMIT_MESSAGE") + hash + end + + let(:expected_tags) do + hash = super() + hash.delete("git.commit.message") + hash + end + + it "omits git.commit.message" do + is_expected.to eq(expected_tags) + end + end + + context "when extended commit message is not provided" do + let(:env) do + hash = super() + hash.delete("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED") + hash + end + + it "does not append extended commit message" do + is_expected.to eq(expected_tags.merge({"git.commit.message" => "appveyor-commit-message"})) + end + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/azure_spec.rb b/spec/datadog/ci/ext/environment/providers/azure_spec.rb new file mode 100644 index 00000000..02e75162 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/azure_spec.rb @@ -0,0 +1,76 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Azure do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUILD_BUILDID" => "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME" => "azure-pipelines-name", + "BUILD_REPOSITORY_URI" => "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL" => "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID" => "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH" => "master", + "BUILD_SOURCESDIRECTORY" => "/foo/bar", + "BUILD_SOURCEVERSION" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE" => "azure-pipelines-commit-message", + "SYSTEM_JOBID" => "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID" => "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI" => "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID" => "azure-pipelines-project-id", + "SYSTEM_STAGEDISPLAYNAME" => "azure-stage", + "TF_BUILD" => "True", + "HOME" => "/not-my-home", + "USERPROFILE" => "/not-my-home" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url" => "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id" => "azure-pipelines-build-id", + "ci.pipeline.name" => "azure-pipelines-name", + "ci.pipeline.number" => "azure-pipelines-build-id", + "ci.pipeline.url" => "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name" => "azurepipelines", + "ci.stage.name" => "azure-stage", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.author.email" => "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name" => "azure-pipelines-commit-author", + "git.commit.message" => "azure-pipelines-commit-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://azure-pipelines-server-uri.com/build.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + + context "when pipeline URL cannot be defined" do + let(:env) do + hash = super() + hash.delete("BUILD_BUILDID") + hash + end + + let(:expected_tags) do + hash = super() + hash["_dd.ci.env_vars"] = "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":null,\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}" + ["ci.pipeline.id", "ci.pipeline.number", "ci.pipeline.url", "ci.job.url"].each do |key| + hash.delete(key) + end + hash + end + + it "omits URLs" do + is_expected.to eq(expected_tags) + end + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/base_spec.rb b/spec/datadog/ci/ext/environment/providers/base_spec.rb new file mode 100644 index 00000000..3a1b5d6a --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/base_spec.rb @@ -0,0 +1,22 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Base do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUILD_URL" => "https://build.io/build/34432432", + "PIPELINE_NAME" => "My simple project" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) { {} } + + it "always returns empty hash" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/bitbucket_spec.rb b/spec/datadog/ci/ext/environment/providers/bitbucket_spec.rb new file mode 100644 index 00000000..70e0e4f7 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/bitbucket_spec.rb @@ -0,0 +1,58 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Bitbucket do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BITBUCKET_BRANCH" => "master", + "BITBUCKET_BUILD_NUMBER" => "bitbucket-build-num", + "BITBUCKET_CLONE_DIR" => "/foo/bar", + "BITBUCKET_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN" => "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID" => "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME" => "bitbucket-repo" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.job.url" => "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id" => "bitbucket-uuid", + "ci.pipeline.name" => "bitbucket-repo", + "ci.pipeline.number" => "bitbucket-build-num", + "ci.pipeline.url" => "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name" => "bitbucket", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://bitbucket-repo-url.com/repo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + + context "when no BITBUCKET_PIPELINE_UUID provided" do + let(:env) do + hash = super() + hash.delete("BITBUCKET_PIPELINE_UUID") + hash + end + + let(:expected_tags) do + hash = super() + hash.delete("ci.pipeline.id") + hash + end + + it "omits pipeline_id" do + is_expected.to eq(expected_tags) + end + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/bitrise_spec.rb b/spec/datadog/ci/ext/environment/providers/bitrise_spec.rb new file mode 100644 index 00000000..aed03e21 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/bitrise_spec.rb @@ -0,0 +1,41 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Bitrise do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BITRISE_BUILD_NUMBER" => "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG" => "bitrise-pipeline-id", + "BITRISE_BUILD_URL" => "https://bitrise-build-url.com//", + "BITRISE_GIT_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITRISE_GIT_MESSAGE" => "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR" => "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID" => "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL" => "https://bitrise-build-url.com/repo.git" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.pipeline.id" => "bitrise-pipeline-id", + "ci.pipeline.name" => "bitrise-pipeline-name", + "ci.pipeline.number" => "bitrise-pipeline-number", + "ci.pipeline.url" => "https://bitrise-build-url.com//", + "ci.provider.name" => "bitrise", + "ci.workspace_path" => "/foo/bar", + "git.commit.message" => "bitrise-git-commit-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://bitrise-build-url.com/repo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/buddy_spec.rb b/spec/datadog/ci/ext/environment/providers/buddy_spec.rb new file mode 100644 index 00000000..d731b318 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/buddy_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Buddy do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUDDY" => "true", + "BUDDY_EXECUTION_BRANCH" => "master", + "BUDDY_EXECUTION_ID" => "buddy-execution-id", + "BUDDY_EXECUTION_REVISION" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL" => "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME" => "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE" => "Create buddy.yml", + "BUDDY_EXECUTION_TAG" => "v1.0", + "BUDDY_EXECUTION_URL" => "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID" => "456", + "BUDDY_PIPELINE_NAME" => "Deploy to Production", + "BUDDY_SCM_URL" => "https://github.com/buddyworks/my-project.git" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.pipeline.id" => "456/buddy-execution-id", + "ci.pipeline.name" => "Deploy to Production", + "ci.pipeline.number" => "buddy-execution-id", + "ci.pipeline.url" => "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name" => "buddy", + "git.branch" => "master", + "git.commit.committer.email" => "mikebenson@buddy.works", + "git.commit.committer.name" => "Mike Benson", + "git.commit.message" => "Create buddy.yml", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://github.com/buddyworks/my-project.git", + "git.tag" => "v1.0" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/buildkite_spec.rb b/spec/datadog/ci/ext/environment/providers/buildkite_spec.rb new file mode 100644 index 00000000..b1bd484b --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/buildkite_spec.rb @@ -0,0 +1,51 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Buildkite do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUILDKITE" => "true", + "BUILDKITE_BRANCH" => "master", + "BUILDKITE_BUILD_AUTHOR" => "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL" => "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH" => "/foo/bar", + "BUILDKITE_BUILD_ID" => "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER" => "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL" => "https://buildkite-build-url.com", + "BUILDKITE_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID" => "buildkite-job-id", + "BUILDKITE_MESSAGE" => "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG" => "buildkite-pipeline-name", + "BUILDKITE_REPO" => "http://hostname.com/repo.git", + "BUILDKITE_TAG" => "" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url" => "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id" => "buildkite-pipeline-id", + "ci.pipeline.name" => "buildkite-pipeline-name", + "ci.pipeline.number" => "buildkite-pipeline-number", + "ci.pipeline.url" => "https://buildkite-build-url.com", + "ci.provider.name" => "buildkite", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.author.email" => "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name" => "buildkite-git-commit-author-name", + "git.commit.message" => "buildkite-git-commit-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "http://hostname.com/repo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/circleci_spec.rb b/spec/datadog/ci/ext/environment/providers/circleci_spec.rb new file mode 100644 index 00000000..8de10e6d --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/circleci_spec.rb @@ -0,0 +1,44 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Circleci do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "CIRCLECI" => "circleCI", + "CIRCLE_BRANCH" => "origin/master", + "CIRCLE_BUILD_NUM" => "circleci-pipeline-number", + "CIRCLE_BUILD_URL" => "https://circleci-build-url.com/", + "CIRCLE_JOB" => "circleci-job-name", + "CIRCLE_PROJECT_REPONAME" => "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL" => "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID" => "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY" => "/foo/bar" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name" => "circleci-job-name", + "ci.job.url" => "https://circleci-build-url.com/", + "ci.pipeline.id" => "circleci-pipeline-id", + "ci.pipeline.name" => "circleci-pipeline-name", + "ci.pipeline.url" => "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name" => "circleci", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://circleci-build-url.com/repo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/codefresh_spec.rb b/spec/datadog/ci/ext/environment/providers/codefresh_spec.rb new file mode 100644 index 00000000..37a1e69a --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/codefresh_spec.rb @@ -0,0 +1,33 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Codefresh do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "CF_BUILD_ID" => "6410367cee516146a4c4c66e", + "CF_BUILD_URL" => "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME" => "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME" => "mah-job-name" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name" => "mah-job-name", + "ci.pipeline.id" => "6410367cee516146a4c4c66e", + "ci.pipeline.name" => "My simple project/Example Java Project Pipeline", + "ci.pipeline.url" => "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name" => "codefresh" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/github_actions_spec.rb b/spec/datadog/ci/ext/environment/providers/github_actions_spec.rb new file mode 100644 index 00000000..aa0ac8af --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/github_actions_spec.rb @@ -0,0 +1,45 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::GithubActions do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "GITHUB_ACTION" => "run", + "GITHUB_JOB" => "github-job-name", + "GITHUB_REF" => "master", + "GITHUB_REPOSITORY" => "ghactions-repo", + "GITHUB_RUN_ID" => "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER" => "ghactions-pipeline-number", + "GITHUB_SERVER_URL" => "https://ghenterprise.com", + "GITHUB_SHA" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW" => "ghactions-pipeline-name", + "GITHUB_WORKSPACE" => "/foo/bar" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"GITHUB_SERVER_URL\":\"https://ghenterprise.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\"}", + "ci.job.name" => "github-job-name", + "ci.job.url" => "https://ghenterprise.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id" => "ghactions-pipeline-id", + "ci.pipeline.name" => "ghactions-pipeline-name", + "ci.pipeline.number" => "ghactions-pipeline-number", + "ci.pipeline.url" => "https://ghenterprise.com/ghactions-repo/actions/runs/ghactions-pipeline-id", + "ci.provider.name" => "github", + "ci.workspace_path" => "/foo/bar", + "git.branch" => "master", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://ghenterprise.com/ghactions-repo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/gitlab_spec.rb b/spec/datadog/ci/ext/environment/providers/gitlab_spec.rb new file mode 100644 index 00000000..7b1a22c9 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/gitlab_spec.rb @@ -0,0 +1,75 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Gitlab do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "CI_COMMIT_AUTHOR" => "John Doe ", + "CI_COMMIT_MESSAGE" => "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME" => "origin/master", + "CI_COMMIT_SHA" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP" => "2021-07-21T11:43:07-04:00", + "CI_JOB_ID" => "gitlab-job-id", + "CI_JOB_NAME" => "gitlab-job-name", + "CI_JOB_STAGE" => "gitlab-stage-name", + "CI_JOB_URL" => "https://gitlab.com/job", + "CI_PIPELINE_ID" => "gitlab-pipeline-id", + "CI_PIPELINE_IID" => "gitlab-pipeline-number", + "CI_PIPELINE_URL" => "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR" => "foo/bar", + "CI_PROJECT_PATH" => "gitlab-pipeline-name", + "CI_PROJECT_URL" => "https://gitlab.com/repo", + "CI_REPOSITORY_URL" => "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI" => "gitlab" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name" => "gitlab-job-name", + "ci.job.url" => "https://gitlab.com/job", + "ci.pipeline.id" => "gitlab-pipeline-id", + "ci.pipeline.name" => "gitlab-pipeline-name", + "ci.pipeline.number" => "gitlab-pipeline-number", + "ci.pipeline.url" => "https://foo/repo/-/pipelines/1234", + "ci.provider.name" => "gitlab", + "ci.stage.name" => "gitlab-stage-name", + "ci.workspace_path" => "foo/bar", + "git.branch" => "master", + "git.commit.author.date" => "2021-07-21T11:43:07-04:00", + "git.commit.author.email" => "john@doe.com", + "git.commit.author.name" => "John Doe", + "git.commit.message" => "gitlab-git-commit-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://gitlab.com/repo/myrepo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + + context "when CI_COMMIT_AUTHOR is malformed" do + context "no < symbol" do + let(:env) do + super().merge({"CI_COMMIT_AUTHOR" => "John Doe john@doe.com>"}) + end + + let(:expected_tags) do + hash = super() + hash.delete("git.commit.author.name") + hash.merge({"git.commit.author.email" => "John Doe john@doe.com>"}) + end + + it "puts CI_COMMIT_AUTHOR under git.commit.author.email" do + is_expected.to eq(expected_tags) + end + end + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/jenkins_spec.rb b/spec/datadog/ci/ext/environment/providers/jenkins_spec.rb new file mode 100644 index 00000000..3604c267 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/jenkins_spec.rb @@ -0,0 +1,61 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Jenkins do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUILD_NUMBER" => "jenkins-pipeline-number", + "BUILD_TAG" => "jenkins-pipeline-id", + "BUILD_URL" => "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID" => "jenkins-custom-trace-id", + "GIT_BRANCH" => "origin/master", + "GIT_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1" => "https://jenkins.com/repo/sample.git", + "GIT_URL_2" => "https://jenkins.com/repo/otherSample.git", + "JENKINS_URL" => "jenkins", + "JOB_NAME" => "jobName/KEY1=VALUE1,KEY2=VALUE2/master", + "JOB_URL" => "https://jenkins.com/job" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "_dd.ci.env_vars" => "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id" => "jenkins-pipeline-id", + "ci.pipeline.name" => "jobName", + "ci.pipeline.number" => "jenkins-pipeline-number", + "ci.pipeline.url" => "https://jenkins.com/pipeline", + "ci.provider.name" => "jenkins", + "git.branch" => "master", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://jenkins.com/repo/sample.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + + context "no git branch info" do + let(:env) do + hash = super() + hash.delete("GIT_BRANCH") + hash + end + + let(:expected_tags) do + hash = super() + hash.delete("git.branch") + hash.merge({"ci.pipeline.name" => "jobName/master"}) + end + + it "does not remove branch name from job name" do + is_expected.to eq(expected_tags) + end + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/local_git_spec.rb b/spec/datadog/ci/ext/environment/providers/local_git_spec.rb new file mode 100644 index 00000000..7fde44ca --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/local_git_spec.rb @@ -0,0 +1,54 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::LocalGit do + let(:env) { {} } + let(:environment_variables) { {} } + + describe "#tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example git repository" do + include_context "with git fixture", "gitdir_with_commit" + + let(:expected_tags) do + { + "ci.workspace_path" => "#{Dir.pwd}/spec/support/fixtures/git", + "git.branch" => "master", + "git.commit.author.date" => "2011-02-16T13:00:00+00:00", + "git.commit.author.email" => "bot@friendly.test", + "git.commit.author.name" => "Friendly bot", + "git.commit.committer.date" => "2021-06-17T18:35:10+00:00", + "git.commit.committer.email" => "marco.costa@datadoghq.com", + "git.commit.committer.name" => "Marco Costa", + "git.commit.message" => "First commit!", + "git.commit.sha" => "9322ca1d57975b49b8c00b449d21b06660ce8b5b", + "git.repository_url" => "https://datadoghq.com/git/test.git" + } + end + + it "matches expected tags" do + is_expected.to eq(expected_tags) + end + end + end + + describe "#committer" do + include_context "with git fixture", "gitdir_with_commit" + + subject(:committer_email) do + ClimateControl.modify(environment_variables) { described_class.new(env).git_commit_committer_email } + end + + it "returns committer from the latest commit in the repository" do + is_expected.to eq("marco.costa@datadoghq.com") + end + + context "when git show -s returns nothing" do + before do + allow(Open3).to receive(:capture2e).and_return(["", double(success?: true)]) + end + + it "returns nil and does not fail" do + is_expected.to be_nil + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/teamcity_spec.rb b/spec/datadog/ci/ext/environment/providers/teamcity_spec.rb new file mode 100644 index 00000000..20308ed7 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/teamcity_spec.rb @@ -0,0 +1,29 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Teamcity do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "BUILD_URL" => "https://teamcity.com/repo", + "TEAMCITY_BUILDCONF_NAME" => "Test 1", + "TEAMCITY_VERSION" => "2022.10 (build 116751)" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.job.name" => "Test 1", + "ci.job.url" => "https://teamcity.com/repo", + "ci.provider.name" => "teamcity" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/travis_spec.rb b/spec/datadog/ci/ext/environment/providers/travis_spec.rb new file mode 100644 index 00000000..b71a13f8 --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/travis_spec.rb @@ -0,0 +1,45 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::Travis do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "TRAVIS" => "travisCI", + "TRAVIS_BRANCH" => "origin/tags/0.1.0", + "TRAVIS_BUILD_DIR" => "/foo/bar", + "TRAVIS_BUILD_ID" => "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER" => "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL" => "https://travisci.com/pipeline", + "TRAVIS_COMMIT" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE" => "travis-commit-message", + "TRAVIS_JOB_WEB_URL" => "https://travisci.com/job", + "TRAVIS_REPO_SLUG" => "user/repo", + "TRAVIS_TAG" => "origin/tags/0.1.0" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "ci.job.url" => "https://travisci.com/job", + "ci.pipeline.id" => "travis-pipeline-id", + "ci.pipeline.name" => "user/repo", + "ci.pipeline.number" => "travis-pipeline-number", + "ci.pipeline.url" => "https://travisci.com/pipeline", + "ci.provider.name" => "travisci", + "ci.workspace_path" => "/foo/bar", + "git.commit.message" => "travis-commit-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "https://github.com/user/repo.git", + "git.tag" => "0.1.0" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment/providers/user_defined_tags_spec.rb b/spec/datadog/ci/ext/environment/providers/user_defined_tags_spec.rb new file mode 100644 index 00000000..c7d25efe --- /dev/null +++ b/spec/datadog/ci/ext/environment/providers/user_defined_tags_spec.rb @@ -0,0 +1,43 @@ +RSpec.describe ::Datadog::CI::Ext::Environment::Providers::UserDefinedTags do + describe ".tags" do + include_context "extract tags from environment with given provider and use a subject" + + context "example fixture" do + let(:env) do + { + "DD_GIT_BRANCH" => "usersupplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE" => "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL" => "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME" => "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE" => "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL" => "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME" => "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE" => "usersupplied-message", + "DD_GIT_COMMIT_SHA" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL" => "git@github.com:DataDog/userrepo.git" + } + end + # Modify HOME so that '~' expansion matches CI home directory. + let(:environment_variables) { super().merge("HOME" => env["HOME"]) } + + let(:expected_tags) do + { + "git.branch" => "usersupplied-branch", + "git.commit.author.date" => "usersupplied-authordate", + "git.commit.author.email" => "usersupplied-authoremail", + "git.commit.author.name" => "usersupplied-authorname", + "git.commit.committer.date" => "usersupplied-comitterdate", + "git.commit.committer.email" => "usersupplied-comitteremail", + "git.commit.committer.name" => "usersupplied-comittername", + "git.commit.message" => "usersupplied-message", + "git.commit.sha" => "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url" => "git@github.com:DataDog/userrepo.git" + } + end + + it "matches CI tags" do + is_expected.to eq(expected_tags) + end + end + end +end diff --git a/spec/datadog/ci/ext/environment_spec.rb b/spec/datadog/ci/ext/environment_spec.rb index ba953553..15b280e7 100644 --- a/spec/datadog/ci/ext/environment_spec.rb +++ b/spec/datadog/ci/ext/environment_spec.rb @@ -1,7 +1,12 @@ require "json" RSpec.describe ::Datadog::CI::Ext::Environment do - FIXTURE_DIR = "#{File.dirname(__FILE__)}/fixtures/" # rubocop:disable all + let(:logger) { instance_double(Datadog::Core::Logger) } + before do + allow(Datadog).to receive(:logger).and_return(logger) + allow(logger).to receive(:debug) + allow(logger).to receive(:error) + end describe ".tags" do subject(:extracted_tags) do @@ -11,16 +16,6 @@ let(:env) { {} } let(:environment_variables) { {} } - shared_context "with git fixture" do |git_fixture| - let(:environment_variables) do - super().merge("GIT_DIR" => "#{FIXTURE_DIR}/git/#{git_fixture}", "GIT_WORK_TREE" => "#{FIXTURE_DIR}/git/") - end - end - - shared_context "without git installed" do - before { allow(Open3).to receive(:capture2e).and_raise(Errno::ENOENT, "No such file or directory - git") } - end - Dir.glob("#{FIXTURE_DIR}/ci/*.json").sort.each do |filename| # Parse each CI provider file File.open(filename) do |f| @@ -39,7 +34,7 @@ is_expected .to eq( { - "ci.workspace_path" => "#{Dir.pwd}/spec/datadog/ci/ext/fixtures/git", + "ci.workspace_path" => "#{Dir.pwd}/spec/support/fixtures/git", "git.branch" => "master", "git.commit.author.date" => "2011-02-16T13:00:00+00:00", "git.commit.author.email" => "bot@friendly.test", @@ -72,7 +67,7 @@ context "with a newly created git repository" do include_context "with git fixture", "gitdir_empty" it "matches tags" do - is_expected.to eq("ci.workspace_path" => "#{Dir.pwd}/spec/datadog/ci/ext/fixtures/git") + is_expected.to eq("ci.workspace_path" => "#{Dir.pwd}/spec/support/fixtures/git") end end @@ -80,7 +75,7 @@ include_context "with git fixture", "gitdir_with_commit" it "matches tags" do is_expected.to eq( - "ci.workspace_path" => "#{Dir.pwd}/spec/datadog/ci/ext/fixtures/git", + "ci.workspace_path" => "#{Dir.pwd}/spec/support/fixtures/git", "git.branch" => "master", "git.commit.author.date" => "2011-02-16T13:00:00+00:00", "git.commit.author.email" => "bot@friendly.test", @@ -107,49 +102,139 @@ include_context "without git installed" it "does not fail" do - allow(Datadog.logger).to receive(:debug) - is_expected.to eq({}) - expect(Datadog.logger).to have_received(:debug).with(/No such file or directory - git/).at_least(1).time + expect(logger).to have_received(:debug).with(/No such file or directory - git/).at_least(1).time end end context "user provided metadata" do - include_context "with git fixture", "gitdir_with_commit" - let(:env) do - { - "DD_GIT_REPOSITORY_URL" => "https://datadoghq.com/git/user-provided.git", - "DD_GIT_COMMIT_SHA" => "9322ca1d57975b49b8c00b449d21b06660ce8b5c", - "DD_GIT_BRANCH" => "my-branch", - "DD_GIT_TAG" => "my-tag", - "DD_GIT_COMMIT_MESSAGE" => "provided message", - "DD_GIT_COMMIT_AUTHOR_NAME" => "user", - "DD_GIT_COMMIT_AUTHOR_EMAIL" => "user@provided.com", - "DD_GIT_COMMIT_AUTHOR_DATE" => "2021-06-18T18:35:10+00:00", - "DD_GIT_COMMIT_COMMITTER_NAME" => "user committer", - "DD_GIT_COMMIT_COMMITTER_EMAIL" => "user-committer@provided.com", - "DD_GIT_COMMIT_COMMITTER_DATE" => "2021-06-19T18:35:10+00:00" - } - end + context "when required values are present" do + include_context "with git fixture", "gitdir_with_commit" - it "returns user provided metadata" do - is_expected.to eq( + let(:env) do { - "ci.workspace_path" => "#{Dir.pwd}/spec/datadog/ci/ext/fixtures/git", - "git.branch" => env["DD_GIT_BRANCH"], - "git.tag" => env["DD_GIT_TAG"], - "git.commit.author.date" => env["DD_GIT_COMMIT_AUTHOR_DATE"], - "git.commit.author.email" => env["DD_GIT_COMMIT_AUTHOR_EMAIL"], - "git.commit.author.name" => env["DD_GIT_COMMIT_AUTHOR_NAME"], - "git.commit.committer.date" => env["DD_GIT_COMMIT_COMMITTER_DATE"], - "git.commit.committer.email" => env["DD_GIT_COMMIT_COMMITTER_EMAIL"], - "git.commit.committer.name" => env["DD_GIT_COMMIT_COMMITTER_NAME"], - "git.commit.message" => env["DD_GIT_COMMIT_MESSAGE"], - "git.commit.sha" => env["DD_GIT_COMMIT_SHA"], - "git.repository_url" => env["DD_GIT_REPOSITORY_URL"] + "DD_GIT_REPOSITORY_URL" => "https://datadoghq.com/git/user-provided.git", + "DD_GIT_COMMIT_SHA" => "9322CA1d57975b49b8c00b449d21b06660ce8b5c", + "DD_GIT_BRANCH" => "my-branch", + "DD_GIT_TAG" => "my-tag", + "DD_GIT_COMMIT_MESSAGE" => "provided message", + "DD_GIT_COMMIT_AUTHOR_NAME" => "user", + "DD_GIT_COMMIT_AUTHOR_EMAIL" => "user@provided.com", + "DD_GIT_COMMIT_AUTHOR_DATE" => "2021-06-18T18:35:10+00:00", + "DD_GIT_COMMIT_COMMITTER_NAME" => "user committer", + "DD_GIT_COMMIT_COMMITTER_EMAIL" => "user-committer@provided.com", + "DD_GIT_COMMIT_COMMITTER_DATE" => "2021-06-19T18:35:10+00:00" } - ) + end + + it "returns user provided metadata" do + is_expected.to eq( + { + "ci.workspace_path" => "#{Dir.pwd}/spec/support/fixtures/git", + "git.branch" => env["DD_GIT_BRANCH"], + "git.tag" => env["DD_GIT_TAG"], + "git.commit.author.date" => env["DD_GIT_COMMIT_AUTHOR_DATE"], + "git.commit.author.email" => env["DD_GIT_COMMIT_AUTHOR_EMAIL"], + "git.commit.author.name" => env["DD_GIT_COMMIT_AUTHOR_NAME"], + "git.commit.committer.date" => env["DD_GIT_COMMIT_COMMITTER_DATE"], + "git.commit.committer.email" => env["DD_GIT_COMMIT_COMMITTER_EMAIL"], + "git.commit.committer.name" => env["DD_GIT_COMMIT_COMMITTER_NAME"], + "git.commit.message" => env["DD_GIT_COMMIT_MESSAGE"], + "git.commit.sha" => env["DD_GIT_COMMIT_SHA"], + "git.repository_url" => env["DD_GIT_REPOSITORY_URL"] + } + ) + end + end + + context "with no git information extracted" do + include_context "without git installed" + + context "when DD_GIT_REPOSITORY_URL is missing" do + let(:env) do + { + "DD_GIT_COMMIT_SHA" => "9322ca1d57975b49b8c00b449d21b06660ce8b5c" + } + end + + it "logs an error" do + is_expected.to eq( + { + "git.commit.sha" => env["DD_GIT_COMMIT_SHA"] + } + ) + + expect(logger).to have_received(:error).with( + "DD_GIT_REPOSITORY_URL is not set or empty; no repo URL was automatically extracted" + ) + end + end + + context "when DD_GIT_COMMIT_SHA is missing" do + let(:env) do + { + "DD_GIT_REPOSITORY_URL" => "https://datadoghq.com/git/user-provided.git" + } + end + + it "logs an error" do + is_expected.to eq( + { + "git.repository_url" => env["DD_GIT_REPOSITORY_URL"] + } + ) + + expect(logger).to have_received(:error).with( + "DD_GIT_COMMIT_SHA must be a full-length git SHA. No value was set and no SHA was automatically extracted." + ) + end + end + + context "when DD_GIT_COMMIT_SHA has invalid length" do + let(:env) do + { + "DD_GIT_COMMIT_SHA" => "9322ca1d57975b49b8c00b449d21b06660ce8b5", + "DD_GIT_REPOSITORY_URL" => "https://datadoghq.com/git/user-provided.git" + } + end + + it "logs an error" do + is_expected.to eq( + { + "git.commit.sha" => env["DD_GIT_COMMIT_SHA"], + "git.repository_url" => env["DD_GIT_REPOSITORY_URL"] + } + ) + + expect(logger).to have_received(:error).with( + "DD_GIT_COMMIT_SHA must be a full-length git SHA. Expected SHA length 40, was 39." + ) + end + end + + context "when DD_GIT_COMMIT_SHA is not a valid hex number" do + let(:env) do + { + "DD_GIT_COMMIT_SHA" => "9322ca1d57975by9b8c00b449d21b06660ce8b5c", + "DD_GIT_REPOSITORY_URL" => "https://datadoghq.com/git/user-provided.git" + } + end + + it "logs an error" do + is_expected.to eq( + { + "git.commit.sha" => env["DD_GIT_COMMIT_SHA"], + "git.repository_url" => env["DD_GIT_REPOSITORY_URL"] + } + ) + + expect(logger).to have_received(:error).with( + "DD_GIT_COMMIT_SHA must be a full-length git SHA. " \ + "Expected SHA to be a valid HEX number, got 9322ca1d57975by9b8c00b449d21b06660ce8b5c." + ) + end + end end end end diff --git a/spec/datadog/ci/utils/git_spec.rb b/spec/datadog/ci/utils/git_spec.rb new file mode 100644 index 00000000..c17700c5 --- /dev/null +++ b/spec/datadog/ci/utils/git_spec.rb @@ -0,0 +1,49 @@ +RSpec.describe ::Datadog::CI::Utils::Git do + describe ".normalize_ref" do + subject { described_class.normalize_ref(ref) } + + context "when input is nil" do + let(:ref) { nil } + + it { is_expected.to be_nil } + end + + context "when input is github ref" do + let(:ref) { "refs/heads/master" } + + it "strips everything out except ref name" do + is_expected.to eq("master") + end + end + + context "when input includes tags" do + let(:ref) { "refs/heads/tags/0.1.0" } + + it "strips everything out except ref name" do + is_expected.to eq("0.1.0") + end + end + end + + describe ".is_git_tag?" do + subject { described_class.is_git_tag?(ref) } + + context "when input is nil" do + let(:ref) { nil } + + it { is_expected.to be_falsey } + end + + context "when input is a branch" do + let(:ref) { "refs/heads/master" } + + it { is_expected.to be_falsey } + end + + context "when input includes tags" do + let(:ref) { "refs/heads/tags/0.1.0" } + + it { is_expected.to be_truthy } + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9670bf67..c55d75cd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,8 @@ require_relative "support/span_helpers" require_relative "support/test_helpers" require_relative "support/platform_helpers" +require_relative "support/git_helpers" +require_relative "support/provider_test_helpers" require "rspec/collection_matchers" require "climate_control" diff --git a/spec/datadog/ci/ext/fixtures/ci/appveyor.json b/spec/support/fixtures/ci/appveyor.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/appveyor.json rename to spec/support/fixtures/ci/appveyor.json diff --git a/spec/datadog/ci/ext/fixtures/ci/azurepipelines.json b/spec/support/fixtures/ci/azurepipelines.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/azurepipelines.json rename to spec/support/fixtures/ci/azurepipelines.json diff --git a/spec/datadog/ci/ext/fixtures/ci/bitbucket.json b/spec/support/fixtures/ci/bitbucket.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/bitbucket.json rename to spec/support/fixtures/ci/bitbucket.json diff --git a/spec/datadog/ci/ext/fixtures/ci/bitrise.json b/spec/support/fixtures/ci/bitrise.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/bitrise.json rename to spec/support/fixtures/ci/bitrise.json diff --git a/spec/datadog/ci/ext/fixtures/ci/buddy.json b/spec/support/fixtures/ci/buddy.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/buddy.json rename to spec/support/fixtures/ci/buddy.json diff --git a/spec/datadog/ci/ext/fixtures/ci/buildkite.json b/spec/support/fixtures/ci/buildkite.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/buildkite.json rename to spec/support/fixtures/ci/buildkite.json diff --git a/spec/datadog/ci/ext/fixtures/ci/circleci.json b/spec/support/fixtures/ci/circleci.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/circleci.json rename to spec/support/fixtures/ci/circleci.json diff --git a/spec/datadog/ci/ext/fixtures/ci/codefresh.json b/spec/support/fixtures/ci/codefresh.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/codefresh.json rename to spec/support/fixtures/ci/codefresh.json diff --git a/spec/datadog/ci/ext/fixtures/ci/github.json b/spec/support/fixtures/ci/github.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/github.json rename to spec/support/fixtures/ci/github.json diff --git a/spec/datadog/ci/ext/fixtures/ci/gitlab.json b/spec/support/fixtures/ci/gitlab.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/gitlab.json rename to spec/support/fixtures/ci/gitlab.json diff --git a/spec/datadog/ci/ext/fixtures/ci/jenkins.json b/spec/support/fixtures/ci/jenkins.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/jenkins.json rename to spec/support/fixtures/ci/jenkins.json diff --git a/spec/datadog/ci/ext/fixtures/ci/teamcity.json b/spec/support/fixtures/ci/teamcity.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/teamcity.json rename to spec/support/fixtures/ci/teamcity.json diff --git a/spec/datadog/ci/ext/fixtures/ci/travisci.json b/spec/support/fixtures/ci/travisci.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/travisci.json rename to spec/support/fixtures/ci/travisci.json diff --git a/spec/datadog/ci/ext/fixtures/ci/usersupplied.json b/spec/support/fixtures/ci/usersupplied.json similarity index 100% rename from spec/datadog/ci/ext/fixtures/ci/usersupplied.json rename to spec/support/fixtures/ci/usersupplied.json diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_empty/HEAD b/spec/support/fixtures/git/gitdir_empty/HEAD similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_empty/HEAD rename to spec/support/fixtures/git/gitdir_empty/HEAD diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_empty/config b/spec/support/fixtures/git/gitdir_empty/config similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_empty/config rename to spec/support/fixtures/git/gitdir_empty/config diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_empty/objects/.keep b/spec/support/fixtures/git/gitdir_empty/objects/.keep similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_empty/objects/.keep rename to spec/support/fixtures/git/gitdir_empty/objects/.keep diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_empty/refs/.keep b/spec/support/fixtures/git/gitdir_empty/refs/.keep similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_empty/refs/.keep rename to spec/support/fixtures/git/gitdir_empty/refs/.keep diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/HEAD b/spec/support/fixtures/git/gitdir_with_commit/HEAD similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/HEAD rename to spec/support/fixtures/git/gitdir_with_commit/HEAD diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/config b/spec/support/fixtures/git/gitdir_with_commit/config similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/config rename to spec/support/fixtures/git/gitdir_with_commit/config diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/index b/spec/support/fixtures/git/gitdir_with_commit/index similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/index rename to spec/support/fixtures/git/gitdir_with_commit/index diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/logs/HEAD b/spec/support/fixtures/git/gitdir_with_commit/logs/HEAD similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/logs/HEAD rename to spec/support/fixtures/git/gitdir_with_commit/logs/HEAD diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/logs/refs/heads/master b/spec/support/fixtures/git/gitdir_with_commit/logs/refs/heads/master similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/logs/refs/heads/master rename to spec/support/fixtures/git/gitdir_with_commit/logs/refs/heads/master diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/3e/84e4841fc8f3c76e54fb1becaa8863a2cede30 b/spec/support/fixtures/git/gitdir_with_commit/objects/3e/84e4841fc8f3c76e54fb1becaa8863a2cede30 similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/3e/84e4841fc8f3c76e54fb1becaa8863a2cede30 rename to spec/support/fixtures/git/gitdir_with_commit/objects/3e/84e4841fc8f3c76e54fb1becaa8863a2cede30 diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/spec/support/fixtures/git/gitdir_with_commit/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 rename to spec/support/fixtures/git/gitdir_with_commit/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/93/22ca1d57975b49b8c00b449d21b06660ce8b5b b/spec/support/fixtures/git/gitdir_with_commit/objects/93/22ca1d57975b49b8c00b449d21b06660ce8b5b similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/objects/93/22ca1d57975b49b8c00b449d21b06660ce8b5b rename to spec/support/fixtures/git/gitdir_with_commit/objects/93/22ca1d57975b49b8c00b449d21b06660ce8b5b diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/refs/heads/master b/spec/support/fixtures/git/gitdir_with_commit/refs/heads/master similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/refs/heads/master rename to spec/support/fixtures/git/gitdir_with_commit/refs/heads/master diff --git a/spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/refs/tags/v1.2.3 b/spec/support/fixtures/git/gitdir_with_commit/refs/tags/v1.2.3 similarity index 100% rename from spec/datadog/ci/ext/fixtures/git/gitdir_with_commit/refs/tags/v1.2.3 rename to spec/support/fixtures/git/gitdir_with_commit/refs/tags/v1.2.3 diff --git a/spec/support/git_helpers.rb b/spec/support/git_helpers.rb new file mode 100644 index 00000000..70579e9d --- /dev/null +++ b/spec/support/git_helpers.rb @@ -0,0 +1,11 @@ +FIXTURE_DIR = "#{File.dirname(__FILE__)}/fixtures/" # rubocop:disable all + +shared_context "with git fixture" do |git_fixture| + let(:environment_variables) do + super().merge("GIT_DIR" => "#{FIXTURE_DIR}/git/#{git_fixture}", "GIT_WORK_TREE" => "#{FIXTURE_DIR}/git/") + end +end + +shared_context "without git installed" do + before { allow(Open3).to receive(:capture2e).and_raise(Errno::ENOENT, "No such file or directory - git") } +end diff --git a/spec/support/provider_test_helpers.rb b/spec/support/provider_test_helpers.rb new file mode 100644 index 00000000..b9e98d15 --- /dev/null +++ b/spec/support/provider_test_helpers.rb @@ -0,0 +1,10 @@ +shared_context "extract tags from environment with given provider and use a subject" do |git_fixture| + subject(:extracted_tags) do + ClimateControl.modify(environment_variables) do + ::Datadog::CI::Ext::Environment::Extractor.new(env, provider_klass: described_class).tags + end + end + + let(:env) { {} } + let(:environment_variables) { {} } +end