firespring_dev_commands 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|