firespring_dev_commands 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/lib/firespring_dev_commands/audit/report/item.rb +33 -0
- data/lib/firespring_dev_commands/audit/report/levels.rb +36 -0
- data/lib/firespring_dev_commands/audit/report.rb +49 -0
- data/lib/firespring_dev_commands/aws/account/info.rb +15 -0
- data/lib/firespring_dev_commands/aws/account.rb +164 -0
- data/lib/firespring_dev_commands/aws/cloudformation/parameters.rb +26 -0
- data/lib/firespring_dev_commands/aws/cloudformation.rb +188 -0
- data/lib/firespring_dev_commands/aws/codepipeline.rb +96 -0
- data/lib/firespring_dev_commands/aws/credentials.rb +136 -0
- data/lib/firespring_dev_commands/aws/login.rb +131 -0
- data/lib/firespring_dev_commands/aws/parameter.rb +32 -0
- data/lib/firespring_dev_commands/aws/profile.rb +55 -0
- data/lib/firespring_dev_commands/aws/s3.rb +42 -0
- data/lib/firespring_dev_commands/aws.rb +10 -0
- data/lib/firespring_dev_commands/boolean.rb +7 -0
- data/lib/firespring_dev_commands/common.rb +112 -0
- data/lib/firespring_dev_commands/daterange.rb +171 -0
- data/lib/firespring_dev_commands/docker/compose.rb +271 -0
- data/lib/firespring_dev_commands/docker/status.rb +38 -0
- data/lib/firespring_dev_commands/docker.rb +276 -0
- data/lib/firespring_dev_commands/dotenv.rb +6 -0
- data/lib/firespring_dev_commands/env.rb +38 -0
- data/lib/firespring_dev_commands/eol/product_version.rb +86 -0
- data/lib/firespring_dev_commands/eol.rb +58 -0
- data/lib/firespring_dev_commands/git/info.rb +13 -0
- data/lib/firespring_dev_commands/git.rb +420 -0
- data/lib/firespring_dev_commands/jira/issue.rb +33 -0
- data/lib/firespring_dev_commands/jira/project.rb +13 -0
- data/lib/firespring_dev_commands/jira/user/type.rb +20 -0
- data/lib/firespring_dev_commands/jira/user.rb +31 -0
- data/lib/firespring_dev_commands/jira.rb +78 -0
- data/lib/firespring_dev_commands/logger.rb +8 -0
- data/lib/firespring_dev_commands/node/audit.rb +39 -0
- data/lib/firespring_dev_commands/node.rb +107 -0
- data/lib/firespring_dev_commands/php/audit.rb +71 -0
- data/lib/firespring_dev_commands/php.rb +109 -0
- data/lib/firespring_dev_commands/rake.rb +24 -0
- data/lib/firespring_dev_commands/ruby/audit.rb +30 -0
- data/lib/firespring_dev_commands/ruby.rb +113 -0
- data/lib/firespring_dev_commands/second.rb +22 -0
- data/lib/firespring_dev_commands/tar/pax_header.rb +49 -0
- data/lib/firespring_dev_commands/tar/type_flag.rb +49 -0
- data/lib/firespring_dev_commands/tar.rb +149 -0
- data/lib/firespring_dev_commands/templates/aws.rb +84 -0
- data/lib/firespring_dev_commands/templates/base_interface.rb +54 -0
- data/lib/firespring_dev_commands/templates/ci.rb +138 -0
- data/lib/firespring_dev_commands/templates/docker/application.rb +177 -0
- data/lib/firespring_dev_commands/templates/docker/default.rb +200 -0
- data/lib/firespring_dev_commands/templates/docker/node/application.rb +145 -0
- data/lib/firespring_dev_commands/templates/docker/php/application.rb +190 -0
- data/lib/firespring_dev_commands/templates/docker/ruby/application.rb +146 -0
- data/lib/firespring_dev_commands/templates/eol.rb +23 -0
- data/lib/firespring_dev_commands/templates/git.rb +147 -0
- data/lib/firespring_dev_commands/version.rb +11 -0
- data/lib/firespring_dev_commands.rb +21 -0
- metadata +436 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'aws-sdk-codepipeline'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
class Aws
|
5
|
+
# Class for performing codepipeline functions
|
6
|
+
class CodePipeline
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@client = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create/set a new client if none is present
|
14
|
+
# Return the client
|
15
|
+
def client
|
16
|
+
@client ||= ::Aws::CodePipeline::Client.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a list of all pipelines in the aws account
|
20
|
+
# Optionally filter by the regex_match
|
21
|
+
def pipelines(regex_match = nil)
|
22
|
+
raise 'regex_match must be a regexp' if regex_match && !regex_match.is_a?(Regexp)
|
23
|
+
|
24
|
+
params = {}
|
25
|
+
response = client.list_pipelines(params)
|
26
|
+
pipelines = response.pipelines
|
27
|
+
|
28
|
+
next_token = response.next_token
|
29
|
+
while next_token
|
30
|
+
params[:next_token] = next_token
|
31
|
+
response = client.list_pipelines(params)
|
32
|
+
pipelines += response.pipelines
|
33
|
+
next_token = response.next_token
|
34
|
+
end
|
35
|
+
|
36
|
+
pipelines.select! { |it| it.name.match(regex_match) } if regex_match
|
37
|
+
pipelines
|
38
|
+
end
|
39
|
+
|
40
|
+
# Find all pipelines matching the regex_match and find the status for each of them
|
41
|
+
def status(name)
|
42
|
+
pipeline = client.get_pipeline_state(name: name)
|
43
|
+
print_pipeline_information(pipeline)
|
44
|
+
rescue ::Aws::CodePipeline::Errors::PipelineNotFoundException
|
45
|
+
LOG.error "No pipeline found with name #{name}".light_yellow
|
46
|
+
end
|
47
|
+
|
48
|
+
# Iterate over each pipeline stage and call the method to print stage information for each
|
49
|
+
private def print_pipeline_information(pipeline)
|
50
|
+
puts pipeline.pipeline_name.light_white
|
51
|
+
pipeline.stage_states.each do |stage_state|
|
52
|
+
print_stage_state_information(stage_state)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Print Stage information
|
57
|
+
# Iterate over each stage action state and call the method to print information for each
|
58
|
+
# Ignore the source steps because they are usually boring
|
59
|
+
private def print_stage_state_information(stage_state)
|
60
|
+
# Source step is not exciting - don't print it
|
61
|
+
return if stage_state.stage_name.to_s.strip == 'Source'
|
62
|
+
|
63
|
+
puts " Stage: #{stage_state.stage_name.light_blue}"
|
64
|
+
stage_state.action_states.each do |action_state|
|
65
|
+
print_action_state_information(action_state)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Print Action State information
|
70
|
+
# Call the method to print execution information
|
71
|
+
private def print_action_state_information(action_state)
|
72
|
+
puts " Action: #{action_state.action_name.light_blue}"
|
73
|
+
print_latest_execution_information(action_state.latest_execution)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Print the latest execution information
|
77
|
+
private def print_latest_execution_information(latest_execution)
|
78
|
+
status = latest_execution&.status
|
79
|
+
last_status_change = latest_execution&.last_status_change
|
80
|
+
error_details = latest_execution&.error_details
|
81
|
+
|
82
|
+
puts " Date: #{last_status_change&.to_s&.light_white}"
|
83
|
+
puts " State: #{colorize_status(status)}"
|
84
|
+
puts " Details: #{error_details&.message&.light_yellow}" if error_details
|
85
|
+
end
|
86
|
+
|
87
|
+
# Colorize the status output based on the success or failure of the pipeline
|
88
|
+
private def colorize_status(status)
|
89
|
+
return status.light_green if status == 'Succeeded'
|
90
|
+
return status.light_yellow if %w(Abandoned InProgress).include?(status)
|
91
|
+
|
92
|
+
status&.light_red
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'aws-sdk-sts'
|
2
|
+
require 'aws-sdk-ssm'
|
3
|
+
require 'inifile'
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
|
7
|
+
module Dev
|
8
|
+
class Aws
|
9
|
+
# Class contains methods for interacting with your Aws credentials
|
10
|
+
class Credentials
|
11
|
+
# The local file where temporary credentials are stored
|
12
|
+
CONFIG_FILE = "#{Dev::Aws::CONFIG_DIR}/credentials".freeze
|
13
|
+
|
14
|
+
# The account the profile is currently logged in to
|
15
|
+
def logged_in_account
|
16
|
+
::Aws::STS::Client.new.get_caller_identity.account
|
17
|
+
end
|
18
|
+
|
19
|
+
# The region associated with the current login
|
20
|
+
def logged_in_region
|
21
|
+
::Aws::STS::Client.new.send(:config).region
|
22
|
+
end
|
23
|
+
|
24
|
+
# Whether or not the current credentials are still active
|
25
|
+
def active?(profile = Dev::Aws::Profile.new.current)
|
26
|
+
# If there is a metadata uri then we are in an AWS env - assume we are good
|
27
|
+
return true if ENV.fetch('ECS_CONTAINER_METADATA_URI', nil)
|
28
|
+
|
29
|
+
# Otherwise there should either be an aws config directory or access key configured
|
30
|
+
return false unless File.exist?(Dev::Aws::CONFIG_DIR) || ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
31
|
+
|
32
|
+
# TODO: I'd prefer to still validate creds if using a METADATA_URI
|
33
|
+
# However this appears to require additional permissions which might not be present. Is there a better check here?
|
34
|
+
# return false if !ENV.fetch('ECS_CONTAINER_METADATA_URI', nil) && !(File.exist?(Dev::Aws::CONFIG_DIR) || ENV.fetch('AWS_ACCESS_KEY_ID', nil))
|
35
|
+
|
36
|
+
# Check for expired credentials
|
37
|
+
begin
|
38
|
+
::Aws::STS::Client.new(profile: profile).get_caller_identity
|
39
|
+
rescue
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check for invalid credentials
|
44
|
+
begin
|
45
|
+
# TODO: Is there a better check we can do here?
|
46
|
+
::Aws::SSM::Client.new(profile: profile).describe_parameters(max_results: 1)
|
47
|
+
rescue
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
# If the credentials are valid, make sure they are set in the ruby process environment for use later
|
52
|
+
export!
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Setup base Aws credential settings
|
57
|
+
def base_setup!
|
58
|
+
# Make the base config directory
|
59
|
+
FileUtils.mkdir_p(Dev::Aws::CONFIG_DIR)
|
60
|
+
|
61
|
+
puts
|
62
|
+
puts 'Configuring default credential values'
|
63
|
+
|
64
|
+
# Write access key / secret key in the credentials file
|
65
|
+
credini = IniFile.new(filename: "#{Dev::Aws::CONFIG_DIR}/credentials", default: 'default')
|
66
|
+
defaultini = credini['default']
|
67
|
+
|
68
|
+
access_key_default = defaultini['aws_access_key_id']
|
69
|
+
defaultini['aws_access_key_id'] = Dev::Common.new.ask('AWS Access Key ID', access_key_default)
|
70
|
+
|
71
|
+
secret_key_default = defaultini['aws_secret_access_key']
|
72
|
+
defaultini['aws_secret_access_key'] = Dev::Common.new.ask('AWS Secret Access Key', secret_key_default)
|
73
|
+
|
74
|
+
credini.write
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write Aws account specific settings to the credentials file
|
78
|
+
def write!(account, creds)
|
79
|
+
# Write access key / secret key / session token in the credentials file
|
80
|
+
credini = IniFile.new(filename: CONFIG_FILE, default: 'default')
|
81
|
+
defaultini = credini[account]
|
82
|
+
|
83
|
+
defaultini['aws_access_key_id'] = creds.access_key_id
|
84
|
+
defaultini['aws_secret_access_key'] = creds.secret_access_key
|
85
|
+
defaultini['aws_session_token'] = creds.session_token
|
86
|
+
|
87
|
+
credini.write
|
88
|
+
end
|
89
|
+
|
90
|
+
# Export our current credentials into the ruby environment
|
91
|
+
def export!
|
92
|
+
export_profile_credentials!
|
93
|
+
export_container_credentials!
|
94
|
+
end
|
95
|
+
|
96
|
+
# Exports the credentials if there is an active credentials uri
|
97
|
+
def export_container_credentials!
|
98
|
+
# If we already have creds defined, don't do anything
|
99
|
+
return if ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
100
|
+
|
101
|
+
# If a container credentials url is not present, don't do anything
|
102
|
+
ecs_creds = ENV.fetch('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI', nil)
|
103
|
+
return unless ecs_creds
|
104
|
+
|
105
|
+
# Otherwise query the local creds, parse the json response, and store in the environment
|
106
|
+
response = Net::HTTP.get_response(URI.parse("http://169.254.170.2#{ecs_creds}"))
|
107
|
+
raise 'Error getting container credentials' unless response.is_a?(Net::HTTPSuccess)
|
108
|
+
|
109
|
+
creds = JSON.parse(response.body)
|
110
|
+
ENV['AWS_ACCESS_KEY_ID'] = creds['AccessKeyId']
|
111
|
+
ENV['AWS_SECRET_ACCESS_KEY'] = creds['SecretAccessKey']
|
112
|
+
ENV['AWS_SESSION_TOKEN'] = creds['Token']
|
113
|
+
ENV['AWS_DEFAULT_REGION'] = logged_in_region
|
114
|
+
end
|
115
|
+
|
116
|
+
# Exports the credentials if there is a configured aws profile
|
117
|
+
def export_profile_credentials!
|
118
|
+
# If we already have creds defined, don't do anything
|
119
|
+
return if ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
120
|
+
|
121
|
+
# If a profile config file is not present, don't do anything
|
122
|
+
return unless File.exist?(CONFIG_FILE)
|
123
|
+
|
124
|
+
# Otherwise load access key / secret key / session token from the credentials file into the environment
|
125
|
+
credini = IniFile.new(filename: CONFIG_FILE, default: 'default')
|
126
|
+
profile_credentials = credini[Dev::Aws::Profile.new.current]
|
127
|
+
return unless profile_credentials
|
128
|
+
|
129
|
+
ENV['AWS_ACCESS_KEY_ID'] = profile_credentials['aws_access_key_id']
|
130
|
+
ENV['AWS_SECRET_ACCESS_KEY'] = profile_credentials['aws_secret_access_key']
|
131
|
+
ENV['AWS_SESSION_TOKEN'] = profile_credentials['aws_session_token']
|
132
|
+
ENV['AWS_DEFAULT_REGION'] = logged_in_region
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'aws-sdk-ecr'
|
2
|
+
require 'aws-sdk-sts'
|
3
|
+
require 'inifile'
|
4
|
+
|
5
|
+
module Dev
|
6
|
+
class Aws
|
7
|
+
# Class containing methods for helping a user log in to aws
|
8
|
+
class Login
|
9
|
+
# Main interface for logging in to an AWS account
|
10
|
+
# If an account is not specified the user is given an account selection menu
|
11
|
+
# If an account registry has been configured, the user is also logged in to the docker registry
|
12
|
+
def login!(account = nil)
|
13
|
+
# If more than one child account has been configured, have the user select the account they want to log in to
|
14
|
+
account ||= Dev::Aws::Account.new.select
|
15
|
+
|
16
|
+
# Authorize if our creds are not active
|
17
|
+
authorize!(account) unless Dev::Aws::Credentials.new.active?(account)
|
18
|
+
|
19
|
+
# Ensure the local env is pointed to the profile we selected
|
20
|
+
Dev::Aws::Profile.new.write!(account)
|
21
|
+
|
22
|
+
# Load credentials into the ENV for subprocesses
|
23
|
+
Dev::Aws::Credentials.new.export!
|
24
|
+
|
25
|
+
# Login in to the docker registry if the user has configured one
|
26
|
+
registry_login! if Dev::Aws::Account.new.registry
|
27
|
+
end
|
28
|
+
|
29
|
+
# Authorize your local credentials
|
30
|
+
# User is prompted for an MFA code
|
31
|
+
# Temporary credentials are written back to the credentials file
|
32
|
+
def authorize!(account)
|
33
|
+
# Make sure the account has been set up
|
34
|
+
cfgini = IniFile.new(filename: "#{Dev::Aws::CONFIG_DIR}/config", default: 'default')
|
35
|
+
unless cfgini.has_section?("profile #{account}")
|
36
|
+
Dev::Aws::Account.new.write!(account)
|
37
|
+
cfgini = IniFile.new(filename: "#{Dev::Aws::CONFIG_DIR}/config", default: 'default')
|
38
|
+
end
|
39
|
+
|
40
|
+
defaultini = cfgini['default']
|
41
|
+
profileini = cfgini["profile #{account}"]
|
42
|
+
|
43
|
+
serial = profileini['mfa_serial'] || defaultini['mfa_serial']
|
44
|
+
role = profileini['role_arn'] || defaultini['role_arn']
|
45
|
+
session_name = profileini['role_session_name'] || defaultini['role_session_name']
|
46
|
+
session_duration = profileini['session_duration'] || defaultini['session_duration']
|
47
|
+
|
48
|
+
puts
|
49
|
+
puts " Logging in to #{account} as #{role}".light_yellow
|
50
|
+
puts
|
51
|
+
|
52
|
+
code = ENV['AWS_TOKEN_CODE'] || Dev::Common.new.ask("Enter the MFA code for the #{ENV.fetch('USERNAME', '')} user serial #{serial}")
|
53
|
+
raise 'MFA is required' unless code.to_s.strip
|
54
|
+
|
55
|
+
sts = ::Aws::STS::Client.new(profile: 'default')
|
56
|
+
creds = sts.assume_role(
|
57
|
+
serial_number: serial,
|
58
|
+
role_arn: role,
|
59
|
+
role_session_name: session_name,
|
60
|
+
token_code: code,
|
61
|
+
duration_seconds: session_duration
|
62
|
+
).credentials
|
63
|
+
puts
|
64
|
+
|
65
|
+
Dev::Aws::Credentials.new.write!(account, creds)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Authroizes the docker cli to pull/push images from the Aws container registry (e.g. if docker compose needs to pull an image)
|
69
|
+
# Authroizes the docker ruby library to pull/push images from the Aws container registry
|
70
|
+
def registry_login!(registry_id: Dev::Aws::Account.new.registry, region: Dev::Aws::DEFAULT_REGION)
|
71
|
+
raise 'registry_id is required' if registry_id.to_s.strip.empty?
|
72
|
+
raise 'region is required' if region.to_s.strip.empty?
|
73
|
+
|
74
|
+
registry = "#{registry_id}.dkr.ecr.#{region}.amazonaws.com"
|
75
|
+
docker_cli_login!(registry: registry, region: region)
|
76
|
+
docker_lib_login!(registry_id: registry_id, region: region)
|
77
|
+
|
78
|
+
ENV['ECR_REGISTRY_ID'] ||= registry_id
|
79
|
+
ENV['ECR_REGISTRY'] ||= registry
|
80
|
+
end
|
81
|
+
|
82
|
+
# Authroizes the docker cli to pull/push images from the Aws container registry
|
83
|
+
# (e.g. if docker compose needs to pull an image)
|
84
|
+
# @deprecated Please use {Dev::Aws::Login#registry_login!} instead
|
85
|
+
def docker_login!(registry_id: Dev::Aws::Account.new.registry, region: Dev::Aws::DEFAULT_REGION)
|
86
|
+
warn '[DEPRECATION] `Dev::Aws::Login#docker_login!` is deprecated. Please use `Dev::Aws::Login#registry_login!` instead.'
|
87
|
+
docker_cli_login!(registry: "#{registry_id}.dkr.ecr.#{region}.amazonaws.com", region: region)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Authroizes the docker cli to pull/push images from the Aws container registry
|
91
|
+
# (e.g. if docker compose needs to pull an image)
|
92
|
+
private def docker_cli_login!(registry:, region:)
|
93
|
+
print(' Logging in to ECR in docker... ')
|
94
|
+
login_cmd = "aws --profile=#{Dev::Aws::Profile.new.current} ecr --region=#{region} get-login-password"
|
95
|
+
login_cmd << ' | '
|
96
|
+
login_cmd << "docker login --password-stdin --username AWS #{registry}"
|
97
|
+
Dev::Common.new.run_command([login_cmd])
|
98
|
+
puts
|
99
|
+
end
|
100
|
+
|
101
|
+
# Authroizes the docker ruby library to pull/push images from the Aws container registry
|
102
|
+
# @deprecated Please use {Dev::Aws::Login#registry_login!} instead
|
103
|
+
def ecr_login!(registry_id: Dev::Aws::Account.new.registry, region: Dev::Aws::DEFAULT_REGION)
|
104
|
+
warn '[DEPRECATION] `Dev::Aws::Login#ecr_login!` is deprecated. Please use `Dev::Aws::Login#registry_login!` instead.'
|
105
|
+
docker_lib_login!(registry_id: registry_id, region: region)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Authroizes the docker ruby library to pull/push images from the Aws container registry
|
109
|
+
private def docker_lib_login!(registry_id:, region:)
|
110
|
+
# Grab your authentication token from AWS ECR
|
111
|
+
ecr_client = ::Aws::ECR::Client.new(region: region)
|
112
|
+
tokens = ecr_client.get_authorization_token(registry_ids: Array(registry_id)).authorization_data
|
113
|
+
tokens.each do |token|
|
114
|
+
# Remove the https:// to authenticate
|
115
|
+
repo_url = token.proxy_endpoint.gsub('https://', '')
|
116
|
+
|
117
|
+
# Authorization token is given as username:password, split it out
|
118
|
+
user_pass_token = Base64.decode64(token.authorization_token).split(':')
|
119
|
+
|
120
|
+
# Call the authenticate method with the options
|
121
|
+
::Docker.authenticate!(
|
122
|
+
username: user_pass_token.first,
|
123
|
+
password: user_pass_token.last,
|
124
|
+
email: 'none',
|
125
|
+
serveraddress: repo_url
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'aws-sdk-ssm'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
class Aws
|
5
|
+
# Class containing methods for get/put ssm parameters in Aws
|
6
|
+
class Parameter
|
7
|
+
attr_accessor :client
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@client = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create/set a new client if none is present
|
14
|
+
# Return the client
|
15
|
+
def client
|
16
|
+
@client ||= ::Aws::SSM::Client.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the value of the given parameter name
|
20
|
+
def get_value(name, with_decryption: true)
|
21
|
+
get(name, with_decryption: with_decryption)&.value
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve the ssm parameter object with the given name
|
25
|
+
def get(name, with_decryption: true)
|
26
|
+
client.get_parameter(name: name, with_decryption: with_decryption)&.parameter
|
27
|
+
rescue ::Aws::SSM::Errors::ParameterNotFound
|
28
|
+
raise "parameter #{name} does not exist in #{Dev::Aws::Profile.new.current}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Load any existing profile if we haven't set one in the environment
|
2
|
+
require 'dotenv'
|
3
|
+
|
4
|
+
module Dev
|
5
|
+
class Aws
|
6
|
+
# Class containing methods to help write/maintain aws profile information
|
7
|
+
class Profile
|
8
|
+
# The filename where we store the local profile information
|
9
|
+
CONFIG_FILE = "#{Dir.home}/.env.profile".freeze
|
10
|
+
|
11
|
+
# The name of the profile identifier we use
|
12
|
+
IDENTIFIER = 'AWS_PROFILE'.freeze
|
13
|
+
|
14
|
+
# Always load the env profile
|
15
|
+
Dotenv.load(CONFIG_FILE) if File.exist?(CONFIG_FILE)
|
16
|
+
|
17
|
+
# Retrieve the current profile value
|
18
|
+
# Returns nil if one has not been configured
|
19
|
+
def current
|
20
|
+
ENV.fetch(IDENTIFIER, nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Write the new profile value to the env file
|
24
|
+
def write!(profile)
|
25
|
+
override = Dev::Env.new(CONFIG_FILE)
|
26
|
+
override.set(IDENTIFIER, profile)
|
27
|
+
override.write
|
28
|
+
|
29
|
+
# Update any existing ENV variables
|
30
|
+
Dotenv.overload(CONFIG_FILE)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Print the profile info for the current account
|
34
|
+
def info
|
35
|
+
Dev::Aws::Credentials.new.export!
|
36
|
+
puts
|
37
|
+
puts " Currently logged in to the #{Dev::Aws::Account.new.name_by_account(current)} (#{current})".light_yellow
|
38
|
+
puts
|
39
|
+
puts ' To use this profile in your local aws cli, you must either pass the profile as a command line argument ' \
|
40
|
+
'or export the corresponding aws variable:'.light_white
|
41
|
+
puts " aws --profile=#{current} s3 ls"
|
42
|
+
puts ' -OR-'.light_white
|
43
|
+
puts " export #{IDENTIFIER}=#{current}"
|
44
|
+
puts ' aws s3 ls'
|
45
|
+
puts
|
46
|
+
puts ' To use temporary credentials in your terminal, run the following:'.light_white
|
47
|
+
puts " export AWS_DEFAULT_REGION=#{ENV.fetch('AWS_DEFAULT_REGION', nil)}"
|
48
|
+
puts " export AWS_ACCESS_KEY_ID=#{ENV.fetch('AWS_ACCESS_KEY_ID', nil)}"
|
49
|
+
puts " export AWS_SECRET_ACCESS_KEY=#{ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)}"
|
50
|
+
puts " export AWS_SESSION_TOKEN=#{ENV.fetch('AWS_SESSION_TOKEN', nil)}"
|
51
|
+
puts
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
class Aws
|
5
|
+
# Class for performing S3 functions
|
6
|
+
class S3
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@client = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create/set a new client if none is present
|
14
|
+
# Return the client
|
15
|
+
def client
|
16
|
+
@client ||= ::Aws::S3::Client.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Puts the local filename into the given bucket named as the given key
|
20
|
+
# Optionally specify an acl. defaults to 'private'
|
21
|
+
# Returns the URL of the uploaded file
|
22
|
+
def put(bucket:, key:, filename:, acl: 'private')
|
23
|
+
begin
|
24
|
+
File.open(filename, 'rb') do |file|
|
25
|
+
client.put_object(bucket: bucket, key: key, body: file, acl: acl)
|
26
|
+
end
|
27
|
+
rescue => e
|
28
|
+
raise "s3 file upload failed: #{e.message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
url = "https://#{bucket}.s3.#{Dev::Aws::Credentials.new.logged_in_region}.amazonaws.com/#{key}"
|
32
|
+
LOG.info "Uploaded #{url}"
|
33
|
+
url
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finds the specific name of the appropriate "cf-templates-" bucket to use to upload cloudformation templates to
|
37
|
+
def cf_bucket
|
38
|
+
client.list_buckets.buckets.find { |bucket| bucket.name.match(/^cf-templates-.*-#{Dev::Aws::Credentials.new.logged_in_region}/) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Dev
|
2
|
+
# Class contains base aws constants
|
3
|
+
class Aws
|
4
|
+
# The config dir for the user's AWS settings
|
5
|
+
CONFIG_DIR = "#{Dir.home}/.aws".freeze
|
6
|
+
|
7
|
+
# The default region used if none have been configured in the AWS settings
|
8
|
+
DEFAULT_REGION = 'us-east-1'.freeze
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
# Class contains several common useful development methods
|
5
|
+
class Common
|
6
|
+
# Runs a command in a subshell.
|
7
|
+
# By default, the subshell is connected to the stdin/stdout/stderr of the current program
|
8
|
+
# By default, the current environment is passed to the subshell
|
9
|
+
# You can capture the output of the command by setting capture to true
|
10
|
+
def run_command(command, stdin: $stdin, stdout: $stdout, stderr: $stderr, env: ENV, capture: false)
|
11
|
+
command = Array(command)
|
12
|
+
output = nil
|
13
|
+
|
14
|
+
# If capture was specified, write stdout to a pipe so we can return it
|
15
|
+
stdoutread, stdout = ::IO.pipe if capture
|
16
|
+
|
17
|
+
# Spawn a subprocess to run the command
|
18
|
+
pid = ::Process.spawn(env, *command, in: stdin, out: stdout, err: stderr)
|
19
|
+
|
20
|
+
# Wait for the subprocess to finish and capture the result
|
21
|
+
_, result = ::Process.wait2(pid)
|
22
|
+
|
23
|
+
# If capture was specified, close the write pipe, read the output from the read pipe, close the read pipe, and return the output
|
24
|
+
if capture
|
25
|
+
stdout.close
|
26
|
+
output = stdoutread.readlines.join
|
27
|
+
stdoutread.close
|
28
|
+
end
|
29
|
+
|
30
|
+
# If the exitstatus was non-zero, exit with an error
|
31
|
+
unless result.exitstatus.zero?
|
32
|
+
puts output if capture
|
33
|
+
LOG.error "#{result.exitstatus} exit status while running [ #{command.join(' ')} ]\n".red
|
34
|
+
exit result.exitstatus
|
35
|
+
end
|
36
|
+
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
# Wraps a block of code in a y/n question.
|
41
|
+
# If the user answers 'y' then the block is executed.
|
42
|
+
# If the user answers 'n' then the block is skipped.
|
43
|
+
def with_confirmation(message, default = 'y')
|
44
|
+
print "\n #{message}? ".light_green
|
45
|
+
print '('.light_green << 'y'.light_yellow << '/'.light_green << 'n'.light_yellow << ') '.light_green
|
46
|
+
|
47
|
+
answer = default
|
48
|
+
answer = $stdin.gets unless ENV['NON_INTERACTIVE'] == 'true'
|
49
|
+
|
50
|
+
unless answer.strip.casecmp('y').zero?
|
51
|
+
puts "\n Cancelled.\n".light_yellow
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
puts
|
55
|
+
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
|
59
|
+
# Asks for user input using the given message and returns it
|
60
|
+
# If a default was specified and the user doesn't give any input, the default will be returned
|
61
|
+
def ask(message, default = nil)
|
62
|
+
msg = " #{message}"
|
63
|
+
msg << " [#{default}]" if default
|
64
|
+
msg << ': '
|
65
|
+
print msg
|
66
|
+
answer = $stdin.gets.to_s.strip
|
67
|
+
return default if default && answer == ''
|
68
|
+
|
69
|
+
answer
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method breaks up a string by spaces, however if it finds quoted strings in it,
|
73
|
+
# it attempts to preserve those as a single element
|
74
|
+
# e.g. "foo 'bin baz' bar" => [foo, 'bin baz', bar]
|
75
|
+
def tokenize(str)
|
76
|
+
str.split(/\s(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/)
|
77
|
+
.reject(&:empty?)
|
78
|
+
.map { |s| s.gsub(/(^ +)|( +$)|(^["']+)|(["']+$)/, '') }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks if CODEBUILD_INITIATOR or INITIATOR env variable are set
|
82
|
+
# If they are not set, it assumes it is not running in codebuild and return false
|
83
|
+
# Otherwise it returns true
|
84
|
+
def running_codebuild?
|
85
|
+
return false if ENV['CODEBUILD_INITIATOR'].to_s.strip.empty? && ENV['INITIATOR'].to_s.strip.empty?
|
86
|
+
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
# Remove all leading non '{' characters
|
91
|
+
# Remove all trailing non '}' characters
|
92
|
+
def strip_non_json(str)
|
93
|
+
str.sub(/\A[^{]*{/m, '{').sub(/}[^}]*\z/m, '}')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Takes two versions and attempts to compare them
|
97
|
+
# Returns true if the actual_version is greater than the required version (false otherwise)
|
98
|
+
def version_greater_than(required_version, actual_version)
|
99
|
+
required_version = required_version.to_s.split('.')
|
100
|
+
actual_version = actual_version.to_s.split('.')
|
101
|
+
|
102
|
+
required_version.each_with_index do |required, index|
|
103
|
+
required = required.to_i
|
104
|
+
actual = actual_version[index].to_i
|
105
|
+
return true if actual > required
|
106
|
+
next if actual == required
|
107
|
+
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|