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
7 changes: 7 additions & 0 deletions app/fetchers/stack_list_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ def filter(message, dataset)
dataset = dataset.where(name: message.names)
end

if message.requested?(:default)
condition = { name: Stack.default.name }.yield_self do |c|
ActiveModel::Type::Boolean.new.cast(message.default) ? c : Sequel.~(c)
end
dataset = dataset.where(condition)
end

if message.requested?(:label_selector)
dataset = LabelSelectorQueryGenerator.add_selector_queries(
label_klass: StackLabelModel,
Expand Down
2 changes: 1 addition & 1 deletion app/messages/purge_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class PurgeMessage < BaseMessage
]

validates_with NoAdditionalParamsValidator
validates :purge, inclusion: { in: %w(true false), message: "only accepts values 'true' or 'false'" }, allow_nil: true
validates :purge, boolean_string: true, allow_nil: true

def self.from_params(params)
super(params, [])
Expand Down
4 changes: 2 additions & 2 deletions app/messages/security_group_list_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class SecurityGroupListMessage < ListMessage
validates :names, array: true, allow_nil: true
validates :running_space_guids, array: true, allow_nil: true
validates :staging_space_guids, array: true, allow_nil: true
validates :globally_enabled_running, inclusion: { in: %w(true false) }, allow_nil: true
validates :globally_enabled_staging, inclusion: { in: %w(true false) }, allow_nil: true
validates :globally_enabled_running, boolean_string: true, allow_nil: true
validates :globally_enabled_staging, boolean_string: true, allow_nil: true

def self.from_params(params)
super(params, %w(names running_space_guids staging_space_guids))
Expand Down
2 changes: 1 addition & 1 deletion app/messages/service_offerings_list_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ServiceOfferingsListMessage < MetadataListMessage
register_allowed_keys(@single_keys + @array_keys)

validates_with NoAdditionalParamsValidator
validates :available, inclusion: { in: %w(true false), message: "only accepts values 'true' or 'false'" }, allow_nil: true
validates :available, boolean_string: true, allow_nil: true

