Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit 66ce2d8

Browse files
authored
Merge pull request cloudfoundry#1917 from cloudfoundry/introduce-kpack-droplets-175081086
Introduce "kpack" Droplets and Support Custom Start Commands [finishes #175081086](https://www.pivotaltracker.com/story/show/175081086)
2 parents 15591ef + f05a752 commit 66ce2d8

14 files changed

Lines changed: 392 additions & 75 deletions

File tree

app/actions/droplet_create.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ def create_buildpack_droplet(build)
6363
droplet
6464
end
6565

66+
def create_kpack_droplet(build)
67+
droplet = droplet_from_build(build)
68+
69+
DropletModel.db.transaction do
70+
droplet.save
71+
droplet.kpack_lifecycle_data = build.kpack_lifecycle_data
72+
end
73+
74+
droplet.reload
75+
Steno.logger('build_completed').info("droplet created: #{droplet.guid}")
76+
record_audit_event(droplet, build.package, user_audit_info_from_build(build))
77+
droplet
78+
end
79+
6680
private
6781

6882
def droplet_from_build(build)

app/actions/droplet_update.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def update(droplet, message)
99

1010
if message.requested?(:image)
1111
raise InvalidDroplet.new('Droplet image can only be updated on staged droplets') unless droplet.staged?
12-
raise InvalidDroplet.new('Images can only be updated for docker droplets') unless droplet.docker?
12+
raise InvalidDroplet.new('Images can only be updated for docker droplets') unless droplet.has_docker_image?
1313

1414
droplet.docker_receipt_image = message.image
1515
end

app/models/runtime/droplet_model.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ class DropletModel < Sequel::Model(:droplets)
3131
class: 'VCAP::CloudController::BuildpackLifecycleDataModel',
3232
key: :droplet_guid,
3333
primary_key: :guid
34+
one_to_one :kpack_lifecycle_data,
35+
class: 'VCAP::CloudController::KpackLifecycleDataModel',
36+
key: :droplet_guid,
37+
primary_key: :guid
3438
one_to_many :labels, class: 'VCAP::CloudController::DropletLabelModel', key: :resource_guid, primary_key: :guid
3539
one_to_many :annotations, class: 'VCAP::CloudController::DropletAnnotationModel', key: :resource_guid, primary_key: :guid
3640

3741
add_association_dependencies buildpack_lifecycle_data: :destroy
42+
add_association_dependencies kpack_lifecycle_data: :destroy
3843
add_association_dependencies labels: :destroy
3944
add_association_dependencies annotations: :destroy
4045

@@ -84,6 +89,14 @@ def docker?
8489
lifecycle_type == DockerLifecycleDataModel::LIFECYCLE_TYPE
8590
end
8691

92+
def kpack?
93+
lifecycle_type == KpackLifecycleDataModel::LIFECYCLE_TYPE
94+
end
95+
96+
def has_docker_image?
97+
docker? || kpack?
98+
end
99+
87100
def docker_ports
88101
exposed_ports = []
89102
if self.execution_metadata.present?
@@ -137,14 +150,13 @@ def fail_to_stage!(reason='StagingError', details='staging failed')
137150

138151
def lifecycle_type
139152
return BuildpackLifecycleDataModel::LIFECYCLE_TYPE if buildpack_lifecycle_data
153+
return KpackLifecycleDataModel::LIFECYCLE_TYPE if kpack_lifecycle_data
140154

141155
DockerLifecycleDataModel::LIFECYCLE_TYPE
142156
end
143157

144158
def lifecycle_data
145-
return buildpack_lifecycle_data if buildpack_lifecycle_data
146-
147-
DockerLifecycleDataModel.new
159+
buildpack_lifecycle_data || kpack_lifecycle_data || DockerLifecycleDataModel.new
148160
end
149161

150162
def in_final_state?

app/presenters/v3/droplet_presenter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def build_links
4747
}.tap do |links|
4848
links[:package] = { href: url_builder.build_url(path: "/v3/packages/#{droplet.package_guid}") } if droplet.package_guid.present?
4949
links[:upload] = { href: url_builder.build_url(path: "/v3/droplets/#{droplet.guid}/upload"), method: 'POST' } if droplet.state == DropletModel::AWAITING_UPLOAD_STATE
50-
if droplet.state == DropletModel::STAGED_STATE && !droplet.docker?
50+
if droplet.state == DropletModel::STAGED_STATE && droplet.buildpack?
5151
links[:download] = { href: url_builder.build_url(path: "/v3/droplets/#{droplet.guid}/download") }
5252
end
5353
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Sequel.migration do
2+
up do
3+
alter_table :kpack_lifecycle_data do
4+
add_index :droplet_guid, name: :kpack_lifecycle_droplet_guid_index
5+
end
6+
end
7+
8+
down do
9+
alter_table :kpack_lifecycle_data do
10+
drop_index :droplet_guid, name: :kpack_lifecycle_droplet_guid_index
11+
end
12+
end
13+
end

lib/cloud_controller/kpack/stager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def blobstore_url_generator
180180

