Skip to content
Merged
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
1 change: 1 addition & 0 deletions app/actions/build_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
state: BuildModel::STAGING_STATE,
package_guid: package.guid,
app: package.app,
lifecycle_type: lifecycle.type,
staging_memory_in_mb: staging_details.staging_memory_in_mb,
staging_disk_in_mb: staging_details.staging_disk_in_mb,
staging_log_rate_limit: staging_details.staging_log_rate_limit_bytes_per_second,
Expand Down
2 changes: 1 addition & 1 deletion app/actions/droplet_copy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def copy(destination_app, user_audit_info)
raise InvalidCopyError.new('source droplet is not staged') unless @source_droplet.staged?
raise InvalidCopyError.new('source and destination lifecycle types do not match') unless @source_droplet.lifecycle_type == destination_app.lifecycle_type

new_droplet = DropletModel.new(state: DropletModel::COPYING_STATE, app: destination_app)
new_droplet = DropletModel.new(state: DropletModel::COPYING_STATE, app: destination_app, lifecycle_type: destination_app.lifecycle_type)

# Needed to execute serializers and deserializers correctly on source and destination models
CLONED_ATTRIBUTES.each do |attr|
Expand Down
2 changes: 2 additions & 0 deletions app/actions/droplet_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def create(app, message, user_audit_info)
droplet = DropletModel.new(
app: app,
state: DropletModel::AWAITING_UPLOAD_STATE,
lifecycle_type: BuildpackLifecycleDataModel::LIFECYCLE_TYPE,
process_types: message.process_types || DEFAULT_PROCESS_TYPES,
execution_metadata: ''
)
Expand Down Expand Up @@ -87,6 +88,7 @@ def droplet_from_build(build)
app: build.app,
package_guid: build.package_guid,
state: DropletModel::STAGING_STATE,
lifecycle_type: build.lifecycle_type,
build: build
)
end
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/runtime/apps_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def upload_droplet(guid)

enqueued_job = nil
DropletModel.db.transaction do
droplet = DropletModel.create(app: process.app, state: DropletModel::PROCESSING_UPLOAD_STATE)
droplet = DropletModel.create(app: process.app, state: DropletModel::PROCESSING_UPLOAD_STATE, lifecycle_type: BuildpackLifecycleDataModel::LIFECYCLE_TYPE)
BuildpackLifecycleDataModel.create(droplet:)

droplet_upload_job = Jobs::V2::UploadDropletFromUser.new(droplet_path, droplet.guid)
Expand Down
27 changes: 1 addition & 26 deletions app/fetchers/app_list_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,7 @@ def filter(message, dataset)
dataset = dataset.where(guid: buildpack_lifecycle_data_dataset.select(:app_guid))
end

if message.requested?(:lifecycle_type)
case message.lifecycle_type
when BuildpackLifecycleDataModel::LIFECYCLE_TYPE
dataset = dataset.where(
guid: BuildpackLifecycleDataModel.
where(Sequel.~(app_guid: nil)).
select(:app_guid)
)
when DockerLifecycleDataModel::LIFECYCLE_TYPE
dataset = dataset.exclude(
guid: BuildpackLifecycleDataModel.
where(Sequel.~(app_guid: nil)).
select(:app_guid)
).exclude(
guid: CNBLifecycleDataModel.
where(Sequel.~(app_guid: nil)).
select(:app_guid)
)
when CNBLifecycleDataModel::LIFECYCLE_TYPE
dataset = dataset.where(
guid: CNBLifecycleDataModel.
where(Sequel.~(app_guid: nil)).
select(:app_guid)
)
end
end
dataset = dataset.where(lifecycle_type: message.lifecycle_type) if message.requested?(:lifecycle_type)

if message.requested?(:organization_guids)
dataset = dataset.
Expand Down
11 changes: 0 additions & 11 deletions app/models/runtime/app_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,6 @@ def validate
validates_includes Lifecycles::TYPES, :lifecycle_type
end

def lifecycle_type
return self[:lifecycle_type] if self[:lifecycle_type].present?

# Fallback for records written before the lifecycle_type column
# existed. Remove once existing rows are backfilled (see #5067).
return BuildpackLifecycleDataModel::LIFECYCLE_TYPE if buildpack_lifecycle_data
return CNBLifecycleDataModel::LIFECYCLE_TYPE if cnb_lifecycle_data

DockerLifecycleDataModel::LIFECYCLE_TYPE
end

def lifecycle_data
# The lifecycle_data row can be destroyed independently of this
# app; fall back to a frozen empty instance so callers never see
Expand Down
18 changes: 0 additions & 18 deletions app/models/runtime/build_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,6 @@ def validate
validates_includes Lifecycles::TYPES, :lifecycle_type
end

