artifact_tools 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|