181181
def create_droplet_and_get_guid(staging_details)
182182
build = VCAP::CloudController::BuildModel.find(guid: staging_details.staging_guid)
183-
droplet = VCAP::CloudController::DropletCreate.new.create_docker_droplet(build)
183+
droplet = VCAP::CloudController::DropletCreate.new.create_kpack_droplet(build)
184184
droplet.guid
185185
end
186186
end

lib/cloud_controller/opi/apps_client.rb

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,35 @@ def to_hash
104104
end
105105
end
106106

107+
class KpackLifecycle
108+
CNB_LAUNCHER_PATH = '/cnb/lifecycle/launcher'.freeze
109+
110+
def initialize(process)
111+
@process = process
112+
end
113+
114+
def to_hash
115+
command = if @process.started_command.presence
116+
['/bin/sh', '-c', "#{CNB_LAUNCHER_PATH} #{@process.started_command}"]
117+
else
118+
[]
119+
end
120+
{
121+
docker_lifecycle: {
122+
command: command,
123+
image: @process.desired_droplet.docker_receipt_image,
124+
}
125+
}
126+
end
127+
end
128+
107129
def lifecycle_for(process)
108-
if process.app.droplet.lifecycle_type == VCAP::CloudController::Lifecycles::DOCKER
130+
case process.app.droplet.lifecycle_type
131+
when VCAP::CloudController::Lifecycles::DOCKER
109132
DockerLifecycle.new(process)
110-
elsif process.app.droplet.lifecycle_type == VCAP::CloudController::Lifecycles::BUILDPACK
133+
when VCAP::CloudController::Lifecycles::KPACK
134+
KpackLifecycle.new(process)
135+
when VCAP::CloudController::Lifecycles::BUILDPACK
111136
BuildpackLifecycle.new(process)
112137
else
113138
raise("lifecycle type `#{process.app.lifecycle_type}` is invalid")

spec/support/fakes/blueprints.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ module VCAP::CloudController
121121
guid { Sham.guid }
122122
droplet_hash { nil }
123123
sha256_checksum { nil }
124-
state { VCAP::CloudController::DropletModel::STAGING_STATE }
124+
state { VCAP::CloudController::DropletModel::STAGED_STATE }
125125
app { AppModel.make(droplet_guid: guid) }
126126
buildpack_lifecycle_data { nil.tap { |_| object.save } }
127+
kpack_lifecycle_data { nil.tap { |_| object.save } }
127128
end
128129

129130
DropletModel.blueprint(:kpack) do
@@ -134,6 +135,7 @@ module VCAP::CloudController
134135
app { AppModel.make(:kpack, droplet_guid: guid) }
135136
state { VCAP::CloudController::DropletModel::STAGED_STATE }
136137
buildpack_lifecycle_data { nil.tap { |_| object.save } }
138+
kpack_lifecycle_data { KpackLifecycleDataModel.make(droplet: object.save) }
137139
end
138140

139141
DeploymentModel.blueprint do

spec/unit/actions/droplet_create_spec.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,72 @@ module VCAP::CloudController
220220
end
221221
end
222222
end
223+
224+
describe '#create_kpack_droplet' do
225+
let!(:kpack_lifecycle_data) { KpackLifecycleDataModel.make(build: build) }
226+
227+
it 'sets it on the droplet' do
228+
expect {
229+
droplet_create.create_kpack_droplet(build)
230+
}.to change { [DropletModel.count, Event.count] }.by([1, 1])
231+
232+
droplet = DropletModel.last
233+
234+
expect(droplet.state).to eq(DropletModel::STAGING_STATE)
235+
expect(droplet.app).to eq(app)
236+
expect(droplet.package).to eq(package)
237+
expect(droplet.build).to eq(build)
238+
expect(droplet.kpack?).to be true
239+
240+
kpack_lifecycle_data.reload
241+
expect(kpack_lifecycle_data.droplet).to eq(droplet)
242+
243+
event = Event.last
244+
expect(event.type).to eq('audit.app.droplet.create')
245+
expect(event.actor).to eq('schneider')
246+
expect(event.actor_type).to eq('user')
247+
expect(event.actor_name).to eq('bob@loblaw.com')
248+
expect(event.actor_username).to eq('bobert')
249+
expect(event.actee).to eq(app.guid)
250+
expect(event.actee_type).to eq('app')
251+
expect(event.actee_name).to eq(app.name)
252+
expect(event.timestamp).to be
253+
expect(event.space_guid).to eq(app.space_guid)
254+
expect(event.organization_guid).to eq(app.space.organization.guid)
255+
expect(event.metadata).to eq({
256+
'droplet_guid' => droplet.guid,
257+
'package_guid' => package.guid,
258+
})
259+
end
260+
261+
context 'when the build does not contain created_by fields' do
262+
let(:build) do
263+
BuildModel.make(
264+
app: app,
265+
package: package,
266+
)
267+
end
268+
269+
it 'sets the actor to UNKNOWN' do
270+
expect {
271+
droplet_create.create_kpack_droplet(build)
272+
}.to change { [DropletModel.count, Event.count] }.by([1, 1])
273+
274+
droplet = DropletModel.last
275+
expect(droplet.build).to eq(build)
276+
277+
event = Event.last
278+
expect(event.type).to eq('audit.app.droplet.create')
279+
expect(event.actor_type).to eq('user')
280+
expect(event.actor).to eq('UNKNOWN')
281+
expect(event.actor_name).to eq('')
282+
expect(event.actor_username).to eq('')
283+
expect(event.metadata).to eq({
284+
'droplet_guid' => droplet.guid,
285+
'package_guid' => package.guid,
286+
})
287+
end
288+
end
289+
end
223290
end
224291
end