def before_create
# Inherit lifecycle_type from associated app if not explicitly set
self[:lifecycle_type] = app&.lifecycle_type if self[:lifecycle_type].blank?

super
end

def lifecycle_type
return self[:lifecycle_type] if self[:lifecycle_type].present?

# Fallback for records written before the lifecycle_type column
# existed. Remove once existing rows are backfilled (see #5067).
return BuildpackLifecycleDataModel::LIFECYCLE_TYPE if buildpack_lifecycle_data
return CNBLifecycleDataModel::LIFECYCLE_TYPE if cnb_lifecycle_data

DockerLifecycleDataModel::LIFECYCLE_TYPE
end

def buildpack_lifecycle?
lifecycle_type == BuildpackLifecycleDataModel::LIFECYCLE_TYPE
end
Expand Down
18 changes: 0 additions & 18 deletions app/models/runtime/droplet_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@ class DropletModel < Sequel::Model(:droplets)
serializes_via_json :process_types
serializes_via_json :sidecars

def before_create
# Inherit lifecycle_type from associated app if not explicitly set
self[:lifecycle_type] = app&.lifecycle_type if self[:lifecycle_type].blank?

super
end

def around_destroy
yield
rescue Sequel::ForeignKeyConstraintViolation => e
Expand Down Expand Up @@ -175,17 +168,6 @@ def fail_to_stage!(reason='StagingError', details='staging failed')
save_changes(raise_on_save_failure: true)
end

def lifecycle_type
return self[:lifecycle_type] if self[:lifecycle_type].present?

# Fallback for records written before the lifecycle_type column
# existed. Remove once existing rows are backfilled (see #5067).
return BuildpackLifecycleDataModel::LIFECYCLE_TYPE if buildpack_lifecycle_data
return CNBLifecycleDataModel::LIFECYCLE_TYPE if cnb_lifecycle_data

DockerLifecycleDataModel::LIFECYCLE_TYPE
end

def lifecycle_data
# The lifecycle_data row can be destroyed independently of this
# droplet; fall back to a frozen empty instance so callers never
Expand Down
2 changes: 1 addition & 1 deletion app/presenters/v3/process_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ProcessPresenter < BasePresenter
class << self
# :labels and :annotations come from MetadataPresentationHelpers
def associated_resources
super + [{ app: %i[buildpack_lifecycle_data cnb_lifecycle_data] }]
super + [:app]
end
end

Expand Down
38 changes: 38 additions & 0 deletions db/migrations/20260825120000_backfill_lifecycle_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Sequel.migration do
no_transaction

up do
tables = [
{ table: :apps, guid_column: :app_guid },
{ table: :droplets, guid_column: :droplet_guid },
{ table: :builds, guid_column: :build_guid }
]
batch_size = 1000

tables.each do |t|
table = t[:table]
guid_column = t[:guid_column]

loop do
guids = self[table].where(lifecycle_type: nil).limit(batch_size).select_map(:guid)
break if guids.empty?

guids_with_buildpack = self[:buildpack_lifecycle_data].where(guid_column => guids).select_map(guid_column)
guids_with_cnb = self[:cnb_lifecycle_data].where(guid_column => guids).select_map(guid_column) - guids_with_buildpack
guids_docker = guids - guids_with_buildpack - guids_with_cnb

transaction do
self[table].where(guid: guids_with_buildpack, lifecycle_type: nil).update(lifecycle_type: 'buildpack') unless guids_with_buildpack.empty?
self[table].where(guid: guids_with_cnb, lifecycle_type: nil).update(lifecycle_type: 'cnb') unless guids_with_cnb.empty?
self[table].where(guid: guids_docker, lifecycle_type: nil).update(lifecycle_type: 'docker') unless guids_docker.empty?
end

break if guids.size < batch_size
end
end
end

down do
# Intentionally left empty: backfilled values are correct and should not be reverted
end
end
41 changes: 41 additions & 0 deletions db/migrations/20260825120100_make_lifecycle_type_non_nullable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Sequel.migration do
no_transaction

up do
%i[apps droplets builds].each do |table|
if database_type == :postgres
transaction do
run("ALTER TABLE #{table} ALTER COLUMN lifecycle_type SET DEFAULT 'buildpack'")
alter_table(table) do
add_constraint({ name: :"#{table}_lifecycle_type_not_null", not_valid: true, if_not_exists: true }) do
Sequel.lit('lifecycle_type IS NOT NULL')
end
end
end

