video-iac/scripts/sync-gitea-repos.rb

135 lines
3.7 KiB
Ruby

#!/usr/bin/env ruby
require 'net/http'
require 'json'
require 'uri'
require 'yaml'
require 'optparse'
# Configuration mapping
ENV_CONFIG = {
'stg' => {
url: "https://git.staging.jamkazam.com/api/v1",
token_var: "GITEA_TOKEN_STG"
},
'prd' => {
url: "https://git.jamkazam.com/api/v1",
token_var: "GITEA_TOKEN_PRD"
}
}
options = { env: 'stg' }
OptionParser.new do |opts|
opts.banner = "Usage: ruby sync-gitea-repos.rb [options] <bitbucket_user> <bitbucket_pass>"
opts.on("-e", "--env ENV", "Environment (stg or prd, default stg)") { |v| options[:env] = v }
end.parse!
if ARGV.length < 2
puts "❌ Error: Missing Bitbucket credentials."
puts "Usage: ruby sync-gitea-repos.rb --env [stg|prd] <bb_user> <bb_pass>"
exit 1
end
BB_USER = ARGV[0]
BB_PASS = ARGV[1]
# Select config
config = ENV_CONFIG[options[:env]]
if config.nil?
puts "❌ Error: Invalid environment '#{options[:env]}'. Use 'stg' or 'prd'."
exit 1
end
# Resolve Token
GITEA_TOKEN = ENV[config[:token_var]] || ENV['GITEA_TOKEN']
GITEA_URL = config[:url]
GITEA_OWNER = "seth"
MANIFEST_FILE = File.expand_path('gitea-repos.yaml', __dir__)
if GITEA_TOKEN.nil? || GITEA_TOKEN.empty?
puts "❌ Error: API Token environment variable '#{config[:token_var]}' is not set."
exit 1
end
def gitea_request(method, path, payload = nil)
uri = URI.parse("#{GITEA_URL}#{path}")
header = {
'Content-Type' => 'application/json',
'Authorization' => "token #{GITEA_TOKEN}"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 300
case method
when :get then req = Net::HTTP::Get.new(uri.request_uri, header)
when :post then req = Net::HTTP::Post.new(uri.request_uri, header); req.body = payload.to_json if payload
when :delete then req = Net::HTTP::Delete.new(uri.request_uri, header)
end
http.request(req)
end
puts "🚀 Reconciling Gitea mirrors for #{options[:env].upcase}..."
puts "🔗 API: #{GITEA_URL}"
# 1. Load Manifest
manifest = YAML.load_file(MANIFEST_FILE)
desired_repos = manifest['repositories']
desired_names = desired_repos.map { |r| r['name'] }
# 2. Get Current State from Gitea
puts "🔍 Fetching current repositories..."
resp = gitea_request(:get, "/users/#{GITEA_OWNER}/repos")
if resp.code != "200"
puts "❌ Failed to fetch repos: #{resp.code} #{resp.body}"
exit 1
end
current_repos = JSON.parse(resp.body)
current_names = current_repos.map { |r| r['name'] }
# 3. Reconcile: Delete repos not in manifest
repos_to_delete = current_names - desired_names
repos_to_delete.each do |name|
puts "🗑️ Deleting repository '#{name}' (not in manifest)..."
gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}")
end
# 4. Reconcile: Create/Update mirrors
desired_repos.each do |repo|
name = repo['name']
url = repo['url']
if current_names.include?(name)
repo_info = current_repos.find { |r| r['name'] == name }
if repo_info['empty']
puts "⚠️ Mirror '#{name}' exists but is EMPTY. Recreating..."
gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}")
else
puts "✅ Mirror '#{name}' already exists. Skipping."
next
end
end
puts "🚀 Creating mirror for '#{name}'..."
payload = {
"clone_addr" => url,
"auth_username" => BB_USER,
"auth_password" => BB_PASS,
"repo_name" => name,
"mirror" => true,
"mirror_interval" => "10m",
"service" => "bitbucket",
"repo_owner" => GITEA_OWNER,
"lfs" => true,
"releases" => true
}
res = gitea_request(:post, "/repos/migrate", payload)
if res.code == "201"
puts "✨ Successfully created mirror for '#{name}'."
else
puts "❌ Failed to create mirror for '#{name}': #{res.code} #{res.body}"
end
end
puts "\n🎯 Reconcile complete for #{options[:env].upcase}."