Class: Fastlane::SecretsManagerStorage::Storage

Inherits:
Match::Storage::Interface
  • Object
show all
Defined in:
lib/fastlane/plugin/secrets_manager_storage/storage.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path_prefix: nil, tags: {}, region: nil, username: nil, readonly: nil, team_id: nil, team_name: nil, api_key_path: nil, api_key: nil) ⇒ Storage

Returns a new instance of Storage.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 44

def initialize(
  path_prefix: nil,
  tags: {},
  region: nil,
  username: nil,
  readonly: nil,
  team_id: nil,
  team_name: nil,
  api_key_path: nil,
  api_key: nil
)
  @path_prefix = path_prefix
  @tags = tags
  @region = region || ENV["AWS_REGION"]
  @username = username
  @readonly = readonly
  @team_id = team_id
  @team_name = team_name
  @api_key_path = api_key_path
  @api_key = api_key

  @client = Aws::SecretsManager::Client.new(region: region)
  UI.message("Initializing match for AWS Secrets Manager at #{@path_prefix} in #{@region}")
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



19
20
21
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 19

def api_key
  @api_key
end

#api_key_pathObject (readonly)

Returns the value of attribute api_key_path.



18
19
20
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 18

def api_key_path
  @api_key_path
end

#git_urlObject (readonly)

Returns the value of attribute git_url.



13
14
15
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 13

def git_url
  @git_url
end

#path_prefixObject (readonly)

Returns the value of attribute path_prefix.



10
11
12
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 10

def path_prefix
  @path_prefix
end

#readonlyObject (readonly)

Returns the value of attribute readonly.



15
16
17
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 15

def readonly
  @readonly
end

#regionObject (readonly)

Returns the value of attribute region.



12
13
14
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 12

def region
  @region
end

#tagsObject (readonly)

Returns the value of attribute tags.



11
12
13
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 11

def tags
  @tags
end

#team_idObject (readonly)

Returns the value of attribute team_id.



16
17
18
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 16

def team_id
  @team_id
end

#team_nameObject (readonly)

Returns the value of attribute team_name.



17
18
19
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 17

def team_name
  @team_name
end

#usernameObject (readonly)

Returns the value of attribute username.



14
15
16
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 14

def username
  @username
end

Class Method Details

.configure(params) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 21

def self.configure(params)
  if params[:git_url].to_s.length > 0
    UI.important("Looks like you still define a `git_url` somewhere, even though")
    UI.important("you use AWS Secrets Manager. You can remove the `git_url`")
    UI.important("from your Matchfile and Fastfile")
    UI.message("The above is just a warning, fastlane will continue as usual now...")
  end

  return(
    self.new(
      path_prefix: params[:secrets_manager_path_prefix],
      tags: params[:secrets_manager_tags],
      region: params[:secrets_manager_region],
      username: params[:username],
      readonly: params[:readonly],
      team_id: params[:team_id],
      team_name: params[:team_name],
      api_key_path: params[:api_key_path],
      api_key: params[:api_key],
    )
  )
end

Instance Method Details

#api_tokenObject



136
137
138
139
140
141
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 136

def api_token
  api_token =
    Spaceship::ConnectAPI::Token.from(hash: self.api_key, filepath: self.api_key_path)
  api_token ||= Spaceship::ConnectAPI.token
  return api_token
end

#create_or_update_secret(current_file, secret_name) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 189

def create_or_update_secret(current_file, secret_name)
  full_secret_path = generate_secret_path(secret_name)
  secret_specific_tags = generate_tags_for_secret(current_file)
  begin
    @client.describe_secret(secret_id: full_secret_path)
    UI.verbose("Secret '#{secret_name}' already exists, updating...")
    @client.put_secret_value(
      secret_id: full_secret_path,
      secret_binary: IO.binread(current_file),
    )
    unless secret_specific_tags.empty?
      @client.tag_resource(
        secret_id: full_secret_path,
        tags: convert_hash_to_array_of_key_values(secret_specific_tags),
      )
    end
  rescue Aws::SecretsManager::Errors::ResourceNotFoundException
    UI.verbose("Secret '#{secret_name}' doesn't exist, creating...")
    @client.create_secret(
      name: full_secret_path,
      secret_binary: File.open(current_file, "rb").read,
      tags: convert_hash_to_array_of_key_values(tags.merge(secret_specific_tags)),
    )
  end
end

#currently_used_team_idObject



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 118