VCAP::Migration.with_concurrent_timeout(self) do
run("ALTER TABLE #{table} VALIDATE CONSTRAINT #{table}_lifecycle_type_not_null")
end

transaction do
run("ALTER TABLE #{table} ALTER COLUMN lifecycle_type SET NOT NULL")
alter_table(table) { drop_constraint(:"#{table}_lifecycle_type_not_null", if_exists: true) }
end
else
alter_table(table) do
set_column_default :lifecycle_type, 'buildpack'
set_column_not_null :lifecycle_type
end
end
end
end

down do
%i[apps droplets builds].each do |table|
alter_table(table) do
set_column_allow_null :lifecycle_type
set_column_default :lifecycle_type, nil
end
end
end
end
21 changes: 0 additions & 21 deletions lib/tasks/db.rake
Original file line number Diff line number Diff line change
Expand Up @@ -192,27 +192,6 @@ namespace :db do
VCAP::BigintMigration.backfill(logger, db, args.table.to_sym, batch_size: args.batch_size.to_i, iterations: args.iterations.to_i)
end

# One-off backfill - to be removed in a future version.
desc 'Backfill lifecycle_type column on apps, droplets, and builds (pass -1 for batches_per_run to drain)'
task :lifecycle_type_backfill, %i[batch_size batches_per_run] => :environment do |_t, args|
args.with_defaults(batch_size: 1_000, batches_per_run: 10)

RakeConfig.context = :api

batch_size = args.batch_size.to_i
batches_per_run = args.batches_per_run.to_i
BackgroundJobEnvironment.new(RakeConfig.config).setup_environment do
# Ensure we always log to stdout (regardless of `stdout_sink_enabled`).
VCAP::CloudController::StenoConfigurer.new(RakeConfig.config.get(:logging)).configure do |steno_config_hash|
steno_config_hash[:sinks] << Steno::Sink::IO.new($stdout)
end
logger = Steno.logger('cc.db.lifecycle_type_backfill')
logger.info("starting lifecycle_type backfill (batch_size: #{batch_size}, batches_per_run: #{batches_per_run})")
VCAP::CloudController::Jobs::Runtime::LifecycleTypeBackfill.new(batch_size:, batches_per_run:).perform
logger.info('finished lifecycle_type backfill')
end
end

namespace :dev do
desc 'Migrate the database set in spec/support/bootstrap/db_config'
task migrate: :environment do
Expand Down
57 changes: 57 additions & 0 deletions spec/migrations/20260825120000_backfill_lifecycle_type_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'spec_helper'
require 'migrations/helpers/migration_shared_context'

RSpec.describe 'migration to backfill lifecycle_type on apps, droplets, and builds', isolation: :truncation, type: :migration do
include_context 'migration' do
let(:migration_filename) { '20260825120000_backfill_lifecycle_type.rb' }
end

before do
db[:apps].insert(guid: 'buildpack-app-guid')
db[:apps].insert(guid: 'cnb-app-guid')
db[:apps].insert(guid: 'docker-app-guid')
db[:apps].insert(guid: 'already-set-guid')
db[:apps].insert(guid: 'both-app-guid')

db[:buildpack_lifecycle_data].insert(guid: 'bld-app-guid', app_guid: 'buildpack-app-guid')
db[:cnb_lifecycle_data].insert(guid: 'cnb-app-guid-data', app_guid: 'cnb-app-guid')
db[:buildpack_lifecycle_data].insert(guid: 'bld-both-guid', app_guid: 'both-app-guid')
db[:cnb_lifecycle_data].insert(guid: 'cnb-both-guid', app_guid: 'both-app-guid')

db[:apps].where(guid: 'already-set-guid').update(lifecycle_type: 'docker')

db[:droplets].insert(guid: 'buildpack-droplet-guid', state: 'STAGED')
db[:droplets].insert(guid: 'cnb-droplet-guid', state: 'STAGED')
db[:droplets].insert(guid: 'docker-droplet-guid', state: 'STAGED')

db[:buildpack_lifecycle_data].insert(guid: 'bld-droplet-guid', droplet_guid: 'buildpack-droplet-guid')
db[:cnb_lifecycle_data].insert(guid: 'cnb-droplet-guid-data', droplet_guid: 'cnb-droplet-guid')

db[:builds].insert(guid: 'buildpack-build-guid')
db[:builds].insert(guid: 'cnb-build-guid')
db[:builds].insert(guid: 'docker-build-guid')

db[:buildpack_lifecycle_data].insert(guid: 'bld-build-guid', build_guid: 'buildpack-build-guid')
db[:cnb_lifecycle_data].insert(guid: 'cnb-build-guid-data', build_guid: 'cnb-build-guid')
end