spec/unit/actions/droplet_update_spec.rb

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,28 @@ module VCAP::CloudController
5454
expect(Hash[updated_droplet.annotations.map { |a| [a.key, a.value] }]).
5555
to eq({ 'University' => 'Toronto', 'reason' => 'add some more annotations' })
5656
end
57-
context 'trying to update a buildpack droplet image' do
57+
end
58+
59+
context 'image updates' do
60+
context 'when the droplet is not STAGED' do
61+
let!(:droplet) { DropletModel.make(:kpack, state: VCAP::CloudController::DropletModel::STAGING_STATE) }
62+
63+
let(:message) do
64+
VCAP::CloudController::DropletUpdateMessage.new({
65+
image: 'new-image-reference'
66+
})
67+
end
68+
69+
it 'returns an error saying that a droplet update cannot occur during staging' do
70+
expect(message).to be_valid
71+
expect { droplet_update.update(droplet, message)
72+
}.to raise_error(DropletUpdate::InvalidDroplet, 'Droplet image can only be updated on staged droplets')
73+
end
74+
end
75+
76+
context 'when the droplet type is buildpack' do
77+
let!(:droplet) { DropletModel.make(:buildpack) }
78+
5879
let(:message) do
5980
VCAP::CloudController::DropletUpdateMessage.new({
6081
image: 'some-image-reference',
@@ -69,42 +90,59 @@ module VCAP::CloudController
6990
},
7091
})
7192
end
93+
7294
it 'returns an error saying that a buildpack droplet image cannot be updated' do
7395
expect(message).to be_valid
7496
expect { droplet_update.update(droplet, message)
7597
}.to raise_error(DropletUpdate::InvalidDroplet, 'Images can only be updated for docker droplets')
7698
end
7799
end
78-
end
79-
context 'docker droplet update' do
80-
let!(:docker_droplet) do
81-
VCAP::CloudController::DropletModel.make(:kpack)
82-
end
83100

84-
let(:message) do
85-
VCAP::CloudController::DropletUpdateMessage.new({
86-
image: 'new-image-reference'
87-
})
88-
end
89-
context 'the image of a staged docker droplet is requested to be updated' do
90-
before do
91-
docker_droplet.update(docker_receipt_image: 'some-image-reference')
101+
context 'when the droplet type is docker' do
102+
let!(:docker_droplet) do
103+
VCAP::CloudController::DropletModel.make(:docker)
92104
end
93-
it 'updates the droplet record with new image reference' do
94-
expect(message).to be_valid
95-
updated_droplet = droplet_update.update(docker_droplet, message)
96-
expect(updated_droplet.docker_receipt_image).to eq 'new-image-reference'
105+
106+
let(:message) do
107+
VCAP::CloudController::DropletUpdateMessage.new({
108+
image: 'new-image-reference'
109+
})
110+
end
111+
112+
context 'the image of a staged docker droplet is requested to be updated' do
113+
before do
114+
docker_droplet.update(docker_receipt_image: 'some-image-reference')
115+
end
116+
117+
it 'updates the droplet record with new image reference' do
118+
expect(message).to be_valid
119+
updated_droplet = droplet_update.update(docker_droplet, message)
120+
expect(updated_droplet.docker_receipt_image).to eq 'new-image-reference'
121+
end
97122
end
98123
end
99-
context 'the image of a staging docker droplet is requested to be updated' do
100-
before do
101-
docker_droplet.update(state: VCAP::CloudController::DropletModel::STAGING_STATE)
124+
125+
context 'when the droplet type is kpack' do
126+
let!(:docker_droplet) do
127+
VCAP::CloudController::DropletModel.make(:kpack)
102128
end
103129

104-
it 'returns an error saying that a droplet update cannot occur during staging' do
105-
expect(message).to be_valid
106-
expect { droplet_update.update(docker_droplet, message)
107-
}.to raise_error(DropletUpdate::InvalidDroplet, 'Droplet image can only be updated on staged droplets')
130+
let(:message) do
131+
VCAP::CloudController::DropletUpdateMessage.new({
132+
image: 'new-image-reference'
133+
})
134+
end
135+
136+
context 'the image of a staged kpack droplet is requested to be updated' do
137+
before do
138+
docker_droplet.update(docker_receipt_image: 'some-image-reference')
139+
end
140+
141+
it 'updates the droplet record with new image reference' do
142+
expect(message).to be_valid
143+
updated_droplet = droplet_update.update(docker_droplet, message)
144+
expect(updated_droplet.docker_receipt_image).to eq 'new-image-reference'
145+
end
108146
end
109147
end
110148
end

0 commit comments

Comments
 (0)