Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find builds on play store and firebase if they were uploaded directly by CI #772

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions app/jobs/trigger_submissions_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ class TriggerSubmissionsJob < ApplicationJob
def perform(workflow_run_id, retry_count = 0)
workflow_run = WorkflowRun.find(workflow_run_id)
Coordinators::TriggerSubmissions.call(workflow_run)
rescue Installations::Error => ex
raise unless ex.reason == :artifact_not_found
rescue Installations::Error
if retry_count >= MAX_RETRIES
workflow_run&.triggering_release&.fail!
else
Expand Down
19 changes: 18 additions & 1 deletion app/libs/coordinators/trigger_submissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,24 @@ def initialize(workflow_run)

def call
return unless release_platform_run.on_track?
workflow_run.build.attach_artifact!

# Attempt to attach artifact only if platform is android
if release_platform_run.android?
begin
workflow_run.build.attach_artifact!
rescue Installations::Error => ex
if ex.reason == :artifact_not_found
# We can ignore the error if we find the build is found in store
workflow_run.build.mark_available_without_artifact
else
raise
end
end
elsif release_platform_run.ios?
# There are only two cases for platform - android/ios, keeping it elsif for clarity
# We don't handle uploads for ios (yet?)
workflow_run.build.mark_available_without_artifact
end

if workflow_run.release_candidate? && release_platform_run.hotfix?
Coordinators::StartProductionRelease.call(release_platform_run, workflow_run.build.id)
Expand Down
16 changes: 6 additions & 10 deletions app/models/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,8 @@ def size_in_mb
def attach_artifact!
# return mock_attach_artifact if sandbox_mode?
return if artifacts_url.blank?

artifact_data = get_build_artifact

if artifact_data.blank?
update!(generated_at: workflow_run.finished_at)
notify!("A new build is available!", :build_available_v2, notification_params)
return
end

stream = artifact_data[:stream]
artifact_metadata = artifact_data[:artifact]

Expand All @@ -95,6 +88,11 @@ def attach_artifact!
notify!("A new build is available!", :build_available_v2, notification_params, slack_file_id, display_name)
end

def mark_available_without_artifact
update!(generated_at: workflow_run.finished_at)
notify!("A new build is available!", :build_available_v2, notification_params)
end