it 'backfills lifecycle_type for apps, droplets, and builds, does not overwrite existing values, and prefers buildpack over cnb' do
Sequel::Migrator.run(db, migrations_path, target: current_migration_index, allow_missing_migration_files: true)

expect(db[:apps].first(guid: 'buildpack-app-guid')[:lifecycle_type]).to eq('buildpack')
expect(db[:apps].first(guid: 'cnb-app-guid')[:lifecycle_type]).to eq('cnb')
expect(db[:apps].first(guid: 'docker-app-guid')[:lifecycle_type]).to eq('docker')
expect(db[:apps].first(guid: 'already-set-guid')[:lifecycle_type]).to eq('docker')
expect(db[:apps].first(guid: 'both-app-guid')[:lifecycle_type]).to eq('buildpack')

expect(db[:droplets].first(guid: 'buildpack-droplet-guid')[:lifecycle_type]).to eq('buildpack')
expect(db[:droplets].first(guid: 'cnb-droplet-guid')[:lifecycle_type]).to eq('cnb')
expect(db[:droplets].first(guid: 'docker-droplet-guid')[:lifecycle_type]).to eq('docker')

expect(db[:builds].first(guid: 'buildpack-build-guid')[:lifecycle_type]).to eq('buildpack')
expect(db[:builds].first(guid: 'cnb-build-guid')[:lifecycle_type]).to eq('cnb')
expect(db[:builds].first(guid: 'docker-build-guid')[:lifecycle_type]).to eq('docker')

expect { Sequel::Migrator.run(db, migrations_path, target: current_migration_index, allow_missing_migration_files: true) }.not_to raise_error
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'spec_helper'
require 'migrations/helpers/migration_shared_context'

RSpec.describe 'migration to make lifecycle_type non-nullable on apps, droplets, and builds', isolation: :truncation, type: :migration do
include_context 'migration' do
let(:migration_filename) { '20260825120100_make_lifecycle_type_non_nullable.rb' }
end

before do
db[:apps].insert(guid: 'app-guid', lifecycle_type: 'buildpack')
db[:droplets].insert(guid: 'droplet-guid', state: 'STAGED', lifecycle_type: 'buildpack')
db[:builds].insert(guid: 'build-guid', lifecycle_type: 'buildpack')
end

it 'makes lifecycle_type non-nullable, is reversible, and is idempotent' do
Sequel::Migrator.run(db, migrations_path, target: current_migration_index, allow_missing_migration_files: true)

expect(db.schema(:apps).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be false
expect(db.schema(:droplets).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be false
expect(db.schema(:builds).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be false

expect { Sequel::Migrator.run(db, migrations_path, target: current_migration_index, allow_missing_migration_files: true) }.not_to raise_error

Sequel::Migrator.run(db, migrations_path, target: current_migration_index - 1, allow_missing_migration_files: true)

expect(db.schema(:apps).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be true
expect(db.schema(:droplets).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be true
expect(db.schema(:builds).find { |col| col[0] == :lifecycle_type }[1][:allow_null]).to be true
end
end
5 changes: 1 addition & 4 deletions spec/request/apps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -997,12 +997,9 @@

it 'filters by lifecycle_type' do
create(:app_model, name: 'name1')
docker_app_model = create(:app_model, name: 'name2')
create(:app_model, :docker, name: 'name2')
create(:app_model, name: 'name3')

docker_app_model.buildpack_lifecycle_data = nil
docker_app_model.save

get '/v3/apps?lifecycle_type=buildpack', nil, admin_header

expected_pagination = {
Expand Down
4 changes: 2 additions & 2 deletions spec/request/processes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@
expect(parsed_response['resources'].count).to eq(4)

expect { get_processes.call }.to have_queried_db_times(/SELECT .* FROM .apps. /i, 1) # instead of 4 w/o eager loading
expect { get_processes.call }.to have_queried_db_times(/SELECT .* FROM .buildpack_lifecycle_data. /i, 1) # instead of 4 w/o eager loading
expect { get_processes.call }.to have_queried_db_times(/SELECT .* FROM .cnb_lifecycle_data. /i, 1) # instead of 2 w/o eager loading
expect { get_processes.call }.to have_queried_db_times(/SELECT .* FROM .buildpack_lifecycle_data. /i, 0) # lifecycle_type is now a column, no join needed
expect { get_processes.call }.to have_queried_db_times(/SELECT .* FROM .cnb_lifecycle_data. /i, 0) # lifecycle_type is now a column, no join needed
end
end

Expand Down
Loading
Loading