diff --git a/plugins/kubernetes/app/models/kubernetes/resource.rb b/plugins/kubernetes/app/models/kubernetes/resource.rb index caf9aee109..5db14a0541 100644 --- a/plugins/kubernetes/app/models/kubernetes/resource.rb +++ b/plugins/kubernetes/app/models/kubernetes/resource.rb @@ -237,12 +237,6 @@ def ignore_404 end end - class ConfigMap < Base - end - - class HorizontalPodAutoscaler < Base - end - class Service < Base private @@ -429,9 +423,6 @@ def request_delete end end - class CronJob < Base - end - class Pod < Base def deploy delete @@ -448,7 +439,8 @@ def deploy end def self.build(*args) - "Kubernetes::Resource::#{args.first.fetch(:kind)}".constantize.new(*args) + klass = "Kubernetes::Resource::#{args.first.fetch(:kind)}".safe_constantize || Base + klass.new(*args) end end end diff --git a/plugins/kubernetes/app/models/kubernetes/role_validator.rb b/plugins/kubernetes/app/models/kubernetes/role_validator.rb index 489d1f546e..91671df6e0 100644 --- a/plugins/kubernetes/app/models/kubernetes/role_validator.rb +++ b/plugins/kubernetes/app/models/kubernetes/role_validator.rb @@ -2,16 +2,7 @@ module Kubernetes class RoleValidator VALID_LABEL = /\A[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?\z/ # also used in js ... cannot use /i - IGNORED = ['ConfigMap', 'HorizontalPodAutoscaler', 'PodDisruptionBudget'].freeze - SUPPORTED_KINDS = [ - ['Deployment'], - ['DaemonSet'], - ['Deployment', 'Service'], - ['Service', 'StatefulSet'], - ['Job'], - ['CronJob'], - ['Pod'], - ].freeze + ALLOWED_DUPLICATE_KINDS = ['ConfigMap', 'Service'].freeze def initialize(elements) @elements = elements.compact @@ -22,8 +13,9 @@ def validate return ["No content found"] if @elements.blank? return ["Only hashes supported"] unless @elements.all? { |e| e.is_a?(Hash) } validate_name + validate_name_kinds_are_unique validate_namespace - validate_kinds + validate_single_primary_kind validate_api_version validate_containers validate_container_name @@ -74,16 +66,19 @@ def validate_namespace @errors << "Namespaces need to be unique" if map_attributes([:metadata, :namespace]).uniq.size != 1 end - def validate_kinds + # multiple pods in a single role will make validations misbehave (recommend they all have the same role etc) + def validate_single_primary_kind kinds = map_attributes([:kind]) - IGNORED.each { |k| kinds.delete k } - uniq_element!(kinds, 'Service') # ignore multiple services - kinds.sort_by!(&:to_s) - - return if SUPPORTED_KINDS.include?(kinds) - supported = SUPPORTED_KINDS.map { |c| c.join(' + ') }.join(', ') - @errors << "Unsupported combination of kinds: #{kinds.join(' + ')}" \ - ", supported combinations are: #{supported} and #{IGNORED.join(", ")}" + return if kinds.count { |k| RoleConfigFile::PRIMARY_KINDS.include?(k) } < 2 + @errors << "Only use a maximum of 1 primary kind in a role (#{RoleConfigFile::PRIMARY_KINDS.join(", ")})" + end + + # template_filler.rb sets name for everything except for ConfigMaps and Service so we need to make sure + # users dont use the same kind otherwise they get a duplicate name + def validate_name_kinds_are_unique + kinds = map_attributes([:kind]) - ALLOWED_DUPLICATE_KINDS + return if kinds.uniq.size == kinds.size + @errors << "Only use a maximum of 1 of each kind in a role (except #{ALLOWED_DUPLICATE_KINDS.join(" and ")})" end def validate_api_version @@ -257,14 +252,6 @@ def validate_host_volume_paths # helpers below - # [1,2,3,1,4] -> [2,3,4,1] - def uniq_element!(array, element) - if array.count(element) > 1 - array.delete(element) - array << element - end - end - def find_stateful_set @elements.detect { |t| t[:kind] == "StatefulSet" } end diff --git a/plugins/kubernetes/app/models/kubernetes/template_filler.rb b/plugins/kubernetes/app/models/kubernetes/template_filler.rb index 676d0bdfb0..1e8a6aa943 100644 --- a/plugins/kubernetes/app/models/kubernetes/template_filler.rb +++ b/plugins/kubernetes/app/models/kubernetes/template_filler.rb @@ -24,7 +24,11 @@ def to_hash(verification: false) case kind when 'HorizontalPodAutoscaler' + set_name set_hpa_scale_target_name + when 'ConfigMap' # rubocop:disable Lint/EmptyWhen + # referenced in other resources so we cannot change the name + # NOTE: may cause multiple projects to override each others ConfigMaps if they chose duplicate names when *Kubernetes::RoleConfigFile::SERVICE_KINDS set_service_name prefix_service_cluster_ip @@ -53,6 +57,8 @@ def to_hash(verification: false) set_image_pull_secrets set_resource_blue_green if blue_green_color set_init_containers + else + set_name end template end @@ -121,7 +127,9 @@ def set_service_name end def generate_service_name(config_name) + # when no service name was chosen we use the name from the config, which could lead to duplication return config_name unless name = @doc.kubernetes_role.service_name.presence + if name.include?(Kubernetes::Role::GENERATED) raise( Samson::Hooks::UserError, diff --git a/plugins/kubernetes/test/models/kubernetes/resource_test.rb b/plugins/kubernetes/test/models/kubernetes/resource_test.rb index ddd8d34245..1c9928f54c 100644 --- a/plugins/kubernetes/test/models/kubernetes/resource_test.rb +++ b/plugins/kubernetes/test/models/kubernetes/resource_test.rb @@ -26,8 +26,6 @@ def delete_resource! end let(:origin) { "http://foobar.server" } - let(:kind) { 'Service' } - let(:api_version) { 'v1' } let(:template) do { kind: kind, @@ -46,262 +44,278 @@ def delete_resource! let(:resource) do Kubernetes::Resource.build(template, deploy_group, autoscaled: false, delete_resource: false) end - let(:url) { "#{origin}/api/v1/namespaces/pod1/services/some-project" } let(:base_url) { File.dirname(url) } + let(:url) do + path = (api_version == 'v1' ? "api/#{api_version}" : "apis/#{api_version}") + endpoint = "#{origin}/#{path}" + "#{endpoint}/namespaces/pod1/#{kind.downcase.pluralize}/some-project" + end before { Kubernetes::Resource::Base.any_instance.expects(:sleep).with { raise }.never } - it "does not modify passed in template" do - content = File.read(File.expand_path("../../../app/models/kubernetes/resource.rb", __dir__)) - restore_usages = content.scan('restore_template do').size - template_modified = content.scan(/@template.*(=|dig_set|delete)/).size - template_modified.must_equal restore_usages + 4 - end + describe Kubernetes::Resource::Base do + let(:kind) { 'ConfigMap' } # Type that falls back to Base + let(:api_version) { 'v1' } - describe ".build" do - it "builds based on kind" do - Kubernetes::Resource.build({kind: 'Service'}, deploy_group, autoscaled: false, delete_resource: false). - class.must_equal Kubernetes::Resource::Service + it "does not modify passed in template" do + content = File.read(File.expand_path("../../../app/models/kubernetes/resource.rb", __dir__)) + restore_usages = content.scan('restore_template do').size + template_modified = content.scan(/@template.*(=|dig_set|delete)/).size + template_modified.must_equal restore_usages + 4 end - end - describe "#name" do - it "returns the name" do - resource.name.must_equal 'some-project' + it "falls back to using Base" do + Kubernetes::Resource.build({kind: 'ConfigMap'}, deploy_group, autoscaled: false, delete_resource: false). + class.must_equal Kubernetes::Resource::Base end - end - describe "#namespace" do - it "returns the namespace" do - resource.namespace.must_equal 'pod1' + describe ".build" do + it "builds based on kind" do + Kubernetes::Resource.build({kind: 'Service'}, deploy_group, autoscaled: false, delete_resource: false). + class.must_equal Kubernetes::Resource::Service + end end - end - - describe "#deploy" do - let(:kind) { 'Deployment' } - let(:api_version) { 'extensions/v1beta1' } - let(:url) { "#{origin}/apis/extensions/v1beta1/namespaces/pod1/deployments/some-project" } - it "creates when missing" do - assert_request(:get, url, to_return: [{status: 404}, {body: "{}"}]) do - assert_request(:post, base_url, to_return: {body: "{}"}) do - resource.deploy - end + describe "#name" do + it "returns the name" do + resource.name.must_equal 'some-project' + end + end - # not auto-cached - assert resource.running? - assert resource.running? + describe "#namespace" do + it "returns the namespace" do + resource.namespace.must_equal 'pod1' end end - it "updates existing" do - assert_request(:get, url, to_return: {body: "{}"}, times: 2) do - assert_request(:put, url, to_return: {body: "{}"}, with: ->(x) { x.body.must_include '"replicas":2'; true }) do - resource.deploy + describe "#deploy" do + let(:kind) { 'Deployment' } + let(:api_version) { 'extensions/v1beta1' } + let(:url) { "#{origin}/apis/extensions/v1beta1/namespaces/pod1/deployments/some-project" } + + it "creates when missing" do + assert_request(:get, url, to_return: [{status: 404}, {body: "{}"}]) do + assert_request(:post, base_url, to_return: {body: "{}"}) do + resource.deploy + end + + # not auto-cached + assert resource.running? + assert resource.running? end + end + + it "updates existing" do + assert_request(:get, url, to_return: {body: "{}"}, times: 2) do + args = ->(x) { x.body.must_include '"replicas":2'; true } + assert_request(:put, url, to_return: {body: "{}"}, with: args) do + resource.deploy + end - # not auto-cached - assert resource.running? - assert resource.running? + # not auto-cached + assert resource.running? + assert resource.running? + end end - end - it "keeps replicase when autoscaled, to not revert autoscaler changes" do - assert_request(:get, url, to_return: {body: {spec: {replicas: 5}}.to_json}) do - assert_request(:put, url, to_return: {body: "{}"}, with: ->(x) { x.body.must_include '"replicas":5'; true }) do - autoscaled! - resource.deploy + it "keeps replicase when autoscaled, to not revert autoscaler changes" do + assert_request(:get, url, to_return: {body: {spec: {replicas: 5}}.to_json}) do + args = ->(x) { x.body.must_include '"replicas":5'; true } + assert_request(:put, url, to_return: {body: "{}"}, with: args) do + autoscaled! + resource.deploy + end end end - end - it "shows errors to users when resource was invalid" do - assert_request(:get, url, to_return: {status: 404}) do - error = '{"message":"Foo.extensions \"app\" is invalid:"}' - assert_request(:post, base_url, to_return: {body: error, status: 400}) do - e = assert_raises(Samson::Hooks::UserError) { resource.deploy } - e.message.must_include "Kubernetes error some-project pod1 Pod1: Foo" + it "shows errors to users when resource was invalid" do + assert_request(:get, url, to_return: {status: 404}) do + error = '{"message":"Foo.extensions \"app\" is invalid:"}' + assert_request(:post, base_url, to_return: {body: error, status: 400}) do + e = assert_raises(Samson::Hooks::UserError) { resource.deploy } + e.message.must_include "Kubernetes error some-project pod1 Pod1: Foo" + end end end - end - describe "updating matchLabels" do - before { template[:spec][:selector] = {matchLabels: {foo: "bar"}} } + describe "updating matchLabels" do + before { template[:spec][:selector] = {matchLabels: {foo: "bar"}} } - it "explains why it is a bad idea" do - old = {spec: {selector: {matchLabels: {foo: "baz"}}}} - assert_request(:get, url, to_return: {body: old.to_json}) do - e = assert_raises(Samson::Hooks::UserError) { resource.deploy } - e.message.must_equal( - "Updating spec.selector.matchLabels from {:foo=>\"baz\"} to {:foo=>\"bar\"} " \ - "can only be done can only be done by deleting and redeploying or old pods would not be deleted." - ) + it "explains why it is a bad idea" do + old = {spec: {selector: {matchLabels: {foo: "baz"}}}} + assert_request(:get, url, to_return: {body: old.to_json}) do + e = assert_raises(Samson::Hooks::UserError) { resource.deploy } + e.message.must_equal( + "Updating spec.selector.matchLabels from {:foo=>\"baz\"} to {:foo=>\"bar\"} " \ + "can only be done can only be done by deleting and redeploying or old pods would not be deleted." + ) + end + end + + it "allows removing a label" do + old = {spec: {selector: {matchLabels: {foo: "bar", bar: "baz"}}}} + assert_request(:get, url, to_return: {body: old.to_json}) do + assert_request(:put, url, to_return: {body: "{}"}) do + resource.deploy + end + end + end + + it "allows it for blue-green deploys" do + template[:spec][:selector][:matchLabels][:blue_green] = "blue" + assert_request(:get, url, to_return: {body: "{}"}) do + assert_request(:put, url, to_return: {body: "{}"}) do + resource.deploy + end + end end end - it "allows removing a label" do - old = {spec: {selector: {matchLabels: {foo: "bar", bar: "baz"}}}} - assert_request(:get, url, to_return: {body: old.to_json}) do - assert_request(:put, url, to_return: {body: "{}"}) do + describe "delete_resource" do + before { delete_resource! } + + it "deletes when delete was requested" do + assert_request(:get, url, to_return: {body: "{}"}) do + resource.expects(:delete) resource.deploy end end - end - it "allows it for blue-green deploys" do - template[:spec][:selector][:matchLabels][:blue_green] = "blue" - assert_request(:get, url, to_return: {body: "{}"}) do - assert_request(:put, url, to_return: {body: "{}"}) do + it "does nothing when delete was requested but was not running" do + assert_request(:get, url, to_return: {status: 404}) do resource.deploy end end end end - describe "delete_resource" do - before { delete_resource! } - - it "deletes when delete was requested" do + describe "#running?" do + it "is true when running" do assert_request(:get, url, to_return: {body: "{}"}) do - resource.expects(:delete) - resource.deploy + assert resource.running? end end - it "does nothing when delete was requested but was not running" do + it "is false when not running" do assert_request(:get, url, to_return: {status: 404}) do - resource.deploy + refute resource.running? end end - end - end - describe "#running?" do - it "is true when running" do - assert_request(:get, url, to_return: {body: "{}"}) do - assert resource.running? + it "raises when a non 404 exception is raised" do + assert_request(:get, url, to_return: {status: 500}, times: 4) do + assert_raises(Kubeclient::HttpError) { resource.running? } + end end - end - it "is false when not running" do - assert_request(:get, url, to_return: {status: 404}) do - refute resource.running? + it "raises SSL exception is raised" do + assert_request(:get, url, to_raise: OpenSSL::SSL::SSLError, times: 4) do + assert_raises(OpenSSL::SSL::SSLError) { resource.running? } + end end end - it "raises when a non 404 exception is raised" do - assert_request(:get, url, to_return: {status: 500}, times: 4) do - assert_raises(Kubeclient::HttpError) { resource.running? } + describe "#delete" do + around { |t| assert_request(:delete, url, to_return: {body: "{}"}, &t) } + + it "deletes" do + assert_request(:get, url, to_return: [{body: "{}"}, {status: 404}]) do + resource.delete + end end - end - it "raises SSL exception is raised" do - assert_request(:get, url, to_raise: OpenSSL::SSL::SSLError, times: 4) do - assert_raises(OpenSSL::SSL::SSLError) { resource.running? } + it "fetches after deleting" do + assert_request(:get, url, to_return: [{body: "{}"}, {status: 404}]) do + resource.delete + refute resource.running? + end end - end - end - describe "#delete" do - around { |t| assert_request(:delete, url, to_return: {body: "{}"}, &t) } + it "fails when deletion fails" do + tries = 9 + assert_request(:get, url, to_return: {body: "{}"}, times: tries + 1) do + resource.expects(:sleep).times(tries) - it "deletes" do - assert_request(:get, url, to_return: [{body: "{}"}, {status: 404}]) do - resource.delete + e = assert_raises(RuntimeError) { resource.delete } + e.message.must_equal "Unable to delete resource (some-project pod1 Pod1)" + end end end - it "fetches after deleting" do - assert_request(:get, url, to_return: [{body: "{}"}, {status: 404}]) do - resource.delete - refute resource.running? + describe "#uid" do + it "returns the uid of the created resource" do + assert_request(:get, url, to_return: {body: {metadata: {uid: 123}}.to_json}) do + resource.uid.must_equal 123 + end end - end - it "fails when deletion fails" do - tries = 9 - assert_request(:get, url, to_return: {body: "{}"}, times: tries + 1) do - resource.expects(:sleep).times(tries) - - e = assert_raises(RuntimeError) { resource.delete } - e.message.must_equal "Unable to delete resource (some-project pod1 Pod1)" + it "returns nil when resource is missing" do + assert_request(:get, url, to_return: {status: 404}) do + resource.uid.must_be_nil + end end end - end - describe "#uid" do - it "returns the uid of the created resource" do - assert_request(:get, url, to_return: {body: {metadata: {uid: 123}}.to_json}) do - resource.uid.must_equal 123 + describe "#prerequisite?" do + it "is not a prerequisite by default" do + refute resource.prerequisite? end - end - it "returns nil when resource is missing" do - assert_request(:get, url, to_return: {status: 404}) do - resource.uid.must_be_nil + it "is a prerequisite when labeled" do + template[:metadata][:annotations] = {"samson/prerequisite": true} + assert resource.prerequisite? end end - end - - describe "#prerequisite?" do - it "is not a prerequisite by default" do - refute resource.prerequisite? - end - it "is a prerequisite when labeled" do - template[:metadata][:annotations] = {"samson/prerequisite": true} - assert resource.prerequisite? - end - end - - describe "#primary?" do - it "is primary when it is a primary resource" do - template[:kind] = "Deployment" - assert resource.primary? - end + describe "#primary?" do + it "is primary when it is a primary resource" do + template[:kind] = "Deployment" + assert resource.primary? + end - it "is not primary when it is a secondary resource" do - template[:kind] = "Service" - refute resource.primary? + it "is not primary when it is a secondary resource" do + template[:kind] = "Service" + refute resource.primary? + end end - end - describe "#desired_pod_count" do - it "reads the value from config" do - template[:spec] = {replicas: 3} - resource.desired_pod_count.must_equal 3 - end + describe "#desired_pod_count" do + it "reads the value from config" do + template[:spec] = {replicas: 3} + resource.desired_pod_count.must_equal 3 + end - it "expects a constant number of pods when using autoscaling" do - assert_request(:get, url, to_return: {body: {spec: {replicas: 4}}.to_json}) do - autoscaled! - resource.desired_pod_count.must_equal 4 + it "expects a constant number of pods when using autoscaling" do + assert_request(:get, url, to_return: {body: {spec: {replicas: 4}}.to_json}) do + autoscaled! + resource.desired_pod_count.must_equal 4 + end end - end - it "uses template amount when creating with autoscaling" do - assert_request(:get, url, to_return: {status: 404}) do - autoscaled! - resource.desired_pod_count.must_equal 2 + it "uses template amount when creating with autoscaling" do + assert_request(:get, url, to_return: {status: 404}) do + autoscaled! + resource.desired_pod_count.must_equal 2 + end end - end - it "is 1 when not set" do - template[:spec].delete :replicas - resource.desired_pod_count.must_equal 1 - end + it "is 1 when not set" do + template[:spec].delete :replicas + resource.desired_pod_count.must_equal 1 + end - it "is 0 when pod is deleted" do - delete_resource! - resource.desired_pod_count.must_equal 0 + it "is 0 when pod is deleted" do + delete_resource! + resource.desired_pod_count.must_equal 0 + end end - end - describe "#loop_sleep" do - it "sleeps when not in test" do - Rails.env.expects(:test?) - resource.expects(:sleep) - resource.send(:loop_sleep) + describe "#loop_sleep" do + it "sleeps when not in test" do + Rails.env.expects(:test?) + resource.expects(:sleep) + resource.send(:loop_sleep) + end end end @@ -327,7 +341,6 @@ def daemonset_stub(scheduled, misscheduled) let(:kind) { 'DaemonSet' } let(:api_version) { 'extensions/v1beta1' } - let(:url) { "#{origin}/apis/extensions/v1beta1/namespaces/pod1/daemonsets/some-project" } describe "#client" do it "uses the extension client because it is in beta" do @@ -444,7 +457,6 @@ def deployment_stub(replica_count) let(:kind) { 'Deployment' } let(:api_version) { 'extensions/v1beta1' } - let(:url) { "#{origin}/apis/extensions/v1beta1/namespaces/pod1/deployments/some-project" } describe "#client" do it "uses the extension client because it is in beta" do @@ -515,7 +527,6 @@ def deployment_stub(replica_count) let(:kind) { 'StatefulSet' } let(:api_version) { 'apps/v1beta1' } - let(:url) { "#{origin}/apis/apps/v1beta1/namespaces/pod1/statefulsets/some-project" } describe "#client" do it "uses the apps client because it is in beta" do @@ -651,7 +662,6 @@ def deployment_stub(replica_count) describe Kubernetes::Resource::Job do let(:kind) { 'Job' } let(:api_version) { 'batch/v1' } - let(:url) { "#{origin}/apis/batch/v1/namespaces/pod1/jobs/some-project" } describe "#client" do it "uses the extension client because it is in beta" do @@ -697,35 +707,12 @@ def deployment_stub(replica_count) end end - describe Kubernetes::Resource::CronJob do - let(:kind) { 'CronJob' } - let(:api_version) { 'batch/v1beta1' } - let(:url) { "#{origin}/apis/batch/v1beta1/namespaces/pod1/cronjobs/some-project" } - - describe "#client" do - it "uses the batch client" do - resource.send(:client).must_equal deploy_group.kubernetes_cluster.client('batch/v1beta1') - end - end - - describe "#deploy" do - let(:client) { resource.send(:client) } - before { template[:spec] = {template: {spec: {}}} } - - it "creates when missing" do - assert_request(:get, url, to_return: {status: 404}) do - assert_request(:post, base_url, to_return: {body: "{}"}) do - resource.deploy - end - end - end - end - end - describe Kubernetes::Resource::Service do + let(:api_version) { 'v1' } + let(:kind) { 'Service' } + describe "#deploy" do let(:old) { {metadata: {resourceVersion: "A", foo: "B"}, spec: {clusterIP: "C"}} } - let(:api_version) { 'v1' } let(:expected_body) { template.deep_merge(metadata: {resourceVersion: "A"}, spec: {clusterIP: "C"}) } it "creates when missing" do @@ -800,23 +787,6 @@ def deployment_stub(replica_count) end end - describe Kubernetes::Resource::ConfigMap do - let(:kind) { 'ConfigMap' } - let(:api_version) { 'v1' } - let(:url) { "#{origin}/api/v1/namespaces/pod1/configmaps/some-project" } - - # a simple test to make sure basics work - describe "#deploy" do - it "creates when missing" do - assert_request(:get, url, to_return: {status: 404}) do - assert_request(:post, base_url, to_return: {body: "{}"}) do - resource.deploy - end - end - end - end - end - describe Kubernetes::Resource::Pod do let(:kind) { 'Pod' } let(:api_version) { 'v1' } @@ -872,22 +842,6 @@ def deployment_stub(replica_count) end end - describe Kubernetes::Resource::HorizontalPodAutoscaler do - describe "#deploy" do - let(:kind) { 'HorizontalPodAutoscaler' } - let(:api_version) { 'autoscaling/v1' } - let(:url) { "#{origin}/apis/autoscaling/v1/namespaces/pod1/horizontalpodautoscalers/some-project" } - - it "updates" do - assert_request(:get, url, to_return: {body: '{}'}) do - assert_request(:put, url, to_return: {body: '{}'}) do - resource.deploy - end - end - end - end - end - describe Kubernetes::Resource::PodDisruptionBudget do def assert_create_and_delete_requests(**args, &block) assert_request(:get, url, to_return: [{body: '{}'}, {status: 404}]) do @@ -899,7 +853,6 @@ def assert_create_and_delete_requests(**args, &block) let(:kind) { 'PodDisruptionBudget' } let(:api_version) { 'policy/v1beta1' } - let(:url) { "#{origin}/apis/policy/v1beta1/namespaces/pod1/poddisruptionbudgets/some-project" } let(:create_url) { File.dirname(url) } describe "#deploy" do diff --git a/plugins/kubernetes/test/models/kubernetes/role_config_file_test.rb b/plugins/kubernetes/test/models/kubernetes/role_config_file_test.rb index 5cae2e69a2..8d98e964fb 100644 --- a/plugins/kubernetes/test/models/kubernetes/role_config_file_test.rb +++ b/plugins/kubernetes/test/models/kubernetes/role_config_file_test.rb @@ -35,7 +35,7 @@ end it "blows up on unsupported" do - assert content.sub!('Deployment', 'SomethingElse') + assert content.sub!('Service', 'Deployment') assert_raises(Samson::Hooks::UserError) { config_file } end diff --git a/plugins/kubernetes/test/models/kubernetes/role_validator_test.rb b/plugins/kubernetes/test/models/kubernetes/role_validator_test.rb index f76b7f7c84..c0ccdfaaa5 100644 --- a/plugins/kubernetes/test/models/kubernetes/role_validator_test.rb +++ b/plugins/kubernetes/test/models/kubernetes/role_validator_test.rb @@ -75,9 +75,16 @@ Kubernetes::RoleValidator.new(["bad", {kind: "Good"}]).validate.must_equal ["Only hashes supported"] end - it "reports invalid types" do + it "allows invalid types" do role.first[:kind] = "Ohno" - errors.to_s.must_include "Unsupported combination of kinds: Ohno + Service, supported" + refute errors + end + + it "does not allow multiple deployables" do + role[1][:kind] = "Pod" + errors.must_include( + "Only use a maximum of 1 primary kind in a role (Deployment, DaemonSet, StatefulSet, Job, CronJob, Pod)" + ) end describe 'StatefulSet' do @@ -159,6 +166,12 @@ errors.must_be_nil end + it "allows duplicate ConfigMaps" do + role[0][:kind] = "ConfigMap" + role << role[0].dup + errors.must_be_nil + end + it "reports numeric cpu" do role.first[:spec][:template][:spec][:containers].first[:resources] = {limits: {cpu: 1}} errors.must_include "Numeric cpu resources are not supported" @@ -184,12 +197,6 @@ errors.must_equal ["Container name foo_bar did not match \\A[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?\\z"] end - # release_doc does not support that and it would lead to chaos - it 'reports job mixed with deploy' do - role.concat job_role - errors.to_s.must_include "Unsupported combination of kinds: Deployment + Job + Service, supported" - end - it "reports non-string labels" do role.first[:metadata][:labels][:role_id] = 1 errors.must_include "Deployment metadata.labels.role_id must be a String" @@ -239,6 +246,12 @@ errors.must_include "Needs apiVersion specified" end + it "fails when there are duplicate kinds" do + role[0][:kind] = "foo" + role[1][:kind] = "foo" + errors.must_include "Only use a maximum of 1 of each kind in a role (except ConfigMap and Service)" + end + describe "#validate_team_labels" do with_env KUBERNETES_ENFORCE_TEAMS: "true" diff --git a/plugins/kubernetes/test/models/kubernetes/template_filler_test.rb b/plugins/kubernetes/test/models/kubernetes/template_filler_test.rb index 30c24fc022..bdfd329108 100644 --- a/plugins/kubernetes/test/models/kubernetes/template_filler_test.rb +++ b/plugins/kubernetes/test/models/kubernetes/template_filler_test.rb @@ -138,6 +138,11 @@ def add_init_container(container) must_equal doc.kubernetes_release.deploy.url end + it "sets name for unknown kinds" do + raw_template[:kind] = "foobar" + template.to_hash[:metadata][:name].must_equal "test-app-server" + end + describe "unqiue deployments" do let(:labels) do hash = template.to_hash @@ -752,6 +757,10 @@ def secret_annotations(hash) it "matches the resource name" do template.to_hash.dig_fetch(:spec, :scaleTargetRef, :name).must_equal("test-app-server") end + + it "sets the name" do + template.to_hash.dig_fetch(:metadata, :name).must_equal("test-app-server") + end end describe "blue-green" do