mirror of
https://github.com/czhu12/canine.git
synced 2025-12-21 10:49:49 -06:00
merged
This commit is contained in:
86
app/javascript/controllers/manifest_browser_controller.js
Normal file
86
app/javascript/controllers/manifest_browser_controller.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
import { EditorView, basicSetup } from "codemirror"
|
||||||
|
import { EditorState } from "@codemirror/state"
|
||||||
|
import { yaml } from "@codemirror/lang-yaml"
|
||||||
|
import { oneDark } from "@codemirror/theme-one-dark"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["file", "content", "filename", "editor"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.setupEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.editorView) {
|
||||||
|
this.editorView.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEditor() {
|
||||||
|
const initialContent = this.contentTarget.value || ''
|
||||||
|
|
||||||
|
// Create the editor state with YAML syntax highlighting, dark theme, and read-only
|
||||||
|
const state = EditorState.create({
|
||||||
|
doc: initialContent,
|
||||||
|
extensions: [
|
||||||
|
basicSetup,
|
||||||
|
yaml(),
|
||||||
|
oneDark,
|
||||||
|
EditorState.readOnly.of(true),
|
||||||
|
EditorView.theme({
|
||||||
|
"&": {
|
||||||
|
fontSize: "14px",
|
||||||
|
border: "1px solid #374151",
|
||||||
|
borderRadius: "0.5rem",
|
||||||
|
height: "450px"
|
||||||
|
},
|
||||||
|
".cm-content": {
|
||||||
|
padding: "12px"
|
||||||
|
},
|
||||||
|
".cm-scroller": {
|
||||||
|
fontFamily: "'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
|
||||||
|
overflow: "auto"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create the editor view
|
||||||
|
this.editorView = new EditorView({
|
||||||
|
state,
|
||||||
|
parent: this.editorTarget
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hide the original textarea
|
||||||
|
this.contentTarget.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFile(event) {
|
||||||
|
const fileButton = event.currentTarget
|
||||||
|
const manifestKey = fileButton.dataset.manifestKey
|
||||||
|
|
||||||
|
// Update active state
|
||||||
|
this.fileTargets.forEach(file => {
|
||||||
|
file.classList.remove("active")
|
||||||
|
})
|
||||||
|
fileButton.classList.add("active")
|
||||||
|
|
||||||
|
// Update content display
|
||||||
|
const content = fileButton.dataset.manifestContent
|
||||||
|
|
||||||
|
// Update CodeMirror editor
|
||||||
|
if (this.editorView) {
|
||||||
|
this.editorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: this.editorView.state.doc.length,
|
||||||
|
insert: content
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filename display
|
||||||
|
this.filenameTarget.textContent = manifestKey
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ class Projects::DeploymentJob < ApplicationJob
|
|||||||
project = deployment.project
|
project = deployment.project
|
||||||
connection = K8::Connection.new(project, user, allow_anonymous: true)
|
connection = K8::Connection.new(project, user, allow_anonymous: true)
|
||||||
kubectl = create_kubectl(deployment, connection)
|
kubectl = create_kubectl(deployment, connection)
|
||||||
|
kubectl.register_after_apply do |yaml_content|
|
||||||
|
deployment.add_manifest(yaml_content)
|
||||||
|
end
|
||||||
|
|
||||||
# Create namespace
|
# Create namespace
|
||||||
apply_namespace(project, kubectl)
|
apply_namespace(project, kubectl)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Table name: deployments
|
# Table name: deployments
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
|
# manifests :jsonb
|
||||||
# status :integer default("in_progress"), not null
|
# status :integer default("in_progress"), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
@@ -24,4 +25,19 @@ class Deployment < ApplicationRecord
|
|||||||
after_update_commit do
|
after_update_commit do
|
||||||
self.build.broadcast_build
|
self.build.broadcast_build
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_manifest(yaml)
|
||||||
|
manifest = YAML.safe_load(yaml)
|
||||||
|
kind = manifest["kind"]&.downcase
|
||||||
|
name = manifest.dig("metadata", "name")
|
||||||
|
manifest_key = "#{kind}/#{name}"
|
||||||
|
|
||||||
|
self.manifests ||= {}
|
||||||
|
self.manifests[manifest_key] = yaml
|
||||||
|
save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_manifests?
|
||||||
|
manifests.keys.any?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ class K8::Kubectl
|
|||||||
raise "Kubeconfig is required"
|
raise "Kubeconfig is required"
|
||||||
end
|
end
|
||||||
@runner = runner
|
@runner = runner
|
||||||
|
@after_apply_blocks = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_after_apply(&block)
|
||||||
|
@after_apply_blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_yaml(yaml_content)
|
def apply_yaml(yaml_content)
|
||||||
@@ -25,6 +30,10 @@ class K8::Kubectl
|
|||||||
runner.call(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
|
runner.call(command, envs: { "KUBECONFIG" => kubeconfig_file.path })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@after_apply_blocks.each do |block|
|
||||||
|
block.call(yaml_content)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(command)
|
def call(command)
|
||||||
|
|||||||
@@ -7,25 +7,25 @@
|
|||||||
<hr class="mt-3 mb-4 border-t border-base-300" />
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
<%= render "edit_form", cluster: @cluster %>
|
<%= render "edit_form", cluster: @cluster %>
|
||||||
|
|
||||||
<div class="mt-6" data-controller="kubeconfig-editor">
|
<div class="mt-6" data-controller="content-toggle">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-2xl font-bold">Credentials</h2>
|
<h2 class="text-2xl font-bold">Credentials</h2>
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-outline"
|
class="btn btn-sm btn-outline"
|
||||||
data-action="click->kubeconfig-editor#toggleEdit"
|
data-action="click->content-toggle#toggleEdit"
|
||||||
data-kubeconfig-editor-target="editButton">
|
data-content-toggle-target="editButton">
|
||||||
<iconify-icon icon="lucide:pen" height="16"></iconify-icon>
|
<iconify-icon icon="lucide:pen" height="16"></iconify-icon>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<hr class="mt-3 mb-4 border-t border-base-300" />
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
|
|
||||||
<div data-kubeconfig-editor-target="placeholder" class="p-4 bg-base-200 rounded-lg">
|
<div data-content-toggle-target="placeholder" class="p-4 bg-base-200 rounded-lg">
|
||||||
<p class="text-sm text-gray-500">Kubeconfig is hidden for security. Click edit to view and modify.</p>
|
<p class="text-sm text-gray-500">Kubeconfig is hidden for security. Click edit to view and modify.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-kubeconfig-editor-target="editorContainer" class="hidden">
|
<div data-content-toggle-target="editorContainer" class="hidden">
|
||||||
<%= form_with(model: @cluster, url: cluster_path(@cluster), method: :patch) do |form| %>
|
<%= form_with(model: @cluster, url: cluster_path(@cluster), method: :patch) do |form| %>
|
||||||
<%= form.hidden_field :kubeconfig_yaml_format, value: "true" %>
|
<%= form.hidden_field :kubeconfig_yaml_format, value: "true" %>
|
||||||
<div class="form-group" data-controller="yaml-editor">
|
<div class="form-group" data-controller="yaml-editor">
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-footer flex gap-2">
|
<div class="form-footer flex gap-2">
|
||||||
<%= form.submit "Save", class: "btn btn-primary" %>
|
<%= form.submit "Save", class: "btn btn-primary" %>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-outline"
|
class="btn btn-outline"
|
||||||
data-action="click->kubeconfig-editor#cancelEdit">
|
data-action="click->content-toggle#cancelEdit">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
45
app/views/projects/deployments/_manifest_browser.html.erb
Normal file
45
app/views/projects/deployments/_manifest_browser.html.erb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<div data-controller="manifest-browser" class="grid grid-cols-12 gap-4">
|
||||||
|
<!-- Left Column: File List -->
|
||||||
|
<div class="col-span-3 flex flex-col">
|
||||||
|
<div class="px-4 py-3">
|
||||||
|
<h3 class="text-sm font-bold text-base-content/70">Files</h3>
|
||||||
|
</div>
|
||||||
|
<div class="border border-base-300 rounded-lg overflow-y-auto" style="height: 450px;">
|
||||||
|
<div class="p-4">
|
||||||
|
<ul class="menu menu-sm p-0 gap-1">
|
||||||
|
<% deployment.manifests.keys.each_with_index do |manifest_key, index| %>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-manifest-browser-target="file"
|
||||||
|
data-manifest-key="<%= manifest_key %>"
|
||||||
|
data-manifest-content="<%= ERB::Util.html_escape(deployment.manifests[manifest_key]) %>"
|
||||||
|
data-action="click->manifest-browser#selectFile"
|
||||||
|
class="<%= 'active' if index == 0 %>"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<span class="truncate"><%= manifest_key %></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Content Display -->
|
||||||
|
<div class="col-span-9 flex flex-col">
|
||||||
|
<div class="px-4 py-3">
|
||||||
|
<h3 class="text-sm font-bold text-base-content/70" data-manifest-browser-target="filename"><%= deployment.manifests.keys.first %></h3>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
data-manifest-browser-target="content"
|
||||||
|
class="hidden"
|
||||||
|
><%= deployment.manifests.values.first %></textarea>
|
||||||
|
<div data-manifest-browser-target="editor" style="height: 450px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -23,15 +23,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="mt-2 mb-6 border-base-content/10" />
|
<hr class="mt-2 mb-6 border-base-content/10" />
|
||||||
<h2 class="text-xl font-bold mb-4">Build Logs</h2>
|
<h2 class="text-2xl font-bold">Build Logs</h2>
|
||||||
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<%= render "log_outputs/logs", loggable: @build %>
|
<%= render "log_outputs/logs", loggable: @build %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @build.deployment %>
|
<% if @build.deployment %>
|
||||||
<h2 class="text-xl font-bold mb-4">Release Logs</h2>
|
<div class="mt-6">
|
||||||
<div class="my-4">
|
<h2 class="text-2xl font-bold">Release Logs</h2>
|
||||||
<%= render "log_outputs/logs", loggable: @build.deployment %>
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
|
<div class="my-4">
|
||||||
|
<%= render "log_outputs/logs", loggable: @build.deployment %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if @build.deployment&.has_manifests? %>
|
||||||
|
<div class="mt-6" data-controller="content-toggle">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h2 class="text-2xl font-bold">Deployment Manifests</h2>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
data-action="click->content-toggle#toggleEdit"
|
||||||
|
data-content-toggle-target="editButton">
|
||||||
|
<iconify-icon icon="lucide:eye" height="16"></iconify-icon>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
|
|
||||||
|
<div data-content-toggle-target="placeholder" class="p-4 bg-base-200 rounded-lg">
|
||||||
|
<p class="text-sm text-gray-500">Click view to see the Kubernetes manifests that were deployed.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-content-toggle-target="editorContainer" class="hidden">
|
||||||
|
<%= render "projects/deployments/manifest_browser", deployment: @build.deployment %>
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
data-action="click->content-toggle#cancelEdit">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddKubernetesManifestsToDeployments < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
add_column :deployments, :manifests, :jsonb, default: {}
|
||||||
|
end
|
||||||
|
end
|
||||||
17
db/schema.rb
generated
17
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2025_11_10_152921) do
|
ActiveRecord::Schema[7.2].define(version: 2025_11_14_025053) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -171,6 +171,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_10_152921) do
|
|||||||
t.integer "status", default: 0, null: false
|
t.integer "status", default: 0, null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.jsonb "manifests", default: {}
|
||||||
t.index ["build_id"], name: "index_deployments_on_build_id", unique: true
|
t.index ["build_id"], name: "index_deployments_on_build_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -425,6 +426,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_10_152921) do
|
|||||||
t.text "postdestroy_command"
|
t.text "postdestroy_command"
|
||||||
t.bigint "project_fork_cluster_id"
|
t.bigint "project_fork_cluster_id"
|
||||||
t.integer "project_fork_status", default: 0
|
t.integer "project_fork_status", default: 0
|
||||||
|
t.string "docker_command"
|
||||||
t.index ["cluster_id"], name: "index_projects_on_cluster_id"
|
t.index ["cluster_id"], name: "index_projects_on_cluster_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -445,19 +447,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_10_152921) do
|
|||||||
t.index ["user_id"], name: "index_providers_on_user_id"
|
t.index ["user_id"], name: "index_providers_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "resource_constraints", force: :cascade do |t|
|
|
||||||
t.string "constrainable_type", null: false
|
|
||||||
t.bigint "constrainable_id", null: false
|
|
||||||
t.bigint "cpu_request"
|
|
||||||
t.bigint "cpu_limit"
|
|
||||||
t.bigint "memory_request"
|
|
||||||
t.bigint "memory_limit"
|
|
||||||
t.integer "gpu_request"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["constrainable_type", "constrainable_id"], name: "index_resource_constraints_on_constrainable"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "services", force: :cascade do |t|
|
create_table "services", force: :cascade do |t|
|
||||||
t.bigint "project_id", null: false
|
t.bigint "project_id", null: false
|
||||||
t.integer "service_type", null: false
|
t.integer "service_type", null: false
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Table name: deployments
|
# Table name: deployments
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
|
# manifests :jsonb
|
||||||
# status :integer default("in_progress"), not null
|
# status :integer default("in_progress"), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
|||||||
105
spec/models/deployment_spec.rb
Normal file
105
spec/models/deployment_spec.rb
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: deployments
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# manifests :jsonb
|
||||||
|
# status :integer default("in_progress"), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# build_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_deployments_on_build_id (build_id) UNIQUE
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (build_id => builds.id)
|
||||||
|
#
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Deployment, type: :model do
|
||||||
|
let(:build) { create(:build) }
|
||||||
|
let(:deployment) { create(:deployment, build: build) }
|
||||||
|
|
||||||
|
describe '#add_manifest' do
|
||||||
|
let(:deployment_yaml) do
|
||||||
|
<<~YAML
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-app
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
YAML
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:service_yaml) do
|
||||||
|
<<~YAML
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: test-service
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
YAML
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:configmap_yaml) do
|
||||||
|
<<~YAML
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: test-config
|
||||||
|
data:
|
||||||
|
key: value
|
||||||
|
YAML
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stores manifest with key format kind/name' do
|
||||||
|
expect(deployment.has_manifests?).to be false
|
||||||
|
|
||||||
|
deployment.add_manifest(deployment_yaml)
|
||||||
|
|
||||||
|
expect(deployment.manifests['deployment/test-app']).to eq(deployment_yaml)
|
||||||
|
expect(deployment.has_manifests?).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stores multiple manifests with different keys' do
|
||||||
|
deployment.add_manifest(deployment_yaml)
|
||||||
|
deployment.add_manifest(service_yaml)
|
||||||
|
deployment.add_manifest(configmap_yaml)
|
||||||
|
|
||||||
|
expect(deployment.manifests['deployment/test-app']).to eq(deployment_yaml)
|
||||||
|
expect(deployment.manifests['service/test-service']).to eq(service_yaml)
|
||||||
|
expect(deployment.manifests['configmap/test-config']).to eq(configmap_yaml)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'overwrites manifest if same kind/name is added again' do
|
||||||
|
deployment.add_manifest(deployment_yaml)
|
||||||
|
|
||||||
|
updated_yaml = <<~YAML
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test-app
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
YAML
|
||||||
|
|
||||||
|
deployment.add_manifest(updated_yaml)
|
||||||
|
|
||||||
|
expect(deployment.manifests['deployment/test-app']).to eq(updated_yaml)
|
||||||
|
expect(deployment.manifests.keys.length).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'persists the changes to the database' do
|
||||||
|
deployment.add_manifest(deployment_yaml)
|
||||||
|
|
||||||
|
deployment.reload
|
||||||
|
expect(deployment.manifests['deployment/test-app']).to eq(deployment_yaml)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user