def notification_params
workflow_run.notification_params.merge(
artifact_present: has_artifact?
Expand All @@ -112,12 +110,10 @@ def set_version_name
self.version_name = [release_platform_run.release_version, build_suffix.presence].compact.join(BUILD_SUFFIX_SEPARATOR)
end

# TODO: not just return nil but actually handle and raise it so it can be retried
def get_build_artifact
ci_cd_provider.get_artifact(artifacts_url, artifact_name_pattern, external_workflow_run_id: workflow_run.external_id)
rescue Installations::Error => ex
raise ex unless ex.reason == :artifact_not_found
elog(ex, level: :error)
nil
raise
end
end
18 changes: 15 additions & 3 deletions app/models/google_firebase_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,23 @@ def retryable? = failed?
def trigger!
return unless actionable?
return unless may_prepare?

event_stamp!(reason: :triggered, kind: :notice, data: stamp_data)
# return mock_upload_to_firebase if sandbox_mode?

preprocess!
if build.has_artifact?
# upload build only if we have it
preprocess!
else
release_info = provider.find_build(build.build_number, build.version_name, release_platform_run.platform)
if release_info.ok?
# We can proceed to next step if build was already uploaded by ci
prepare_and_update!(release_info.value!)
StoreSubmissions::GoogleFirebase::UpdateBuildNotesJob.perform_async(id, release_info.value!.release.id)
else
raise BuildNotFound, "Unable to find build #{build.build_number}"
end
end

event_stamp!(reason: :triggered, kind: :notice, data: stamp_data)
end

def upload_build!
Expand Down
15 changes: 12 additions & 3 deletions app/models/play_store_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ class PlayStoreSubmission < StoreSubmission
state :created, initial: true
state(*STATES.keys)

event :preprocess, after: :on_preprocess! do
event :preprocess, after_commit: :on_preprocess! do
transitions from: CHANGEABLE_STATES, to: :preprocessing
end

event :start_prepare, after: :on_start_prepare! do
event :start_prepare, after_commit: :on_start_prepare! do
transitions from: [:created, :preprocessing, :prepared, :failed], to: :preparing
end

Expand Down Expand Up @@ -142,7 +142,16 @@ def internal_channel?
def trigger!
return unless actionable?

preprocess!
if build.has_artifact?
# upload build only if we have it
preprocess!
elsif provider.find_build(build.build_number, raise_on_lock_error: false).present?
# skip upload step and go directly to start_prepare
start_prepare!
else
raise BuildNotFound, "Unable to find build #{build.build_number}"
end

event_stamp!(reason: :triggered, kind: :notice, data: stamp_data)
end

Expand Down
78 changes: 71 additions & 7 deletions spec/libs/coordinators/actions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,78 @@
expect(result.error.message).to eq("submission is not triggerable")
end

it "triggers the submission" do
expect {
result = described_class.trigger_submission!(submission)
expect(result).to be_ok
expect(submission.reload.preprocessing?).to be(true)
}.to change(StoreSubmissions::PlayStore::UploadJob.jobs, :size).by(1)
context "when build artifact is available" do
let(:build) { create(:build, :with_artifact, release_platform_run:, workflow_run:) }

before do
allow(build).to receive(:attach_artifact!).and_return(true)
end

shared_examples "build upload" do |upload_klass|
it "uploads the build to store" do
expect {
result = described_class.trigger_submission!(submission)
expect(result).to be_ok
expect(submission.reload.preprocessing?).to be(true)
}.to change(upload_klass.jobs, :size).by(1)

expect(upload_klass.jobs.last["args"]).to eq([submission.id])
end
end

context "when submitting to play store" do
include_examples "build upload", StoreSubmissions::PlayStore::UploadJob
end

context "when submitting to firebase" do
let(:submission) { create(:google_firebase_submission, parent_release: beta_release, build:) }

include_examples "build upload", StoreSubmissions::GoogleFirebase::UploadJob
end
end

context "when build artifact is not available" do
shared_examples "externally uploaded" do |store_provider_klass|
let(:store_provider) { instance_double(store_provider_klass) }

before do
allow(submission).to receive(:provider).and_return(store_provider)
allow(build).to receive(:attach_artifact!).and_raise(Installations::Error, reason: :artifact_not_found)
end

context "when build is externally uploaded to store" do
before do
allow(store_provider).to receive_message_chain(:find_build, :present?).and_return(true)
end

it "triggers submission" do
result = described_class.trigger_submission!(submission)
expect(result).to be_ok
expect(submission.reload.preparing?).to be(true)
end
end

context "when build is not externally uploaded to store" do
before do
allow(store_provider).to receive_message_chain(:find_build, :present?).and_return(false)
end

it "does not trigger submission" do
result = described_class.trigger_submission!(submission)
expect(result).not_to be_ok
end
end
end

context "when submitting to play store" do
include_examples "externally uploaded", GooglePlayStoreIntegration
end

context "when submitting to firebase" do
let(:submission) { create(:google_firebase_submission, parent_release: beta_release, build:) }

expect(StoreSubmissions::PlayStore::UploadJob.jobs.last["args"]).to eq([submission.id])
include_examples "externally uploaded", GoogleFirebaseIntegration
end
end
end

Expand Down
13 changes: 12 additions & 1 deletion spec/libs/coordinators/trigger_submissions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@

it "attaches the artifact to the build for the workflow run" do
ci_cd_double = instance_double(GithubIntegration)
allow(ci_cd_double).to receive(:get_artifact)
allow(ci_cd_double).to receive(:get_artifact).and_return({
stream: Artifacts::Stream.new("spec/fixtures/storage/test_artifact.aab.zip", is_archive: true),
artifact: {
generated_at: Time.zone.now,
size_in_bytes: 10,
name: "test_artifact_aab.zip",
id: "123456"
}
})

release_platform_run = create(:release_platform_run, :on_track)
allow(release_platform_run).to receive(:ci_cd_provider).and_return(ci_cd_double)
Expand All @@ -27,6 +35,7 @@
release_platform_run = create(:release_platform_run, :on_track)
internal_release = create(:internal_release, release_platform_run:)
workflow_run = create(:workflow_run, :finished, release_platform_run:, triggering_release: internal_release)
create(:build, :with_artifact, release_platform_run:, workflow_run:)
described_class.call(workflow_run)
expect(internal_release.store_submissions.size).to eq(1)
end
Expand All @@ -36,6 +45,7 @@
release_platform_run = create(:release_platform_run, :on_track, release:)
beta_release = create(:beta_release, release_platform_run:)
workflow_run = create(:workflow_run, :finished, :rc, release_platform_run:, triggering_release: beta_release)
create(:build, :with_artifact, release_platform_run:, workflow_run:)
described_class.call(workflow_run)
expect(release_platform_run.production_releases.size).to eq(1)
end
Expand All @@ -45,6 +55,7 @@
release_platform_run = create(:release_platform_run, :on_track, release:)
internal_release = create(:internal_release, release_platform_run:)
workflow_run = create(:workflow_run, :finished, :internal, release_platform_run:, triggering_release: internal_release)
create(:build, :with_artifact, release_platform_run:, workflow_run:)
described_class.call(workflow_run)
expect(release_platform_run.production_releases.size).to eq(0)
end
Expand Down
6 changes: 3 additions & 3 deletions spec/libs/coordinators/update_build_on_production_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@

it "attaches the new build to the production store submission" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
new_build = create(:build, :with_artifact, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(store_submission, :build_id).to(new_build.id)
end

it "updates the build number on the production release" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
new_build = create(:build, :with_artifact, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(production_release, :build_id).to(new_build.id)
Expand All @@ -112,7 +112,7 @@
it "retriggers the store submission if the submission was previously triggered" do
allow(StoreSubmissions::PlayStore::UploadJob).to receive(:perform_async)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
new_build = create(:build, :with_artifact, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.preprocessing?).to be(true)
expect(StoreSubmissions::PlayStore::UploadJob).to have_received(:perform_async).with(store_submission.id).once
Expand Down
1 change: 1 addition & 0 deletions spec/models/google_firebase_submission_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
let(:providable_dbl) { instance_double(GoogleFirebaseIntegration) }

before do
submission.build.artifact = create(:build_artifact, build: submission.build)
allow_any_instance_of(described_class).to receive(:provider).and_return(providable_dbl)
end

Expand Down
4 changes: 2 additions & 2 deletions spec/models/pre_prod_release_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
let(:workflow_run) { create(:workflow_run, triggering_release: pre_prod_release) }

it "triggers the first submission" do
build = create(:build, workflow_run:)
build = create(:build, :with_artifact, workflow_run:)
pre_prod_release.trigger_submissions!
expect(pre_prod_release.store_submissions.count).to eq(1)
expect(pre_prod_release.store_submissions.sole.build).to eq(build)
Expand All @@ -19,7 +19,7 @@
describe "#rollout_complete!" do
let(:pre_prod_release) { create(:internal_release) }
let(:workflow_run) { create(:workflow_run, triggering_release: pre_prod_release) }
let(:build) { create(:build, workflow_run:) }
let(:build) { create(:build, :with_artifact, workflow_run:) }
let(:submission) { create(:play_store_submission, parent_release: pre_prod_release, build:, sequence_number: 1) }
let(:providable_dbl) { instance_double(GooglePlayStoreIntegration) }

Expand Down
Loading