validates :fields, allow_nil: true, fields: {
allowed: {
Expand Down
2 changes: 1 addition & 1 deletion app/messages/service_plans_list_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ServicePlansListMessage < MetadataListMessage

validates_with NoAdditionalParamsValidator
validates_with IncludeParamValidator, valid_values: %w(space.organization service_offering)
validates :available, inclusion: { in: %w(true false), message: "only accepts values 'true' or 'false'" }, allow_nil: true
validates :available, boolean_string: true, allow_nil: true

validates :fields, allow_nil: true, fields: {
allowed: {
Expand Down
5 changes: 2 additions & 3 deletions app/messages/stacks_list_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

module VCAP::CloudController
class StacksListMessage < MetadataListMessage
register_allowed_keys [
:names,
]
register_allowed_keys [:names, :default,]

validates_with NoAdditionalParamsValidator

validates :names, array: true, allow_nil: true
validates :default, boolean_string: true, allow_nil: true

def self.from_params(params)
super(params, %w(names))
Expand Down
12 changes: 12 additions & 0 deletions app/messages/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ def boolean?(value)
end
end

class BooleanStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, message: "must be 'true' or 'false'" unless boolean?(value)
end

private

def boolean?(value)
['true', 'false'].include? value
end
end

class HashValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, message: 'must be an object' unless value.is_a?(Hash)
Expand Down
6 changes: 6 additions & 0 deletions app/models/runtime/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ def before_destroy
super
end

def default?
self == Stack.default
rescue MissingDefaultStackError
false
end

def self.configure(file_path)
@config_file = if file_path
ConfigFile.new(file_path)
Expand Down
1 change: 1 addition & 0 deletions app/presenters/v3/stack_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def to_hash
updated_at: stack.updated_at,
name: stack.name,
description: stack.description,
default: stack.default?,
metadata: {
labels: hashified_labels(stack.labels),
annotations: hashified_annotations(stack.annotations),
Expand Down
3 changes: 3 additions & 0 deletions docs/v3/source/includes/api_resources/_stacks.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"updated_at": "2018-11-09T22:43:28Z",
"name": "my-stack",
"description": "Here is my stack!",
"default": true,
"metadata": {
"labels": <%= metadata.fetch(:labels, {}).to_json(space: ' ', object_nl: ' ')%>,
"annotations": <%= metadata.fetch(:annotations, {}).to_json(space: ' ', object_nl: ' ')%>
Expand Down Expand Up @@ -40,6 +41,7 @@
"updated_at": "2018-11-09T22:43:28Z",
"name": "my-stack-1",
"description": "This is my first stack!",
"default": true,
"metadata": {
"labels": {},
"annotations": {}
Expand All @@ -56,6 +58,7 @@
"updated_at": "2018-11-09T22:43:29Z",
"name": "my-stack-2",
"description": "This is my second stack!",
"default": false,
"metadata": {
"labels": {},
"annotations": {}
Expand Down
4 changes: 4 additions & 0 deletions docs/v3/source/includes/concepts/_lifecycles.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ This is the default lifecycle for Cloud Foundry for VMs. When staging an app wit
compiled using a buildpack, resulting in a droplet. When running an app with this lifecycle, a container running a rootfs
will be created and the droplet will be expanded inside that container to be executed.

If buildpacks are not specified, then Cloud Foundry will automatically detect a
compatible buildpack, based on the files in an app's package. If a stack is not
specified, then the app will default to the operator-configured default stack.

**Note**: This lifecycle is not supported on Cloud Foundry for Kubernetes.

#### Buildpack lifecycle object
Expand Down
1 change: 1 addition & 0 deletions docs/v3/source/includes/resources/stacks/_list.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Retrieve all stacks.
Name | Type | Description
---- | ---- | ------------
**names** | _list of strings_ | Comma-delimited list of app names to filter by
**default** | _boolean_ | If true, only return the default stack
**page** | _integer_ | Page to display; valid values are integers >= 1
**per_page** | _integer_ | Number of results per page; <br>valid values are 1 through 5000
**order_by** | _string_ | Value to sort by. Defaults to ascending; prepend with `-` to sort descending<br>Valid values are `created_at`, `updated_at`, and `name`
Expand Down
1 change: 1 addition & 0 deletions docs/v3/source/includes/resources/stacks/_object.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Name | Type | Description
**updated_at** | _[timestamp](#timestamps)_ | The time with zone when the object was last updated
**name** | _string_ | The name of the stack
**description** | _string_ | The description of the stack
**default** | _boolean_ | Whether the stack is configured to be the default stack for new applications.
**metadata.labels** | [_label object_](#labels) | Labels applied to the stack
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the stack
**links** | [_links object_](#links) | Links to related resources
128 changes: 83 additions & 45 deletions spec/request/stacks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
require 'request_spec_shared_examples'

RSpec.describe 'Stacks Request' do
let(:stack_config_file) { File.join(Paths::FIXTURES, 'config/stacks.yml') }
let(:default_stack_name) { 'default-stack-name' }
let(:org) { VCAP::CloudController::Organization.make(created_at: 3.days.ago) }
let(:space) { VCAP::CloudController::Space.make(organization: org) }

before { VCAP::CloudController::Stack.configure(stack_config_file) }

describe 'GET /v3/stacks' do
before { VCAP::CloudController::Stack.dataset.destroy }
let(:user) { make_user }
Expand All @@ -13,57 +17,44 @@

context 'lists all stacks' do
it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS do
let(:stacks_response_object) do
{
'pagination' => {
'total_results' => 3,
'total_pages' => 2,
'first' => {
'href' => "#{link_prefix}/v3/stacks?page=1&per_page=2"
},
'last' => {
'href' => "#{link_prefix}/v3/stacks?page=2&per_page=2"
},
'next' => {
'href' => "#{link_prefix}/v3/stacks?page=2&per_page=2"
},
'previous' => nil
},
'resources' => [
{
'name' => stack1.name,
'description' => stack1.description,
'guid' => stack1.guid,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
'links' => {
'self' => {
'href' => "#{link_prefix}/v3/stacks/#{stack1.guid}"
}
let(:stacks_response_objects) do
[
{
'name' => stack1.name,
'description' => stack1.description,
'guid' => stack1.guid,
'default' => false,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
'links' => {
'self' => {
'href' => "#{link_prefix}/v3/stacks/#{stack1.guid}"
}
},
{
'name' => stack2.name,
'description' => stack2.description,
'guid' => stack2.guid,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
'links' => {
'self' => {
'href' => "#{link_prefix}/v3/stacks/#{stack2.guid}"
}
}
},
{
'name' => stack2.name,
'description' => stack2.description,
'guid' => stack2.guid,
'default' => true,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
'links' => {
'self' => {
'href' => "#{link_prefix}/v3/stacks/#{stack2.guid}"
}
}
]
}
}
]
end

let(:expected_codes_and_responses) do
Hash.new(code: 200, response_object: stacks_response_object)
Hash.new(code: 200, response_objects: stacks_response_objects)
end
let!(:stack1) { VCAP::CloudController::Stack.make }
let!(:stack2) { VCAP::CloudController::Stack.make }
let!(:stack2) { VCAP::CloudController::Stack.make(name: default_stack_name) }
end
end

Expand All @@ -82,6 +73,7 @@
let(:params) do
{
names: ['foo', 'bar'],
default: true,
page: '2',
per_page: '10',
order_by: 'updated_at',
Expand All @@ -91,11 +83,12 @@
updated_ats: { gt: Time.now.utc.iso8601 },
}
end
let!(:stack) { VCAP::CloudController::Stack.make(name: default_stack_name) }
end

context 'When stacks exist' do
let!(:stack1) { VCAP::CloudController::Stack.make }
let!(:stack2) { VCAP::CloudController::Stack.make }
let!(:stack2) { VCAP::CloudController::Stack.make(name: default_stack_name) }
let!(:stack3) { VCAP::CloudController::Stack.make }

it 'returns a paginated list of stacks' do
Expand All @@ -122,6 +115,7 @@
'name' => stack1.name,
'description' => stack1.description,
'guid' => stack1.guid,
'default' => false,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
Expand All @@ -135,6 +129,7 @@
'name' => stack2.name,
'description' => stack2.description,
'guid' => stack2.guid,
'default' => true,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
Expand Down Expand Up @@ -171,6 +166,7 @@
'name' => stack1.name,
'description' => stack1.description,
'guid' => stack1.guid,
'default' => false,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
Expand All @@ -184,6 +180,7 @@
'name' => stack3.name,
'description' => stack3.description,
'guid' => stack3.guid,
'default' => false,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
Expand All @@ -198,6 +195,43 @@
)
end

it 'returns a list of stacks filtered by whether they are default' do
get '/v3/stacks?default=true', nil, user_header

expect(parsed_response).to be_a_response_like(
{
'pagination' => {
'total_results' => 1,
'total_pages' => 1,
'first' => {
'href' => "#{link_prefix}/v3/stacks?default=true&page=1&per_page=50"
},
'last' => {
'href' => "#{link_prefix}/v3/stacks?default=true&page=1&per_page=50"
},
'next' => nil,
'previous' => nil
},
'resources' => [
{
'name' => stack2.name,
'description' => stack2.description,
'guid' => stack2.guid,
'default' => true,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
'links' => {
'self' => {
'href' => "#{link_prefix}/v3/stacks/#{stack2.guid}"
}
}
}
]
}
)
end

context 'when there are labels' do
let!(:stack1_label) { VCAP::CloudController::StackLabelModel.make(
key_name: 'release',
Expand Down Expand Up @@ -234,6 +268,7 @@
'name' => stack1.name,
'description' => stack1.description,
'guid' => stack1.guid,
'default' => false,
'metadata' => {
'labels' => {
'release' => 'stable'
Expand Down Expand Up @@ -267,6 +302,7 @@
'name' => stack.name,
'description' => stack.description,
'guid' => stack.guid,
'default' => false,
'metadata' => { 'labels' => {}, 'annotations' => {} },
'created_at' => iso8601,
'updated_at' => iso8601,
Expand Down Expand Up @@ -581,6 +617,7 @@
{
'name' => 'the-name',
'description' => 'the-description',
'default' => false,
'metadata' => {
'labels' => {
'potato' => 'yam'
Expand Down Expand Up @@ -642,6 +679,7 @@
{
'name' => stack.name,
'description' => stack.description,
'default' => false,
'metadata' => {
'labels' => {
'potato' => 'yam'
Expand Down
Loading