artifact_tools 0.0.5
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/bin/artifact_download +6 -0
- data/bin/artifact_upload +6 -0
- data/lib/artifact_tools/client.rb +102 -0
- data/lib/artifact_tools/config_file.rb +69 -0
- data/lib/artifact_tools/downloader.rb +86 -0
- data/lib/artifact_tools/hasher.rb +20 -0
- data/lib/artifact_tools/uploader.rb +91 -0
- data/lib/artifact_tools/version.rb +5 -0
- data/lib/artifact_tools.rb +4 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 289e3ecd5615e51b01f2a42a2fb09420a52f21f4c0467fce0b0718b6f81da06b
|
4
|
+
data.tar.gz: c7cd3a30aca98803c044ab2aef7df0086f517f5cb18d13ef542bda889ba60544
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6b7ae6fd0ad77a9521bb8cd7eefc61af5346817956efcbbca52e6e52ca090ee8ee6d7ede6760acfba5a28170eae4a3d9db4109e8bbac684ee19d1b1422b06d0
|
7
|
+
data.tar.gz: d823934f9d66748ac73c040d778905b455a71bbf37143a1c47a7287062f1ebdca435f204f5fc52d0466438d74cc22b2f44f87fde229fd83da5d546b622854de7
|
data/bin/artifact_upload
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/ssh'
|
4
|
+
require 'net/scp'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'digest'
|
7
|
+
require 'artifact_tools/hasher'
|
8
|
+
|
9
|
+
module ArtifactTools
|
10
|
+
# Notifies that there was a mismatch between expected hash of the
|
11
|
+
# file(according to the configuration file) and the actual hash of the
|
12
|
+
# fetched file
|
13
|
+
class HashMismatchError < RuntimeError
|
14
|
+
end
|
15
|
+
|
16
|
+
# Use an object of this class to put/fetch files from storage specified with {ConfigFile}
|
17
|
+
class Client
|
18
|
+
include ArtifactTools::Hasher
|
19
|
+
|
20
|
+
# @param config [Hash] Configuration
|
21
|
+
# @param user [String] User name to connect to server with, overrides
|
22
|
+
# ARTIFACT_STORAGE_USER and the on stored in config
|
23
|
+
def initialize(config:, user: nil)
|
24
|
+
@config = config
|
25
|
+
user ||= ENV['ARTIFACT_STORAGE_USER'] || @config['user']
|
26
|
+
@ssh = Net::SSH.start(@config['server'], user, non_interactive: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Fetch a file from store
|
30
|
+
#
|
31
|
+
# @param file [String] Path to file to fetch. Fetches all files from config if omitted.
|
32
|
+
# @param dest [String] Optional prefix to add to local path of the file being fetched. Uses cwd if omitted.
|
33
|
+
# @param match [Regexp] Optionally fetch only files matching this pattern.
|
34
|
+
# @param verify [Boolean] Whether to verify the checksum after the file is received. Slows the fetch.
|
35
|
+
#
|
36
|
+
# @raise [HashMismatchError] In case checksum doesn't match the one stored in the config file.
|
37
|
+
def fetch(file: nil, dest: nil, match: nil, verify: false, force: false)
|
38
|
+
files = @config['files'].keys
|
39
|
+
files = [file] if file
|
40
|
+
files.each do |entry|
|
41
|
+
next if match && !entry.match?(match)
|
42
|
+
|
43
|
+
entry_hash = @config['files'][entry]['hash']
|
44
|
+
remote = compose_remote(entry, entry_hash)
|
45
|
+
local = compose_local(dest, entry)
|
46
|
+
next if !force && local_file_matches(local, entry_hash)
|
47
|
+
|
48
|
+
@ssh.scp.download!(remote, local)
|
49
|
+
verify(entry_hash, local) if verify
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Put a file to storage
|
54
|
+
#
|
55
|
+
# @param file [String] Path to the file to store.
|
56
|
+
def put(file:)
|
57
|
+
hash = file_hash(file)
|
58
|
+
remote = compose_remote(file, hash)
|
59
|
+
ensure_remote_path_exists(remote)
|
60
|
+
@ssh.scp.upload!(file, remote)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def compose_remote(file, hash)
|
66
|
+
basename = File.basename(file)
|
67
|
+
"#{@config['dir']}/#{hash}/#{basename}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_path_exists(local)
|
71
|
+
dirname = File.dirname(local)
|
72
|
+
return if File.directory?(dirname)
|
73
|
+
|
74
|
+
FileUtils.mkdir_p(dirname)
|
75
|
+
end
|
76
|
+
|
77
|
+
def ensure_remote_path_exists(remote)
|
78
|
+
dirname = File.dirname(remote)
|
79
|
+
return if File.directory?(dirname)
|
80
|
+
|
81
|
+
@ssh.exec!("mkdir -p #{dirname}")
|
82
|
+
end
|
83
|
+
|
84
|
+
def compose_local(dest, file)
|
85
|
+
local = file
|
86
|
+
local = "#{dest}/#{local}" if dest
|
87
|
+
ensure_path_exists(local)
|
88
|
+
local
|
89
|
+
end
|
90
|
+
|
91
|
+
def verify(expected_hash, path)
|
92
|
+
actual_hash = file_hash(path)
|
93
|
+
return unless expected_hash != actual_hash
|
94
|
+
|
95
|
+
raise HashMismatchError, "File #{path} has hash: #{actual_hash} while it should have: #{expected_hash}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def local_file_matches(local_file, expected_hash)
|
99
|
+
File.exist?(local_file) && file_hash(local_file) == expected_hash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'artifact_tools/hasher'
|
5
|
+
|
6
|
+
module ArtifactTools
|
7
|
+
# Store configuration information about artifacts and where they are stored.
|
8
|
+
#
|
9
|
+
# It has to contain at least the fields from {REQUIRED_FIELDS} while allowing
|
10
|
+
# any key/value which has a value for the user.
|
11
|
+
class ConfigFile
|
12
|
+
include ArtifactTools::Hasher
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
REQUIRED_FIELDS = %w[server dir files].freeze
|
16
|
+
|
17
|
+
# Initialize config file
|
18
|
+
#
|
19
|
+
# @param config [Hash] Provide configuration. Mandatory fields are {REQUIRED_FIELDS}
|
20
|
+
def initialize(config:)
|
21
|
+
raise 'Invalid config' unless REQUIRED_FIELDS.all? { |k| config.keys.include?(k) }
|
22
|
+
|
23
|
+
raise 'Invalid config' unless [NilClass, Hash].any? { |klass| config['files'].is_a?(klass) }
|
24
|
+
|
25
|
+
@config = config
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create ConfigFile from file in YAML format
|
29
|
+
#
|
30
|
+
# @param file [String] Path to file in YAML format.
|
31
|
+
def self.from_file(file)
|
32
|
+
ConfigFile.new(config: YAML.load_file(file))
|
33
|
+
# Leave error propagation as this is development tool
|
34
|
+
end
|
35
|
+
|
36
|
+
# Saves configuration to file
|
37
|
+
#
|
38
|
+
# @param file [String] Save in this file. Overwrites the file if present.
|
39
|
+
def save(file)
|
40
|
+
File.write(file, @config.to_yaml)
|
41
|
+
# Leave error propagation as this is development tool
|
42
|
+
end
|
43
|
+
|
44
|
+
# Append file to configuration.
|
45
|
+
#
|
46
|
+
# @param file [String] Path to the file to store in the configuration
|
47
|
+
# @param store_path [String] Use this path as key in the configuration. Optional, if omitted uses file
|
48
|
+
# @param opts [Hash] Additional fields to store for the file
|
49
|
+
#
|
50
|
+
# @note If file exists in the config with key *store_path* then its
|
51
|
+
# properties will be merged, where new ones will have priority.
|
52
|
+
def append_file(file:, store_path: nil, **opts)
|
53
|
+
store_path ||= file
|
54
|
+
|
55
|
+
# Convert symbols to String
|
56
|
+
opts = hash_keys_to_strings(opts)
|
57
|
+
|
58
|
+
@config['files'] ||= {}
|
59
|
+
@config['files'][store_path] = opts
|
60
|
+
@config['files'][store_path]['hash'] ||= file_hash(file)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def hash_keys_to_strings(hash)
|
66
|
+
hash.transform_keys(&:to_s)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'artifact_tools/client'
|
4
|
+
require 'artifact_tools/config_file'
|
5
|
+
require 'optparse'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module ArtifactTools
|
9
|
+
# Downloader allows the user to fetch files from a store specified. All this
|
10
|
+
# information is provided by {ConfigFile}.
|
11
|
+
class Downloader
|
12
|
+
# Downloads requested files
|
13
|
+
#
|
14
|
+
# @param [Hash] args the arguments for downloading artifacts
|
15
|
+
# @argument args :config_file [String] Path to configuration file
|
16
|
+
# @argument args :user [String] User to use for download connection
|
17
|
+
# @argument args :dest_dir [String] Where to download artifacts to
|
18
|
+
# @argument args :verify [Boolean] Whether to verify checksums after download.
|
19
|
+
# @argument args :force [Boolean] Whether to download files even if they are already
|
20
|
+
# present with the exected hash
|
21
|
+
# @argument args :match [Regexp] Whether to verify checksums after download.
|
22
|
+
def initialize(args = { verify: true, force: false })
|
23
|
+
config = load_config(args[:config_file])
|
24
|
+
c = ArtifactTools::Client.new(config: config.config, user: args[:user])
|
25
|
+
c.fetch(dest: args[:dest_dir], verify: args[:verify], match: args[:match], force: args[:force])
|
26
|
+
end
|
27
|
+
|
28
|
+
@default_opts = {
|
29
|
+
verify: true,
|
30
|
+
force: false,
|
31
|
+
dest_dir: '.'
|
32
|
+
}
|
33
|
+
@parse_opts_handlers = {
|
34
|
+
['-c FILE', '--configuration=FILE', 'Pass configuration file'] => lambda { |f, options, _opts|
|
35
|
+
options[:config_file] = f
|
36
|
+
},
|
37
|
+
['-d DIR', '--destination=DIR', 'Store files in directory'] => lambda { |d, options, _opts|
|
38
|
+
options[:dest_dir] = d
|
39
|
+
},
|
40
|
+
['-v', '--[no-]verify', TrueClass, "Verify hash on downloaded files. Default: #{@default_opts[:verify]}."] =>
|
41
|
+
lambda { |v, options, _opts|
|
42
|
+
options[:verify] = v
|
43
|
+
},
|
44
|
+
[
|
45
|
+
'-f', '--[no-]force', TrueClass,
|
46
|
+
"Force download of files if they are present with expected hash. Default: #{@default_opts[:force]}."
|
47
|
+
] => lambda { |v, options, _opts|
|
48
|
+
options[:force] = v
|
49
|
+
},
|
50
|
+
['-u USER', '--user=USER', 'Access server with this username'] => ->(u, options, _opts) { options[:user] = u },
|
51
|
+
['-m REGEXP', '--match=REGEXP', Regexp, 'Download only file which match regular expression'] =>
|
52
|
+
lambda { |v, options, _opts|
|
53
|
+
options[:match] = v
|
54
|
+
},
|
55
|
+
['-h', '--help', 'Show this message'] => lambda { |_h, _options, opts|
|
56
|
+
puts opts
|
57
|
+
exit
|
58
|
+
}
|
59
|
+
}
|
60
|
+
# Parse command line options to options suitable to Downloader.new
|
61
|
+
#
|
62
|
+
# @param arguments [Array(String)] Command line options to parse and use.
|
63
|
+
# Hint: pass ARGV
|
64
|
+
def self.parse(arguments)
|
65
|
+
options = @default_opts
|
66
|
+
arguments << '-h' if arguments.empty?
|
67
|
+
OptionParser.new do |opts|
|
68
|
+
opts.banner = "Usage: #{__FILE__} [options]"
|
69
|
+
@parse_opts_handlers.each do |args, handler|
|
70
|
+
opts.on(*args) { |v| handler.call(v, options, opts) }
|
71
|
+
end
|
72
|
+
end.parse!(arguments)
|
73
|
+
|
74
|
+
raise OptionParser::MissingArgument, 'Missing -c/--configuration option' unless options.key?(:config_file)
|
75
|
+
|
76
|
+
options
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def load_config(config_file)
|
82
|
+
ArtifactTools::ConfigFile.from_file(config_file)
|
83
|
+
# TODO: error check
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArtifactTools
|
4
|
+
# wrapper for the hashing algo used
|
5
|
+
module Hasher
|
6
|
+
# Calculate hash of a file
|
7
|
+
#
|
8
|
+
# @param path [String] Path to file to hash.
|
9
|
+
def file_hash(path)
|
10
|
+
hash_algo.file(path).hexdigest
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def hash_algo
|
16
|
+
# TODO: decide on used algorithm
|
17
|
+
Digest::SHA1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'artifact_tools/client'
|
4
|
+
require 'artifact_tools/config_file'
|
5
|
+
require 'optparse'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module ArtifactTools
|
9
|
+
# Uploader allows the user to upload files to a store specified by
|
10
|
+
# {ConfigFile}.
|
11
|
+
class Uploader
|
12
|
+
include ArtifactTools::Hasher
|
13
|
+
# Upload requested files
|
14
|
+
#
|
15
|
+
# @param config_file [String] Path to configuration file
|
16
|
+
# @param append [Boolean] Whether to append files to config file
|
17
|
+
# @param files [Array(String)] Paths to files to upload
|
18
|
+
def initialize(config_file:, files:, append: false)
|
19
|
+
# TODO: check for clashes of files, do hash checks?
|
20
|
+
@config_file = config_file
|
21
|
+
@append = append
|
22
|
+
@config = load_config(@config_file)
|
23
|
+
c = ArtifactTools::Client.new(config: @config.config)
|
24
|
+
files.each do |file|
|
25
|
+
update_file(c, file)
|
26
|
+
end
|
27
|
+
@config.save(config_file)
|
28
|
+
end
|
29
|
+
|
30
|
+
@default_append_opt = false
|
31
|
+
@parse_opts_handlers = {
|
32
|
+
['-c FILE', '--configuration=FILE', 'Pass configuration file.'] => lambda { |_opts, v, options|
|
33
|
+
options[:config_file] = v
|
34
|
+
},
|
35
|
+
['-a', '--append',
|
36
|
+
"Append uploaded files to configuration file, if missing. Default: #{@default_append_opt}."] =>
|
37
|
+
->(_opts, v, options) { options[:append] = v },
|
38
|
+
['-h', '--help', 'Show this message'] => lambda { |opts, _v, _options|
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
# Parse command line options to options suitable to Downloader.new
|
45
|
+
#
|
46
|
+
# @param arguments [Array(String)] Command line options to parse and use.
|
47
|
+
# Hint: pass ARGV
|
48
|
+
def self.parse(arguments)
|
49
|
+
options = { append: @default_append_opt }
|
50
|
+
arguments << '-h' if arguments.empty?
|
51
|
+
OptionParser.new do |opts|
|
52
|
+
opts.banner = "Usage: #{__FILE__} [options]"
|
53
|
+
@parse_opts_handlers.each do |args, handler|
|
54
|
+
opts.on(*args) { |v| handler.call(opts, v, options) }
|
55
|
+
end
|
56
|
+
end.parse!(arguments)
|
57
|
+
|
58
|
+
raise OptionParser::MissingArgument, 'Missing -c/--configuration option' unless options.key?(:config_file)
|
59
|
+
|
60
|
+
options.merge({ files: arguments.dup })
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def load_config(config_file)
|
66
|
+
ArtifactTools::ConfigFile.from_file(config_file)
|
67
|
+
end
|
68
|
+
|
69
|
+
def relative_to_config(file, config_file)
|
70
|
+
file = File.expand_path(file)
|
71
|
+
config_file = File.expand_path(config_file)
|
72
|
+
config_file_dirname = File.dirname(config_file)
|
73
|
+
return nil unless file.start_with?(config_file_dirname)
|
74
|
+
|
75
|
+
file[(config_file_dirname.length + 1)..]
|
76
|
+
end
|
77
|
+
|
78
|
+
# update the current file remotely and append it to the config if needed
|
79
|
+
def update_file(client, file)
|
80
|
+
client.put(file: file)
|
81
|
+
hash = file_hash(file)
|
82
|
+
puts "#{hash} #{file}"
|
83
|
+
return unless @append
|
84
|
+
|
85
|
+
rel_path = relative_to_config(file, @config_file)
|
86
|
+
raise "#{file} is not relative to config: #{@config_file}" unless rel_path
|
87
|
+
|
88
|
+
@config.append_file(file: file, store_path: rel_path, hash: hash)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: artifact_tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vitosha Labs Open Source team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-scp
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.16'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.16'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
executables:
|
128
|
+
- artifact_download
|
129
|
+
- artifact_upload
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- bin/artifact_download
|
134
|
+
- bin/artifact_upload
|
135
|
+
- lib/artifact_tools.rb
|
136
|
+
- lib/artifact_tools/client.rb
|
137
|
+
- lib/artifact_tools/config_file.rb
|
138
|
+
- lib/artifact_tools/downloader.rb
|
139
|
+
- lib/artifact_tools/hasher.rb
|
140
|
+
- lib/artifact_tools/uploader.rb
|
141
|
+
- lib/artifact_tools/version.rb
|
142
|
+
homepage: https://github.com/vitoshalabs/artifact_tools
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata:
|
146
|
+
rubygems_mfa_required: 'true'
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: 2.7.0
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubygems_version: 3.1.6
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Provides tools to manage repository artifacts.
|
166
|
+
test_files: []
|