Class: Match::Storage::S3Storage

Inherits:
Interface show all
Defined in:
match/lib/match/storage/s3_storage.rb

Overview

Store the code signing identities on AWS S3

Constant Summary

Constants inherited from Interface

Interface::MATCH_VERSION_FILE_NAME

Instance Attribute Summary collapse

Attributes inherited from Interface

#working_directory

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Interface

#clear_changes, #configure, #save_changes!

Constructor Details

#initialize(s3_region: nil, s3_access_key: nil, s3_secret_access_key: nil, s3_bucket: nil, s3_object_prefix: nil, readonly: nil, username: nil, team_id: nil, team_name: nil, api_key_path: nil, api_key: nil) ⇒ S3Storage

Returns a new instance of S3Storage.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'match/lib/match/storage/s3_storage.rb', line 54

def initialize(s3_region: nil,
               s3_access_key: nil,
               s3_secret_access_key: nil,
               s3_bucket: nil,
               s3_object_prefix: nil,
               readonly: nil,
               username: nil,
               team_id: nil,
               team_name: nil,
               api_key_path: nil,
               api_key: nil)
  @s3_bucket = s3_bucket
  @s3_region = s3_region
  @s3_client = Fastlane::Helper::S3ClientHelper.new(access_key: s3_access_key, secret_access_key: s3_secret_access_key, region: s3_region)
  @s3_object_prefix = s3_object_prefix.to_s
  @readonly = readonly
  @username = username
  @team_id = team_id
  @team_name = team_name
  @api_key_path = api_key_path
  @api_key = api_key
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



23
24
25
# File 'match/lib/match/storage/s3_storage.rb', line 23

def api_key
  @api_key
end

#api_key_pathObject (readonly)

Returns the value of attribute api_key_path.



22
23
24
# File 'match/lib/match/storage/s3_storage.rb', line 22

def api_key_path
  @api_key_path
end

#readonlyObject (readonly)

Returns the value of attribute readonly.



18
19
20
# File 'match/lib/match/storage/s3_storage.rb', line 18

def readonly
  @readonly
end

#s3_bucketObject (readonly)

Returns the value of attribute s3_bucket.



14
15
16
# File 'match/lib/match/storage/s3_storage.rb', line 14

def s3_bucket
  @s3_bucket
end

#s3_clientObject (readonly)

Returns the value of attribute s3_client.



16
17
18
# File 'match/lib/match/storage/s3_storage.rb', line 16

def s3_client
  @s3_client
end

#s3_object_prefixObject (readonly)

Returns the value of attribute s3_object_prefix.



17
18
19
# File 'match/lib/match/storage/s3_storage.rb', line 17

def s3_object_prefix
  @s3_object_prefix
end

#s3_regionObject (readonly)

Returns the value of attribute s3_region.



15
16
17
# File 'match/lib/match/storage/s3_storage.rb', line 15

def s3_region
  @s3_region
end

#team_idObject (readonly)

Returns the value of attribute team_id.



20
21
22
# File 'match/lib/match/storage/s3_storage.rb', line 20

def team_id
  @team_id
end

#team_nameObject (readonly)

Returns the value of attribute team_name.



21
22
23
# File 'match/lib/match/storage/s3_storage.rb', line 21

def team_name
  @team_name
end

#usernameObject (readonly)

Returns the value of attribute username.



19
20
21
# File 'match/lib/match/storage/s3_storage.rb', line 19

def username
  @username
end

Class Method Details

.configure(params) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'match/lib/match/storage/s3_storage.rb', line 25

def self.configure(params)
  s3_region = params[:s3_region]
  s3_access_key = params[:s3_access_key]
  s3_secret_access_key = params[:s3_secret_access_key]
  s3_bucket = params[:s3_bucket]
  s3_object_prefix = params[:s3_object_prefix]

  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 S3 Storage. 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(
    s3_region: s3_region,
    s3_access_key: s3_access_key,
    s3_secret_access_key: s3_secret_access_key,
    s3_bucket: s3_bucket,
    s3_object_prefix: s3_object_prefix,
    readonly: params[:readonly],
    username: params[:username],
    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

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



154
155
156
157
158
159
160
# File 'match/lib/match/storage/s3_storage.rb', line 154

def delete_files(files_to_delete: [], custom_message: nil)
  files_to_delete.each do |file_name|
    target_path = s3_object_path(file_name)
    UI.verbose("Deleting '#{target_path}' from S3 Storage...")
    s3_client.delete_file(s3_bucket, target_path)
  end
end

#downloadObject

Call this method for the initial clone/download of the user’s certificates & profiles As part of this method, the ‘self.working_directory` attribute will be set



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'match/lib/match/storage/s3_storage.rb', line 97

def download
  # Check if we already have a functional working_directory
  return if @working_directory && Dir.exist?(@working_directory)

  # No existing working directory, creating a new one now
  self.working_directory = Dir.mktmpdir

  # If team_id is defined, use `:team/` as a prefix (appending it at the end of the `s3_object_prefix` if one provided by the user),
  # so that we limit the download to only files that are specific to this team, and avoid downloads + decryption of unnecessary files.
  key_prefix = team_id.nil? ? s3_object_prefix : File.join(s3_object_prefix, team_id, '').delete_prefix('/')

  s3_client.find_bucket!(s3_bucket).objects(prefix: key_prefix).each do |object|
    # Prevent download if the file path is a directory.
    # We need to check if string ends with "/" instead of using `File.directory?` because
    # the string represent a remote location, not a local file in disk.
    next if object.key.end_with?("/")

    file_path = strip_s3_object_prefix(object.key) # :s3_object_prefix:team_id/path/to/file

    # strip s3_prefix from file_path
    download_path = File.join(self.working_directory, file_path)

    FileUtils.mkdir_p(File.expand_path("..", download_path))
    UI.verbose("Downloading file from S3 '#{file_path}' on bucket #{self.s3_bucket}")

    object.download_file(download_path)
  end
  UI.verbose("Successfully downloaded files from S3 to #{self.working_directory}")
end

#generate_matchfile_content(template: nil) ⇒ Object

Implement this for the ‘fastlane match init` command This method must return the content of the Matchfile that should be generated



173
174
175
# File 'match/lib/match/storage/s3_storage.rb', line 173

def generate_matchfile_content(template: nil)
  return "s3_bucket(\"#{self.s3_bucket}\")"
end

#human_readable_descriptionObject

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



129
130
131
# File 'match/lib/match/storage/s3_storage.rb', line 129

def human_readable_description
  return "S3 Bucket [#{s3_bucket}] on region #{s3_region}"
end

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



166
167
168
# File 'match/lib/match/storage/s3_storage.rb', line 166

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



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'match/lib/match/storage/s3_storage.rb', line 78

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



162
163
164
# File 'match/lib/match/storage/s3_storage.rb', line 162

def skip_docs
  false
end

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



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'match/lib/match/storage/s3_storage.rb', line 133

def upload_files(files_to_upload: [], custom_message: nil)
  # `files_to_upload` is an array of files that need to be uploaded to S3
  # 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 |file_name|
    # Go from
    #   "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/:team_id/profiles/development/Development_me.mobileprovision"
    # to
    #   ":s3_object_prefix:team_id/profiles/development/Development_me.mobileprovision"
    #
    target_path = s3_object_path(file_name)
    UI.verbose("Uploading '#{target_path}' to S3 Storage...")

    body = File.read(file_name)
    acl = 'private'
    s3_url = s3_client.upload_file(s3_bucket, target_path, body, acl)
    UI.verbose("Uploaded '#{s3_url}' to S3 Storage.")
  end
end