mirror of
https://github.com/czhu12/canine.git
synced 2026-01-06 03:30:16 -06:00
added deployment split
This commit is contained in:
@@ -1,121 +1,15 @@
|
||||
class Projects::DeploymentJob < ApplicationJob
|
||||
DEPLOYABLE_RESOURCES = %w[ConfigMap Secrets Deployment CronJob Service Ingress Pv Pvc]
|
||||
def perform(deployment, user)
|
||||
@logger = deployment
|
||||
def perform(deployment, user = nil)
|
||||
project = deployment.project
|
||||
connection = K8::Connection.new(project, user, allow_anonymous: true)
|
||||
@kubectl = K8::Kubectl.new(connection)
|
||||
deployment_method = project.deployment_configuration&.deployment_method || "legacy"
|
||||
|
||||
chart_builder = K8::Helm::ChartBuilder.new(
|
||||
project.name,
|
||||
deployment,
|
||||
).connect(connection)
|
||||
chart_builder.register_before_install do |yaml_content|
|
||||
deployment.add_manifest(yaml_content)
|
||||
service_class = case deployment_method
|
||||
when "helm"
|
||||
Deployments::HelmDeploymentService
|
||||
else
|
||||
Deployments::LegacyDeploymentService
|
||||
end
|
||||
|
||||
apply_namespace(project) if project.managed_namespace?
|
||||
upload_registry_secrets(@kubectl, deployment)
|
||||
chart_builder << apply_config_map(project)
|
||||
chart_builder << apply_secrets(project)
|
||||
|
||||
deploy_volumes(project, chart_builder)
|
||||
predeploy(project, connection)
|
||||
deploy_services(project, chart_builder)
|
||||
chart_builder.install_chart(project.name)
|
||||
kill_one_off_containers(project)
|
||||
postdeploy(project, connection)
|
||||
|
||||
mark_services_healthy(project)
|
||||
deployment.completed!
|
||||
project.deployed!
|
||||
#rescue StandardError => e
|
||||
# @logger.error("Deployment failed: #{e.message}")
|
||||
# puts e.full_message
|
||||
# deployment.failed!
|
||||
end
|
||||
|
||||
def apply_namespace(project)
|
||||
namespace_yaml = K8::Namespace.new(project).to_yaml
|
||||
@kubectl.apply_yaml(namespace_yaml)
|
||||
end
|
||||
|
||||
def upload_registry_secrets(kubectl, deployment)
|
||||
project = deployment.project
|
||||
@logger.info("Creating registry secret for #{project.container_image_reference}", color: :yellow)
|
||||
provider = project.build_provider
|
||||
result = Providers::GenerateConfigJson.execute(
|
||||
provider:,
|
||||
)
|
||||
raise StandardError, result.message if result.failure?
|
||||
|
||||
secret_yaml = K8::Secrets::RegistrySecret.new(project, result.docker_config_json).to_yaml
|
||||
kubectl.apply_yaml(secret_yaml)
|
||||
end
|
||||
|
||||
DEPLOYABLE_RESOURCES.each do |resource_type|
|
||||
define_method(:"apply_#{resource_type.underscore}") do |service|
|
||||
K8::Stateless.const_get(resource_type).new(service)
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_volumes(project, chart_builder)
|
||||
project.volumes.each do |volume|
|
||||
begin
|
||||
chart_builder << apply_pv(volume)
|
||||
chart_builder << apply_pvc(volume)
|
||||
volume.deployed!
|
||||
rescue StandardError => e
|
||||
volume.failed!
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_services(project, chart_builder)
|
||||
project.services.each do |service|
|
||||
deploy_service(service, chart_builder)
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_service(service, chart_builder)
|
||||
if service.background_service?
|
||||
chart_builder << apply_deployment(service)
|
||||
elsif service.cron_job?
|
||||
chart_builder << apply_cron_job(service)
|
||||
elsif service.web_service?
|
||||
chart_builder << apply_deployment(service)
|
||||
chart_builder << apply_service(service)
|
||||
if service.domains.any? && service.allow_public_networking?
|
||||
chart_builder << apply_ingress(service)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_services_healthy(project)
|
||||
project.services.each(&:healthy!)
|
||||
end
|
||||
|
||||
def kill_one_off_containers(project)
|
||||
@kubectl.call("-n #{project.namespace} delete pods -l oneoff=true")
|
||||
end
|
||||
|
||||
def predeploy(project, connection)
|
||||
return unless project.predeploy_command.present?
|
||||
|
||||
run_command(project.predeploy_command, project, "predeploy", connection)
|
||||
end
|
||||
|
||||
def postdeploy(project, connection)
|
||||
return unless project.postdeploy_command.present?
|
||||
|
||||
run_command(project.postdeploy_command, project, "postdeploy", connection)
|
||||
end
|
||||
|
||||
def run_command(command, project, type, connection)
|
||||
command_job = K8::Stateless::Command.new(project, type, command).connect(connection)
|
||||
command_job.delete_if_exists!
|
||||
@kubectl.apply_yaml(command_job.to_yaml)
|
||||
command_job.wait_for_completion
|
||||
service_class.new(deployment, user).deploy
|
||||
end
|
||||
end
|
||||
|
||||
29
app/models/deployment_configuration.rb
Normal file
29
app/models/deployment_configuration.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: deployment_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# deployment_method :integer default("legacy"), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# project_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_deployment_configurations_on_project_id (project_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (project_id => projects.id)
|
||||
#
|
||||
class DeploymentConfiguration < ApplicationRecord
|
||||
belongs_to :project
|
||||
|
||||
validates :project, presence: true
|
||||
validates :deployment_method, presence: true
|
||||
|
||||
enum :deployment_method, {
|
||||
legacy: 0,
|
||||
helm: 1
|
||||
}
|
||||
end
|
||||
@@ -51,6 +51,7 @@ class Project < ApplicationRecord
|
||||
|
||||
has_one :project_credential_provider, dependent: :destroy
|
||||
has_one :build_configuration, dependent: :destroy
|
||||
has_one :deployment_configuration, dependent: :destroy
|
||||
|
||||
has_one :child_fork, class_name: "ProjectFork", foreign_key: :child_project_id, dependent: :destroy
|
||||
has_many :forks, class_name: "ProjectFork", foreign_key: :parent_project_id, dependent: :destroy
|
||||
|
||||
@@ -15,14 +15,18 @@
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# external_id :string
|
||||
# sso_provider_id :bigint
|
||||
# user_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_providers_on_user_id (user_id)
|
||||
# index_providers_on_sso_provider_id (sso_provider_id)
|
||||
# index_providers_on_sso_provider_id_and_uid (sso_provider_id,uid) UNIQUE WHERE (sso_provider_id IS NOT NULL)
|
||||
# index_providers_on_user_id (user_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (sso_provider_id => sso_providers.id)
|
||||
# fk_rails_... (user_id => users.id)
|
||||
#
|
||||
class Provider < ApplicationRecord
|
||||
|
||||
76
app/services/deployments/base_deployment_service.rb
Normal file
76
app/services/deployments/base_deployment_service.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
class Deployments::BaseDeploymentService
|
||||
DEPLOYABLE_RESOURCES = %w[ConfigMap Secrets Deployment CronJob Service Ingress Pv Pvc].freeze
|
||||
|
||||
def initialize(deployment, user)
|
||||
@deployment = deployment
|
||||
@user = user
|
||||
@project = deployment.project
|
||||
@logger = deployment
|
||||
end
|
||||
|
||||
def deploy
|
||||
raise NotImplementedError, "Subclasses must implement #deploy"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_connection
|
||||
@connection = K8::Connection.new(@project, @user, allow_anonymous: true)
|
||||
@kubectl = K8::Kubectl.new(@connection)
|
||||
end
|
||||
|
||||
def apply_namespace
|
||||
@logger.info("Creating namespace: #{@project.namespace}", color: :yellow)
|
||||
namespace_yaml = K8::Namespace.new(@project).to_yaml
|
||||
@kubectl.apply_yaml(namespace_yaml)
|
||||
end
|
||||
|
||||
def upload_registry_secrets
|
||||
@logger.info("Creating registry secret for #{@project.container_image_reference}", color: :yellow)
|
||||
provider = @project.build_provider
|
||||
result = Providers::GenerateConfigJson.execute(provider:)
|
||||
raise StandardError, result.message if result.failure?
|
||||
|
||||
secret_yaml = K8::Secrets::RegistrySecret.new(@project, result.docker_config_json).to_yaml
|
||||
@kubectl.apply_yaml(secret_yaml)
|
||||
end
|
||||
|
||||
def deploy_services
|
||||
@project.services.each do |service|
|
||||
deploy_service(service)
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_service(service)
|
||||
raise NotImplementedError, "Subclasses must implement #deploy_service"
|
||||
end
|
||||
|
||||
def kill_one_off_containers
|
||||
@kubectl.call("-n #{@project.namespace} delete pods -l oneoff=true")
|
||||
end
|
||||
|
||||
def predeploy
|
||||
return unless @project.predeploy_command.present?
|
||||
|
||||
run_command(@project.predeploy_command, "predeploy")
|
||||
end
|
||||
|
||||
def postdeploy
|
||||
return unless @project.postdeploy_command.present?
|
||||
|
||||
run_command(@project.postdeploy_command, "postdeploy")
|
||||
end
|
||||
|
||||
def run_command(command, type)
|
||||
@logger.info("Running command: `#{command}`...", color: :yellow)
|
||||
command_job = K8::Stateless::Command.new(@project, type, command).connect(@connection)
|
||||
command_job.delete_if_exists!
|
||||
@kubectl.apply_yaml(command_job.to_yaml)
|
||||
command_job.wait_for_completion
|
||||
end
|
||||
|
||||
def complete_deployment!
|
||||
@deployment.completed!
|
||||
@project.deployed!
|
||||
end
|
||||
end
|
||||
1
app/services/deployments/deployment_failure.rb
Normal file
1
app/services/deployments/deployment_failure.rb
Normal file
@@ -0,0 +1 @@
|
||||
class Deployments::DeploymentFailure < StandardError; end
|
||||
71
app/services/deployments/helm_deployment_service.rb
Normal file
71
app/services/deployments/helm_deployment_service.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
class Deployments::HelmDeploymentService < Deployments::BaseDeploymentService
|
||||
def deploy
|
||||
setup_connection
|
||||
setup_chart_builder
|
||||
|
||||
apply_namespace if @project.managed_namespace?
|
||||
upload_registry_secrets
|
||||
apply_config_and_secrets
|
||||
|
||||
deploy_volumes
|
||||
predeploy
|
||||
deploy_services
|
||||
@chart_builder.install_chart(@project.name)
|
||||
kill_one_off_containers
|
||||
postdeploy
|
||||
|
||||
mark_services_healthy
|
||||
complete_deployment!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_chart_builder
|
||||
@chart_builder = K8::Helm::ChartBuilder.new(
|
||||
@project.name,
|
||||
@deployment
|
||||
).connect(@connection)
|
||||
|
||||
@chart_builder.register_before_install do |yaml_content|
|
||||
@deployment.add_manifest(yaml_content)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_config_and_secrets
|
||||
@chart_builder << build_resource("ConfigMap", @project)
|
||||
@chart_builder << build_resource("Secrets", @project)
|
||||
end
|
||||
|
||||
def build_resource(resource_type, target)
|
||||
K8::Stateless.const_get(resource_type).new(target)
|
||||
end
|
||||
|
||||
def deploy_volumes
|
||||
@project.volumes.each do |volume|
|
||||
@chart_builder << build_resource("Pv", volume)
|
||||
@chart_builder << build_resource("Pvc", volume)
|
||||
volume.deployed!
|
||||
rescue StandardError => e
|
||||
volume.failed!
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_service(service)
|
||||
if service.background_service?
|
||||
@chart_builder << build_resource("Deployment", service)
|
||||
elsif service.cron_job?
|
||||
@chart_builder << build_resource("CronJob", service)
|
||||
elsif service.web_service?
|
||||
@chart_builder << build_resource("Deployment", service)
|
||||
@chart_builder << build_resource("Service", service)
|
||||
if service.domains.any? && service.allow_public_networking?
|
||||
@chart_builder << build_resource("Ingress", service)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_services_healthy
|
||||
@project.services.each(&:healthy!)
|
||||
end
|
||||
end
|
||||
96
app/services/deployments/legacy_deployment_service.rb
Normal file
96
app/services/deployments/legacy_deployment_service.rb
Normal file
@@ -0,0 +1,96 @@
|
||||
class Deployments::LegacyDeploymentService < Deployments::BaseDeploymentService
|
||||
def initialize(deployment, user)
|
||||
super
|
||||
@marked_resources = []
|
||||
end
|
||||
|
||||
def deploy
|
||||
setup_connection
|
||||
|
||||
apply_namespace if @project.managed_namespace?
|
||||
upload_registry_secrets
|
||||
apply_resource("ConfigMap", @project)
|
||||
apply_resource("Secrets", @project)
|
||||
|
||||
deploy_volumes
|
||||
predeploy
|
||||
deploy_services
|
||||
sweep_unused_resources
|
||||
kill_one_off_containers
|
||||
postdeploy
|
||||
|
||||
complete_deployment!
|
||||
rescue StandardError => e
|
||||
@logger.error("Deployment failed: #{e.message}")
|
||||
puts e.full_message
|
||||
@deployment.failed!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_connection
|
||||
@connection = K8::Connection.new(@project, @user, allow_anonymous: true)
|
||||
runner = Cli::RunAndLog.new(@deployment)
|
||||
@kubectl = K8::Kubectl.new(@connection, runner)
|
||||
end
|
||||
|
||||
def apply_resource(resource_type, target)
|
||||
@logger.info("Creating #{resource_type}: #{target.name}", color: :yellow)
|
||||
resource = K8::Stateless.const_get(resource_type).new(target)
|
||||
@kubectl.apply_yaml(resource.to_yaml)
|
||||
@marked_resources << resource
|
||||
end
|
||||
|
||||
def deploy_volumes
|
||||
@project.volumes.each do |volume|
|
||||
apply_resource("Pv", volume)
|
||||
apply_resource("Pvc", volume)
|
||||
volume.deployed!
|
||||
rescue StandardError => e
|
||||
@logger.error("Volume deployment failed: #{e.message}")
|
||||
volume.failed!
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_service(service)
|
||||
if service.background_service?
|
||||
apply_resource("Deployment", service)
|
||||
restart_deployment(service)
|
||||
elsif service.cron_job?
|
||||
apply_resource("CronJob", service)
|
||||
elsif service.web_service?
|
||||
apply_resource("Deployment", service)
|
||||
apply_resource("Service", service)
|
||||
if service.domains.any? && service.allow_public_networking?
|
||||
apply_resource("Ingress", service)
|
||||
end
|
||||
restart_deployment(service)
|
||||
end
|
||||
service.healthy!
|
||||
end
|
||||
|
||||
def restart_deployment(service)
|
||||
@logger.info("Restarting deployment: #{service.name}", color: :yellow)
|
||||
@kubectl.call("-n #{service.project.namespace} rollout restart deployment/#{service.name}")
|
||||
end
|
||||
|
||||
def sweep_unused_resources
|
||||
resources_to_sweep = DEPLOYABLE_RESOURCES.reject { |r| [ "Pv" ].include?(r) }
|
||||
kubectl = K8::Kubectl.new(@connection)
|
||||
|
||||
resources_to_sweep.each do |resource_type|
|
||||
results = YAML.safe_load(kubectl.call("get #{resource_type.downcase} -o yaml -n #{@project.namespace}"))
|
||||
results["items"].each do |resource|
|
||||
if @marked_resources.select { |r|
|
||||
r.is_a?(K8::Stateless.const_get(resource_type))
|
||||
}.none? { |applied_resource|
|
||||
applied_resource.name == resource["metadata"]["name"]
|
||||
} && resource.dig("metadata", "labels", "caninemanaged") == "true"
|
||||
@logger.info("Deleting #{resource_type}: #{resource['metadata']['name']}", color: :yellow)
|
||||
kubectl.call("delete #{resource_type.downcase} #{resource['metadata']['name']} -n #{@project.namespace}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,7 +29,7 @@ class K8::Stateless::Command < K8::Base
|
||||
sleep(3.0)
|
||||
retries += 1
|
||||
if retries > 30
|
||||
raise Projects::DeploymentJob::DeploymentFailure, "Predeploy command `#{command}` took too long to complete"
|
||||
raise Deployments::DeploymentFailure, "Predeploy command `#{command}` took too long to complete"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -37,7 +37,7 @@ class K8::Stateless::Command < K8::Base
|
||||
def done?
|
||||
_statuses = statuses
|
||||
if _statuses.include?("Failed") || _statuses.include?("ActiveDeadlineExceeded")
|
||||
raise Projects::DeploymentJob::DeploymentFailure, "Predeploy command `#{command}` failed"
|
||||
raise Deployments::DeploymentFailure, "Predeploy command `#{command}` failed"
|
||||
end
|
||||
_statuses.include?("Complete")
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
class CreateDeploymentConfigurations < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :deployment_configurations do |t|
|
||||
t.references :project, null: false, foreign_key: true
|
||||
t.integer :deployment_method, null: false, default: 0
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
Project.find_each do |project|
|
||||
DeploymentConfiguration.create!(project: project, deployment_method: :legacy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
db/schema.rb
generated
31
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_12_13_221841) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -168,6 +168,14 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
t.index ["service_id"], name: "index_cron_schedules_on_service_id"
|
||||
end
|
||||
|
||||
create_table "deployment_configurations", force: :cascade do |t|
|
||||
t.bigint "project_id", null: false
|
||||
t.integer "deployment_method", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["project_id"], name: "index_deployment_configurations_on_project_id"
|
||||
end
|
||||
|
||||
create_table "deployments", force: :cascade do |t|
|
||||
t.bigint "build_id", null: false
|
||||
t.integer "status", default: 0, null: false
|
||||
@@ -393,6 +401,22 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
t.index ["recipient_type", "recipient_id"], name: "index_noticed_notifications_on_recipient"
|
||||
end
|
||||
|
||||
create_table "oidc_configurations", force: :cascade do |t|
|
||||
t.string "issuer", null: false
|
||||
t.string "client_id", null: false
|
||||
t.string "client_secret", null: false
|
||||
t.string "authorization_endpoint"
|
||||
t.string "token_endpoint"
|
||||
t.string "userinfo_endpoint"
|
||||
t.string "jwks_uri"
|
||||
t.string "scopes", default: "openid email profile"
|
||||
t.string "uid_claim", default: "sub", null: false
|
||||
t.string "email_claim", default: "email"
|
||||
t.string "name_claim", default: "name"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "project_add_ons", force: :cascade do |t|
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "add_on_id", null: false
|
||||
@@ -464,6 +488,9 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
t.datetime "last_used_at"
|
||||
t.string "registry_url"
|
||||
t.string "external_id"
|
||||
t.bigint "sso_provider_id"
|
||||
t.index ["sso_provider_id", "uid"], name: "index_providers_on_sso_provider_id_and_uid", unique: true, where: "(sso_provider_id IS NOT NULL)"
|
||||
t.index ["sso_provider_id"], name: "index_providers_on_sso_provider_id"
|
||||
t.index ["user_id"], name: "index_providers_on_user_id"
|
||||
end
|
||||
|
||||
@@ -597,6 +624,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
add_foreign_key "builds", "projects"
|
||||
add_foreign_key "clusters", "accounts"
|
||||
add_foreign_key "cron_schedules", "services"
|
||||
add_foreign_key "deployment_configurations", "projects"
|
||||
add_foreign_key "deployments", "builds"
|
||||
add_foreign_key "environment_variables", "projects"
|
||||
add_foreign_key "project_add_ons", "add_ons"
|
||||
@@ -607,6 +635,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_26_014509) do
|
||||
add_foreign_key "project_forks", "projects", column: "parent_project_id"
|
||||
add_foreign_key "projects", "clusters"
|
||||
add_foreign_key "projects", "clusters", column: "project_fork_cluster_id"
|
||||
add_foreign_key "providers", "sso_providers"
|
||||
add_foreign_key "providers", "users"
|
||||
add_foreign_key "services", "projects"
|
||||
add_foreign_key "sso_providers", "accounts"
|
||||
|
||||
24
spec/factories/deployment_configurations.rb
Normal file
24
spec/factories/deployment_configurations.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: deployment_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# deployment_method :integer default("legacy"), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# project_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_deployment_configurations_on_project_id (project_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (project_id => projects.id)
|
||||
#
|
||||
FactoryBot.define do
|
||||
factory :deployment_configuration do
|
||||
project
|
||||
deployment_method { :legacy }
|
||||
end
|
||||
end
|
||||
@@ -15,14 +15,18 @@
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# external_id :string
|
||||
# sso_provider_id :bigint
|
||||
# user_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_providers_on_user_id (user_id)
|
||||
# index_providers_on_sso_provider_id (sso_provider_id)
|
||||
# index_providers_on_sso_provider_id_and_uid (sso_provider_id,uid) UNIQUE WHERE (sso_provider_id IS NOT NULL)
|
||||
# index_providers_on_user_id (user_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (sso_provider_id => sso_providers.id)
|
||||
# fk_rails_... (user_id => users.id)
|
||||
#
|
||||
FactoryBot.define do
|
||||
|
||||
@@ -22,12 +22,12 @@ class MockChartBuilder
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Projects::HelmDeploymentJob do
|
||||
RSpec.describe Deployments::HelmDeploymentService do
|
||||
let(:project) { create(:project) }
|
||||
let(:build) { create(:build, project: project) }
|
||||
let(:deployment) { create(:deployment, build: build) }
|
||||
let(:user) { project.account.owner }
|
||||
let(:job) { described_class.new }
|
||||
let(:service_instance) { described_class.new(deployment, user) }
|
||||
let(:mock_chart_builder) { MockChartBuilder.new }
|
||||
|
||||
let!(:web_service) do
|
||||
@@ -61,7 +61,7 @@ RSpec.describe Projects::HelmDeploymentJob do
|
||||
double(failure?: false, docker_config_json: '{}')
|
||||
)
|
||||
|
||||
job.perform(deployment, user)
|
||||
service_instance.deploy
|
||||
end
|
||||
|
||||
def find_resource(kind, name = nil)
|
||||
142
spec/services/deployments/legacy_deployment_service_spec.rb
Normal file
142
spec/services/deployments/legacy_deployment_service_spec.rb
Normal file
@@ -0,0 +1,142 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Deployments::LegacyDeploymentService do
|
||||
let(:project) { create(:project) }
|
||||
let(:build) { create(:build, project: project) }
|
||||
let(:deployment) { create(:deployment, build: build) }
|
||||
let(:user) { project.account.owner }
|
||||
let(:service_instance) { described_class.new(deployment, user) }
|
||||
let(:applied_yamls) { [] }
|
||||
|
||||
let!(:web_service) do
|
||||
create(:service,
|
||||
project: project,
|
||||
name: 'web',
|
||||
service_type: :web_service,
|
||||
allow_public_networking: true
|
||||
).tap { |s| create(:domain, service: s, domain_name: 'example.com') }
|
||||
end
|
||||
|
||||
let!(:worker_service) do
|
||||
create(:service, :background_service,
|
||||
project: project,
|
||||
name: 'worker'
|
||||
)
|
||||
end
|
||||
|
||||
let!(:cron_service) do
|
||||
create(:service, :cron_job,
|
||||
project: project,
|
||||
name: 'scheduler'
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow_any_instance_of(K8::Kubectl).to receive(:apply_yaml) do |_instance, yaml|
|
||||
applied_yamls << yaml
|
||||
end
|
||||
allow_any_instance_of(K8::Kubectl).to receive(:call).and_return("items: []")
|
||||
allow(Providers::GenerateConfigJson).to receive(:execute).and_return(
|
||||
double(failure?: false, docker_config_json: '{}')
|
||||
)
|
||||
|
||||
service_instance.deploy
|
||||
end
|
||||
|
||||
def find_applied_resource(kind, name = nil)
|
||||
applied_yamls.find do |yaml_str|
|
||||
yaml = YAML.safe_load(yaml_str)
|
||||
matches_kind = yaml['kind'] == kind
|
||||
matches_name = name.nil? || yaml.dig('metadata', 'name') == name
|
||||
matches_kind && matches_name
|
||||
end
|
||||
end
|
||||
|
||||
def parse_yaml(yaml_str)
|
||||
YAML.safe_load(yaml_str)
|
||||
end
|
||||
|
||||
describe 'deployment status' do
|
||||
it 'marks deployment as completed' do
|
||||
expect(deployment.reload.status).to eq('completed')
|
||||
end
|
||||
|
||||
it 'marks project as deployed' do
|
||||
expect(project.reload.status).to eq('deployed')
|
||||
end
|
||||
|
||||
it 'marks services as healthy' do
|
||||
expect(web_service.reload.status).to eq('healthy')
|
||||
expect(worker_service.reload.status).to eq('healthy')
|
||||
expect(cron_service.reload.status).to eq('healthy')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'web service resources' do
|
||||
it 'applies a Deployment' do
|
||||
yaml_str = find_applied_resource('Deployment', 'web')
|
||||
expect(yaml_str).to be_present
|
||||
|
||||
yaml = parse_yaml(yaml_str)
|
||||
expect(yaml.dig('spec', 'selector', 'matchLabels', 'app')).to eq('web')
|
||||
end
|
||||
|
||||
it 'applies a Service' do
|
||||
yaml_str = find_applied_resource('Service', 'web-service')
|
||||
expect(yaml_str).to be_present
|
||||
|
||||
yaml = parse_yaml(yaml_str)
|
||||
expect(yaml.dig('spec', 'selector', 'app')).to eq('web')
|
||||
end
|
||||
|
||||
it 'applies an Ingress with domain' do
|
||||
yaml_str = find_applied_resource('Ingress', 'web-ingress')
|
||||
expect(yaml_str).to be_present
|
||||
|
||||
yaml = parse_yaml(yaml_str)
|
||||
expect(yaml.dig('spec', 'rules', 0, 'host')).to eq('example.com')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'background worker resources' do
|
||||
it 'applies a Deployment' do
|
||||
yaml_str = find_applied_resource('Deployment', 'worker')
|
||||
expect(yaml_str).to be_present
|
||||
|
||||
yaml = parse_yaml(yaml_str)
|
||||
expect(yaml.dig('spec', 'selector', 'matchLabels', 'app')).to eq('worker')
|
||||
end
|
||||
|
||||
it 'does not apply a Service' do
|
||||
yaml_str = find_applied_resource('Service', 'worker-service')
|
||||
expect(yaml_str).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'cron job resources' do
|
||||
it 'applies a CronJob with schedule' do
|
||||
yaml_str = find_applied_resource('CronJob', 'scheduler')
|
||||
expect(yaml_str).to be_present
|
||||
|
||||
yaml = parse_yaml(yaml_str)
|
||||
expect(yaml.dig('spec', 'schedule')).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shared resources' do
|
||||
it 'applies ConfigMap' do
|
||||
yaml_str = find_applied_resource('ConfigMap')
|
||||
expect(yaml_str).to be_present
|
||||
end
|
||||
|
||||
it 'applies Secrets' do
|
||||
yaml_str = find_applied_resource('Secret')
|
||||
expect(yaml_str).to be_present
|
||||
end
|
||||
|
||||
it 'applies Namespace when managed' do
|
||||
yaml_str = find_applied_resource('Namespace')
|
||||
expect(yaml_str).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user