Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gem 'rake', require: false
group :test do
gem 'codeclimate-test-reporter'
gem 'pry'
gem 'rexml'
gem 'rubocop'
end
Comment on lines 9 to 14
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rexml is added only in the :test group, but the PR description indicates it's required by newer aws-sdk-core (a runtime dependency). If this gem is needed at runtime, it should be added as a normal dependency (e.g., in the gemspec) or outside the :test group so non-test installs don't break.

Copilot uses AI. Check for mistakes.

Expand Down
7 changes: 5 additions & 2 deletions lib/moonshot/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ def ssh
@config.ssh_instance ||= SSHTargetSelector.new(
stack, asg_name: @config.ssh_auto_scaling_group_name
).choose!
cb = SSHCommandBuilder.new(@config.ssh_config, @config.ssh_instance)
teleport_config = TeleportConfig.new(
stack.name, @config.ssh_config.ssh_user, ENV['AWS_REGION']
Comment on lines +189 to +190
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AWS_REGION environment variable is accessed without validation. If ENV['AWS_REGION'] is nil or empty, this will create a TeleportConfig with an empty string for the region parameter (due to .to_s conversion), which could lead to incorrect proxy URLs in production environments (e.g., ".teleport.cloudservices.acquia.io") and malformed hostnames. Consider adding validation to raise a clear error message if AWS_REGION is not set, similar to how CodeDeploySetup handles this case.

Suggested change
teleport_config = TeleportConfig.new(
stack.name, @config.ssh_config.ssh_user, ENV['AWS_REGION']
region = ENV['AWS_REGION'].to_s.strip
if region.empty?
raise 'AWS_REGION environment variable must be set (for example, "us-east-1") to establish a Teleport SSH connection.'
end
teleport_config = TeleportConfig.new(
stack.name, @config.ssh_config.ssh_user, region

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +190
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If @config.ssh_config.ssh_user is nil (for example, if neither the environment variable MOONSHOT_SSH_USER nor a configuration value is set), it will be converted to an empty string by TeleportConfig. This will result in an invalid Teleport hostname format like '@i-xxx.region.account' (missing username) and bot_user? will always return false (since '' != 'clouddatabot'). Consider validating that ssh_user is set before constructing the TeleportConfig, or add validation within TeleportConfig.initialize.

Suggested change
teleport_config = TeleportConfig.new(
stack.name, @config.ssh_config.ssh_user, ENV['AWS_REGION']
ssh_user = @config.ssh_config.ssh_user
if ssh_user.nil? || ssh_user.to_s.empty?
raise 'SSH user is not configured. Please set MOONSHOT_SSH_USER or configure ssh_user in your Moonshot configuration.'
end
teleport_config = TeleportConfig.new(
stack.name, ssh_user, ENV['AWS_REGION']

Copilot uses AI. Check for mistakes.
)
cb = SSHCommandBuilder.new(@config.ssh_config, @config.ssh_instance, teleport_config)
result = cb.build(@config.ssh_command)

warn "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..."
warn "Opening SSH connection to #{@config.ssh_instance} (#{result.host})..."
exec(result.cmd)
end

Expand Down
29 changes: 13 additions & 16 deletions lib/moonshot/ssh_command_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,30 @@
require 'shellwords'

module Moonshot
# Create an ssh command from configuration.
# Create a tsh ssh command from configuration.
class SSHCommandBuilder
Result = Struct.new(:cmd, :ip)
Result = Struct.new(:cmd, :host)

def initialize(ssh_config, instance_id)
@config = ssh_config
@instance_id = instance_id
def initialize(ssh_config, instance_id, teleport_config)
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SSHCommandBuilder constructor signature has changed from 2 parameters (ssh_config, instance_id) to 3 parameters (ssh_config, instance_id, teleport_config). This is a breaking API change that could affect any external code or plugins that construct SSHCommandBuilder instances directly. While the code in this PR has been updated accordingly, consider whether this warrants a major version bump or additional documentation about the breaking change.

Copilot uses AI. Check for mistakes.
@config = ssh_config
@instance_id = instance_id
@teleport_config = teleport_config
end
Comment on lines +10 to 14
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSHCommandBuilder's initializer now requires a teleport_config, but there is still at least one in-repo caller using the old 2-arg form (lib/moonshot/tools/asg_rollout/hook_exec_environment.rb:16). This will raise ArgumentError at runtime; either update that caller to construct/pass a TeleportConfig, or make teleport_config optional with a backwards-compatible fallback.

Copilot uses AI. Check for mistakes.

def build(command = nil)
cmd = ['ssh', '-t']
cmd = ['tsh', 'ssh']
cmd << @config.ssh_options if @config.ssh_options
cmd << "-i #{@config.ssh_identity_file}" if @config.ssh_identity_file
cmd << "-l #{@config.ssh_user}" if @config.ssh_user
cmd << instance_ip
cmd << "--proxy=#{@teleport_config.proxy_url}"
cmd << "-i #{@teleport_config.identity_file}" if @teleport_config.bot_user?
cmd << "#{@teleport_config.ssh_user}@#{instance_host}"
Comment on lines +17 to +21
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSHCommandBuilder no longer adds any default TTY/agent-forwarding flags, but the updated specs expect -tA to always be present (similar to the old ssh -t behavior). If -tA is required for Teleport access, add it to the generated command (in the same position/order the specs expect) or ensure it is set consistently via configuration.

Copilot uses AI. Check for mistakes.
cmd << Shellwords.escape(command) if command
Result.new(cmd.join(' '), instance_ip)
Result.new(cmd.join(' '), instance_host)
end

private

def instance_ip
@instance_ip ||= Aws::EC2::Client.new
.describe_instances(instance_ids: [@instance_id])
.reservations.first.instances.first.public_ip_address
rescue StandardError
raise "Failed to determine public IP address for instance #{@instance_id}!"
def instance_host
@instance_host ||= @teleport_config.host_for(@instance_id)
end
end
end
57 changes: 57 additions & 0 deletions lib/moonshot/teleport_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Moonshot
# Encapsulates Teleport SSH configuration derived from the stack name,
# SSH user, and AWS region. Determines the correct proxy URL, account ID,
# and identity file path for both normal users and bot users.
class TeleportConfig
Comment on lines +4 to +7
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class documentation states it 'Encapsulates Teleport SSH configuration' but doesn't explain the key decision logic: how prod vs dev environments are determined (by checking if 'prod' appears in the stack name) or what account IDs these constants represent. Consider adding more detailed documentation explaining the environment detection logic, the meaning of the hardcoded account IDs, and example stack names that would be classified as prod vs dev.

Copilot uses AI. Check for mistakes.
PROD_ACCOUNT_ID = '546349603759'
DEV_ACCOUNT_ID = '672327909798'
Comment on lines +8 to +9
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded AWS account IDs (PROD_ACCOUNT_ID and DEV_ACCOUNT_ID) are exposed in the code. While account IDs are generally not considered highly sensitive secrets on their own, hardcoding them makes it difficult to support additional environments or accounts without code changes. Consider whether these should be configurable via environment variables or configuration files to improve flexibility and follow the principle of configuration over hardcoding.

Suggested change
PROD_ACCOUNT_ID = '546349603759'
DEV_ACCOUNT_ID = '672327909798'
PROD_ACCOUNT_ID = ENV['TELEPORT_PROD_ACCOUNT_ID'] || '546349603759'
DEV_ACCOUNT_ID = ENV['TELEPORT_DEV_ACCOUNT_ID'] || '672327909798'

Copilot uses AI. Check for mistakes.
BOT_USER = 'clouddatabot'

PROD_PROXY_TEMPLATE = '%<region>s.teleport.cloudservices.acquia.io'
DEV_PROXY = 'teleport.dev.cloudservices.acquia.io'

PROD_IDENTITY_TEMPLATE = '/opt/machine-id/%<region>s/identity'
DEV_IDENTITY = '/opt/machine-id/dev-us-east-1/identity'
Comment on lines +15 to +16
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeleportConfig's identity file constants use '/opt/machine-id/...', but the new spec expects 'tbot-auth-.../identity' paths for the bot user. As written, TeleportConfig#identity_file will not match the spec (and likely the intended runtime path). Please align the constants/logic and the spec so they agree on the actual identity file location.

Suggested change
PROD_IDENTITY_TEMPLATE = '/opt/machine-id/%<region>s/identity'
DEV_IDENTITY = '/opt/machine-id/dev-us-east-1/identity'
PROD_IDENTITY_TEMPLATE = '/opt/tbot-auth-%<region>s/identity'
DEV_IDENTITY = '/opt/tbot-auth-dev-us-east-1/identity'

Copilot uses AI. Check for mistakes.

attr_reader :proxy_url, :account_id, :region, :ssh_user

def initialize(stack_name, ssh_user, region)
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialize method converts nil parameters to empty strings via .to_s without validation. This leads to multiple issues: (1) empty region produces malformed URLs like '.teleport.cloudservices.acquia.io' and invalid identity paths like 'tbot-auth-/identity', (2) empty ssh_user produces invalid Teleport hostnames like '@instance.region.account', (3) empty stack_name will cause incorrect environment detection (will default to dev since 'prod' won't be found). Add validation to ensure all three parameters are present and non-empty before proceeding.

Suggested change
def initialize(stack_name, ssh_user, region)
def initialize(stack_name, ssh_user, region)
missing_params = []
missing_params << 'stack_name' if stack_name.to_s.strip.empty?
missing_params << 'ssh_user' if ssh_user.to_s.strip.empty?
missing_params << 'region' if region.to_s.strip.empty?
unless missing_params.empty?
raise ArgumentError, "TeleportConfig requires non-empty: #{missing_params.join(', ')}"
end

Copilot uses AI. Check for mistakes.
@stack_name = stack_name.to_s
@ssh_user = ssh_user.to_s
@region = region.to_s

Comment on lines +20 to +24
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeleportConfig coerces a nil region to an empty string via region.to_s, which can silently build an invalid proxy URL/hostname when AWS_REGION is unset. Previously this would fail earlier via AWS SDK missing-region behavior; consider validating region is present/non-empty and raising an ArgumentError when it isn't.

Copilot uses AI. Check for mistakes.
if prod?
@account_id = PROD_ACCOUNT_ID
@proxy_url = format(PROD_PROXY_TEMPLATE, region: @region)
else
@account_id = DEV_ACCOUNT_ID
@proxy_url = DEV_PROXY
end
end

def bot_user?
@ssh_user == BOT_USER
end

# Returns the Teleport identity file path for bot users; nil for normal users.
def identity_file
return nil unless bot_user?

prod? ? format(PROD_IDENTITY_TEMPLATE, region: @region) : DEV_IDENTITY
end

# Constructs the Teleport node name used as SSH hostname.
# Format: <instance_id>.<region>.<account_id>
def host_for(instance_id)
"#{instance_id}.#{@region}.#{@account_id}"
end

private

def prod?
@stack_name.include?('prod')
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prod? method uses a simple string inclusion check (include?('prod')) to determine the environment. This could lead to false positives if 'prod' appears anywhere in the stack name, not just as the environment identifier. For example, a stack named 'product-dev' or 'reproduce-test' would incorrectly be identified as production. Consider using a more robust pattern match or explicit environment detection.

Suggested change
@stack_name.include?('prod')
@stack_name.match?(/\bprod\b/)

Copilot uses AI. Check for mistakes.
end
end
end
3 changes: 2 additions & 1 deletion lib/plugins/rotate_asg_instances.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'aws-sdk'
require 'aws-sdk-autoscaling'
require 'aws-sdk-ec2'

module Moonshot
module Plugins
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/rotate_asg_instances/asg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ASG # rubocop:disable Metrics/ClassLength

def initialize(resources)
@resources = resources
@ssh = Moonshot::RotateAsgInstances::SSH.new
@ssh = Moonshot::RotateAsgInstances::SSH.new(@resources)
@ilog = @resources.ilog
end

Expand Down
11 changes: 10 additions & 1 deletion lib/plugins/rotate_asg_instances/ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ def initialize(response)
end

class SSH
def initialize(resources)
@resources = resources
end

# As per the standard it is raising correctly but still giving an error.
def test_ssh_connection(instance_id)
Retriable.retriable(base_interval: 5, tries: 3) do
Expand All @@ -29,7 +33,12 @@ def exec(command, instance_id)
private

def build_command(command, instance_id)
cb = SSHCommandBuilder.new(Moonshot.config.ssh_config, instance_id)
teleport_config = Moonshot::TeleportConfig.new(
@resources.controller.stack.name,
Moonshot.config.ssh_config.ssh_user,
Comment on lines +36 to +38
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Moonshot.config.ssh_config.ssh_user is nil (when neither the environment variable MOONSHOT_SSH_USER nor a configuration value is set), it will be converted to an empty string by TeleportConfig. This results in an invalid Teleport hostname format like '@i-xxx.region.account' (missing username) and bot_user? will always return false. Consider validating that ssh_user is set before constructing the TeleportConfig, or add validation within TeleportConfig.initialize.

Suggested change
teleport_config = Moonshot::TeleportConfig.new(
@resources.controller.stack.name,
Moonshot.config.ssh_config.ssh_user,
ssh_user = Moonshot.config.ssh_config.ssh_user
if ssh_user.nil? || ssh_user.strip.empty?
raise ArgumentError,
'SSH user is not configured. Set MOONSHOT_SSH_USER or ssh_config.ssh_user.'
end
teleport_config = Moonshot::TeleportConfig.new(
@resources.controller.stack.name,
ssh_user,

Copilot uses AI. Check for mistakes.
ENV['AWS_REGION']
Comment on lines +36 to +39
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AWS_REGION environment variable is accessed without validation. If ENV['AWS_REGION'] is nil or empty, this will create a TeleportConfig with an empty string for the region parameter (due to .to_s conversion), which could lead to incorrect proxy URLs in production environments (e.g., ".teleport.cloudservices.acquia.io") and malformed hostnames. Consider adding validation to raise a clear error message if AWS_REGION is not set, similar to how CodeDeploySetup handles this case.

Suggested change
teleport_config = Moonshot::TeleportConfig.new(
@resources.controller.stack.name,
Moonshot.config.ssh_config.ssh_user,
ENV['AWS_REGION']
region = ENV['AWS_REGION']
if region.nil? || region.empty?
raise 'AWS_REGION environment variable must be set for SSH teleport configuration'
end
teleport_config = Moonshot::TeleportConfig.new(
@resources.controller.stack.name,
Moonshot.config.ssh_config.ssh_user,
region

Copilot uses AI. Check for mistakes.
)
cb = SSHCommandBuilder.new(Moonshot.config.ssh_config, instance_id, teleport_config)
cb.build(command).cmd
end
end
Expand Down
17 changes: 12 additions & 5 deletions spec/moonshot/plugins/rotate_asg_instances/asg_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,32 @@ def stub_cf_client
end

describe '#shutdown_instance' do
let(:public_ip_address) { '10.234.32.21' }
let(:instance) { instance_double(Aws::EC2::Instance) }
let(:command_builder) { Moonshot::SSHCommandBuilder }
subject { super().send(:shutdown_instance, instance_id) }

before(:each) do
ENV['AWS_REGION'] = 'us-east-1'
moonshot_config.ssh_config.ssh_user = 'ci_user'
moonshot_config.ssh_config.ssh_options = ssh_options
allow(Aws::EC2::Instance).to receive(:new).and_return(instance)
allow_any_instance_of(command_builder).to receive(:instance_ip).and_return(public_ip_address)
allow(instance).to receive(:exists?).and_return(true)
allow(instance).to receive(:state).and_return({ name: 'running' })
allow(instance).to receive(:wait_until_stopped)
allow(ilog).to receive(:info)
end

after(:each) do
ENV.delete('AWS_REGION')
end

context 'when ssh_options are not defined' do
let(:ssh_options) { nil }

it 'issues a shutdown without options to the instance' do
expect_any_instance_of(ssh_executor).to receive(:run).with(
"ssh -t -l #{moonshot_config.ssh_config.ssh_user} #{public_ip_address} sudo\\ shutdown\\ -h\\ now"
'tsh ssh --proxy=teleport.dev.cloudservices.acquia.io -tA ' \
"ci_user@#{instance_id}.us-east-1.672327909798 sudo\\ shutdown\\ -h\\ now"
)
subject
end
Expand All @@ -143,8 +149,9 @@ def stub_cf_client

it 'issues a shutdown with options to the instance' do
expect_any_instance_of(ssh_executor).to receive(:run).with(
'ssh -t -v -o UserKnownHostsFile=/dev/null ' \
"-l ci_user #{public_ip_address} sudo\\ shutdown\\ -h\\ now"
'tsh ssh -v -o UserKnownHostsFile=/dev/null ' \
'--proxy=teleport.dev.cloudservices.acquia.io -tA ' \
"ci_user@#{instance_id}.us-east-1.672327909798 sudo\\ shutdown\\ -h\\ now"
)
subject
end
Expand Down
2 changes: 1 addition & 1 deletion spec/moonshot/plugins/rotate_asg_instances/ssh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

let(:config) { resources.controller.config }

subject { described_class.new }
subject { described_class.new(resources) }

describe '#test_ssh_connection' do
it 'raise error if #test_ssh_connection fails' do
Expand Down
25 changes: 14 additions & 11 deletions spec/moonshot/ssh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@
c.app_name = 'MyApp'
c.environment_name = 'prod'
c.ssh_config.ssh_user = 'joeuser'
c.ssh_config.ssh_identity_file = '/Users/joeuser/.ssh/thegoods.key'
c.ssh_command = 'cat /etc/passwd'
Moonshot::Controller.new(c)
end

let(:stack_double) { instance_double(Moonshot::Stack, name: 'MyApp-prod') }

describe 'Moonshot::Controller#ssh' do
before(:each) do
ENV.delete('MOONSHOT_SSH_OPTIONS')
ENV['AWS_REGION'] = 'us-east-1'
allow(subject).to receive(:stack).and_return(stack_double)
end

after(:each) do
ENV.delete('AWS_REGION')
end

context 'normally' do
it 'should execute an ssh command with proper parameters' do
it 'should execute a tsh ssh command with proper parameters' do
ts = instance_double(Moonshot::SSHTargetSelector)
expect(Moonshot::SSHTargetSelector).to receive(:new).and_return(ts)
expect(ts).to receive(:choose!).and_return('i-04683a82f2dddcc04')

expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
.and_return('123.123.123.123')
expect(subject).to receive(:exec)
.with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength
.with('tsh ssh --proxy=us-east-1.teleport.cloudservices.acquia.io -tA joeuser@i-04683a82f2dddcc04.us-east-1.546349603759 cat\ /etc/passwd') # rubocop:disable LineLength
expect { subject.ssh }
.to output("Opening SSH connection to i-04683a82f2dddcc04 (123.123.123.123)...\n")
.to output("Opening SSH connection to i-04683a82f2dddcc04 (i-04683a82f2dddcc04.us-east-1.546349603759)...\n")
.to_stderr
end
end
Expand All @@ -37,13 +42,11 @@
c
end

it 'should execute an ssh command with proper parameters' do
expect_any_instance_of(Moonshot::SSHCommandBuilder).to receive(:instance_ip).exactly(2)
.and_return('123.123.123.123')
it 'should execute a tsh ssh command with proper parameters' do
expect(subject).to receive(:exec)
.with('ssh -t -i /Users/joeuser/.ssh/thegoods.key -l joeuser 123.123.123.123 cat\ /etc/passwd') # rubocop:disable LineLength
.with('tsh ssh --proxy=us-east-1.teleport.cloudservices.acquia.io -tA joeuser@i-012012012012012.us-east-1.546349603759 cat\ /etc/passwd') # rubocop:disable LineLength
expect { subject.ssh }
.to output("Opening SSH connection to i-012012012012012 (123.123.123.123)...\n").to_stderr
.to output("Opening SSH connection to i-012012012012012 (i-012012012012012.us-east-1.546349603759)...\n").to_stderr
end
end
end
Expand Down
75 changes: 75 additions & 0 deletions spec/moonshot/teleport_config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
describe Moonshot::TeleportConfig do
let(:instance_id) { 'i-0036e48e43b79740f' }

describe 'prod environment (stack name contains "prod")' do
subject { described_class.new('myapp-prod-us-east-1', 'joeuser', 'us-east-1') }

it 'uses region-based proxy URL' do
expect(subject.proxy_url).to eq('us-east-1.teleport.cloudservices.acquia.io')
end

it 'uses the prod account ID' do
expect(subject.account_id).to eq('546349603759')
end

it 'builds the Teleport hostname correctly' do
expect(subject.host_for(instance_id)).to eq(
'i-0036e48e43b79740f.us-east-1.546349603759'
)
end

it 'is not a bot user for a normal user' do
expect(subject.bot_user?).to be false
end

it 'returns nil identity_file for normal user' do
expect(subject.identity_file).to be_nil
end

context 'with bot user (clouddatabot)' do
subject { described_class.new('myapp-prod-us-east-1', 'clouddatabot', 'us-east-1') }

it 'is identified as a bot user' do
expect(subject.bot_user?).to be true
end

it 'uses the region-based identity file path' do
expect(subject.identity_file).to eq('/opt/machine-id/us-east-1/identity')
end
end
end

describe 'dev environment (stack name does not contain "prod")' do
subject { described_class.new('myapp-dev-jsmith', 'joeuser', 'us-east-1') }

it 'uses the shared dev proxy URL' do
expect(subject.proxy_url).to eq('teleport.dev.cloudservices.acquia.io')
end

it 'uses the dev account ID' do
expect(subject.account_id).to eq('672327909798')
end

it 'builds the Teleport hostname correctly' do
expect(subject.host_for(instance_id)).to eq(
'i-0036e48e43b79740f.us-east-1.672327909798'
)
end

it 'is not a bot user for a normal user' do
expect(subject.bot_user?).to be false
end

context 'with bot user (clouddatabot)' do
subject { described_class.new('myapp-dev-jsmith', 'clouddatabot', 'us-east-1') }

it 'is identified as a bot user' do
expect(subject.bot_user?).to be true
end

it 'uses the dev identity file path' do
expect(subject.identity_file).to eq('/opt/machine-id/dev-us-east-1/identity')
end
end
end
end