Files
ubicloud/bin/regen-screenshots
Jeremy Evans 4b819d3cb2 Change all create_with_id to create
It hasn't been necessary to use create_with_id since
ebc79622df, in December 2024.

I have plans to introduce:

```ruby
def create_with_id(id, values)
  obj = new(values)
  obj.id = id
  obj.save_changes
end
```

This will make it easier to use the same id when creating
multiple objects.  The first step is removing the existing
uses of create_with_id.
2025-08-06 01:55:51 +09:00

481 lines
15 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
DOCUMENTATION_DIR = "../documentation/"
unless File.directory?(DOCUMENTATION_DIR)
warn "Documentation site must be checked out in ../documentation"
exit(1)
end
require "find"
require "capybara"
require "capybara/dsl"
require "capybara/cuprite"
require "puma/cli"
require "nio"
require "securerandom"
ENV["RACK_ENV"] = "test"
# Enable shared connections for Sequel DB, to allow server and screenshot code to share the same transaction
ENV["SHARED_CONNECTION"] = "1"
# Set fake stripe secret key in order to take screenshots of billing pages
ENV["STRIPE_SECRET_KEY"] = "1"
# Set fake GitHub app name in order to take screenshots of GitHub Runner pages
ENV["GITHUB_APP_NAME"] = "1"
require_relative "../loader"
PORT = 8383
db_name = DB.get { current_database.function }
raise "Doesn't look like a test database (#{db_name}), not generating screenshots" unless db_name.end_with?("test")
Capybara.exact = true
Capybara.default_selector = :css
Capybara.default_driver = :cuprite
Capybara.server_port = PORT
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800], browser_options: {timeout: 15}, base_url: "http://localhost:#{PORT}")
end
require "tilt/erubi"
require "tilt/string"
queue = Queue.new
server = Puma::CLI.new(["-s", "-e", "test", "-b", "tcp://localhost:#{PORT}", "-t", "1:1", "config.ru"])
server.launcher.events.on_booted { queue.push(nil) }
Thread.new do
server.launcher.run
end
queue.pop
::Mail.defaults do
delivery_method :test
end
class RegenScreenshots
include Capybara::DSL
SCREENSHOTS = {}
Find.find(DOCUMENTATION_DIR) do |file|
if File.file?(file) && file.end_with?("screenshot.png")
SCREENSHOTS[file.delete_prefix(DOCUMENTATION_DIR)] = file
end
end
def screenshot(filename)
unless (path = SCREENSHOTS.delete(filename))
raise "No existing screenshot for #{filename} in documentation site"
end
# rubocop:disable Lint/Debugger
save_screenshot(path:)
# rubocop:enable Lint/Debugger
puts "Saved screenshot: #{filename}"
end
def resize(height, width: 1200)
Capybara.current_session.driver.browser.resize(width:, height:)
end
def call
visit "/"
click_link "Create a new account"
password = SecureRandom.base64(48)
fill_in "Full Name", with: "Demo"
fill_in "Email Address", with: "demo@example.com"
fill_in "Password", with: password
fill_in "Password Confirmation", with: password
click_button "Create Account"
mail = Mail::TestMailer.deliveries.shift
body = mail.parts[1].decoded
unless (match = %r{(/verify-account\?key=[^"]+)"}.match(body))
raise "no verify link in email"
end
visit match[1]
click_button "Verify Account"
project = Project.first
resize(900, width: 1600)
visit "/"
screenshot "quick-start/managed-services-1-screenshot.png"
find("#billing-icon").hover
screenshot "github-actions-integration/quickstart-1-screenshot.png"
Project.define_method(:has_valid_payment_method?) { true }
click_link "GitHub Runners"
screenshot "github-actions-integration/quickstart-2-screenshot.png"
GithubInstallation.create(installation_id: 123, name: "ubicloud-demo", type: "Organization", project_id: project.id, allocator_preferences: {"family_filter" => ["premium", "standard"]})
page.refresh
click_link "Settings", class: "text-gray-500"
screenshot "github-actions-integration/use-premium-runners-1-screenshot.png"
resize(650)
click_link "Tokens"
screenshot "quick-start/cli-1-screenshot.png"
click_button "Create Token"
ApiKey.dataset.update(id: "bf444ee6-2532-8153-975e-af787dbc796e")
page.refresh
screenshot "quick-start/cli-2-screenshot.png"
click_link "PostgreSQL"
screenshot "managed-postgresql/overview-1-screenshot.png"
resize(1400)
click_link "Create PostgreSQL Database"
screenshot "managed-postgresql/create-screenshot.png"
screenshot "quick-start/using-kamal-with-ubicloud-3-screenshot.png"
pg_resource = PostgresResource.create(
name: "postgresql-demo",
location_id: Location::HETZNER_FSN1_ID,
target_vm_size: "standard-2",
target_storage_size_gib: 10,
superuser_password: "1",
project_id: project.id
) do |pg|
pg.id = UBID.parse("pgmjy3v4ef1y7gdpzv6b3fchef").to_uuid
end
Project.define_method(:postgres_resources_dataset) do |*a|
super(*a).with_extend do
define_method(:all) { [pg_resource] }
define_method(:first) { |*| pg_resource }
end
end
pg_vm = Vm.create(
name: "postgresql-demo-vm",
memory_gib: 8,
vcpus: 2,
cores: 2,
location_id: Location::HETZNER_FSN1_ID,
project_id: project.id,
unix_user: "postgres",
public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7b8ZxEjGHV54sF4z/7H5z9hGJtYI5RvV2kz8KjQhYJvQG8W1vZK8dG9vLWJyZWFrZXItdGVzdEBleGFtcGxlLmNvbYIxP634e6Y0p2FQI+CvLVgNxYqaQZxjx84+SN/XY4FOR4",
boot_image: "ubuntu-jammy",
family: "standard"
)
pg_timeline = PostgresTimeline.create(
location_id: Location::HETZNER_FSN1_ID
) do |timeline|
timeline.id = UBID.parse("ptmjy3v4ef1y7gdpzv6b3fchef").to_uuid
end
pg_server = PostgresServer.create(
resource_id: pg_resource.id,
vm_id: pg_vm.id,
timeline_id: pg_timeline.id
) do |server|
server.id = UBID.parse("pvmjy3v4ef1y7gdpzv6b3fchef").to_uuid
end
PostgresResource.define_method(:display_state) { "running" }
# Add default firewall rule to the postgres instance
PostgresFirewallRule.create(postgres_resource_id: pg_resource.id, cidr: "0.0.0.0/0")
PostgresResource.define_method(:connection_string) { "sample-connection-string" }
PostgresResource.define_method(:ca_certificates) { "certs" }
PostgresResource.define_method(:representative_server) { pg_server }
PostgresResource.define_method(:user_config) { {"max_connections" => 1000, "work_mem" => "64MB"} }
PostgresResource.define_method(:pgbouncer_user_config) { {"default_pool_size" => 50} }
Authorization.define_singleton_method(:authorize) { |*| }
PostgresServer.define_method(:storage_size_gib) { 128 }
# Create VictoriaMetrics resources for metrics display
vmr = VictoriaMetricsResource.create(
name: "victoria-metrics-demo",
admin_user: "admin",
admin_password: "password",
target_vm_size: "standard-2",
target_storage_size_gib: 100,
project_id: project.id,
location_id: Location::HETZNER_FSN1_ID
)
vm_metrics = Vm.create(
name: "victoria-metrics-vm",
memory_gib: 8,
vcpus: 2,
cores: 2,
location_id: Location::HETZNER_FSN1_ID,
project_id: project.id,
unix_user: "ubi",
public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7b8ZxEjGHV54sF4z/7H5z9hGJtYI5RvV2kz8KjQhYJvQG8W1vZK8dG9vLWJyZWFrZXItdGVzdEBleGFtcGxlLmNvbYIxP634e6Y0p2FQI+CvLVgNxYqaQZxjx84+SN/XY4FOR4",
boot_image: "ubuntu-jammy",
family: "standard"
)
VictoriaMetricsServer.create(
victoria_metrics_resource_id: vmr.id,
vm_id: vm_metrics.id,
cert: "cert-data",
cert_key: "cert-key-data"
)
# Mock the VictoriaMetrics client to return sample data
mock_client = Object.new
def mock_client.query_range(query:, start_ts:, end_ts:)
# Use a separate RNG for VictoriaMetrics metrics to ensure consistent results
metrics_rng = Random.new(1234)
step = 60 # 1 minute intervals
timestamps = (start_ts..end_ts).step(step).to_a
case query
when /node_filesystem_avail_bytes/
# Return disk usage around 45%
values = timestamps.map { |ts| [ts, metrics_rng.rand(40..49).to_s] }
[{
"labels" => {},
"values" => values
}]
when /node_cpu_seconds_total/
[{
"labels" => {"mode" => "user"},
"values" => timestamps.map { |ts| [ts, metrics_rng.rand(15..64).to_s] }
},
{
"labels" => {"mode" => "system"},
"values" => timestamps.map { |ts| [ts, metrics_rng.rand(8..12).to_s] }
},
{
"labels" => {"mode" => "iowait"},
"values" => timestamps.map { |ts| [ts, metrics_rng.rand(2..4).to_s] }
}]
else
# Default sample data
values = timestamps.map { |ts| [ts, metrics_rng.rand(20..39).to_s] }
[{
"labels" => {},
"values" => values
}]
end
end
VictoriaMetricsServer.define_method(:client) { |*| mock_client }
# Mock the metrics_config method on PostgresServer to return the correct project_id
PostgresServer.define_method(:metrics_config) { {project_id: project.id} }
resize(900, width: 1600)
click_link "PostgreSQL"
click_link "postgresql-demo"
sleep 1 # Wait for metrics charts to load.
screenshot "managed-postgresql/overview-2-screenshot.png"
resize(800, width: 1600)
click_link "Connection"
screenshot "managed-postgresql/connection-screenshot.png"
resize(800, width: 1600)
within("aside nav") do
click_link "Networking"
end
screenshot "managed-postgresql/networking-screenshot.png"
resize(1200, width: 1600)
click_link "Resize"
screenshot "managed-postgresql/resizing-screenshot.png"
resize(850, width: 1600)
click_link "Configuration"
screenshot "managed-postgresql/configuration-screenshot.png"
resize(1000, width: 1600)
within("aside nav") do
click_link "Settings"
end
screenshot "managed-postgresql/settings-screenshot.png"
click_link "Kubernetes"
click_link "Create Kubernetes Cluster"
fill_in "Cluster Name", with: "kubernetes-demo"
find('input[name="cp_nodes"][value="3"]:not([disabled])').trigger("click")
find('select#worker_nodes option[value="5"]:not([disabled])').select_option
resize(900)
screenshot "managed-kubernetes/create-screenshot.png"
kc_ubid = "kcj4veqfy46a4pcxpmj3dzwzf5"
kn_ubid = "kna6c1cyxqba5pbmfthkb0xawa"
k8s_cluster = KubernetesCluster.create(
name: "kubernetes-demo",
version: "v1.32",
cp_node_count: 3,
location_id: Location::HETZNER_FSN1_ID,
target_node_size: "standard-2",
private_subnet_id: Prog::Vnet::SubnetNexus.assemble(
project.id,
name: "#{kc_ubid}-subnet",
location_id: Location::HETZNER_FSN1_ID,
ipv4_range: Prog::Vnet::SubnetNexus.random_private_ipv4(Location[Location::HETZNER_FSN1_ID], project, 18).to_s
).subject.id,
project_id: project.id
) do |kc|
kc.id = UBID.parse(kc_ubid).to_uuid
end
k8s_nodepool = KubernetesNodepool.create(
name: "kubernetes-demo-np",
node_count: 5,
kubernetes_cluster_id: k8s_cluster.id,
target_node_size: "standard-2"
) do |kn|
kn.id = UBID.parse(kn_ubid).to_uuid
end
services_lb = Prog::Vnet::LoadBalancerNexus.assemble(
k8s_cluster.private_subnet_id,
name: k8s_cluster.services_load_balancer_name,
algorithm: "hash_based",
src_port: 443,
dst_port: 6443,
health_check_endpoint: "/",
health_check_protocol: "tcp",
stack: LoadBalancer::Stack::IPV4
).subject
# Associate the load balancer with the Kubernetes cluster
k8s_cluster.update(services_lb_id: services_lb.id)
["le9ec", "e12en", "zd3ka"].each do |suffix|
k8s_cluster.add_cp_vm Prog::Vm::Nexus.assemble_with_sshable(
project.id,
sshable_unix_user: "ubi",
name: "#{kc_ubid}-#{suffix}",
location_id: k8s_cluster.location.id,
private_subnet_id: k8s_cluster.private_subnet_id,
enable_ip4: true
).subject
end
["ubicl", "krn71", "fysgd", "mant4", "m0h11"].each do |suffix|
k8s_nodepool.add_vm Prog::Vm::Nexus.assemble_with_sshable(
project.id,
sshable_unix_user: "ubi",
name: "#{kn_ubid}-#{suffix}",
location_id: k8s_cluster.location.id,
private_subnet_id: k8s_cluster.private_subnet_id,
enable_ip4: true
).subject
end
Project.define_method(:kubernetes_clusters_dataset) do |*a|
super(*a).with_extend do
define_method(:all) { [k8s_cluster] }
define_method(:first) { |*| k8s_cluster }
end
end
KubernetesCluster.define_method(:display_state) { "running" }
Vm.define_method(:display_state) { "running" }
LoadBalancer.define_method(:hostname) { "f9f2x4td37-services.k8s.ubicloud.com" }
click_link "Kubernetes"
click_link "kubernetes-demo"
resize(1400)
screenshot "managed-kubernetes/overview-screenshot.png"
resize(800)
click_link "Users"
fill_in "email", with: "other@example.com"
find("input[name=email]").click
screenshot "security/users-1-screenshot.png"
account2 = Account.create(email: "other@example.com", name: "Other")
click_button "Invite"
page.refresh
page.evaluate_script("$('#user_policy_#{account2.ubid}').focus()")
screenshot "security/users-2-screenshot.png"
click_link "Access Control"
access_control_path = page.current_path
screenshot "security/access-control-1-screenshot.png"
click_link "subject-tags-link"
screenshot "security/subject-tag-1-screenshot.png"
fill_in "name", with: "System-Admins"
click_button "Create"
fill_in "name", with: "Network-Admins"
click_button "Create"
page.refresh
fill_in "name", with: "Database-Admins"
page.evaluate_script("$('#name').focus()")
screenshot "security/subject-tag-2-screenshot.png"
click_button "Create"
resize(1020)
click_link "#{SubjectTag[name: "System-Admins"].ubid}-edit"
screenshot "security/subject-tag-3-screenshot.png"
resize(1140)
check "add[]-#{account2.ubid}-0"
click_button "Add Members"
page.refresh
screenshot "security/subject-tag-4-screenshot.png"
resize(800)
visit access_control_path
click_link "action-tags-link"
fill_in "name", with: "Networking"
click_button "Create"
page.refresh
screenshot "security/action-tag-1-screenshot.png"
resize(1140)
click_link "Manage"
check "add[]-tazzzzzzzz021gzzzz0fw0a110-0"
check "add[]-tazzzzzzzz021gzzzz01b0a111-0"
check "add[]-tazzzzzzzz021gzzzz0ps0a111-0"
screenshot "security/action-tag-2-screenshot.png"
resize(800)
click_button "Add Members"
page.refresh
screenshot "security/action-tag-3-screenshot.png"
resize(850)
visit access_control_path
3.times do
click_button "New Access Control Entry"
end
screenshot "security/access-control-2-screenshot.png"
select "System-Admins", from: "ace-select-1-0"
select "Vm:all", from: "ace-select-1-1"
select "Network-Admins", from: "ace-select-2-0"
select "Networking", from: "ace-select-2-1"
select "Database-Admins", from: "ace-select-3-0"
select "Postgres:all", from: "ace-select-3-1"
screenshot "security/access-control-3-screenshot.png"
resize(1800)
click_link "Compute"
click_link "Create Virtual Machine"
screenshot "quick-start/managed-services-2-screenshot.png"
end
end
DB.transaction(rollback: :always, auto_savepoint: true) do |conn|
DB.temporarily_release_connection(conn) do
RegenScreenshots.new.call
end
end
unless RegenScreenshots::SCREENSHOTS.empty?
warn "Missing screenshots:", RegenScreenshots::SCREENSHOTS.keys.sort
exit(1)
end