def currently_used_team_id
  if self.readonly
    # In readonly mode, we still want to see if the user provided a team_id
    # see `prefixed_working_directory` comments for more details
    return self.team_id
  else
    if self.team_id.to_s.empty?
      UI.user_error!(
        "The `team_id` option is required. fastlane cannot automatically determine portal team id via the App Store Connect API (yet)",
      )
    end

    spaceship =
      ::Match::SpaceshipEnsure.new(self.username, self.team_id, self.team_name, api_token)
    return spaceship.team_id
  end
end

#delete_files(files_to_delete: [], custom_message: nil) ⇒ Object



168
169
170
171
172
173
174
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 168

def delete_files(files_to_delete: [], custom_message: nil)
  files_to_delete.each do |current_file|
    secret_name = current_file.delete_prefix(self.working_directory + "/")

    delete_secret(secret_name)
  end
end

#delete_secret(secret_name) ⇒ Object



215
216
217
218
219
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 215

def delete_secret(secret_name)
  @client.delete_secret({ secret_id: secret_name, recovery_window_in_days: 7 })
rescue Aws::SecretsManager::Errors::ResourceNotFoundException
  UI.verbose("Secret '#{secret_name}' doesn't exist, skipping...")
end

#downloadObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 87

def download
  return if @working_directory

  self.working_directory = Dir.mktmpdir

  next_token = nil
  secret_names = []
  with_aws_authentication_error_handling do
    loop do
      resp =
        @client.list_secrets(
          { next_token: next_token, filters: [{ key: "name", values: [@path_prefix] }] },
        )
      resp.secret_list.each { |secret| secret_names << secret.name }
      next_token = resp.next_token
      break if next_token.nil?
    end

    secret_names.each do |name|
      secret = @client.get_secret_value({ secret_id: name })
      filename = File.join(self.working_directory, name.delete_prefix(self.path_prefix))
      FileUtils.mkdir_p(File.dirname(filename))
      IO.binwrite(filename, secret.secret_binary)
    end
  end

  UI.verbose(
    "Successfully downloaded all Secrets from AWS Secrets Manager to #{self.working_directory}",
  )
end

#generate_matchfile_content(template: nil) ⇒ Object



184
185
186
187
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 184

def generate_matchfile_content(template: nil)
  # Will implement once I figure out how to have a plugin with `match` commands
  raise "Not Implemented"
end

#human_readable_descriptionObject

Returns a short string describing + identifying the current storage backend. This will be printed when nuking a storage



145
146
147
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 145

def human_readable_description
  "AWS Secrets Manager Storage [#{self.path_prefix}]"
end

#list_files(file_name: "", file_ext: "") ⇒ Object



180
181
182
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 180

def list_files(file_name: "", file_ext: "")
  Dir[File.join(working_directory, self.team_id, "**", file_name, "*.#{file_ext}")]
end

#prefixed_working_directoryObject

To make debugging easier, we have a custom exception here



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 70

def prefixed_working_directory
  # We fall back to "*", which means certificates and profiles
  # from all teams that use this bucket would be installed. This is not ideal, but
  # unless the user provides a `team_id`, we can't know which one to use
  # This only happens if `readonly` is activated, and no `team_id` was provided
  @_folder_prefix ||= currently_used_team_id
  if @_folder_prefix.nil?
    # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
    # method, as the value won't change. This way the warning is only printed once
    UI.important(
      "Looks like you run `match` in `readonly` mode, and didn't provide a `team_id`. This will still work, however it is recommended to provide a `team_id` in your Appfile or Matchfile",
    )
    @_folder_prefix = "*"
  end
  return File.join(working_directory, @_folder_prefix)
end

#skip_docsObject



176
177
178
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 176

def skip_docs
  true
end

#upload_files(files_to_upload: [], custom_message: nil) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/fastlane/plugin/secrets_manager_storage/storage.rb', line 149

def upload_files(files_to_upload: [], custom_message: nil)
  # `files_to_upload` is an array of files that need to be uploaded to AWS Secrets Manager
  # Those doesn't mean they're new, it might just be they're changed
  # Either way, we'll upload them using the same technique

  files_to_upload.each do |current_file|
    # Go from
    #   "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
    # to
    #   "profiles/development/Development_me.mobileprovision"
    #

    # We also remove the trailing `/`
    secret_name = current_file.delete_prefix(self.working_directory)
    UI.verbose("Uploading '#{secret_name}' to AWS Secrets Manager...")
    create_or_update_secret(current_file, secret_name)
  end
end