This commit is contained in:
Chris
2025-07-08 08:13:40 -07:00
parent 4803201088
commit c988155b5c
10 changed files with 146 additions and 36 deletions
+1
View File
@@ -10,6 +10,7 @@ class ProjectForks::Create
child_project = parent_project.dup
child_project.branch = pull_request.branch
child_project.name = "#{parent_project.name}-#{pull_request.number}"
child_project.cluster_id = parent_project.project_fork_cluster_id
# Duplicate the project_credential_provider
child_project_credential_provider = parent_project.project_credential_provider.dup
child_project_credential_provider.project = child_project
@@ -0,0 +1,12 @@
class ProjectForks::CreateFromCanineConfig
extend LightService::Action
expects :canine_config, :project
executed do |context|
context.canine_config.services.each do |service_config|
end
context.canine_config.environment_variables.each do |environment_variable|
end
end
end
+35 -11
View File
@@ -46,6 +46,7 @@ class Project < ApplicationRecord
has_one :child_fork, class_name: "ProjectFork", foreign_key: :child_project_id
has_many :forks, class_name: "ProjectFork", foreign_key: :parent_project_id
has_one :project_fork_cluster, class_name: "Cluster", foreign_key: :id, primary_key: :project_fork_cluster_id
validates :name, presence: true,
format: { with: /\A[a-z0-9-]+\z/, message: "must be lowercase, numbers, and hyphens only" }
@@ -56,6 +57,7 @@ class Project < ApplicationRecord
message: "must be in the format 'owner/repository'"
}
validates :project_credential_provider, presence: true
validate :project_fork_cluster_id_is_owned_by_account
validate :name_is_unique_to_cluster, on: :create
after_save_commit do
@@ -78,6 +80,12 @@ class Project < ApplicationRecord
delegate :git?, :github?, :gitlab?, to: :project_credential_provider
delegate :docker_hub?, to: :project_credential_provider
def project_fork_cluster_id_is_owned_by_account
if project_fork_cluster_id.present? && !account.clusters.exists?(id: project_fork_cluster_id)
errors.add(:project_fork_cluster_id, "must be owned by the account")
end
end
def name_is_unique_to_cluster
if cluster.namespaces.include?(name)
errors.add(:name, "must be unique to this cluster")
@@ -108,13 +116,21 @@ class Project < ApplicationRecord
repository_url.split("/").last
end
def full_repository_url
if github?
"https://github.com/#{repository_url}"
elsif gitlab?
"https://gitlab.com/#{repository_url}"
def link_to_view
if forked?
if github?
"https://github.com/#{repository_url}/pull/#{child_fork.number}"
elsif gitlab?
"https://gitlab.com/#{repository_url}/merge_requests/#{child_fork.number}"
end
else
"https://hub.docker.com/r/#{repository_url}"
if github?
"https://github.com/#{repository_url}"
elsif gitlab?
"https://gitlab.com/#{repository_url}"
else
"https://hub.docker.com/r/#{repository_url}"
end
end
end
@@ -134,14 +150,22 @@ class Project < ApplicationRecord
services.each(&:updated!)
end
def container_tag
if forked?
branch.gsub("/", "-")
else
"latest"
end
end
def container_registry_url
container_registry = self.attributes["container_registry_url"].presence || repository_url
if github?
"ghcr.io/#{container_registry}:latest"
"ghcr.io/#{container_registry}:#{container_tag}"
elsif gitlab?
"registry.gitlab.com/#{container_registry}:latest"
"registry.gitlab.com/#{container_registry}:#{container_tag}"
else
"docker.io/#{container_registry}:latest"
"docker.io/#{container_registry}:#{container_tag}"
end
end
@@ -155,14 +179,14 @@ class Project < ApplicationRecord
end
def show_fork_options?
!preview? && git?
!forked? && git?
end
def can_fork?
show_fork_options? && !forks_disabled?
end
def preview?
def forked?
child_fork.present?
end
end
+41
View File
@@ -0,0 +1,41 @@
class CanineConfig::Definition
attr_reader :definition
def initialize(yaml_path, base_project, pull_request)
context = {
"cluster_id": base_project.project_fork_cluster_id,
"cluster_name": base_project.project_fork_cluster.name,
"project_name": "#{base_project.name}-#{pull_request.number}",
"number": pull_request.number,
"title": pull_request.title,
"branch_name": pull_request.branch,
"username": pull_request.user
}
content = if yaml_path.to_s.end_with?('.erb')
erb = ERB.new(File.read(yaml_path))
context_binding = binding
context.each do |key, value|
context_binding.local_variable_set(key, value)
end
erb.result(context_binding)
else
File.read(yaml_path)
end
@definition = YAML.load(content)
end
def services
definition['services'].map do |service|
params = Service.permitted_params(ActionController::Parameters.new(service:))
Service.new(params)
end
end
def environment_variables
definition['environment_variables'].map do |env|
EnvironmentVariable.new(name: env['name'], value: env['value'])
end
end
end
-21
View File
@@ -1,21 +0,0 @@
class ReviewApp::Definition
def initialize(yaml_path, context = {})
content = if yaml_path.end_with?('.erb')
require 'erb'
erb = ERB.new(File.read(yaml_path))
context_binding = binding
context.each { |key, value| context_binding.local_variable_set(key, value) }
erb.result(context_binding)
else
File.read(yaml_path)
end
@definition = YAML.load(content)
end
def to_yaml
{
name: @project.name
}
end
end
+1 -1
View File
@@ -47,7 +47,7 @@
<ul>
<% current_account.projects.order(created_at: :desc).each do |project| %>
<li>
<%= link_to root_projects_path(project), class: "hover:bg-base-content/15 #{'active' if current_page?(root_projects_path(project))}" do %>
<%= link_to root_projects_path(project), class: "hover:bg-base-content/15 #{'active' if request.path.start_with?("/projects/#{project.id}")}" do %>
<div class="flex items-center gap-2">
<%= project.name %>
</div>
+6 -1
View File
@@ -1,3 +1,8 @@
<% if project.forked? %>
<div class="mb-2">
<%= link_to "← Back to original project", project_path(project.parent_project), class: "btn btn-ghost" %>
</div>
<% end %>
<div class="flex flex-col md:flex-row items-center justify-between mb-4">
<div class="self-stretch">
<div>
@@ -15,7 +20,7 @@
</div>
<% end %>
<div class="text-sm">
<%= link_to project.full_repository_url, target: "_blank" do %>
<%= link_to project.link_to_view, target: "_blank" do %>
<% if project.git? %>
<% if project.github? %>
<iconify-icon icon="lucide:github"></iconify-icon>
@@ -0,0 +1,10 @@
name: "name"
services:
- name: "service_1"
container_port: 8080
service_type: "web_service"
environment_variables:
- name: "DATABASE_URL"
value: "redis://redis.cluster.svc.local/<%= number %>"
scripts:
clean_up_command: ""
+2 -2
View File
@@ -82,10 +82,10 @@ RSpec.describe Project, type: :model do
end
end
describe '#full_repository_url' do
describe '#link_to_view' do
it 'returns the full GitHub URL' do
project.repository_url = 'owner/repository-name'
expect(project.full_repository_url).to eq('https://github.com/owner/repository-name')
expect(project.link_to_view).to eq('https://github.com/owner/repository-name')
end
end
@@ -0,0 +1,38 @@
require 'rails_helper'
RSpec.describe CanineConfig::Definition do
let(:yaml_path) { Rails.root.join('resources', 'canine_config', 'example_1.yaml.erb') }
let(:account) { create(:account) }
let(:cluster) { create(:cluster, account:) }
let(:base_project) { create(:project, project_fork_cluster_id: cluster.id, account:) }
let(:pull_request) do
Git::Common::PullRequest.new(
number: 42,
title: 'Test PR',
branch: 'feature/test',
user: 'testuser'
)
end
subject(:definition) { described_class.new(yaml_path, base_project, pull_request) }
describe '#environment_variables' do
it 'returns environment variables from the definition' do
env_vars = definition.environment_variables
expect(env_vars.count).to eq(1)
expect(env_vars.first.name).to eq('DATABASE_URL')
expect(env_vars.first.value).to eq('redis://redis.cluster.svc.local/42')
end
end
describe '#services' do
it 'returns services from the definition' do
services = definition.services
expect(services.count).to eq(1)
expect(services.first.name).to eq('service_1')
expect(services.first.container_port).to eq(8080)
end
end
end